diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 0c6c81bb57..46627311ff 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -167,6 +167,7 @@ sub thread_cleanup { *Slic3r::GUI::PresetHints::DESTROY = sub {}; *Slic3r::GUI::TabIface::DESTROY = sub {}; *Slic3r::OctoPrint::DESTROY = sub {}; + *Slic3r::Duet::DESTROY = sub {}; *Slic3r::PresetUpdater::DESTROY = sub {}; return undef; # this prevents a "Scalars leaked" warning } diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index ac62d63379..315bf2f451 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -26,6 +26,14 @@ our $appController; our $VALUE_CHANGE_EVENT = Wx::NewEventType; # 2) To inform about a preset selection change or a "modified" status change. our $PRESETS_CHANGED_EVENT = Wx::NewEventType; +# 3) To inform about a change of object selection +our $OBJECT_SELECTION_CHANGED_EVENT = Wx::NewEventType; +# 4) To inform about a change of object settings +our $OBJECT_SETTINGS_CHANGED_EVENT = Wx::NewEventType; +# 5) To inform about a remove of object +our $OBJECT_REMOVE_EVENT = Wx::NewEventType; +# 6) To inform about a update of the scene +our $UPDATE_SCENE_EVENT = Wx::NewEventType; sub new { my ($class, %params) = @_; @@ -123,6 +131,8 @@ sub new { $self->update_ui_from_settings; + Slic3r::GUI::update_mode(); + return $self; } @@ -142,7 +152,12 @@ sub _init_tabpanel { }); if (!$self->{no_plater}) { - $panel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($panel), L("Plater")); + $panel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($panel, + event_object_selection_changed => $OBJECT_SELECTION_CHANGED_EVENT, + event_object_settings_changed => $OBJECT_SETTINGS_CHANGED_EVENT, + event_remove_object => $OBJECT_REMOVE_EVENT, + event_update_scene => $UPDATE_SCENE_EVENT, + ), L("Plater")); if (!$self->{no_controller}) { $panel->AddPage($self->{controller} = Slic3r::GUI::Controller->new($panel), L("Controller")); } @@ -163,6 +178,10 @@ sub _init_tabpanel { my $value = $event->GetInt(); $self->{plater}->on_extruders_change($value); } + if ($opt_key eq 'printer_technology'){ + my $value = $event->GetInt();# 0 ~ "ptFFF"; 1 ~ "ptSLA" + $self->{plater}->show_preset_comboboxes($value); + } } # don't save while loading for the first time $self->config->save($Slic3r::GUI::autosave) if $Slic3r::GUI::autosave && $self->{loaded}; @@ -175,7 +194,7 @@ sub _init_tabpanel { my $tab = Slic3r::GUI::get_preset_tab($tab_name); if ($self->{plater}) { - # Update preset combo boxes (Print settings, Filament, Printer) from their respective tabs. + # Update preset combo boxes (Print settings, Filament, Material, Printer) from their respective tabs. my $presets = $tab->get_presets; if (defined $presets){ my $reload_dependent_tabs = $tab->get_dependent_tabs; @@ -183,7 +202,7 @@ sub _init_tabpanel { $self->{plater}->{"selected_item_$tab_name"} = $tab->get_selected_preset_item; if ($tab_name eq 'printer') { # Printer selected at the Printer tab, update "compatible" marks at the print and filament selectors. - for my $tab_name_other (qw(print filament)) { + for my $tab_name_other (qw(print filament sla_material)) { # If the printer tells us that the print or filament preset has been switched or invalidated, # refresh the print or filament tab page. Otherwise just refresh the combo box. my $update_action = ($reload_dependent_tabs && (first { $_ eq $tab_name_other } (@{$reload_dependent_tabs}))) @@ -197,9 +216,43 @@ sub _init_tabpanel { } } }); + + # The following event is emited by the C++ Tab implementation on object selection change. + EVT_COMMAND($self, -1, $OBJECT_SELECTION_CHANGED_EVENT, sub { + my ($self, $event) = @_; + my $obj_idx = $event->GetId; + my $child = $event->GetInt == 1 ? 1 : undef; + + $self->{plater}->select_object($obj_idx < 0 ? undef: $obj_idx, $child); + $self->{plater}->item_changed_selection($obj_idx); + }); + + # The following event is emited by the C++ GUI implementation on object settings change. + EVT_COMMAND($self, -1, $OBJECT_SETTINGS_CHANGED_EVENT, sub { + my ($self, $event) = @_; + + my $line = $event->GetString; + my ($obj_idx, $parts_changed, $part_settings_changed) = split('',$line); + + $self->{plater}->changed_object_settings($obj_idx, $parts_changed, $part_settings_changed); + }); + + # The following event is emited by the C++ GUI implementation on object remove. + EVT_COMMAND($self, -1, $OBJECT_REMOVE_EVENT, sub { + my ($self, $event) = @_; + $self->{plater}->remove(); + }); + + # The following event is emited by the C++ GUI implementation on extruder change for object. + EVT_COMMAND($self, -1, $UPDATE_SCENE_EVENT, sub { + my ($self, $event) = @_; + $self->{plater}->update(); + }); + + Slic3r::GUI::create_preset_tabs($self->{no_controller}, $VALUE_CHANGE_EVENT, $PRESETS_CHANGED_EVENT); $self->{options_tabs} = {}; - for my $tab_name (qw(print filament printer)) { + for my $tab_name (qw(print filament sla_material printer)) { $self->{options_tabs}{$tab_name} = Slic3r::GUI::get_preset_tab("$tab_name"); } @@ -211,8 +264,14 @@ sub _init_tabpanel { # load initial config my $full_config = wxTheApp->{preset_bundle}->full_config; $self->{plater}->on_config_change($full_config); + # Show a correct number of filament fields. - $self->{plater}->on_extruders_change(int(@{$full_config->nozzle_diameter})); + if (defined $full_config->nozzle_diameter){ # nozzle_diameter is undefined when SLA printer is selected + $self->{plater}->on_extruders_change(int(@{$full_config->nozzle_diameter})); + } + + # Show correct preset comboboxes according to the printer_technology + $self->{plater}->show_preset_comboboxes(($full_config->printer_technology eq "FFF") ? 0 : 1); } } diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index a0eef72fea..3e0ecf5eb7 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -49,14 +49,21 @@ my $PreventListEvents = 0; our $appController; sub new { - my ($class, $parent) = @_; + my ($class, $parent, %params) = @_; 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 )]); + + # store input params + $self->{event_object_selection_changed} = $params{event_object_selection_changed}; + $self->{event_object_settings_changed} = $params{event_object_settings_changed}; + $self->{event_remove_object} = $params{event_remove_object}; + $self->{event_update_scene} = $params{event_update_scene}; + # C++ Slic3r::Model with Perl extensions in Slic3r/Model.pm $self->{model} = Slic3r::Model->new; # C++ Slic3r::Print with Perl extensions in Slic3r/Print.pm @@ -74,7 +81,7 @@ sub new { }); # Initialize preview notebook - $self->{preview_notebook} = Wx::Notebook->new($self, -1, wxDefaultPosition, [335,335], wxNB_BOTTOM); + $self->{preview_notebook} = Wx::Notebook->new($self, -1, wxDefaultPosition, [-1,335], wxNB_BOTTOM); # Initialize handlers for canvases my $on_select_object = sub { @@ -128,7 +135,9 @@ sub new { } $_->set_scaling_factor($scale) for @{ $model_object->instances }; - $self->{list}->SetItem($obj_idx, 2, ($model_object->instances->[0]->scaling_factor * 100) . "%"); + # Set object scale on c++ side + Slic3r::GUI::set_object_scale($obj_idx, $model_object->instances->[0]->scaling_factor * 100); + # $object->transform_thumbnail($self->{model}, $obj_idx); #update print and start background processing @@ -159,7 +168,49 @@ sub new { } } }; + + # callbacks for toolbar + my $on_action_add = sub { + $self->add; + }; + + my $on_action_delete = sub { + $self->remove(); + }; + + my $on_action_deleteall = sub { + $self->reset; + }; + + my $on_action_arrange = sub { + $self->arrange; + }; + + my $on_action_more = sub { + $self->increase; + }; + + my $on_action_fewer = sub { + $self->decrease; + }; + + my $on_action_split = sub { + $self->split_object; + }; + my $on_action_cut = sub { + $self->object_cut_dialog; + }; + + my $on_action_settings = sub { + $self->object_settings_dialog; + }; + + my $on_action_layersediting = sub { + my $state = Slic3r::GUI::_3DScene::is_toolbar_item_pressed($self->{canvas3D}, "layersediting"); + $self->on_layer_editing_toggled($state); + }; + # Initialize 3D plater if ($Slic3r::GUI::have_OpenGL) { $self->{canvas3D} = Slic3r::GUI::Plater::3D->new($self->{preview_notebook}, $self->{objects}, $self->{model}, $self->{print}, $self->{config}); @@ -179,7 +230,18 @@ sub new { Slic3r::GUI::_3DScene::register_on_gizmo_scale_uniformly_callback($self->{canvas3D}, $on_gizmo_scale_uniformly); Slic3r::GUI::_3DScene::register_on_gizmo_rotate_callback($self->{canvas3D}, $on_gizmo_rotate); Slic3r::GUI::_3DScene::register_on_update_geometry_info_callback($self->{canvas3D}, $on_update_geometry_info); + Slic3r::GUI::_3DScene::register_action_add_callback($self->{canvas3D}, $on_action_add); + Slic3r::GUI::_3DScene::register_action_delete_callback($self->{canvas3D}, $on_action_delete); + Slic3r::GUI::_3DScene::register_action_deleteall_callback($self->{canvas3D}, $on_action_deleteall); + Slic3r::GUI::_3DScene::register_action_arrange_callback($self->{canvas3D}, $on_action_arrange); + Slic3r::GUI::_3DScene::register_action_more_callback($self->{canvas3D}, $on_action_more); + Slic3r::GUI::_3DScene::register_action_fewer_callback($self->{canvas3D}, $on_action_fewer); + Slic3r::GUI::_3DScene::register_action_split_callback($self->{canvas3D}, $on_action_split); + Slic3r::GUI::_3DScene::register_action_cut_callback($self->{canvas3D}, $on_action_cut); + Slic3r::GUI::_3DScene::register_action_settings_callback($self->{canvas3D}, $on_action_settings); + Slic3r::GUI::_3DScene::register_action_layersediting_callback($self->{canvas3D}, $on_action_layersediting); Slic3r::GUI::_3DScene::enable_gizmos($self->{canvas3D}, 1); + Slic3r::GUI::_3DScene::enable_toolbar($self->{canvas3D}, 1); Slic3r::GUI::_3DScene::enable_shader($self->{canvas3D}, 1); Slic3r::GUI::_3DScene::enable_force_zoom_to_bed($self->{canvas3D}, 1); @@ -248,83 +310,58 @@ sub new { } }); - # toolbar for object manipulation - if (!&Wx::wxMSW) { - Wx::ToolTip::Enable(1); - $self->{htoolbar} = Wx::ToolBar->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTB_HORIZONTAL | wxTB_TEXT | wxBORDER_SIMPLE | wxTAB_TRAVERSAL); - $self->{htoolbar}->AddTool(TB_ADD, L("Add…"), Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_REMOVE, L("Delete"), Wx::Bitmap->new(Slic3r::var("brick_delete.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_RESET, L("Delete All"), Wx::Bitmap->new(Slic3r::var("cross.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_ARRANGE, L("Arrange"), Wx::Bitmap->new(Slic3r::var("bricks.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddSeparator; - $self->{htoolbar}->AddTool(TB_MORE, L("More"), Wx::Bitmap->new(Slic3r::var("add.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_FEWER, L("Fewer"), Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddSeparator; - $self->{htoolbar}->AddTool(TB_45CCW, L("45° ccw"), Wx::Bitmap->new(Slic3r::var("arrow_rotate_anticlockwise.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_45CW, L("45° cw"), Wx::Bitmap->new(Slic3r::var("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_SCALE, L("Scale…"), Wx::Bitmap->new(Slic3r::var("arrow_out.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_SPLIT, L("Split"), Wx::Bitmap->new(Slic3r::var("shape_ungroup.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_CUT, L("Cut…"), Wx::Bitmap->new(Slic3r::var("package.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddSeparator; - $self->{htoolbar}->AddTool(TB_SETTINGS, L("Settings…"), Wx::Bitmap->new(Slic3r::var("cog.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_LAYER_EDITING, L('Layer Editing'), Wx::Bitmap->new(Slic3r::var("variable_layer_height.png"), wxBITMAP_TYPE_PNG), wxNullBitmap, 1, 0, 'Layer Editing'); - } else { - my %tbar_buttons = ( - add => L("Add…"), - remove => L("Delete"), - reset => L("Delete All"), - arrange => L("Arrange"), - increase => "", - decrease => "", - rotate45ccw => "", - rotate45cw => "", - changescale => L("Scale…"), - split => L("Split"), - cut => L("Cut…"), - settings => L("Settings…"), - layer_editing => L("Layer editing"), - ); - $self->{btoolbar} = Wx::BoxSizer->new(wxHORIZONTAL); - for (qw(add remove reset arrange increase decrease rotate45ccw rotate45cw changescale split cut settings)) { - $self->{"btn_$_"} = Wx::Button->new($self, -1, $tbar_buttons{$_}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); - $self->{btoolbar}->Add($self->{"btn_$_"}); - } - $self->{"btn_layer_editing"} = Wx::ToggleButton->new($self, -1, $tbar_buttons{'layer_editing'}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); - $self->{btoolbar}->Add($self->{"btn_layer_editing"}); - } +# # toolbar for object manipulation +# if (!&Wx::wxMSW) { +# Wx::ToolTip::Enable(1); +# $self->{htoolbar} = Wx::ToolBar->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTB_HORIZONTAL | wxTB_TEXT | wxBORDER_SIMPLE | wxTAB_TRAVERSAL); +# $self->{htoolbar}->AddTool(TB_ADD, L("Add…"), Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG), ''); +# $self->{htoolbar}->AddTool(TB_REMOVE, L("Delete"), Wx::Bitmap->new(Slic3r::var("brick_delete.png"), wxBITMAP_TYPE_PNG), ''); +# $self->{htoolbar}->AddTool(TB_RESET, L("Delete All"), Wx::Bitmap->new(Slic3r::var("cross.png"), wxBITMAP_TYPE_PNG), ''); +# $self->{htoolbar}->AddTool(TB_ARRANGE, L("Arrange"), Wx::Bitmap->new(Slic3r::var("bricks.png"), wxBITMAP_TYPE_PNG), ''); +# $self->{htoolbar}->AddSeparator; +# $self->{htoolbar}->AddTool(TB_MORE, L("More"), Wx::Bitmap->new(Slic3r::var("add.png"), wxBITMAP_TYPE_PNG), ''); +# $self->{htoolbar}->AddTool(TB_FEWER, L("Fewer"), Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG), ''); +# $self->{htoolbar}->AddSeparator; +# $self->{htoolbar}->AddTool(TB_45CCW, L("45° ccw"), Wx::Bitmap->new(Slic3r::var("arrow_rotate_anticlockwise.png"), wxBITMAP_TYPE_PNG), ''); +# $self->{htoolbar}->AddTool(TB_45CW, L("45° cw"), Wx::Bitmap->new(Slic3r::var("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG), ''); +# $self->{htoolbar}->AddTool(TB_SCALE, L("Scale…"), Wx::Bitmap->new(Slic3r::var("arrow_out.png"), wxBITMAP_TYPE_PNG), ''); +# $self->{htoolbar}->AddTool(TB_SPLIT, L("Split"), Wx::Bitmap->new(Slic3r::var("shape_ungroup.png"), wxBITMAP_TYPE_PNG), ''); +# $self->{htoolbar}->AddTool(TB_CUT, L("Cut…"), Wx::Bitmap->new(Slic3r::var("package.png"), wxBITMAP_TYPE_PNG), ''); +# $self->{htoolbar}->AddSeparator; +# $self->{htoolbar}->AddTool(TB_SETTINGS, L("Settings…"), Wx::Bitmap->new(Slic3r::var("cog.png"), wxBITMAP_TYPE_PNG), ''); +# $self->{htoolbar}->AddTool(TB_LAYER_EDITING, L('Layer Editing'), Wx::Bitmap->new(Slic3r::var("variable_layer_height.png"), wxBITMAP_TYPE_PNG), wxNullBitmap, 1, 0, 'Layer Editing'); +# } else { +# my %tbar_buttons = ( +# add => L("Add…"), +# remove => L("Delete"), +# reset => L("Delete All"), +# arrange => L("Arrange"), +# increase => "", +# decrease => "", +# rotate45ccw => "", +# rotate45cw => "", +# changescale => L("Scale…"), +# split => L("Split"), +# cut => L("Cut…"), +# settings => L("Settings…"), +# layer_editing => L("Layer editing"), +# ); +# $self->{btoolbar} = Wx::BoxSizer->new(wxHORIZONTAL); +# for (qw(add remove reset arrange increase decrease rotate45ccw rotate45cw changescale split cut settings)) { +# $self->{"btn_$_"} = Wx::Button->new($self, -1, $tbar_buttons{$_}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); +# $self->{btoolbar}->Add($self->{"btn_$_"}); +# } +# $self->{"btn_layer_editing"} = Wx::ToggleButton->new($self, -1, $tbar_buttons{'layer_editing'}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); +# $self->{btoolbar}->Add($self->{"btn_layer_editing"}); +# } ### Panel for right column - $self->{right_panel} = Wx::Panel->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); - - ### Scrolled Window for info boxes - my $scrolled_window_sizer = $self->{scrolled_window_sizer} = Wx::BoxSizer->new(wxVERTICAL); - $scrolled_window_sizer->SetMinSize([310, -1]); - my $scrolled_window_panel = $self->{scrolled_window_panel} = Wx::ScrolledWindow->new($self->{right_panel}, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); - $scrolled_window_panel->SetSizer($scrolled_window_sizer); - $scrolled_window_panel->SetScrollbars(1, 1, 1, 1); - - $self->{list} = Wx::ListView->new($scrolled_window_panel, -1, wxDefaultPosition, wxDefaultSize, - wxLC_SINGLE_SEL | wxLC_REPORT | wxBORDER_SUNKEN | wxTAB_TRAVERSAL | wxWANTS_CHARS ); - $self->{list}->InsertColumn(0, L("Name"), wxLIST_FORMAT_LEFT, 145); - $self->{list}->InsertColumn(1, L("Copies"), wxLIST_FORMAT_CENTER, 45); - $self->{list}->InsertColumn(2, L("Scale"), wxLIST_FORMAT_CENTER, wxLIST_AUTOSIZE_USEHEADER); - EVT_LIST_ITEM_SELECTED($self, $self->{list}, \&list_item_selected); - EVT_LIST_ITEM_DESELECTED($self, $self->{list}, \&list_item_deselected); - EVT_LIST_ITEM_ACTIVATED($self, $self->{list}, \&list_item_activated); - EVT_KEY_DOWN($self->{list}, sub { - 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; - } - }); +# $self->{right_panel} = Wx::Panel->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + $self->{right_panel} = Wx::ScrolledWindow->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + $self->{right_panel}->SetScrollbars(0, 1, 1, 1); # right pane buttons - $self->{btn_export_gcode} = Wx::Button->new($self->{right_panel}, -1, L("Export G-code…"), wxDefaultPosition, [-1, 30], wxBU_LEFT); + $self->{btn_export_gcode} = Wx::Button->new($self->{right_panel}, -1, L("Export G-code…"), wxDefaultPosition, [-1, 30], wxNO_BORDER);#, wxBU_LEFT); $self->{btn_reslice} = Wx::Button->new($self->{right_panel}, -1, L("Slice now"), wxDefaultPosition, [-1, 30], wxBU_LEFT); $self->{btn_print} = Wx::Button->new($self->{right_panel}, -1, L("Print…"), wxDefaultPosition, [-1, 30], wxBU_LEFT); $self->{btn_send_gcode} = Wx::Button->new($self->{right_panel}, -1, L("Send to printer"), wxDefaultPosition, [-1, 30], wxBU_LEFT); @@ -334,25 +371,12 @@ sub new { $self->{btn_print}->Hide; $self->{btn_send_gcode}->Hide; +# export_gcode cog_go.png my %icons = qw( - add brick_add.png - remove brick_delete.png - reset cross.png - arrange bricks.png - export_gcode cog_go.png print arrow_up.png send_gcode arrow_up.png reslice reslice.png export_stl brick_go.png - - increase add.png - decrease delete.png - rotate45cw arrow_rotate_clockwise.png - rotate45ccw arrow_rotate_anticlockwise.png - changescale arrow_out.png - split shape_ungroup.png - cut package.png - settings cog.png ); for (grep $self->{"btn_$_"}, keys %icons) { $self->{"btn_$_"}->SetBitmap(Wx::Bitmap->new(Slic3r::var($icons{$_}), wxBITMAP_TYPE_PNG)); @@ -371,44 +395,45 @@ sub new { EVT_BUTTON($self, $self->{btn_reslice}, \&reslice); EVT_BUTTON($self, $self->{btn_export_stl}, \&export_stl); - if ($self->{htoolbar}) { - EVT_TOOL($self, TB_ADD, sub { $self->add; }); - EVT_TOOL($self, TB_REMOVE, sub { $self->remove() }); # explicitly pass no argument to remove - EVT_TOOL($self, TB_RESET, sub { $self->reset; }); - EVT_TOOL($self, TB_ARRANGE, sub { $self->arrange; }); - EVT_TOOL($self, TB_MORE, sub { $self->increase; }); - EVT_TOOL($self, TB_FEWER, sub { $self->decrease; }); - EVT_TOOL($self, TB_45CW, sub { $_[0]->rotate(-45, Z, 'relative') }); - EVT_TOOL($self, TB_45CCW, sub { $_[0]->rotate(45, Z, 'relative') }); - EVT_TOOL($self, TB_SCALE, sub { $self->changescale(undef); }); - EVT_TOOL($self, TB_SPLIT, sub { $self->split_object; }); - EVT_TOOL($self, TB_CUT, sub { $_[0]->object_cut_dialog }); - EVT_TOOL($self, TB_SETTINGS, sub { $_[0]->object_settings_dialog }); - EVT_TOOL($self, TB_LAYER_EDITING, sub { - my $state = Slic3r::GUI::_3DScene::is_layers_editing_enabled($self->{canvas3D}); - $self->{htoolbar}->ToggleTool(TB_LAYER_EDITING, ! $state); - $self->on_layer_editing_toggled(! $state); - }); - } else { - EVT_BUTTON($self, $self->{btn_add}, sub { $self->add; }); - EVT_BUTTON($self, $self->{btn_remove}, sub { $self->remove() }); # explicitly pass no argument to remove - EVT_BUTTON($self, $self->{btn_reset}, sub { $self->reset; }); - EVT_BUTTON($self, $self->{btn_arrange}, sub { $self->arrange; }); - EVT_BUTTON($self, $self->{btn_increase}, sub { $self->increase; }); - EVT_BUTTON($self, $self->{btn_decrease}, sub { $self->decrease; }); - EVT_BUTTON($self, $self->{btn_rotate45cw}, sub { $_[0]->rotate(-45, Z, 'relative') }); - EVT_BUTTON($self, $self->{btn_rotate45ccw}, sub { $_[0]->rotate(45, Z, 'relative') }); - EVT_BUTTON($self, $self->{btn_changescale}, sub { $self->changescale(undef); }); - EVT_BUTTON($self, $self->{btn_split}, sub { $self->split_object; }); - EVT_BUTTON($self, $self->{btn_cut}, sub { $_[0]->object_cut_dialog }); - EVT_BUTTON($self, $self->{btn_settings}, sub { $_[0]->object_settings_dialog }); - EVT_TOGGLEBUTTON($self, $self->{btn_layer_editing}, sub { $self->on_layer_editing_toggled($self->{btn_layer_editing}->GetValue); }); - } +# if ($self->{htoolbar}) { +# EVT_TOOL($self, TB_ADD, sub { $self->add; }); +# EVT_TOOL($self, TB_REMOVE, sub { $self->remove() }); # explicitly pass no argument to remove +# EVT_TOOL($self, TB_RESET, sub { $self->reset; }); +# EVT_TOOL($self, TB_ARRANGE, sub { $self->arrange; }); +# EVT_TOOL($self, TB_MORE, sub { $self->increase; }); +# EVT_TOOL($self, TB_FEWER, sub { $self->decrease; }); +# EVT_TOOL($self, TB_45CW, sub { $_[0]->rotate(-45, Z, 'relative') }); +# EVT_TOOL($self, TB_45CCW, sub { $_[0]->rotate(45, Z, 'relative') }); +# EVT_TOOL($self, TB_SCALE, sub { $self->changescale(undef); }); +# EVT_TOOL($self, TB_SPLIT, sub { $self->split_object; }); +# EVT_TOOL($self, TB_CUT, sub { $_[0]->object_cut_dialog }); +# EVT_TOOL($self, TB_SETTINGS, sub { $_[0]->object_settings_dialog }); +# EVT_TOOL($self, TB_LAYER_EDITING, sub { +# my $state = Slic3r::GUI::_3DScene::is_layers_editing_enabled($self->{canvas3D}); +# $self->{htoolbar}->ToggleTool(TB_LAYER_EDITING, ! $state); +# $self->on_layer_editing_toggled(! $state); +# }); +# } else { +# EVT_BUTTON($self, $self->{btn_add}, sub { $self->add; }); +# EVT_BUTTON($self, $self->{btn_remove}, sub { $self->remove() }); # explicitly pass no argument to remove +# EVT_BUTTON($self, $self->{btn_remove}, sub { Slic3r::GUI::remove_obj() }); # explicitly pass no argument to remove +# EVT_BUTTON($self, $self->{btn_reset}, sub { $self->reset; }); +# EVT_BUTTON($self, $self->{btn_arrange}, sub { $self->arrange; }); +# EVT_BUTTON($self, $self->{btn_increase}, sub { $self->increase; }); +# EVT_BUTTON($self, $self->{btn_decrease}, sub { $self->decrease; }); +# EVT_BUTTON($self, $self->{btn_rotate45cw}, sub { $_[0]->rotate(-45, Z, 'relative') }); +# EVT_BUTTON($self, $self->{btn_rotate45ccw}, sub { $_[0]->rotate(45, Z, 'relative') }); +# EVT_BUTTON($self, $self->{btn_changescale}, sub { $self->changescale(undef); }); +# EVT_BUTTON($self, $self->{btn_split}, sub { $self->split_object; }); +# EVT_BUTTON($self, $self->{btn_cut}, sub { $_[0]->object_cut_dialog }); +# EVT_BUTTON($self, $self->{btn_settings}, sub { $_[0]->object_settings_dialog }); +# EVT_TOGGLEBUTTON($self, $self->{btn_layer_editing}, sub { $self->on_layer_editing_toggled($self->{btn_layer_editing}->GetValue); }); +# } $_->SetDropTarget(Slic3r::GUI::Plater::DropTarget->new($self)) for grep defined($_), $self, $self->{canvas3D}, $self->{preview3D}, $self->{list}; -# $self, $self->{canvas}, $self->{canvas3D}, $self->{preview3D}, $self->{list}; +# $self, $self->{canvas}, $self->{canvas3D}, $self->{preview3D}; EVT_COMMAND($self, -1, $PROGRESS_BAR_EVENT, sub { my ($self, $event) = @_; @@ -452,20 +477,21 @@ sub new { { my $presets; { - $presets = $self->{presets_sizer} = Wx::FlexGridSizer->new(3, 2, 1, 2); + $presets = $self->{presets_sizer} = Wx::FlexGridSizer->new(4, 2, 1, 2); $presets->AddGrowableCol(1, 1); $presets->SetFlexibleDirection(wxHORIZONTAL); my %group_labels = ( print => L('Print settings'), filament => L('Filament'), + sla_material=> L('SLA material'), printer => L('Printer'), ); - # UI Combo boxes for a print, multiple filaments, and a printer. + # UI Combo boxes for a print, multiple filaments, SLA material and a printer. # Initially a single filament combo box is created, but the number of combo boxes for the filament selection may increase, # once a printer preset with multiple extruders is activated. # $self->{preset_choosers}{$group}[$idx] $self->{preset_choosers} = {}; - for my $group (qw(print filament printer)) { + for my $group (qw(print filament sla_material printer)) { my $text = Wx::StaticText->new($self->{right_panel}, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); $text->SetFont($Slic3r::GUI::small_font); my $choice = Wx::BitmapComboBox->new($self->{right_panel}, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY); @@ -486,15 +512,32 @@ sub new { $presets->Layout; } - my $frequently_changed_parameters_sizer = Wx::BoxSizer->new(wxHORIZONTAL); + my $frequently_changed_parameters_sizer = $self->{frequently_changed_parameters_sizer} = Wx::BoxSizer->new(wxVERTICAL); Slic3r::GUI::add_frequently_changed_parameters($self->{right_panel}, $frequently_changed_parameters_sizer, $presets); + my $expert_mode_part_sizer = Wx::BoxSizer->new(wxVERTICAL); + Slic3r::GUI::add_expert_mode_part( $self->{right_panel}, $expert_mode_part_sizer, + $self->{model}, + $self->{event_object_selection_changed}, + $self->{event_object_settings_changed}, + $self->{event_remove_object}, + $self->{event_update_scene}); +# if ($expert_mode_part_sizer->IsShown(2)==1) +# { +# $expert_mode_part_sizer->Layout; +# $expert_mode_part_sizer->Show(2, 0); # ? Why doesn't work +# $self->{right_panel}->Layout; +# } + my $object_info_sizer; { - my $box = Wx::StaticBox->new($scrolled_window_panel, -1, L("Info")); +# my $box = Wx::StaticBox->new($scrolled_window_panel, -1, L("Info")); + my $box = Wx::StaticBox->new($self->{right_panel}, -1, L("Info")); + $box->SetFont($Slic3r::GUI::small_bold_font); $object_info_sizer = Wx::StaticBoxSizer->new($box, wxVERTICAL); $object_info_sizer->SetMinSize([300,-1]); - my $grid_sizer = Wx::FlexGridSizer->new(3, 4, 5, 5); + #!my $grid_sizer = Wx::FlexGridSizer->new(3, 4, 5, 5); + my $grid_sizer = Wx::FlexGridSizer->new(2, 4, 5, 5); $grid_sizer->SetFlexibleDirection(wxHORIZONTAL); $grid_sizer->AddGrowableCol(1, 1); $grid_sizer->AddGrowableCol(3, 1); @@ -509,28 +552,45 @@ sub new { ); while (my $field = shift @info) { my $label = shift @info; - my $text = Wx::StaticText->new($scrolled_window_panel, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); +# my $text = Wx::StaticText->new($scrolled_window_panel, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + my $text = Wx::StaticText->new($self->{right_panel}, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); $text->SetFont($Slic3r::GUI::small_font); - $grid_sizer->Add($text, 0); + #!$grid_sizer->Add($text, 0); - $self->{"object_info_$field"} = Wx::StaticText->new($scrolled_window_panel, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); +# $self->{"object_info_$field"} = Wx::StaticText->new($scrolled_window_panel, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + $self->{"object_info_$field"} = Wx::StaticText->new($self->{right_panel}, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); $self->{"object_info_$field"}->SetFont($Slic3r::GUI::small_font); if ($field eq 'manifold') { - $self->{object_info_manifold_warning_icon} = Wx::StaticBitmap->new($scrolled_window_panel, -1, Wx::Bitmap->new(Slic3r::var("error.png"), wxBITMAP_TYPE_PNG)); - $self->{object_info_manifold_warning_icon}->Hide; +# $self->{object_info_manifold_warning_icon} = Wx::StaticBitmap->new($scrolled_window_panel, -1, Wx::Bitmap->new(Slic3r::var("error.png"), wxBITMAP_TYPE_PNG)); + $self->{object_info_manifold_warning_icon} = Wx::StaticBitmap->new($self->{right_panel}, -1, Wx::Bitmap->new(Slic3r::var("error.png"), wxBITMAP_TYPE_PNG)); + #$self->{object_info_manifold_warning_icon}->Hide; + $self->{"object_info_manifold_warning_icon_show"} = sub { + if ($self->{object_info_manifold_warning_icon}->IsShown() != $_[0]) { + Slic3r::GUI::set_show_manifold_warning_icon($_[0]); + my $mode = wxTheApp->{app_config}->get("view_mode"); + return if ($mode eq "" || $mode eq "simple"); + $self->{object_info_manifold_warning_icon}->Show($_[0]); + $self->Layout + } + }; + $self->{"object_info_manifold_warning_icon_show"}->(0); my $h_sizer = Wx::BoxSizer->new(wxHORIZONTAL); - $h_sizer->Add($self->{object_info_manifold_warning_icon}, 0); - $h_sizer->Add($self->{"object_info_$field"}, 0); - $grid_sizer->Add($h_sizer, 0, wxEXPAND); + $h_sizer->Add($text, 0); + $h_sizer->Add($self->{object_info_manifold_warning_icon}, 0, wxLEFT, 2); + $h_sizer->Add($self->{"object_info_$field"}, 0, wxLEFT, 2); + #!$grid_sizer->Add($h_sizer, 0, wxEXPAND); + $object_info_sizer->Add($h_sizer, 0, wxEXPAND|wxTOP, 4); } else { + $grid_sizer->Add($text, 0); $grid_sizer->Add($self->{"object_info_$field"}, 0); } } } my $print_info_sizer = $self->{print_info_sizer} = Wx::StaticBoxSizer->new( - Wx::StaticBox->new($scrolled_window_panel, -1, L("Sliced Info")), wxVERTICAL); +# Wx::StaticBox->new($scrolled_window_panel, -1, L("Sliced Info")), wxVERTICAL); + Wx::StaticBox->new($self->{right_panel}, -1, L("Sliced Info")), wxVERTICAL); $print_info_sizer->SetMinSize([300,-1]); my $buttons_sizer = Wx::BoxSizer->new(wxHORIZONTAL); @@ -540,34 +600,53 @@ sub new { $buttons_sizer->Add($self->{btn_reslice}, 0, wxALIGN_RIGHT, 0); $buttons_sizer->Add($self->{btn_print}, 0, wxALIGN_RIGHT, 0); $buttons_sizer->Add($self->{btn_send_gcode}, 0, wxALIGN_RIGHT, 0); - $buttons_sizer->Add($self->{btn_export_gcode}, 0, wxALIGN_RIGHT, 0); - $scrolled_window_sizer->Add($self->{list}, 1, wxEXPAND, 5); - $scrolled_window_sizer->Add($object_info_sizer, 0, wxEXPAND, 0); - $scrolled_window_sizer->Add($print_info_sizer, 0, wxEXPAND, 0); +# $scrolled_window_sizer->Add($self->{list}, 1, wxEXPAND, 5); +# $scrolled_window_sizer->Add($object_info_sizer, 0, wxEXPAND, 0); +# $scrolled_window_sizer->Add($print_info_sizer, 0, wxEXPAND, 0); + #$buttons_sizer->Add($self->{btn_export_gcode}, 0, wxALIGN_RIGHT, 0); + + ### Sizer for info boxes + my $info_sizer = $self->{info_sizer} = Wx::BoxSizer->new(wxVERTICAL); + $info_sizer->SetMinSize([318, -1]); + $info_sizer->Add($object_info_sizer, 0, wxEXPAND | wxBOTTOM, 5); + $info_sizer->Add($print_info_sizer, 0, wxEXPAND | wxBOTTOM, 5); my $right_sizer = Wx::BoxSizer->new(wxVERTICAL); - $right_sizer->SetMinSize([320,-1]); + $self->{right_panel}->SetSizer($right_sizer); + $right_sizer->SetMinSize([320, -1]); $right_sizer->Add($presets, 0, wxEXPAND | wxTOP, 10) if defined $presets; - $right_sizer->Add($frequently_changed_parameters_sizer, 0, wxEXPAND | wxTOP, 0) if defined $frequently_changed_parameters_sizer; - $right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM, 5); - $right_sizer->Add($scrolled_window_panel, 1, wxEXPAND | wxALL, 1); + $right_sizer->Add($frequently_changed_parameters_sizer, 1, wxEXPAND | wxTOP, 0) if defined $frequently_changed_parameters_sizer; + $right_sizer->Add($expert_mode_part_sizer, 0, wxEXPAND | wxTOP, 10) if defined $expert_mode_part_sizer; + $right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM | wxTOP, 10); + $right_sizer->Add($info_sizer, 0, wxEXPAND | wxLEFT, 20); # Show the box initially, let it be shown after the slicing is finished. $self->print_info_box_show(0); - - $self->{right_panel}->SetSizer($right_sizer); + $right_sizer->Add($self->{btn_export_gcode}, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 20); my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); $hsizer->Add($self->{preview_notebook}, 1, wxEXPAND | wxTOP, 1); $hsizer->Add($self->{right_panel}, 0, wxEXPAND | wxLEFT | wxRIGHT, 3); - + my $sizer = Wx::BoxSizer->new(wxVERTICAL); - $sizer->Add($self->{htoolbar}, 0, wxEXPAND, 0) if $self->{htoolbar}; - $sizer->Add($self->{btoolbar}, 0, wxEXPAND, 0) if $self->{btoolbar}; +# $sizer->Add($self->{htoolbar}, 0, wxEXPAND, 0) if $self->{htoolbar}; +# $sizer->Add($self->{btoolbar}, 0, wxEXPAND, 0) if $self->{btoolbar}; $sizer->Add($hsizer, 1, wxEXPAND, 0); $sizer->SetSizeHints($self); $self->SetSizer($sizer); + + # Send sizers/buttons to C++ + Slic3r::GUI::set_objects_from_perl( $self->{right_panel}, + $frequently_changed_parameters_sizer, + $expert_mode_part_sizer, + $info_sizer, + $self->{btn_export_gcode}, + $self->{btn_export_stl}, + $self->{btn_reslice}, + $self->{btn_print}, + $self->{btn_send_gcode}, + $self->{object_info_manifold_warning_icon} ); } # Last correct selected item for each preset @@ -578,6 +657,7 @@ sub new { } $self->update_ui_from_settings(); + $self->Layout; return $self; } @@ -629,13 +709,14 @@ sub on_layer_editing_toggled { Slic3r::GUI::_3DScene::enable_layers_editing($self->{canvas3D}, $new_state); if ($new_state && ! Slic3r::GUI::_3DScene::is_layers_editing_enabled($self->{canvas3D})) { # Initialization of the OpenGL shaders failed. Disable the tool. - if ($self->{htoolbar}) { - $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 0); - $self->{htoolbar}->ToggleTool(TB_LAYER_EDITING, 0); - } else { - $self->{"btn_layer_editing"}->Disable; - $self->{"btn_layer_editing"}->SetValue(0); - } +# if ($self->{htoolbar}) { +# $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 0); +# $self->{htoolbar}->ToggleTool(TB_LAYER_EDITING, 0); +# } else { +# $self->{"btn_layer_editing"}->Disable; +# $self->{"btn_layer_editing"}->SetValue(0); +# } + Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "layersediting", 0); } $self->{canvas3D}->Refresh; $self->{canvas3D}->Update; @@ -657,16 +738,17 @@ sub update_ui_from_settings } } -# Update preset combo boxes (Print settings, Filament, Printer) from their respective tabs. +# Update preset combo boxes (Print settings, Filament, Material, Printer) from their respective tabs. # Called by # Slic3r::GUI::Tab::Print::_on_presets_changed # Slic3r::GUI::Tab::Filament::_on_presets_changed +# Slic3r::GUI::Tab::Material::_on_presets_changed # Slic3r::GUI::Tab::Printer::_on_presets_changed # when the presets are loaded or the user selects another preset. # For Print settings and Printer, synchronize the selection index with their tabs. # For Filament, synchronize the selection index for a single extruder printer only, otherwise keep the selection. sub update_presets { - # $group: one of qw(print filament printer) + # $group: one of qw(print filament sla_material printer) # $presets: PresetCollection my ($self, $group, $presets) = @_; my @choosers = @{$self->{preset_choosers}{$group}}; @@ -682,6 +764,8 @@ sub update_presets { } } elsif ($group eq 'print') { wxTheApp->{preset_bundle}->print->update_platter_ui($choosers[0]); + } elsif ($group eq 'sla_material') { + wxTheApp->{preset_bundle}->sla_material->update_platter_ui($choosers[0]); } elsif ($group eq 'printer') { # Update the print choosers to only contain the compatible presets, update the dirty flags. wxTheApp->{preset_bundle}->print->update_platter_ui($self->{preset_choosers}{print}->[0]); @@ -848,12 +932,10 @@ sub load_model_objects { foreach my $obj_idx (@obj_idx) { my $object = $self->{objects}[$obj_idx]; my $model_object = $self->{model}->objects->[$obj_idx]; - $self->{list}->InsertStringItem($obj_idx, $object->name); - $self->{list}->SetItemFont($obj_idx, Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL)) - if $self->{list}->can('SetItemFont'); # legacy code for wxPerl < 0.9918 not supporting SetItemFont() + + # Add object to list on c++ side + Slic3r::GUI::add_object_to_list($object->name, $model_object); - $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); } @@ -863,8 +945,6 @@ sub load_model_objects { # zoom to objects Slic3r::GUI::_3DScene::zoom_to_volumes($self->{canvas3D}) if $self->{canvas3D}; - $self->{list}->Update; - $self->{list}->Select($obj_idx[-1], 1); $self->object_list_changed; $self->schedule_background_process; @@ -899,7 +979,8 @@ sub remove { splice @{$self->{objects}}, $obj_idx, 1; $self->{model}->delete_object($obj_idx); $self->{print}->delete_object($obj_idx); - $self->{list}->DeleteItem($obj_idx); + # Delete object from list on c++ side + Slic3r::GUI::delete_object_from_list(); $self->object_list_changed; $self->select_object(undef); @@ -919,7 +1000,8 @@ sub reset { @{$self->{objects}} = (); $self->{model}->clear_objects; $self->{print}->clear_objects; - $self->{list}->DeleteAllItems; + # Delete all objects from list on c++ side + Slic3r::GUI::delete_all_objects_from_list(); $self->object_list_changed; $self->select_object(undef); @@ -941,7 +1023,8 @@ sub increase { ); $self->{print}->objects->[$obj_idx]->add_copy($instance->offset); } - $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); + # Set conut of object on c++ side + Slic3r::GUI::set_object_count($obj_idx, $model_object->instances_count); # only autoarrange if user has autocentering enabled $self->stop_background_process; @@ -950,6 +1033,8 @@ sub increase { } else { $self->update; } + + $self->selection_changed; # refresh info (size, volume etc.) $self->schedule_background_process; } @@ -967,7 +1052,8 @@ sub decrease { $model_object->delete_last_instance; $self->{print}->objects->[$obj_idx]->delete_last_copy; } - $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); + # Set conut of object on c++ side + Slic3r::GUI::set_object_count($obj_idx, $model_object->instances_count); } elsif (defined $copies_asked) { # The "decrease" came from the "set number of copies" dialog. $self->remove; @@ -976,11 +1062,7 @@ sub decrease { $self->resume_background_process; return; } - - if ($self->{objects}[$obj_idx]) { - $self->{list}->Select($obj_idx, 0); - $self->{list}->Select($obj_idx, 1); - } + $self->update; $self->schedule_background_process; } @@ -1078,6 +1160,7 @@ sub rotate { # $model_object->center_around_origin; # $self->reset_thumbnail($obj_idx); } + Slic3r::GUI::update_rotation_value(deg2rad($angle), $axis == X ? "x" : ($axis == Y ? "y" : "z")); # update print and start background processing $self->{print}->add_model_object($model_object, $obj_idx); @@ -1126,8 +1209,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 +1217,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; @@ -1169,8 +1251,9 @@ sub changescale { $scale = $self->_get_number_from_user(L('Enter the scale % for the selected object:'), L('Scale'), L('Invalid scaling value entered'), $model_instance->scaling_factor*100, 1); return if ! defined($scale) || $scale eq ''; } - - $self->{list}->SetItem($obj_idx, 2, "$scale%"); + + # Set object scale on c++ side + Slic3r::GUI::set_object_scale($obj_idx, $scale); $scale /= 100; # turn percent into factor my $variation = $scale / $model_instance->scaling_factor; @@ -1291,7 +1374,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 +1592,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 @@ -1569,7 +1656,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 +1672,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::PrintHost::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(""); } @@ -1608,9 +1696,15 @@ sub on_export_completed { # Fill in the "Sliced info" box with the result of the G-code generator. 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 (!$show && ($scrolled_window_sizer->IsShown(2) == $show)); +# my $scrolled_window_panel = $self->{scrolled_window_panel}; +# my $scrolled_window_sizer = $self->{scrolled_window_sizer}; +# return if (!$show && ($scrolled_window_sizer->IsShown(2) == $show)); + my $panel = $self->{right_panel}; + my $sizer = $self->{info_sizer}; + return if (!$sizer || !$show && ($sizer->IsShown(1) == $show)); + + Slic3r::GUI::set_show_print_info($show); + return if (wxTheApp->{app_config}->get("view_mode") eq "simple"); if ($show) { my $print_info_sizer = $self->{print_info_sizer}; @@ -1637,17 +1731,23 @@ sub print_info_box_show { while ( my $label = shift @info) { my $value = shift @info; next if $value eq "N/A"; - my $text = Wx::StaticText->new($scrolled_window_panel, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); +# my $text = Wx::StaticText->new($scrolled_window_panel, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); + my $text = Wx::StaticText->new($panel, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); $text->SetFont($Slic3r::GUI::small_font); $grid_sizer->Add($text, 0); - my $field = Wx::StaticText->new($scrolled_window_panel, -1, $value, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); +# my $field = Wx::StaticText->new($scrolled_window_panel, -1, $value, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + my $field = Wx::StaticText->new($panel, -1, $value, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); $field->SetFont($Slic3r::GUI::small_font); $grid_sizer->Add($field, 0); } } - $scrolled_window_sizer->Show(2, $show); - $scrolled_window_panel->Layout; +# $scrolled_window_sizer->Show(2, $show); +# $scrolled_window_panel->Layout; + $sizer->Show(1, $show); + + $self->Layout; + $panel->Refresh; } sub do_print { @@ -1856,6 +1956,24 @@ sub update { $self->{preview3D}->reload_print if $self->{preview3D}; } +# When a printer technology is changed, the UI needs to be updated to show/hide needed preset combo boxes. +sub show_preset_comboboxes{ + my ($self, $showSLA) = @_; #if showSLA is oposite value to "ptFFF" + + my $choices = $self->{preset_choosers}{filament}; + my $print_filament_ctrls_cnt = 2 + 2 * ($#$choices+1); + + foreach (0..$print_filament_ctrls_cnt-1){ + $self->{presets_sizer}->Show($_, !$showSLA); + } + $self->{presets_sizer}->Show($print_filament_ctrls_cnt , $showSLA); + $self->{presets_sizer}->Show($print_filament_ctrls_cnt+1, $showSLA); + + $self->{frequently_changed_parameters_sizer}->Show(0,!$showSLA); + + $self->Layout; +} + # When a number of extruders changes, the UI needs to be updated to show a single filament selection combo box per extruder. # Also the wxTheApp->{preset_bundle}->filament_presets needs to be resized accordingly # and some reasonable default has to be selected for the additional extruders. @@ -1914,28 +2032,30 @@ 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) { - if ($self->{htoolbar}) { - $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 0); - $self->{htoolbar}->ToggleTool(TB_LAYER_EDITING, 0); - } else { - $self->{"btn_layer_editing"}->Disable; - $self->{"btn_layer_editing"}->SetValue(0); - } +# if ($self->{htoolbar}) { +# $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 0); +# $self->{htoolbar}->ToggleTool(TB_LAYER_EDITING, 0); +# } else { +# $self->{"btn_layer_editing"}->Disable; +# $self->{"btn_layer_editing"}->SetValue(0); +# } + Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "layersediting", 0); Slic3r::GUI::_3DScene::enable_layers_editing($self->{canvas3D}, 0); $self->{canvas3D}->Refresh; $self->{canvas3D}->Update; } elsif (Slic3r::GUI::_3DScene::is_layers_editing_allowed($self->{canvas3D})) { # Want to allow the layer editing, but do it only if the OpenGL supports it. - if ($self->{htoolbar}) { - $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 1); - } else { - $self->{"btn_layer_editing"}->Enable; - } +# if ($self->{htoolbar}) { +# $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 1); +# } else { +# $self->{"btn_layer_editing"}->Enable; +# } + Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "layersediting", 1); } } elsif ($opt_key eq 'extruder_colour') { $update_scheduled = 1; @@ -1959,32 +2079,18 @@ sub on_config_change { $self->schedule_background_process; } -sub list_item_deselected { - my ($self, $event) = @_; - return if $PreventListEvents; - $self->{_lecursor} = Wx::BusyCursor->new(); - if ($self->{list}->GetFirstSelected == -1) { - $self->select_object(undef); -# $self->{canvas}->Refresh; - Slic3r::GUI::_3DScene::deselect_volumes($self->{canvas3D}) if $self->{canvas3D}; - Slic3r::GUI::_3DScene::render($self->{canvas3D}) if $self->{canvas3D}; - } - undef $self->{_lecursor}; -} +sub item_changed_selection{ + my ($self, $obj_idx) = @_; -sub list_item_selected { - my ($self, $event) = @_; - return if $PreventListEvents; - $self->{_lecursor} = Wx::BusyCursor->new(); - my $obj_idx = $event->GetIndex; - $self->select_object($obj_idx); # $self->{canvas}->Refresh; if ($self->{canvas3D}) { - my $selections = $self->collect_selections; - Slic3r::GUI::_3DScene::update_volumes_selection($self->{canvas3D}, \@$selections); + Slic3r::GUI::_3DScene::deselect_volumes($self->{canvas3D}); + if ($obj_idx >= 0){ + my $selections = $self->collect_selections; + Slic3r::GUI::_3DScene::update_volumes_selection($self->{canvas3D}, \@$selections); + } Slic3r::GUI::_3DScene::render($self->{canvas3D}); } - undef $self->{_lecursor}; } sub collect_selections { @@ -1996,6 +2102,7 @@ sub collect_selections { return $selections; } +# doesn't used now sub list_item_activated { my ($self, $event, $obj_idx) = @_; @@ -2096,6 +2203,31 @@ sub object_settings_dialog { } } +sub changed_object_settings { + my ($self, $obj_idx, $parts_changed, $part_settings_changed) = @_; + + # update thumbnail since parts may have changed + if ($parts_changed) { + # recenter and re-align to Z = 0 + my $model_object = $self->{model}->objects->[$obj_idx]; + $model_object->center_around_origin; + $self->reset_thumbnail($obj_idx); + } + + # update print + if ($parts_changed || $part_settings_changed) { + $self->stop_background_process; + $self->{print}->reload_object($obj_idx); + $self->schedule_background_process; + $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); + } else { + $self->resume_background_process; + } +} + # Called to update various buttons depending on whether there are any objects or # whether background processing (export of a G-code, sending to Octoprint, forced background re-slicing) is active. sub object_list_changed { @@ -2103,20 +2235,21 @@ sub object_list_changed { # Enable/disable buttons depending on whether there are any objects on the platter. my $have_objects = @{$self->{objects}} ? 1 : 0; - my $variable_layer_height_allowed = $self->{config}->variable_layer_height && Slic3r::GUI::_3DScene::is_layers_editing_allowed($self->{canvas3D}); - if ($self->{htoolbar}) { - # On OSX or Linux - $self->{htoolbar}->EnableTool($_, $have_objects) - for (TB_RESET, TB_ARRANGE, TB_LAYER_EDITING); - $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 0) if (! $variable_layer_height_allowed); - } else { - # On MSW - my $method = $have_objects ? 'Enable' : 'Disable'; - $self->{"btn_$_"}->$method - for grep $self->{"btn_$_"}, qw(reset arrange reslice export_gcode export_stl print send_gcode layer_editing); - $self->{"btn_layer_editing"}->Disable if (! $variable_layer_height_allowed); - } +# if ($self->{htoolbar}) { +# # On OSX or Linux +# $self->{htoolbar}->EnableTool($_, $have_objects) +# for (TB_RESET, TB_ARRANGE); +# } else { +# # On MSW +# my $method = $have_objects ? 'Enable' : 'Disable'; +# $self->{"btn_$_"}->$method +# for grep $self->{"btn_$_"}, qw(reset arrange reslice export_gcode export_stl print send_gcode); +# } + for my $toolbar_item (qw(deleteall arrange)) { + Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, $toolbar_item, $have_objects); + } + 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; # $model_fits == 1 -> ModelInstance::PVS_Partly_Outside @@ -2130,17 +2263,56 @@ sub selection_changed { my ($self) = @_; my ($obj_idx, $object) = $self->selected_object; my $have_sel = defined $obj_idx; + my $layers_height_allowed = $self->{config}->variable_layer_height && Slic3r::GUI::_3DScene::is_layers_editing_allowed($self->{canvas3D}) && $have_sel; $self->{right_panel}->Freeze; - if ($self->{htoolbar}) { - # On OSX or Linux - $self->{htoolbar}->EnableTool($_, $have_sel) - for (TB_REMOVE, TB_MORE, TB_FEWER, TB_45CW, TB_45CCW, TB_SCALE, TB_SPLIT, TB_CUT, TB_SETTINGS); - } else { - # On MSW - my $method = $have_sel ? 'Enable' : 'Disable'; - $self->{"btn_$_"}->$method - for grep $self->{"btn_$_"}, qw(remove increase decrease rotate45cw rotate45ccw changescale split cut settings); +# if ($self->{htoolbar}) { +# # On OSX or Linux +# $self->{htoolbar}->EnableTool($_, $have_sel) +# for (TB_REMOVE, TB_MORE, TB_45CW, TB_45CCW, TB_SCALE, TB_SPLIT, TB_CUT, TB_SETTINGS); +# +# $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, $layers_height_allowed); +# +# if ($have_sel) { +# my $model_object = $self->{model}->objects->[$obj_idx]; +# $self->{htoolbar}->EnableTool(TB_FEWER, $model_object->instances_count > 1); +# } else { +# $self->{htoolbar}->EnableTool(TB_FEWER, 0); +# } +# +# } else { +# # On MSW +# my $method = $have_sel ? 'Enable' : 'Disable'; +# $self->{"btn_$_"}->$method +# for grep $self->{"btn_$_"}, qw(remove increase rotate45cw rotate45ccw changescale split cut settings); +# +# if ($layers_height_allowed) { +# $self->{"btn_layer_editing"}->Enable; +# } else { +# $self->{"btn_layer_editing"}->Disable; +# } +# +# if ($have_sel) { +# my $model_object = $self->{model}->objects->[$obj_idx]; +# if ($model_object->instances_count > 1) { +# $self->{"btn_decrease"}->Enable; +# } else { +# $self->{"btn_decrease"}->Disable; +# } +# } else { +# $self->{"btn_decrease"}->Disable; +# } +# } + + for my $toolbar_item (qw(delete more fewer split cut settings)) { + Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, $toolbar_item, $have_sel); + } + + Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "layersediting", $layers_height_allowed); + + if ($have_sel) { + my $model_object = $self->{model}->objects->[$obj_idx]; + Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "fewer", $model_object->instances_count > 1); } if ($self->{object_info_size}) { # have we already loaded the info pane? @@ -2157,7 +2329,8 @@ sub selection_changed { $self->{object_info_facets}->SetLabel(sprintf(L('%d (%d shells)'), $model_object->facets_count, $stats->{number_of_parts})); if (my $errors = sum(@$stats{qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges)})) { $self->{object_info_manifold}->SetLabel(sprintf(L("Auto-repaired (%d errors)"), $errors)); - $self->{object_info_manifold_warning_icon}->Show; + #$self->{object_info_manifold_warning_icon}->Show; + $self->{"object_info_manifold_warning_icon_show"}->(1); # we don't show normals_fixed because we never provide normals # to admesh, so it generates normals for all facets @@ -2167,7 +2340,8 @@ sub selection_changed { $self->{object_info_manifold_warning_icon}->SetToolTipString($message); } else { $self->{object_info_manifold}->SetLabel(L("Yes")); - $self->{object_info_manifold_warning_icon}->Hide; + #$self->{object_info_manifold_warning_icon}->Hide; + $self->{"object_info_manifold_warning_icon_show"}->(0); $self->{object_info_manifold}->SetToolTipString(""); $self->{object_info_manifold_warning_icon}->SetToolTipString(""); } @@ -2176,7 +2350,8 @@ sub selection_changed { } } else { $self->{"object_info_$_"}->SetLabel("") for qw(size volume facets materials manifold); - $self->{object_info_manifold_warning_icon}->Hide; + #$self->{object_info_manifold_warning_icon}->Hide; + $self->{"object_info_manifold_warning_icon_show"}->(0); $self->{object_info_manifold}->SetToolTipString(""); $self->{object_info_manifold_warning_icon}->SetToolTipString(""); } @@ -2189,26 +2364,21 @@ sub selection_changed { } sub select_object { - my ($self, $obj_idx) = @_; + my ($self, $obj_idx, $child) = @_; # remove current selection foreach my $o (0..$#{$self->{objects}}) { - $PreventListEvents = 1; $self->{objects}->[$o]->selected(0); - $self->{list}->Select($o, 0); - $PreventListEvents = 0; } - + if (defined $obj_idx) { $self->{objects}->[$obj_idx]->selected(1); - # We use this flag to avoid circular event handling - # Select() happens to fire a wxEVT_LIST_ITEM_SELECTED on Windows, - # whose event handler calls this method again and again and again - $PreventListEvents = 1; - $self->{list}->Select($obj_idx, 1); - $PreventListEvents = 0; + # Select current object in the list on c++ side, if item isn't child + if (!defined $child){ + Slic3r::GUI::select_current_object($obj_idx);} } else { - # TODO: deselect all in list + # Unselect all objects in the list on c++ side + Slic3r::GUI::unselect_objects(); } $self->selection_changed(1); } 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; } diff --git a/resources/icons/add_object.png b/resources/icons/add_object.png new file mode 100644 index 0000000000..ffc958edc4 Binary files /dev/null and b/resources/icons/add_object.png differ diff --git a/resources/icons/bed/mk2_top.png b/resources/icons/bed/mk2_top.png index cab4b966f8..e93430b098 100644 Binary files a/resources/icons/bed/mk2_top.png and b/resources/icons/bed/mk2_top.png differ diff --git a/resources/icons/bed/mk3_top.png b/resources/icons/bed/mk3_top.png index b44007ad44..9674484979 100644 Binary files a/resources/icons/bed/mk3_top.png and b/resources/icons/bed/mk3_top.png differ diff --git a/resources/icons/colorchange_add_off.png b/resources/icons/colorchange_add_off.png new file mode 100644 index 0000000000..6ddeccbe0a Binary files /dev/null and b/resources/icons/colorchange_add_off.png differ diff --git a/resources/icons/colorchange_add_on.png b/resources/icons/colorchange_add_on.png new file mode 100644 index 0000000000..cc800b81e8 Binary files /dev/null and b/resources/icons/colorchange_add_on.png differ diff --git a/resources/icons/colorchange_delete_off.png b/resources/icons/colorchange_delete_off.png new file mode 100644 index 0000000000..c16655271a Binary files /dev/null and b/resources/icons/colorchange_delete_off.png differ diff --git a/resources/icons/colorchange_delete_on.png b/resources/icons/colorchange_delete_on.png new file mode 100644 index 0000000000..8f27ce9fe6 Binary files /dev/null and b/resources/icons/colorchange_delete_on.png differ diff --git a/resources/icons/disclosure_triangle_close.png b/resources/icons/disclosure_triangle_close.png new file mode 100644 index 0000000000..0660422c7f Binary files /dev/null and b/resources/icons/disclosure_triangle_close.png differ diff --git a/resources/icons/disclosure_triangle_open.png b/resources/icons/disclosure_triangle_open.png new file mode 100644 index 0000000000..81112f2a26 Binary files /dev/null and b/resources/icons/disclosure_triangle_open.png differ diff --git a/resources/icons/down_half_circle.png b/resources/icons/down_half_circle.png new file mode 100644 index 0000000000..84aba5c3cd Binary files /dev/null and b/resources/icons/down_half_circle.png differ diff --git a/resources/icons/erase.png b/resources/icons/erase.png new file mode 100644 index 0000000000..4c4cfd755c Binary files /dev/null and b/resources/icons/erase.png differ diff --git a/resources/icons/exclamation_mark_.png b/resources/icons/exclamation_mark_.png new file mode 100644 index 0000000000..5fe7c13556 Binary files /dev/null and b/resources/icons/exclamation_mark_.png differ diff --git a/resources/icons/lambda.png b/resources/icons/lambda.png new file mode 100644 index 0000000000..3be73b1424 Binary files /dev/null and b/resources/icons/lambda.png differ diff --git a/resources/icons/lambda_.png b/resources/icons/lambda_.png new file mode 100644 index 0000000000..8e9d2b0ea2 Binary files /dev/null and b/resources/icons/lambda_.png differ diff --git a/resources/icons/left_half_circle.png b/resources/icons/left_half_circle.png new file mode 100644 index 0000000000..c1e8d3a9c9 Binary files /dev/null and b/resources/icons/left_half_circle.png differ diff --git a/resources/icons/object.png b/resources/icons/object.png new file mode 100644 index 0000000000..c85cbaf2fa Binary files /dev/null and b/resources/icons/object.png differ diff --git a/resources/icons/one_layer_lock_off.png b/resources/icons/one_layer_lock_off.png new file mode 100644 index 0000000000..7dc8b06118 Binary files /dev/null and b/resources/icons/one_layer_lock_off.png differ diff --git a/resources/icons/one_layer_lock_on.png b/resources/icons/one_layer_lock_on.png new file mode 100644 index 0000000000..41b7ff173f Binary files /dev/null and b/resources/icons/one_layer_lock_on.png differ diff --git a/resources/icons/one_layer_unlock_off.png b/resources/icons/one_layer_unlock_off.png new file mode 100644 index 0000000000..c3931f8b9d Binary files /dev/null and b/resources/icons/one_layer_unlock_off.png differ diff --git a/resources/icons/one_layer_unlock_on.png b/resources/icons/one_layer_unlock_on.png new file mode 100644 index 0000000000..99e0db5c26 Binary files /dev/null and b/resources/icons/one_layer_unlock_on.png differ diff --git a/resources/icons/right_half_circle.png b/resources/icons/right_half_circle.png new file mode 100644 index 0000000000..9f182b0b42 Binary files /dev/null and b/resources/icons/right_half_circle.png differ diff --git a/resources/icons/split.png b/resources/icons/split.png new file mode 100644 index 0000000000..c5680ab916 Binary files /dev/null and b/resources/icons/split.png differ diff --git a/resources/icons/toolbar.png b/resources/icons/toolbar.png new file mode 100644 index 0000000000..ce954143db Binary files /dev/null and b/resources/icons/toolbar.png differ diff --git a/resources/icons/up_half_circle.png b/resources/icons/up_half_circle.png new file mode 100644 index 0000000000..9eea07fda7 Binary files /dev/null and b/resources/icons/up_half_circle.png differ diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 1953a9e299..737bc4b5ec 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -204,9 +204,11 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/GLCanvas3DManager.hpp ${LIBDIR}/slic3r/GUI/GLCanvas3DManager.cpp ${LIBDIR}/slic3r/GUI/GLGizmo.hpp - ${LIBDIR}/slic3r/GUI/GLGizmo.cpp + ${LIBDIR}/slic3r/GUI/GLGizmo.cpp ${LIBDIR}/slic3r/GUI/GLTexture.hpp - ${LIBDIR}/slic3r/GUI/GLTexture.cpp + ${LIBDIR}/slic3r/GUI/GLTexture.cpp + ${LIBDIR}/slic3r/GUI/GLToolbar.hpp + ${LIBDIR}/slic3r/GUI/GLToolbar.cpp ${LIBDIR}/slic3r/GUI/Preferences.cpp ${LIBDIR}/slic3r/GUI/Preferences.hpp ${LIBDIR}/slic3r/GUI/Preset.cpp @@ -217,6 +219,10 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/PresetHints.hpp ${LIBDIR}/slic3r/GUI/GUI.cpp ${LIBDIR}/slic3r/GUI/GUI.hpp + ${LIBDIR}/slic3r/GUI/GUI_ObjectParts.cpp + ${LIBDIR}/slic3r/GUI/GUI_ObjectParts.hpp + ${LIBDIR}/slic3r/GUI/LambdaObjectDialog.cpp + ${LIBDIR}/slic3r/GUI/LambdaObjectDialog.hpp ${LIBDIR}/slic3r/GUI/Tab.cpp ${LIBDIR}/slic3r/GUI/Tab.hpp ${LIBDIR}/slic3r/GUI/TabIface.cpp @@ -259,8 +265,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/PrintHost.cpp + ${LIBDIR}/slic3r/Utils/PrintHost.hpp ${LIBDIR}/slic3r/Utils/Bonjour.cpp ${LIBDIR}/slic3r/Utils/Bonjour.hpp ${LIBDIR}/slic3r/Utils/PresetUpdater.cpp @@ -464,7 +476,7 @@ set(XS_XSP_FILES ${XSP_DIR}/Surface.xsp ${XSP_DIR}/SurfaceCollection.xsp ${XSP_DIR}/TriangleMesh.xsp - ${XSP_DIR}/Utils_OctoPrint.xsp + ${XSP_DIR}/Utils_PrintHost.xsp ${XSP_DIR}/Utils_PresetUpdater.xsp ${XSP_DIR}/AppController.xsp ${XSP_DIR}/XS.xsp @@ -513,7 +525,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() @@ -602,6 +614,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/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index a4847fb45e..391b06dac6 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -33,16 +33,6 @@ use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; -package Slic3r::Point3; -use overload - '@{}' => sub { [ $_[0]->x, $_[0]->y, $_[0]->z ] }, #, - 'fallback' => 1; - -sub pp { - my ($self) = @_; - return [ @$self ]; -} - package Slic3r::Pointf; use overload '@{}' => sub { $_[0]->arrayref }, diff --git a/xs/src/admesh/connect.cpp b/xs/src/admesh/connect.cpp index e9129d0079..da5b667209 100644 --- a/xs/src/admesh/connect.cpp +++ b/xs/src/admesh/connect.cpp @@ -25,11 +25,11 @@ #include #include +#include + #include "stl.h" -static void stl_match_neighbors_exact(stl_file *stl, - stl_hash_edge *edge_a, stl_hash_edge *edge_b); static void stl_match_neighbors_nearby(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b); static void stl_record_neighbors(stl_file *stl, @@ -43,7 +43,6 @@ static int stl_load_edge_nearby(stl_file *stl, stl_hash_edge *edge, static void insert_hash_edge(stl_file *stl, stl_hash_edge edge, void (*match_neighbors)(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b)); -static int stl_get_hash_for_edge(int M, stl_hash_edge *edge); static int stl_compare_function(stl_hash_edge *edge_a, stl_hash_edge *edge_b); static void stl_free_edges(stl_file *stl); static void stl_remove_facet(stl_file *stl, int facet_number); @@ -82,37 +81,20 @@ stl_check_facets_exact(stl_file *stl) { for(i = 0; i < stl->stats.number_of_facets; i++) { facet = stl->facet_start[i]; - // Positive and negative zeros are possible in the floats, which are considered equal by the FP unit. - // When using a memcmp on raw floats, those numbers report to be different. - // Unify all +0 and -0 to +0 to make the floats equal under memcmp. - { - uint32_t *f = (uint32_t*)&facet; - for (int j = 0; j < 12; ++ j, ++ f) // 3x vertex + normal: 4x3 = 12 floats - if (*f == 0x80000000) - // Negative zero, switch to positive zero. - *f = 0; - } - - /* If any two of the three vertices are found to be exactally the same, call them degenerate and remove the facet. */ - if( !memcmp(&facet.vertex[0], &facet.vertex[1], - sizeof(stl_vertex)) - || !memcmp(&facet.vertex[1], &facet.vertex[2], - sizeof(stl_vertex)) - || !memcmp(&facet.vertex[0], &facet.vertex[2], - sizeof(stl_vertex))) { + // If any two of the three vertices are found to be exactally the same, call them degenerate and remove the facet. + if (facet.vertex[0] == facet.vertex[1] || + facet.vertex[1] == facet.vertex[2] || + facet.vertex[0] == facet.vertex[2]) { stl->stats.degenerate_facets += 1; stl_remove_facet(stl, i); - i--; + -- i; continue; - } for(j = 0; j < 3; j++) { edge.facet_number = i; edge.which_edge = j; - stl_load_edge_exact(stl, &edge, &facet.vertex[j], - &facet.vertex[(j + 1) % 3]); - - insert_hash_edge(stl, edge, stl_match_neighbors_exact); + stl_load_edge_exact(stl, &edge, &facet.vertex[j], &facet.vertex[(j + 1) % 3]); + insert_hash_edge(stl, edge, stl_record_neighbors); } } stl_free_edges(stl); @@ -131,28 +113,33 @@ stl_load_edge_exact(stl_file *stl, stl_hash_edge *edge, if (stl->error) return; { - float diff_x = ABS(a->x - b->x); - float diff_y = ABS(a->y - b->y); - float diff_z = ABS(a->z - b->z); - float max_diff = STL_MAX(diff_x, diff_y); - max_diff = STL_MAX(diff_z, max_diff); - stl->stats.shortest_edge = STL_MIN(max_diff, stl->stats.shortest_edge); + stl_vertex diff = (*a - *b).cwiseAbs(); + float max_diff = std::max(diff(0), std::max(diff(1), diff(2))); + stl->stats.shortest_edge = std::min(max_diff, stl->stats.shortest_edge); } // Ensure identical vertex ordering of equal edges. // This method is numerically robust. - if ((a->x != b->x) ? - (a->x < b->x) : - ((a->y != b->y) ? - (a->y < b->y) : - (a->z < b->z))) { - memcpy(&edge->key[0], a, sizeof(stl_vertex)); - memcpy(&edge->key[3], b, sizeof(stl_vertex)); + if (stl_vertex_lower(*a, *b)) { } else { - memcpy(&edge->key[0], b, sizeof(stl_vertex)); - memcpy(&edge->key[3], a, sizeof(stl_vertex)); + std::swap(a, b); edge->which_edge += 3; /* this edge is loaded backwards */ } + memcpy(&edge->key[0], a->data(), sizeof(stl_vertex)); + memcpy(&edge->key[sizeof(stl_vertex)], b->data(), sizeof(stl_vertex)); + // Switch negative zeros to positive zeros, so memcmp will consider them to be equal. + for (size_t i = 0; i < 6; ++ i) { + unsigned char *p = edge->key + i * 4; +#ifdef BOOST_LITTLE_ENDIAN + if (p[0] == 0 && p[1] == 0 && p[2] == 0 && p[3] == 0x80) + // Negative zero, switch to positive zero. + p[3] = 0; +#else /* BOOST_LITTLE_ENDIAN */ + if (p[0] == 0x80 && p[1] == 0 && p[2] == 0 && p[3] == 0) + // Negative zero, switch to positive zero. + p[0] = 0; +#endif /* BOOST_LITTLE_ENDIAN */ + } } static void @@ -188,21 +175,17 @@ stl_initialize_facet_check_exact(stl_file *stl) { } } -static void -insert_hash_edge(stl_file *stl, stl_hash_edge edge, +static void insert_hash_edge(stl_file *stl, stl_hash_edge edge, void (*match_neighbors)(stl_file *stl, - stl_hash_edge *edge_a, stl_hash_edge *edge_b)) { - stl_hash_edge *link; - stl_hash_edge *new_edge; - stl_hash_edge *temp; - int chain_number; - + stl_hash_edge *edge_a, stl_hash_edge *edge_b)) +{ if (stl->error) return; - chain_number = stl_get_hash_for_edge(stl->M, &edge); - - link = stl->heads[chain_number]; + int chain_number = edge.hash(stl->M); + stl_hash_edge *link = stl->heads[chain_number]; + stl_hash_edge *new_edge; + stl_hash_edge *temp; if(link == stl->tail) { /* This list doesn't have any edges currently in it. Add this one. */ new_edge = (stl_hash_edge*)malloc(sizeof(stl_hash_edge)); @@ -252,30 +235,17 @@ insert_hash_edge(stl_file *stl, stl_hash_edge edge, } } - -static int -stl_get_hash_for_edge(int M, stl_hash_edge *edge) { - return ((edge->key[0] / 23 + edge->key[1] / 19 + edge->key[2] / 17 - + edge->key[3] /13 + edge->key[4] / 11 + edge->key[5] / 7 ) % M); +// Return 1 if the edges are not matched. +static inline int stl_compare_function(stl_hash_edge *edge_a, stl_hash_edge *edge_b) +{ + // Don't match edges of the same facet + return (edge_a->facet_number == edge_b->facet_number) || (*edge_a != *edge_b); } -static int -stl_compare_function(stl_hash_edge *edge_a, stl_hash_edge *edge_b) { - if(edge_a->facet_number == edge_b->facet_number) { - return 1; /* Don't match edges of the same facet */ - } else { - return memcmp(edge_a, edge_b, SIZEOF_EDGE_SORT); - } -} - -void -stl_check_facets_nearby(stl_file *stl, float tolerance) { - stl_hash_edge edge[3]; - stl_facet facet; - int i; - int j; - - if (stl->error) return; +void stl_check_facets_nearby(stl_file *stl, float tolerance) +{ + if (stl->error) + return; if( (stl->stats.connected_facets_1_edge == stl->stats.number_of_facets) && (stl->stats.connected_facets_2_edge == stl->stats.number_of_facets) @@ -286,27 +256,19 @@ stl_check_facets_nearby(stl_file *stl, float tolerance) { stl_initialize_facet_check_nearby(stl); - for(i = 0; i < stl->stats.number_of_facets; i++) { - facet = stl->facet_start[i]; - // Positive and negative zeros are possible in the floats, which are considered equal by the FP unit. - // When using a memcmp on raw floats, those numbers report to be different. - // Unify all +0 and -0 to +0 to make the floats equal under memcmp. - { - uint32_t *f = (uint32_t*)&facet; - for (int j = 0; j < 12; ++ j, ++ f) // 3x vertex + normal: 4x3 = 12 floats - if (*f == 0x80000000) - // Negative zero, switch to positive zero. - *f = 0; - } - for(j = 0; j < 3; j++) { + for (int i = 0; i < stl->stats.number_of_facets; ++ i) { + //FIXME is the copy necessary? + stl_facet facet = stl->facet_start[i]; + for (int j = 0; j < 3; j++) { if(stl->neighbors_start[i].neighbor[j] == -1) { - edge[j].facet_number = i; - edge[j].which_edge = j; - if(stl_load_edge_nearby(stl, &edge[j], &facet.vertex[j], + stl_hash_edge edge; + edge.facet_number = i; + edge.which_edge = j; + if(stl_load_edge_nearby(stl, &edge, &facet.vertex[j], &facet.vertex[(j + 1) % 3], tolerance)) { /* only insert edges that have different keys */ - insert_hash_edge(stl, edge[j], stl_match_neighbors_nearby); + insert_hash_edge(stl, edge, stl_match_neighbors_nearby); } } } @@ -315,27 +277,17 @@ stl_check_facets_nearby(stl_file *stl, float tolerance) { stl_free_edges(stl); } -static int -stl_load_edge_nearby(stl_file *stl, stl_hash_edge *edge, - stl_vertex *a, stl_vertex *b, float tolerance) { +static int stl_load_edge_nearby(stl_file *stl, stl_hash_edge *edge, stl_vertex *a, stl_vertex *b, float tolerance) +{ // Index of a grid cell spaced by tolerance. - uint32_t vertex1[3] = { - (uint32_t)((a->x - stl->stats.min.x) / tolerance), - (uint32_t)((a->y - stl->stats.min.y) / tolerance), - (uint32_t)((a->z - stl->stats.min.z) / tolerance) - }; - uint32_t vertex2[3] = { - (uint32_t)((b->x - stl->stats.min.x) / tolerance), - (uint32_t)((b->y - stl->stats.min.y) / tolerance), - (uint32_t)((b->z - stl->stats.min.z) / tolerance) - }; + typedef Eigen::Matrix Vec3i; + Vec3i vertex1 = (*a / tolerance).cast(); + Vec3i vertex2 = (*b / tolerance).cast(); + static_assert(sizeof(Vec3i) == 12, "size of Vec3i incorrect"); - if( (vertex1[0] == vertex2[0]) - && (vertex1[1] == vertex2[1]) - && (vertex1[2] == vertex2[2])) { - /* Both vertices hash to the same value */ + if (vertex1 == vertex2) + // Both vertices hash to the same value return 0; - } // Ensure identical vertex ordering of edges, which vertices land into equal grid cells. // This method is numerically robust. @@ -344,30 +296,27 @@ stl_load_edge_nearby(stl_file *stl, stl_hash_edge *edge, ((vertex1[1] != vertex2[1]) ? (vertex1[1] < vertex2[1]) : (vertex1[2] < vertex2[2]))) { - memcpy(&edge->key[0], vertex1, sizeof(stl_vertex)); - memcpy(&edge->key[3], vertex2, sizeof(stl_vertex)); + memcpy(&edge->key[0], vertex1.data(), sizeof(stl_vertex)); + memcpy(&edge->key[sizeof(stl_vertex)], vertex2.data(), sizeof(stl_vertex)); } else { - memcpy(&edge->key[0], vertex2, sizeof(stl_vertex)); - memcpy(&edge->key[3], vertex1, sizeof(stl_vertex)); + memcpy(&edge->key[0], vertex2.data(), sizeof(stl_vertex)); + memcpy(&edge->key[sizeof(stl_vertex)], vertex1.data(), sizeof(stl_vertex)); edge->which_edge += 3; /* this edge is loaded backwards */ } return 1; } -static void -stl_free_edges(stl_file *stl) { - int i; - stl_hash_edge *temp; - - if (stl->error) return; +static void stl_free_edges(stl_file *stl) +{ + if (stl->error) + return; if(stl->stats.malloced != stl->stats.freed) { - for(i = 0; i < stl->M; i++) { - for(temp = stl->heads[i]; stl->heads[i] != stl->tail; - temp = stl->heads[i]) { + for (int i = 0; i < stl->M; i++) { + for (stl_hash_edge *temp = stl->heads[i]; stl->heads[i] != stl->tail; temp = stl->heads[i]) { stl->heads[i] = stl->heads[i]->next; free(temp); - stl->stats.freed++; + ++ stl->stats.freed; } } } @@ -375,8 +324,8 @@ stl_free_edges(stl_file *stl) { free(stl->tail); } -static void -stl_initialize_facet_check_nearby(stl_file *stl) { +static void stl_initialize_facet_check_nearby(stl_file *stl) +{ int i; if (stl->error) return; @@ -467,16 +416,8 @@ stl_record_neighbors(stl_file *stl, } } -static void -stl_match_neighbors_exact(stl_file *stl, - stl_hash_edge *edge_a, stl_hash_edge *edge_b) { - if (stl->error) return; - stl_record_neighbors(stl, edge_a, edge_b); -} - -static void -stl_match_neighbors_nearby(stl_file *stl, - stl_hash_edge *edge_a, stl_hash_edge *edge_b) { +static void stl_match_neighbors_nearby(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b) +{ int facet1; int facet2; int vertex1; @@ -517,9 +458,7 @@ stl_match_neighbors_nearby(stl_file *stl, } -static void -stl_change_vertices(stl_file *stl, int facet_num, int vnot, - stl_vertex new_vertex) { +static void stl_change_vertices(stl_file *stl, int facet_num, int vnot, stl_vertex new_vertex) { int first_facet; int direction; int next_edge; @@ -551,30 +490,30 @@ stl_change_vertices(stl_file *stl, int facet_num, int vnot, } } #if 0 - if (stl->facet_start[facet_num].vertex[pivot_vertex].x == new_vertex.x && - stl->facet_start[facet_num].vertex[pivot_vertex].y == new_vertex.y && - stl->facet_start[facet_num].vertex[pivot_vertex].z == new_vertex.z) + if (stl->facet_start[facet_num].vertex[pivot_vertex](0) == new_vertex(0) && + stl->facet_start[facet_num].vertex[pivot_vertex](1) == new_vertex(1) && + stl->facet_start[facet_num].vertex[pivot_vertex](2) == new_vertex(2)) printf("Changing vertex %f,%f,%f: Same !!!\r\n", - new_vertex.x, new_vertex.y, new_vertex.z); + new_vertex(0), new_vertex(1), new_vertex(2)); else { - if (stl->facet_start[facet_num].vertex[pivot_vertex].x != new_vertex.x) + if (stl->facet_start[facet_num].vertex[pivot_vertex](0) != new_vertex(0)) printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", - stl->facet_start[facet_num].vertex[pivot_vertex].x, - *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex].x), - new_vertex.x, - *reinterpret_cast(&new_vertex.x)); - if (stl->facet_start[facet_num].vertex[pivot_vertex].y != new_vertex.y) + stl->facet_start[facet_num].vertex[pivot_vertex](0), + *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](0)), + new_vertex(0), + *reinterpret_cast(&new_vertex(0))); + if (stl->facet_start[facet_num].vertex[pivot_vertex](1) != new_vertex(1)) printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", - stl->facet_start[facet_num].vertex[pivot_vertex].y, - *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex].y), - new_vertex.y, - *reinterpret_cast(&new_vertex.y)); - if (stl->facet_start[facet_num].vertex[pivot_vertex].z != new_vertex.z) + stl->facet_start[facet_num].vertex[pivot_vertex](1), + *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](1)), + new_vertex(1), + *reinterpret_cast(&new_vertex(1))); + if (stl->facet_start[facet_num].vertex[pivot_vertex](2) != new_vertex(2)) printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", - stl->facet_start[facet_num].vertex[pivot_vertex].z, - *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex].z), - new_vertex.z, - *reinterpret_cast(&new_vertex.z)); + stl->facet_start[facet_num].vertex[pivot_vertex](2), + *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](2)), + new_vertex(2), + *reinterpret_cast(&new_vertex(2))); } #endif stl->facet_start[facet_num].vertex[pivot_vertex] = new_vertex; @@ -595,7 +534,6 @@ Try using a smaller tolerance or don't do a nearby check\n"); } } - static void stl_which_vertices_to_change(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b, int *facet1, int *vertex1, @@ -622,11 +560,10 @@ stl_which_vertices_to_change(stl_file *stl, stl_hash_edge *edge_a, v1b = (edge_b->which_edge + 1) % 3; } - /* Of the first pair, which vertex, if any, should be changed */ - if(!memcmp(&stl->facet_start[edge_a->facet_number].vertex[v1a], - &stl->facet_start[edge_b->facet_number].vertex[v1b], - sizeof(stl_vertex))) { - /* These facets are already equal. No need to change. */ + // Of the first pair, which vertex, if any, should be changed + if(stl->facet_start[edge_a->facet_number].vertex[v1a] == + stl->facet_start[edge_b->facet_number].vertex[v1b]) { + // These facets are already equal. No need to change. *facet1 = -1; } else { if( (stl->neighbors_start[edge_a->facet_number].neighbor[v1a] == -1) @@ -644,10 +581,9 @@ stl_which_vertices_to_change(stl_file *stl, stl_hash_edge *edge_a, } /* Of the second pair, which vertex, if any, should be changed */ - if(!memcmp(&stl->facet_start[edge_a->facet_number].vertex[v2a], - &stl->facet_start[edge_b->facet_number].vertex[v2b], - sizeof(stl_vertex))) { - /* These facets are already equal. No need to change. */ + if(stl->facet_start[edge_a->facet_number].vertex[v2a] == + stl->facet_start[edge_b->facet_number].vertex[v2b]) { + // These facets are already equal. No need to change. *facet2 = -1; } else { if( (stl->neighbors_start[edge_a->facet_number].neighbor[v2a] == -1) @@ -718,40 +654,35 @@ in stl_remove_facet: neighbor = %d numfacets = %d this is wrong\n", } } -void -stl_remove_unconnected_facets(stl_file *stl) { +void stl_remove_unconnected_facets(stl_file *stl) +{ /* A couple of things need to be done here. One is to remove any */ /* completely unconnected facets (0 edges connected) since these are */ /* useless and could be completely wrong. The second thing that needs to */ /* be done is to remove any degenerate facets that were created during */ /* stl_check_facets_nearby(). */ + if (stl->error) + return; - int i; - - if (stl->error) return; - - /* remove degenerate facets */ - for(i = 0; i < stl->stats.number_of_facets; i++) { - if( !memcmp(&stl->facet_start[i].vertex[0], - &stl->facet_start[i].vertex[1], sizeof(stl_vertex)) - || !memcmp(&stl->facet_start[i].vertex[1], - &stl->facet_start[i].vertex[2], sizeof(stl_vertex)) - || !memcmp(&stl->facet_start[i].vertex[0], - &stl->facet_start[i].vertex[2], sizeof(stl_vertex))) { + // remove degenerate facets + for (int i = 0; i < stl->stats.number_of_facets; ++ i) { + if(stl->facet_start[i].vertex[0] == stl->facet_start[i].vertex[1] || + stl->facet_start[i].vertex[0] == stl->facet_start[i].vertex[2] || + stl->facet_start[i].vertex[1] == stl->facet_start[i].vertex[2]) { stl_remove_degenerate(stl, i); i--; } } if(stl->stats.connected_facets_1_edge < stl->stats.number_of_facets) { - /* remove completely unconnected facets */ - for(i = 0; i < stl->stats.number_of_facets; i++) { - if( (stl->neighbors_start[i].neighbor[0] == -1) - && (stl->neighbors_start[i].neighbor[1] == -1) - && (stl->neighbors_start[i].neighbor[2] == -1)) { - /* This facet is completely unconnected. Remove it. */ + // remove completely unconnected facets + for (int i = 0; i < stl->stats.number_of_facets; i++) { + if (stl->neighbors_start[i].neighbor[0] == -1 && + stl->neighbors_start[i].neighbor[1] == -1 && + stl->neighbors_start[i].neighbor[2] == -1) { + // This facet is completely unconnected. Remove it. stl_remove_facet(stl, i); - i--; + -- i; } } } @@ -771,30 +702,24 @@ stl_remove_degenerate(stl_file *stl, int facet) { if (stl->error) return; - if( !memcmp(&stl->facet_start[facet].vertex[0], - &stl->facet_start[facet].vertex[1], sizeof(stl_vertex)) - && !memcmp(&stl->facet_start[facet].vertex[1], - &stl->facet_start[facet].vertex[2], sizeof(stl_vertex))) { + if (stl->facet_start[facet].vertex[0] == stl->facet_start[facet].vertex[1] && + stl->facet_start[facet].vertex[1] == stl->facet_start[facet].vertex[2]) { /* all 3 vertices are equal. Just remove the facet. I don't think*/ /* this is really possible, but just in case... */ printf("removing a facet in stl_remove_degenerate\n"); - stl_remove_facet(stl, facet); return; } - if(!memcmp(&stl->facet_start[facet].vertex[0], - &stl->facet_start[facet].vertex[1], sizeof(stl_vertex))) { + if (stl->facet_start[facet].vertex[0] == stl->facet_start[facet].vertex[1]) { edge1 = 1; edge2 = 2; edge3 = 0; - } else if(!memcmp(&stl->facet_start[facet].vertex[1], - &stl->facet_start[facet].vertex[2], sizeof(stl_vertex))) { + } else if (stl->facet_start[facet].vertex[1] == stl->facet_start[facet].vertex[2]) { edge1 = 0; edge2 = 2; edge3 = 1; - } else if(!memcmp(&stl->facet_start[facet].vertex[2], - &stl->facet_start[facet].vertex[0], sizeof(stl_vertex))) { + } else if (stl->facet_start[facet].vertex[2] == stl->facet_start[facet].vertex[0]) { edge1 = 0; edge2 = 1; edge3 = 2; @@ -883,7 +808,7 @@ stl_fill_holes(stl_file *stl) { stl_load_edge_exact(stl, &edge, &facet.vertex[j], &facet.vertex[(j + 1) % 3]); - insert_hash_edge(stl, edge, stl_match_neighbors_exact); + insert_hash_edge(stl, edge, stl_record_neighbors); } } @@ -939,7 +864,7 @@ stl_fill_holes(stl_file *stl) { stl_load_edge_exact(stl, &edge, &new_facet.vertex[k], &new_facet.vertex[(k + 1) % 3]); - insert_hash_edge(stl, edge, stl_match_neighbors_exact); + insert_hash_edge(stl, edge, stl_record_neighbors); } break; } else { @@ -977,9 +902,7 @@ stl_add_facet(stl_file *stl, stl_facet *new_facet) { stl->facet_start[stl->stats.number_of_facets] = *new_facet; /* note that the normal vector is not set here, just initialized to 0 */ - stl->facet_start[stl->stats.number_of_facets].normal.x = 0.0; - stl->facet_start[stl->stats.number_of_facets].normal.y = 0.0; - stl->facet_start[stl->stats.number_of_facets].normal.z = 0.0; + stl->facet_start[stl->stats.number_of_facets].normal = stl_normal::Zero(); stl->neighbors_start[stl->stats.number_of_facets].neighbor[0] = -1; stl->neighbors_start[stl->stats.number_of_facets].neighbor[1] = -1; diff --git a/xs/src/admesh/normals.cpp b/xs/src/admesh/normals.cpp index b7cf9a8ab7..a8faa44bd2 100644 --- a/xs/src/admesh/normals.cpp +++ b/xs/src/admesh/normals.cpp @@ -27,12 +27,6 @@ #include "stl.h" -static void stl_reverse_vector(float v[]) { - v[0] *= -1; - v[1] *= -1; - v[2] *= -1; -} - static int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag); static void @@ -228,102 +222,52 @@ static int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_ /* Returns 2 if the normal is not within tolerance and backwards */ /* Returns 4 if the status is unknown. */ - float normal[3]; - float test_norm[3]; stl_facet *facet; facet = &stl->facet_start[facet_num]; + stl_normal normal; stl_calculate_normal(normal, facet); stl_normalize_vector(normal); + stl_normal normal_dif = (normal - facet->normal).cwiseAbs(); - if( (ABS(normal[0] - facet->normal.x) < 0.001) - && (ABS(normal[1] - facet->normal.y) < 0.001) - && (ABS(normal[2] - facet->normal.z) < 0.001)) { + const float eps = 0.001f; + if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) { /* It is not really necessary to change the values here */ /* but just for consistency, I will. */ - facet->normal.x = normal[0]; - facet->normal.y = normal[1]; - facet->normal.z = normal[2]; + facet->normal = normal; return 0; } - test_norm[0] = facet->normal.x; - test_norm[1] = facet->normal.y; - test_norm[2] = facet->normal.z; - + stl_normal test_norm = facet->normal; stl_normalize_vector(test_norm); - if( (ABS(normal[0] - test_norm[0]) < 0.001) - && (ABS(normal[1] - test_norm[1]) < 0.001) - && (ABS(normal[2] - test_norm[2]) < 0.001)) { + normal_dif = (normal - test_norm).cwiseAbs(); + if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) { if(normal_fix_flag) { - facet->normal.x = normal[0]; - facet->normal.y = normal[1]; - facet->normal.z = normal[2]; + facet->normal = normal; stl->stats.normals_fixed += 1; } return 1; } - stl_reverse_vector(test_norm); - if( (ABS(normal[0] - test_norm[0]) < 0.001) - && (ABS(normal[1] - test_norm[1]) < 0.001) - && (ABS(normal[2] - test_norm[2]) < 0.001)) { - /* Facet is backwards. */ + test_norm *= -1.f; + normal_dif = (normal - test_norm).cwiseAbs(); + if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) { + // Facet is backwards. if(normal_fix_flag) { - facet->normal.x = normal[0]; - facet->normal.y = normal[1]; - facet->normal.z = normal[2]; + facet->normal = normal; stl->stats.normals_fixed += 1; } return 2; } if(normal_fix_flag) { - facet->normal.x = normal[0]; - facet->normal.y = normal[1]; - facet->normal.z = normal[2]; + facet->normal = normal; stl->stats.normals_fixed += 1; } return 4; } -void stl_calculate_normal(float normal[], stl_facet *facet) { - float v1[3] = { - facet->vertex[1].x - facet->vertex[0].x, - facet->vertex[1].y - facet->vertex[0].y, - facet->vertex[1].z - facet->vertex[0].z - }; - float v2[3] = { - facet->vertex[2].x - facet->vertex[0].x, - facet->vertex[2].y - facet->vertex[0].y, - facet->vertex[2].z - facet->vertex[0].z - }; - normal[0] = (float)((double)v1[1] * (double)v2[2]) - ((double)v1[2] * (double)v2[1]); - normal[1] = (float)((double)v1[2] * (double)v2[0]) - ((double)v1[0] * (double)v2[2]); - normal[2] = (float)((double)v1[0] * (double)v2[1]) - ((double)v1[1] * (double)v2[0]); -} - -void stl_normalize_vector(float v[]) { - double length; - double factor; - float min_normal_length; - - length = sqrt((double)v[0] * (double)v[0] + (double)v[1] * (double)v[1] + (double)v[2] * (double)v[2]); - min_normal_length = 0.000000000001; - if(length < min_normal_length) { - v[0] = 0.0; - v[1] = 0.0; - v[2] = 0.0; - return; - } - factor = 1.0 / length; - v[0] *= factor; - v[1] *= factor; - v[2] *= factor; -} - -void -stl_fix_normal_values(stl_file *stl) { +void stl_fix_normal_values(stl_file *stl) { int i; if (stl->error) return; @@ -333,20 +277,16 @@ stl_fix_normal_values(stl_file *stl) { } } -void -stl_reverse_all_facets(stl_file *stl) { - int i; - float normal[3]; +void stl_reverse_all_facets(stl_file *stl) +{ + if (stl->error) + return; - if (stl->error) return; - - for(i = 0; i < stl->stats.number_of_facets; i++) { + stl_normal normal; + for(int i = 0; i < stl->stats.number_of_facets; i++) { stl_reverse_facet(stl, i); stl_calculate_normal(normal, &stl->facet_start[i]); stl_normalize_vector(normal); - stl->facet_start[i].normal.x = normal[0]; - stl->facet_start[i].normal.y = normal[1]; - stl->facet_start[i].normal.z = normal[2]; + stl->facet_start[i].normal = normal; } } - diff --git a/xs/src/admesh/shared.cpp b/xs/src/admesh/shared.cpp index 8080f3574e..91bb82e006 100644 --- a/xs/src/admesh/shared.cpp +++ b/xs/src/admesh/shared.cpp @@ -169,7 +169,7 @@ stl_write_off(stl_file *stl, char *file) { for(i = 0; i < stl->stats.shared_vertices; i++) { fprintf(fp, "\t%f %f %f\n", - stl->v_shared[i].x, stl->v_shared[i].y, stl->v_shared[i].z); + stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); } for(i = 0; i < stl->stats.number_of_facets; i++) { fprintf(fp, "\t3 %d %d %d\n", stl->v_indices[i].vertex[0], @@ -216,10 +216,10 @@ stl_write_vrml(stl_file *stl, char *file) { for(i = 0; i < (stl->stats.shared_vertices - 1); i++) { fprintf(fp, "\t\t\t\t%f %f %f,\n", - stl->v_shared[i].x, stl->v_shared[i].y, stl->v_shared[i].z); + stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); } fprintf(fp, "\t\t\t\t%f %f %f]\n", - stl->v_shared[i].x, stl->v_shared[i].y, stl->v_shared[i].z); + stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); fprintf(fp, "\t\t}\n"); fprintf(fp, "\t\tDEF STLTriangles IndexedFaceSet {\n"); fprintf(fp, "\t\t\tcoordIndex [\n"); @@ -254,7 +254,7 @@ void stl_write_obj (stl_file *stl, char *file) { } for (i = 0; i < stl->stats.shared_vertices; i++) { - fprintf(fp, "v %f %f %f\n", stl->v_shared[i].x, stl->v_shared[i].y, stl->v_shared[i].z); + fprintf(fp, "v %f %f %f\n", stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); } for (i = 0; i < stl->stats.number_of_facets; i++) { fprintf(fp, "f %d %d %d\n", stl->v_indices[i].vertex[0]+1, stl->v_indices[i].vertex[1]+1, stl->v_indices[i].vertex[2]+1); diff --git a/xs/src/admesh/stl.h b/xs/src/admesh/stl.h index dc0f7ae195..5f7a3c5c14 100644 --- a/xs/src/admesh/stl.h +++ b/xs/src/admesh/stl.h @@ -27,9 +27,7 @@ #include #include -#define STL_MAX(A,B) ((A)>(B)? (A):(B)) -#define STL_MIN(A,B) ((A)<(B)? (A):(B)) -#define ABS(X) ((X) < 0 ? -(X) : (X)) +#include // Size of the binary STL header, free form. #define LABEL_SIZE 80 @@ -39,31 +37,16 @@ #define HEADER_SIZE 84 #define STL_MIN_FILE_SIZE 284 #define ASCII_LINES_PER_FACET 7 -// Comparing an edge by memcmp, 2x3x4 bytes = 24 -#define SIZEOF_EDGE_SORT 24 - -typedef struct { - float x; - float y; - float z; -} stl_vertex; +typedef Eigen::Matrix stl_vertex; +typedef Eigen::Matrix stl_normal; static_assert(sizeof(stl_vertex) == 12, "size of stl_vertex incorrect"); - -typedef struct { - float x; - float y; - float z; -} stl_normal; - static_assert(sizeof(stl_normal) == 12, "size of stl_normal incorrect"); -typedef char stl_extra[2]; - typedef struct { stl_normal normal; stl_vertex vertex[3]; - stl_extra extra; + char extra[2]; } stl_facet; #define SIZEOF_STL_FACET 50 @@ -81,8 +64,12 @@ typedef struct { } stl_edge; typedef struct stl_hash_edge { - // Key of a hash edge: 2x binary copy of a floating point vertex. - uint32_t key[6]; + // Key of a hash edge: sorted vertices of the edge. + unsigned char key[2 * sizeof(stl_vertex)]; + // Compare two keys. + bool operator==(const stl_hash_edge &rhs) { return memcmp(key, rhs.key, sizeof(key)) == 0; } + bool operator!=(const stl_hash_edge &rhs) { return ! (*this == rhs); } + int hash(int M) const { return ((key[0] / 23 + key[1] / 19 + key[2] / 17 + key[3] /13 + key[4] / 11 + key[5] / 7 ) % M); } // Index of a facet owning this edge. int facet_number; // Index of this edge inside the facet with an index of facet_number. @@ -91,8 +78,6 @@ typedef struct stl_hash_edge { struct stl_hash_edge *next; } stl_hash_edge; -static_assert(offsetof(stl_hash_edge, facet_number) == SIZEOF_EDGE_SORT, "size of stl_hash_edge.key incorrect"); - typedef struct { // Index of a neighbor facet. int neighbor[3]; @@ -179,8 +164,8 @@ extern void stl_fix_normal_values(stl_file *stl); extern void stl_reverse_all_facets(stl_file *stl); extern void stl_translate(stl_file *stl, float x, float y, float z); extern void stl_translate_relative(stl_file *stl, float x, float y, float z); -extern void stl_scale_versor(stl_file *stl, float versor[3]); -extern void stl_scale(stl_file *stl, float factor); +extern void stl_scale_versor(stl_file *stl, const stl_vertex &versor); +inline void stl_scale(stl_file *stl, float factor) { stl_scale_versor(stl, stl_vertex(factor, factor, factor)); } extern void stl_rotate_x(stl_file *stl, float angle); extern void stl_rotate_y(stl_file *stl, float angle); extern void stl_rotate_z(stl_file *stl, float angle); @@ -195,8 +180,20 @@ extern void stl_write_obj(stl_file *stl, char *file); extern void stl_write_off(stl_file *stl, char *file); extern void stl_write_dxf(stl_file *stl, char *file, char *label); extern void stl_write_vrml(stl_file *stl, char *file); -extern void stl_calculate_normal(float normal[], stl_facet *facet); -extern void stl_normalize_vector(float v[]); +inline void stl_calculate_normal(stl_normal &normal, stl_facet *facet) { + normal = (facet->vertex[1] - facet->vertex[0]).cross(facet->vertex[2] - facet->vertex[0]); +} +inline void stl_normalize_vector(stl_normal &normal) { + double length = normal.cast().norm(); + if (length < 0.000000000001) + normal = stl_normal::Zero(); + else + normal *= (1.0 / length); +} +inline bool stl_vertex_lower(const stl_vertex &a, const stl_vertex &b) { + return (a(0) != b(0)) ? (a(0) < b(0)) : + ((a(1) != b(1)) ? (a(1) < b(1)) : (a(2) < b(2))); +} extern void stl_calculate_volume(stl_file *stl); extern void stl_repair(stl_file *stl, int fixall_flag, int exact_flag, int tolerance_flag, float tolerance, int increment_flag, float increment, int nearby_flag, int iterations, int remove_unconnected_flag, int fill_holes_flag, int normal_directions_flag, int normal_values_flag, int reverse_all_flag, int verbose_flag); @@ -204,8 +201,8 @@ extern void stl_repair(stl_file *stl, int fixall_flag, int exact_flag, int toler extern void stl_initialize(stl_file *stl); extern void stl_count_facets(stl_file *stl, const char *file); extern void stl_allocate(stl_file *stl); -extern void stl_read(stl_file *stl, int first_facet, int first); -extern void stl_facet_stats(stl_file *stl, stl_facet facet, int first); +extern void stl_read(stl_file *stl, int first_facet, bool first); +extern void stl_facet_stats(stl_file *stl, stl_facet facet, bool &first); extern void stl_reallocate(stl_file *stl); extern void stl_add_facet(stl_file *stl, stl_facet *new_facet); extern void stl_get_size(stl_file *stl); diff --git a/xs/src/admesh/stl_io.cpp b/xs/src/admesh/stl_io.cpp index 1603981fcb..81e29d2860 100644 --- a/xs/src/admesh/stl_io.cpp +++ b/xs/src/admesh/stl_io.cpp @@ -44,9 +44,9 @@ stl_print_edges(stl_file *stl, FILE *file) { for(i = 0; i < edges_allocated; i++) { fprintf(file, "%d, %f, %f, %f, %f, %f, %f\n", stl->edge_start[i].facet_number, - stl->edge_start[i].p1.x, stl->edge_start[i].p1.y, - stl->edge_start[i].p1.z, stl->edge_start[i].p2.x, - stl->edge_start[i].p2.y, stl->edge_start[i].p2.z); + stl->edge_start[i].p1(0), stl->edge_start[i].p1(1), + stl->edge_start[i].p1(2), stl->edge_start[i].p2(0), + stl->edge_start[i].p2(1), stl->edge_start[i].p2(2)); } } @@ -75,11 +75,11 @@ File type : ASCII STL file\n"); Header : %s\n", stl->stats.header); fprintf(file, "============== Size ==============\n"); fprintf(file, "Min X = % f, Max X = % f\n", - stl->stats.min.x, stl->stats.max.x); + stl->stats.min(0), stl->stats.max(0)); fprintf(file, "Min Y = % f, Max Y = % f\n", - stl->stats.min.y, stl->stats.max.y); + stl->stats.min(1), stl->stats.max(1)); fprintf(file, "Min Z = % f, Max Z = % f\n", - stl->stats.min.z, stl->stats.max.z); + stl->stats.min(2), stl->stats.max(2)); fprintf(file, "\ ========= Facet Status ========== Original ============ Final ====\n"); @@ -149,18 +149,18 @@ stl_write_ascii(stl_file *stl, const char *file, const char *label) { for(i = 0; i < stl->stats.number_of_facets; i++) { fprintf(fp, " facet normal % .8E % .8E % .8E\n", - stl->facet_start[i].normal.x, stl->facet_start[i].normal.y, - stl->facet_start[i].normal.z); + stl->facet_start[i].normal(0), stl->facet_start[i].normal(1), + stl->facet_start[i].normal(2)); fprintf(fp, " outer loop\n"); fprintf(fp, " vertex % .8E % .8E % .8E\n", - stl->facet_start[i].vertex[0].x, stl->facet_start[i].vertex[0].y, - stl->facet_start[i].vertex[0].z); + stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), + stl->facet_start[i].vertex[0](2)); fprintf(fp, " vertex % .8E % .8E % .8E\n", - stl->facet_start[i].vertex[1].x, stl->facet_start[i].vertex[1].y, - stl->facet_start[i].vertex[1].z); + stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), + stl->facet_start[i].vertex[1](2)); fprintf(fp, " vertex % .8E % .8E % .8E\n", - stl->facet_start[i].vertex[2].x, stl->facet_start[i].vertex[2].y, - stl->facet_start[i].vertex[2].z); + stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), + stl->facet_start[i].vertex[2](2)); fprintf(fp, " endloop\n"); fprintf(fp, " endfacet\n"); } @@ -264,9 +264,9 @@ void stl_write_vertex(stl_file *stl, int facet, int vertex) { if (stl->error) return; printf(" vertex %d/%d % .8E % .8E % .8E\n", vertex, facet, - stl->facet_start[facet].vertex[vertex].x, - stl->facet_start[facet].vertex[vertex].y, - stl->facet_start[facet].vertex[vertex].z); + stl->facet_start[facet].vertex[vertex](0), + stl->facet_start[facet].vertex[vertex](1), + stl->facet_start[facet].vertex[vertex](2)); } void @@ -309,10 +309,10 @@ stl_write_quad_object(stl_file *stl, char *file) { int i; int j; char *error_msg; - stl_vertex connect_color; - stl_vertex uncon_1_color; - stl_vertex uncon_2_color; - stl_vertex uncon_3_color; + stl_vertex connect_color = stl_vertex::Zero(); + stl_vertex uncon_1_color = stl_vertex::Zero(); + stl_vertex uncon_2_color = stl_vertex::Zero(); + stl_vertex uncon_3_color = stl_vertex::Zero(); stl_vertex color; if (stl->error) return; @@ -330,19 +330,6 @@ stl_write_quad_object(stl_file *stl, char *file) { return; } - connect_color.x = 0.0; - connect_color.y = 0.0; - connect_color.z = 1.0; - uncon_1_color.x = 0.0; - uncon_1_color.y = 1.0; - uncon_1_color.z = 0.0; - uncon_2_color.x = 1.0; - uncon_2_color.y = 1.0; - uncon_2_color.z = 1.0; - uncon_3_color.x = 1.0; - uncon_3_color.y = 0.0; - uncon_3_color.z = 0.0; - fprintf(fp, "CQUAD\n"); for(i = 0; i < stl->stats.number_of_facets; i++) { j = ((stl->neighbors_start[i].neighbor[0] == -1) + @@ -358,21 +345,21 @@ stl_write_quad_object(stl_file *stl, char *file) { color = uncon_3_color; } fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", - stl->facet_start[i].vertex[0].x, - stl->facet_start[i].vertex[0].y, - stl->facet_start[i].vertex[0].z, color.x, color.y, color.z); + stl->facet_start[i].vertex[0](0), + stl->facet_start[i].vertex[0](1), + stl->facet_start[i].vertex[0](2), color(0), color(1), color(2)); fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", - stl->facet_start[i].vertex[1].x, - stl->facet_start[i].vertex[1].y, - stl->facet_start[i].vertex[1].z, color.x, color.y, color.z); + stl->facet_start[i].vertex[1](0), + stl->facet_start[i].vertex[1](1), + stl->facet_start[i].vertex[1](2), color(0), color(1), color(2)); fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", - stl->facet_start[i].vertex[2].x, - stl->facet_start[i].vertex[2].y, - stl->facet_start[i].vertex[2].z, color.x, color.y, color.z); + stl->facet_start[i].vertex[2](0), + stl->facet_start[i].vertex[2](1), + stl->facet_start[i].vertex[2](2), color(0), color(1), color(2)); fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", - stl->facet_start[i].vertex[2].x, - stl->facet_start[i].vertex[2].y, - stl->facet_start[i].vertex[2].z, color.x, color.y, color.z); + stl->facet_start[i].vertex[2](0), + stl->facet_start[i].vertex[2](1), + stl->facet_start[i].vertex[2](2), color(0), color(1), color(2)); } fclose(fp); } @@ -409,17 +396,17 @@ stl_write_dxf(stl_file *stl, char *file, char *label) { for(i = 0; i < stl->stats.number_of_facets; i++) { fprintf(fp, "0\n3DFACE\n8\n0\n"); fprintf(fp, "10\n%f\n20\n%f\n30\n%f\n", - stl->facet_start[i].vertex[0].x, stl->facet_start[i].vertex[0].y, - stl->facet_start[i].vertex[0].z); + stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), + stl->facet_start[i].vertex[0](2)); fprintf(fp, "11\n%f\n21\n%f\n31\n%f\n", - stl->facet_start[i].vertex[1].x, stl->facet_start[i].vertex[1].y, - stl->facet_start[i].vertex[1].z); + stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), + stl->facet_start[i].vertex[1](2)); fprintf(fp, "12\n%f\n22\n%f\n32\n%f\n", - stl->facet_start[i].vertex[2].x, stl->facet_start[i].vertex[2].y, - stl->facet_start[i].vertex[2].z); + stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), + stl->facet_start[i].vertex[2](2)); fprintf(fp, "13\n%f\n23\n%f\n33\n%f\n", - stl->facet_start[i].vertex[2].x, stl->facet_start[i].vertex[2].y, - stl->facet_start[i].vertex[2].z); + stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), + stl->facet_start[i].vertex[2](2)); } fprintf(fp, "0\nENDSEC\n0\nEOF\n"); diff --git a/xs/src/admesh/stlinit.cpp b/xs/src/admesh/stlinit.cpp index e572ce9303..47a37f0a54 100644 --- a/xs/src/admesh/stlinit.cpp +++ b/xs/src/admesh/stlinit.cpp @@ -40,7 +40,7 @@ stl_open(stl_file *stl, const char *file) { stl_initialize(stl); stl_count_facets(stl, file); stl_allocate(stl); - stl_read(stl, 0, 1); + stl_read(stl, 0, true); if (!stl->error) fclose(stl->fp); } @@ -227,7 +227,7 @@ stl_open_merge(stl_file *stl, char *file_to_merge) { Start at num_facets_so_far, the index to the first unused facet. Also say that this isn't our first time so we should augment stats like min and max instead of erasing them. */ - stl_read(stl, num_facets_so_far, 0); + stl_read(stl, num_facets_so_far, false); /* Restore the stl information we overwrote (for stl_read) so that it still accurately reflects the subject part: */ @@ -255,8 +255,7 @@ stl_reallocate(stl_file *stl) { /* Reads the contents of the file pointed to by stl->fp into the stl structure, starting at facet first_facet. The second argument says if it's our first time running this for the stl and therefore we should reset our max and min stats. */ -void -stl_read(stl_file *stl, int first_facet, int first) { +void stl_read(stl_file *stl, int first_facet, bool first) { stl_facet facet; int i; @@ -294,11 +293,11 @@ stl_read(stl_file *stl, int first_facet, int first) { assert(res_normal == 3); int res_outer_loop = fscanf(stl->fp, " outer loop"); assert(res_outer_loop == 0); - int res_vertex1 = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[0].x, &facet.vertex[0].y, &facet.vertex[0].z); + int res_vertex1 = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[0](0), &facet.vertex[0](1), &facet.vertex[0](2)); assert(res_vertex1 == 3); - int res_vertex2 = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[1].x, &facet.vertex[1].y, &facet.vertex[1].z); + int res_vertex2 = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[1](0), &facet.vertex[1](1), &facet.vertex[1](2)); assert(res_vertex2 == 3); - int res_vertex3 = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[2].x, &facet.vertex[2].y, &facet.vertex[2].z); + int res_vertex3 = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[2](0), &facet.vertex[2](1), &facet.vertex[2](2)); assert(res_vertex3 == 3); int res_endloop = fscanf(stl->fp, " endloop"); assert(res_endloop == 0); @@ -311,9 +310,9 @@ stl_read(stl_file *stl, int first_facet, int first) { } // The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition. - if (sscanf(normal_buf[0], "%f", &facet.normal.x) != 1 || - sscanf(normal_buf[1], "%f", &facet.normal.y) != 1 || - sscanf(normal_buf[2], "%f", &facet.normal.z) != 1) { + if (sscanf(normal_buf[0], "%f", &facet.normal(0)) != 1 || + sscanf(normal_buf[1], "%f", &facet.normal(1)) != 1 || + sscanf(normal_buf[2], "%f", &facet.normal(2)) != 1) { // Normal was mangled. Maybe denormals or "not a number" were stored? // Just reset the normal and silently ignore it. memset(&facet.normal, 0, sizeof(facet.normal)); @@ -326,104 +325,45 @@ stl_read(stl_file *stl, int first_facet, int first) { // It may be worth to round these numbers to zero during loading to reduce the number of errors reported // during the STL import. for (size_t j = 0; j < 3; ++ j) { - if (facet.vertex[j].x > -1e-12f && facet.vertex[j].x < 1e-12f) - printf("stl_read: facet %d.x = %e\r\n", j, facet.vertex[j].x); - if (facet.vertex[j].y > -1e-12f && facet.vertex[j].y < 1e-12f) - printf("stl_read: facet %d.y = %e\r\n", j, facet.vertex[j].y); - if (facet.vertex[j].z > -1e-12f && facet.vertex[j].z < 1e-12f) - printf("stl_read: facet %d.z = %e\r\n", j, facet.vertex[j].z); + if (facet.vertex[j](0) > -1e-12f && facet.vertex[j](0) < 1e-12f) + printf("stl_read: facet %d(0) = %e\r\n", j, facet.vertex[j](0)); + if (facet.vertex[j](1) > -1e-12f && facet.vertex[j](1) < 1e-12f) + printf("stl_read: facet %d(1) = %e\r\n", j, facet.vertex[j](1)); + if (facet.vertex[j](2) > -1e-12f && facet.vertex[j](2) < 1e-12f) + printf("stl_read: facet %d(2) = %e\r\n", j, facet.vertex[j](2)); } #endif -#if 1 - { - // Positive and negative zeros are possible in the floats, which are considered equal by the FP unit. - // When using a memcmp on raw floats, those numbers report to be different. - // Unify all +0 and -0 to +0 to make the floats equal under memcmp. - uint32_t *f = (uint32_t*)&facet; - for (int j = 0; j < 12; ++ j, ++ f) // 3x vertex + normal: 4x3 = 12 floats - if (*f == 0x80000000) - // Negative zero, switch to positive zero. - *f = 0; - } -#else - { - // Due to the nature of the floating point numbers, close to zero values may be represented with singificantly higher precision - // than the rest of the vertices. Round them to zero. - float *f = (float*)&facet; - for (int j = 0; j < 12; ++ j, ++ f) // 3x vertex + normal: 4x3 = 12 floats - if (*f > -1e-12f && *f < 1e-12f) - // Negative zero, switch to positive zero. - *f = 0; - } -#endif /* Write the facet into memory. */ - memcpy(stl->facet_start+i, &facet, SIZEOF_STL_FACET); + stl->facet_start[i] = facet; stl_facet_stats(stl, facet, first); - first = 0; } - stl->stats.size.x = stl->stats.max.x - stl->stats.min.x; - stl->stats.size.y = stl->stats.max.y - stl->stats.min.y; - stl->stats.size.z = stl->stats.max.z - stl->stats.min.z; - stl->stats.bounding_diameter = sqrt( - stl->stats.size.x * stl->stats.size.x + - stl->stats.size.y * stl->stats.size.y + - stl->stats.size.z * stl->stats.size.z - ); + stl->stats.size = stl->stats.max - stl->stats.min; + stl->stats.bounding_diameter = stl->stats.size.norm(); } -void -stl_facet_stats(stl_file *stl, stl_facet facet, int first) { - float diff_x; - float diff_y; - float diff_z; - float max_diff; +void stl_facet_stats(stl_file *stl, stl_facet facet, bool &first) +{ + if (stl->error) + return; - if (stl->error) return; + // While we are going through all of the facets, let's find the + // maximum and minimum values for x, y, and z - /* while we are going through all of the facets, let's find the */ - /* maximum and minimum values for x, y, and z */ - - /* Initialize the max and min values the first time through*/ if (first) { - stl->stats.max.x = facet.vertex[0].x; - stl->stats.min.x = facet.vertex[0].x; - stl->stats.max.y = facet.vertex[0].y; - stl->stats.min.y = facet.vertex[0].y; - stl->stats.max.z = facet.vertex[0].z; - stl->stats.min.z = facet.vertex[0].z; - - diff_x = ABS(facet.vertex[0].x - facet.vertex[1].x); - diff_y = ABS(facet.vertex[0].y - facet.vertex[1].y); - diff_z = ABS(facet.vertex[0].z - facet.vertex[1].z); - max_diff = STL_MAX(diff_x, diff_y); - max_diff = STL_MAX(diff_z, max_diff); - stl->stats.shortest_edge = max_diff; - - first = 0; + // Initialize the max and min values the first time through + stl->stats.min = facet.vertex[0]; + stl->stats.max = facet.vertex[0]; + stl_vertex diff = (facet.vertex[1] - facet.vertex[0]).cwiseAbs(); + stl->stats.shortest_edge = std::max(diff(0), std::max(diff(1), diff(2))); + first = false; } - /* now find the max and min values */ - stl->stats.max.x = STL_MAX(stl->stats.max.x, facet.vertex[0].x); - stl->stats.min.x = STL_MIN(stl->stats.min.x, facet.vertex[0].x); - stl->stats.max.y = STL_MAX(stl->stats.max.y, facet.vertex[0].y); - stl->stats.min.y = STL_MIN(stl->stats.min.y, facet.vertex[0].y); - stl->stats.max.z = STL_MAX(stl->stats.max.z, facet.vertex[0].z); - stl->stats.min.z = STL_MIN(stl->stats.min.z, facet.vertex[0].z); - - stl->stats.max.x = STL_MAX(stl->stats.max.x, facet.vertex[1].x); - stl->stats.min.x = STL_MIN(stl->stats.min.x, facet.vertex[1].x); - stl->stats.max.y = STL_MAX(stl->stats.max.y, facet.vertex[1].y); - stl->stats.min.y = STL_MIN(stl->stats.min.y, facet.vertex[1].y); - stl->stats.max.z = STL_MAX(stl->stats.max.z, facet.vertex[1].z); - stl->stats.min.z = STL_MIN(stl->stats.min.z, facet.vertex[1].z); - - stl->stats.max.x = STL_MAX(stl->stats.max.x, facet.vertex[2].x); - stl->stats.min.x = STL_MIN(stl->stats.min.x, facet.vertex[2].x); - stl->stats.max.y = STL_MAX(stl->stats.max.y, facet.vertex[2].y); - stl->stats.min.y = STL_MIN(stl->stats.min.y, facet.vertex[2].y); - stl->stats.max.z = STL_MAX(stl->stats.max.z, facet.vertex[2].z); - stl->stats.min.z = STL_MIN(stl->stats.min.z, facet.vertex[2].z); + // Now find the max and min values. + for (size_t i = 0; i < 3; ++ i) { + stl->stats.min = stl->stats.min.cwiseMin(facet.vertex[i]); + stl->stats.max = stl->stats.max.cwiseMax(facet.vertex[i]); + } } void diff --git a/xs/src/admesh/util.cpp b/xs/src/admesh/util.cpp index f3bf59b56e..fba1ee76bf 100644 --- a/xs/src/admesh/util.cpp +++ b/xs/src/admesh/util.cpp @@ -62,7 +62,7 @@ stl_verify_neighbors(stl_file *stl) { edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3]; edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3]; } - if(memcmp(&edge_a, &edge_b, SIZEOF_EDGE_SORT) != 0) { + if (edge_a.p1 != edge_b.p1 || edge_a.p2 != edge_b.p2) { /* These edges should match but they don't. Print results. */ printf("edge %d of facet %d doesn't match edge %d of facet %d\n", j, i, vnot + 1, neighbor); @@ -73,114 +73,67 @@ stl_verify_neighbors(stl_file *stl) { } } -void -stl_translate(stl_file *stl, float x, float y, float z) { - int i; - int j; - - if (stl->error) return; - - for(i = 0; i < stl->stats.number_of_facets; i++) { - for(j = 0; j < 3; j++) { - stl->facet_start[i].vertex[j].x -= (stl->stats.min.x - x); - stl->facet_start[i].vertex[j].y -= (stl->stats.min.y - y); - stl->facet_start[i].vertex[j].z -= (stl->stats.min.z - z); - } - } - stl->stats.max.x -= (stl->stats.min.x - x); - stl->stats.max.y -= (stl->stats.min.y - y); - stl->stats.max.z -= (stl->stats.min.z - z); - stl->stats.min.x = x; - stl->stats.min.y = y; - stl->stats.min.z = z; +void stl_translate(stl_file *stl, float x, float y, float z) +{ + if (stl->error) + return; + stl_vertex new_min(x, y, z); + stl_vertex shift = new_min - stl->stats.min; + for (int i = 0; i < stl->stats.number_of_facets; ++ i) + for (int j = 0; j < 3; ++ j) + stl->facet_start[i].vertex[j] += shift; + stl->stats.min = new_min; + stl->stats.max += shift; stl_invalidate_shared_vertices(stl); } /* Translates the stl by x,y,z, relatively from wherever it is currently */ -void -stl_translate_relative(stl_file *stl, float x, float y, float z) { - int i; - int j; - - if (stl->error) return; - - for(i = 0; i < stl->stats.number_of_facets; i++) { - for(j = 0; j < 3; j++) { - stl->facet_start[i].vertex[j].x += x; - stl->facet_start[i].vertex[j].y += y; - stl->facet_start[i].vertex[j].z += z; - } - } - stl->stats.min.x += x; - stl->stats.min.y += y; - stl->stats.min.z += z; - stl->stats.max.x += x; - stl->stats.max.y += y; - stl->stats.max.z += z; +void stl_translate_relative(stl_file *stl, float x, float y, float z) +{ + if (stl->error) + return; + stl_vertex shift(x, y, z); + for (int i = 0; i < stl->stats.number_of_facets; ++ i) + for (int j = 0; j < 3; ++ j) + stl->facet_start[i].vertex[j] += shift; + stl->stats.min += shift; + stl->stats.max += shift; stl_invalidate_shared_vertices(stl); } -void -stl_scale_versor(stl_file *stl, float versor[3]) { - int i; - int j; - - if (stl->error) return; - - /* scale extents */ - stl->stats.min.x *= versor[0]; - stl->stats.min.y *= versor[1]; - stl->stats.min.z *= versor[2]; - stl->stats.max.x *= versor[0]; - stl->stats.max.y *= versor[1]; - stl->stats.max.z *= versor[2]; - - /* scale size */ - stl->stats.size.x *= versor[0]; - stl->stats.size.y *= versor[1]; - stl->stats.size.z *= versor[2]; - - /* scale volume */ - if (stl->stats.volume > 0.0) { - stl->stats.volume *= (versor[0] * versor[1] * versor[2]); - } - - for(i = 0; i < stl->stats.number_of_facets; i++) { - for(j = 0; j < 3; j++) { - stl->facet_start[i].vertex[j].x *= versor[0]; - stl->facet_start[i].vertex[j].y *= versor[1]; - stl->facet_start[i].vertex[j].z *= versor[2]; - } - } +void stl_scale_versor(stl_file *stl, const stl_vertex &versor) +{ + if (stl->error) + return; + // Scale extents. + auto s = versor.array(); + stl->stats.min.array() *= s; + stl->stats.max.array() *= s; + // Scale size. + stl->stats.size.array() *= s; + // Scale volume. + if (stl->stats.volume > 0.0) + stl->stats.volume *= versor(0) * versor(1) * versor(2); + // Scale the mesh. + for (int i = 0; i < stl->stats.number_of_facets; ++ i) + for (int j = 0; j < 3; ++ j) + stl->facet_start[i].vertex[j].array() *= s; stl_invalidate_shared_vertices(stl); } -void -stl_scale(stl_file *stl, float factor) { - float versor[3]; - - if (stl->error) return; - - versor[0] = factor; - versor[1] = factor; - versor[2] = factor; - stl_scale_versor(stl, versor); -} - -static void calculate_normals(stl_file *stl) { - float normal[3]; - - if (stl->error) return; +static void calculate_normals(stl_file *stl) +{ + if (stl->error) + return; + stl_normal normal; for(uint32_t i = 0; i < stl->stats.number_of_facets; i++) { stl_calculate_normal(normal, &stl->facet_start[i]); stl_normalize_vector(normal); - stl->facet_start[i].normal.x = normal[0]; - stl->facet_start[i].normal.y = normal[1]; - stl->facet_start[i].normal.z = normal[2]; + stl->facet_start[i].normal = normal; } } @@ -193,9 +146,9 @@ void stl_transform(stl_file *stl, float *trafo3x4) { for (i_vertex = 0; i_vertex < 3; ++ i_vertex) { stl_vertex &v_dst = vertices[i_vertex]; stl_vertex v_src = v_dst; - v_dst.x = trafo3x4[0] * v_src.x + trafo3x4[1] * v_src.y + trafo3x4[2] * v_src.z + trafo3x4[3]; - v_dst.y = trafo3x4[4] * v_src.x + trafo3x4[5] * v_src.y + trafo3x4[6] * v_src.z + trafo3x4[7]; - v_dst.z = trafo3x4[8] * v_src.x + trafo3x4[9] * v_src.y + trafo3x4[10] * v_src.z + trafo3x4[11]; + v_dst(0) = trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2] * v_src(2) + trafo3x4[3]; + v_dst(1) = trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6] * v_src(2) + trafo3x4[7]; + v_dst(2) = trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2) + trafo3x4[11]; } } stl_get_size(stl); @@ -214,8 +167,8 @@ stl_rotate_x(stl_file *stl, float angle) { for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { - stl_rotate(&stl->facet_start[i].vertex[j].y, - &stl->facet_start[i].vertex[j].z, c, s); + stl_rotate(&stl->facet_start[i].vertex[j](1), + &stl->facet_start[i].vertex[j](2), c, s); } } stl_get_size(stl); @@ -234,8 +187,8 @@ stl_rotate_y(stl_file *stl, float angle) { for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { - stl_rotate(&stl->facet_start[i].vertex[j].z, - &stl->facet_start[i].vertex[j].x, c, s); + stl_rotate(&stl->facet_start[i].vertex[j](2), + &stl->facet_start[i].vertex[j](0), c, s); } } stl_get_size(stl); @@ -254,8 +207,8 @@ stl_rotate_z(stl_file *stl, float angle) { for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { - stl_rotate(&stl->facet_start[i].vertex[j].x, - &stl->facet_start[i].vertex[j].y, c, s); + stl_rotate(&stl->facet_start[i].vertex[j](0), + &stl->facet_start[i].vertex[j](1), c, s); } } stl_get_size(stl); @@ -272,142 +225,98 @@ stl_rotate(float *x, float *y, const double c, const double s) { *y = float(s * xold + c * yold); } -extern void -stl_get_size(stl_file *stl) { - int i; - int j; - - if (stl->error) return; - if (stl->stats.number_of_facets == 0) return; - - stl->stats.min.x = stl->facet_start[0].vertex[0].x; - stl->stats.min.y = stl->facet_start[0].vertex[0].y; - stl->stats.min.z = stl->facet_start[0].vertex[0].z; - stl->stats.max.x = stl->facet_start[0].vertex[0].x; - stl->stats.max.y = stl->facet_start[0].vertex[0].y; - stl->stats.max.z = stl->facet_start[0].vertex[0].z; - - for(i = 0; i < stl->stats.number_of_facets; i++) { - for(j = 0; j < 3; j++) { - stl->stats.min.x = STL_MIN(stl->stats.min.x, - stl->facet_start[i].vertex[j].x); - stl->stats.min.y = STL_MIN(stl->stats.min.y, - stl->facet_start[i].vertex[j].y); - stl->stats.min.z = STL_MIN(stl->stats.min.z, - stl->facet_start[i].vertex[j].z); - stl->stats.max.x = STL_MAX(stl->stats.max.x, - stl->facet_start[i].vertex[j].x); - stl->stats.max.y = STL_MAX(stl->stats.max.y, - stl->facet_start[i].vertex[j].y); - stl->stats.max.z = STL_MAX(stl->stats.max.z, - stl->facet_start[i].vertex[j].z); +void stl_get_size(stl_file *stl) +{ + if (stl->error || stl->stats.number_of_facets == 0) + return; + stl->stats.min = stl->facet_start[0].vertex[0]; + stl->stats.max = stl->stats.min; + for (int i = 0; i < stl->stats.number_of_facets; ++ i) { + const stl_facet &face = stl->facet_start[i]; + for (int j = 0; j < 3; ++ j) { + stl->stats.min = stl->stats.min.cwiseMin(face.vertex[j]); + stl->stats.max = stl->stats.max.cwiseMax(face.vertex[j]); } } - stl->stats.size.x = stl->stats.max.x - stl->stats.min.x; - stl->stats.size.y = stl->stats.max.y - stl->stats.min.y; - stl->stats.size.z = stl->stats.max.z - stl->stats.min.z; - stl->stats.bounding_diameter = sqrt( - stl->stats.size.x * stl->stats.size.x + - stl->stats.size.y * stl->stats.size.y + - stl->stats.size.z * stl->stats.size.z - ); + stl->stats.size = stl->stats.max - stl->stats.min; + stl->stats.bounding_diameter = stl->stats.size.norm(); } -void -stl_mirror_xy(stl_file *stl) { - int i; - int j; - float temp_size; +void stl_mirror_xy(stl_file *stl) +{ + if (stl->error) + return; - if (stl->error) return; - - for(i = 0; i < stl->stats.number_of_facets; i++) { - for(j = 0; j < 3; j++) { - stl->facet_start[i].vertex[j].z *= -1.0; + for(int i = 0; i < stl->stats.number_of_facets; i++) { + for(int j = 0; j < 3; j++) { + stl->facet_start[i].vertex[j](2) *= -1.0; } } - temp_size = stl->stats.min.z; - stl->stats.min.z = stl->stats.max.z; - stl->stats.max.z = temp_size; - stl->stats.min.z *= -1.0; - stl->stats.max.z *= -1.0; + float temp_size = stl->stats.min(2); + stl->stats.min(2) = stl->stats.max(2); + stl->stats.max(2) = temp_size; + stl->stats.min(2) *= -1.0; + stl->stats.max(2) *= -1.0; stl_reverse_all_facets(stl); stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */ } -void -stl_mirror_yz(stl_file *stl) { - int i; - int j; - float temp_size; - +void stl_mirror_yz(stl_file *stl) +{ if (stl->error) return; - for(i = 0; i < stl->stats.number_of_facets; i++) { - for(j = 0; j < 3; j++) { - stl->facet_start[i].vertex[j].x *= -1.0; + for (int i = 0; i < stl->stats.number_of_facets; i++) { + for (int j = 0; j < 3; j++) { + stl->facet_start[i].vertex[j](0) *= -1.0; } } - temp_size = stl->stats.min.x; - stl->stats.min.x = stl->stats.max.x; - stl->stats.max.x = temp_size; - stl->stats.min.x *= -1.0; - stl->stats.max.x *= -1.0; + float temp_size = stl->stats.min(0); + stl->stats.min(0) = stl->stats.max(0); + stl->stats.max(0) = temp_size; + stl->stats.min(0) *= -1.0; + stl->stats.max(0) *= -1.0; stl_reverse_all_facets(stl); stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */ } -void -stl_mirror_xz(stl_file *stl) { - int i; - int j; - float temp_size; +void stl_mirror_xz(stl_file *stl) +{ + if (stl->error) + return; - if (stl->error) return; - - for(i = 0; i < stl->stats.number_of_facets; i++) { - for(j = 0; j < 3; j++) { - stl->facet_start[i].vertex[j].y *= -1.0; + for (int i = 0; i < stl->stats.number_of_facets; i++) { + for (int j = 0; j < 3; j++) { + stl->facet_start[i].vertex[j](1) *= -1.0; } } - temp_size = stl->stats.min.y; - stl->stats.min.y = stl->stats.max.y; - stl->stats.max.y = temp_size; - stl->stats.min.y *= -1.0; - stl->stats.max.y *= -1.0; + float temp_size = stl->stats.min(1); + stl->stats.min(1) = stl->stats.max(1); + stl->stats.max(1) = temp_size; + stl->stats.min(1) *= -1.0; + stl->stats.max(1) *= -1.0; stl_reverse_all_facets(stl); stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */ } -static float get_volume(stl_file *stl) { - stl_vertex p0; - stl_vertex p; - stl_normal n; - float height; - float area; - float volume = 0.0; +static float get_volume(stl_file *stl) +{ + if (stl->error) + return 0; - if (stl->error) return 0; - - /* Choose a point, any point as the reference */ - p0.x = stl->facet_start[0].vertex[0].x; - p0.y = stl->facet_start[0].vertex[0].y; - p0.z = stl->facet_start[0].vertex[0].z; - - for(uint32_t i = 0; i < stl->stats.number_of_facets; i++) { - p.x = stl->facet_start[i].vertex[0].x - p0.x; - p.y = stl->facet_start[i].vertex[0].y - p0.y; - p.z = stl->facet_start[i].vertex[0].z - p0.z; - /* Do dot product to get distance from point to plane */ - n = stl->facet_start[i].normal; - height = (n.x * p.x) + (n.y * p.y) + (n.z * p.z); - area = get_area(&stl->facet_start[i]); + // Choose a point, any point as the reference. + stl_vertex p0 = stl->facet_start[0].vertex[0]; + float volume = 0.f; + for(uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + // Do dot product to get distance from point to plane. + float height = stl->facet_start[i].normal.dot(stl->facet_start[i].vertex[0] - p0); + float area = get_area(&stl->facet_start[i]); volume += (area * height) / 3.0f; } return volume; } -void stl_calculate_volume(stl_file *stl) { +void stl_calculate_volume(stl_file *stl) +{ if (stl->error) return; stl->stats.volume = get_volume(stl); if(stl->stats.volume < 0.0) { @@ -416,35 +325,32 @@ void stl_calculate_volume(stl_file *stl) { } } -static float get_area(stl_facet *facet) { - double cross[3][3]; - float sum[3]; - float n[3]; - float area; - int i; - +static float get_area(stl_facet *facet) +{ /* cast to double before calculating cross product because large coordinates can result in overflowing product (bad area is responsible for bad volume and bad facets reversal) */ - for(i = 0; i < 3; i++) { - cross[i][0]=(((double)facet->vertex[i].y * (double)facet->vertex[(i + 1) % 3].z) - - ((double)facet->vertex[i].z * (double)facet->vertex[(i + 1) % 3].y)); - cross[i][1]=(((double)facet->vertex[i].z * (double)facet->vertex[(i + 1) % 3].x) - - ((double)facet->vertex[i].x * (double)facet->vertex[(i + 1) % 3].z)); - cross[i][2]=(((double)facet->vertex[i].x * (double)facet->vertex[(i + 1) % 3].y) - - ((double)facet->vertex[i].y * (double)facet->vertex[(i + 1) % 3].x)); + double cross[3][3]; + for (int i = 0; i < 3; i++) { + cross[i][0]=(((double)facet->vertex[i](1) * (double)facet->vertex[(i + 1) % 3](2)) - + ((double)facet->vertex[i](2) * (double)facet->vertex[(i + 1) % 3](1))); + cross[i][1]=(((double)facet->vertex[i](2) * (double)facet->vertex[(i + 1) % 3](0)) - + ((double)facet->vertex[i](0) * (double)facet->vertex[(i + 1) % 3](2))); + cross[i][2]=(((double)facet->vertex[i](0) * (double)facet->vertex[(i + 1) % 3](1)) - + ((double)facet->vertex[i](1) * (double)facet->vertex[(i + 1) % 3](0))); } - sum[0] = cross[0][0] + cross[1][0] + cross[2][0]; - sum[1] = cross[0][1] + cross[1][1] + cross[2][1]; - sum[2] = cross[0][2] + cross[1][2] + cross[2][2]; + stl_normal sum; + sum(0) = cross[0][0] + cross[1][0] + cross[2][0]; + sum(1) = cross[0][1] + cross[1][1] + cross[2][1]; + sum(2) = cross[0][2] + cross[1][2] + cross[2][2]; - /* This should already be done. But just in case, let's do it again */ + // This should already be done. But just in case, let's do it again. + //FIXME this is questionable. the "sum" normal should be accurate, while the normal "n" may be calculated with a low accuracy. + stl_normal n; stl_calculate_normal(n, facet); stl_normalize_vector(n); - - area = 0.5 * (n[0] * sum[0] + n[1] * sum[1] + n[2] * sum[2]); - return area; + return 0.5f * n.dot(sum); } void stl_repair(stl_file *stl, diff --git a/xs/src/libslic3r/BoundingBox.cpp b/xs/src/libslic3r/BoundingBox.cpp index 68136b9160..d3cca7ff24 100644 --- a/xs/src/libslic3r/BoundingBox.cpp +++ b/xs/src/libslic3r/BoundingBox.cpp @@ -7,9 +7,9 @@ namespace Slic3r { template BoundingBoxBase::BoundingBoxBase(const std::vector &points); -template BoundingBoxBase::BoundingBoxBase(const std::vector &points); +template BoundingBoxBase::BoundingBoxBase(const std::vector &points); -template BoundingBox3Base::BoundingBox3Base(const std::vector &points); +template BoundingBox3Base::BoundingBox3Base(const std::vector &points); BoundingBox::BoundingBox(const Lines &lines) { @@ -22,8 +22,7 @@ BoundingBox::BoundingBox(const Lines &lines) *this = BoundingBox(points); } -void -BoundingBox::polygon(Polygon* polygon) const +void BoundingBox::polygon(Polygon* polygon) const { polygon->points.clear(); polygon->points.resize(4); @@ -37,8 +36,7 @@ BoundingBox::polygon(Polygon* polygon) const polygon->points[3](1) = this->max(1); } -Polygon -BoundingBox::polygon() const +Polygon BoundingBox::polygon() const { Polygon p; this->polygon(&p); @@ -72,24 +70,23 @@ BoundingBoxBase::scale(double factor) this->max *= factor; } template void BoundingBoxBase::scale(double factor); -template void BoundingBoxBase::scale(double factor); -template void BoundingBoxBase::scale(double factor); +template void BoundingBoxBase::scale(double factor); +template void BoundingBoxBase::scale(double factor); template void BoundingBoxBase::merge(const PointClass &point) { if (this->defined) { - this->min(0) = std::min(point(0), this->min(0)); - this->min(1) = std::min(point(1), this->min(1)); - this->max(0) = std::max(point(0), this->max(0)); - this->max(1) = std::max(point(1), this->max(1)); + this->min = this->min.cwiseMin(point); + this->max = this->max.cwiseMax(point); } else { - this->min = this->max = point; + this->min = point; + this->max = point; this->defined = true; } } template void BoundingBoxBase::merge(const Point &point); -template void BoundingBoxBase::merge(const Pointf &point); +template void BoundingBoxBase::merge(const Vec2d &point); template void BoundingBoxBase::merge(const std::vector &points) @@ -97,7 +94,7 @@ BoundingBoxBase::merge(const std::vector &points) this->merge(BoundingBoxBase(points)); } template void BoundingBoxBase::merge(const Points &points); -template void BoundingBoxBase::merge(const Pointfs &points); +template void BoundingBoxBase::merge(const Pointfs &points); template void BoundingBoxBase::merge(const BoundingBoxBase &bb) @@ -105,10 +102,8 @@ BoundingBoxBase::merge(const BoundingBoxBase &bb) assert(bb.defined || bb.min(0) >= bb.max(0) || bb.min(1) >= bb.max(1)); if (bb.defined) { if (this->defined) { - this->min(0) = std::min(bb.min(0), this->min(0)); - this->min(1) = std::min(bb.min(1), this->min(1)); - this->max(0) = std::max(bb.max(0), this->max(0)); - this->max(1) = std::max(bb.max(1), this->max(1)); + this->min = this->min.cwiseMin(bb.min); + this->max = this->max.cwiseMax(bb.max); } else { this->min = bb.min; this->max = bb.max; @@ -117,25 +112,28 @@ BoundingBoxBase::merge(const BoundingBoxBase &bb) } } template void BoundingBoxBase::merge(const BoundingBoxBase &bb); -template void BoundingBoxBase::merge(const BoundingBoxBase &bb); +template void BoundingBoxBase::merge(const BoundingBoxBase &bb); template void BoundingBox3Base::merge(const PointClass &point) { if (this->defined) { - this->min(2) = std::min(point(2), this->min(2)); - this->max(2) = std::max(point(2), this->max(2)); + this->min = this->min.cwiseMin(point); + this->max = this->max.cwiseMax(point); + } else { + this->min = point; + this->max = point; + this->defined = true; } - BoundingBoxBase::merge(point); } -template void BoundingBox3Base::merge(const Pointf3 &point); +template void BoundingBox3Base::merge(const Vec3d &point); template void BoundingBox3Base::merge(const std::vector &points) { this->merge(BoundingBox3Base(points)); } -template void BoundingBox3Base::merge(const Pointf3s &points); +template void BoundingBox3Base::merge(const Pointf3s &points); template void BoundingBox3Base::merge(const BoundingBox3Base &bb) @@ -143,13 +141,16 @@ BoundingBox3Base::merge(const BoundingBox3Base &bb) assert(bb.defined || bb.min(0) >= bb.max(0) || bb.min(1) >= bb.max(1) || bb.min(2) >= bb.max(2)); if (bb.defined) { if (this->defined) { - this->min(2) = std::min(bb.min(2), this->min(2)); - this->max(2) = std::max(bb.max(2), this->max(2)); + this->min = this->min.cwiseMin(bb.min); + this->max = this->max.cwiseMax(bb.max); + } else { + this->min = bb.min; + this->max = bb.max; + this->defined = true; } - BoundingBoxBase::merge(bb); } } -template void BoundingBox3Base::merge(const BoundingBox3Base &bb); +template void BoundingBox3Base::merge(const BoundingBox3Base &bb); template PointClass BoundingBoxBase::size() const @@ -157,14 +158,14 @@ BoundingBoxBase::size() const return PointClass(this->max(0) - this->min(0), this->max(1) - this->min(1)); } template Point BoundingBoxBase::size() const; -template Pointf BoundingBoxBase::size() const; +template Vec2d BoundingBoxBase::size() const; template PointClass BoundingBox3Base::size() const { return PointClass(this->max(0) - this->min(0), this->max(1) - this->min(1), this->max(2) - this->min(2)); } -template Pointf3 BoundingBox3Base::size() const; +template Vec3d BoundingBox3Base::size() const; template double BoundingBoxBase::radius() const { @@ -174,7 +175,7 @@ template double BoundingBoxBase::radius() const return 0.5 * sqrt(x*x+y*y); } template double BoundingBoxBase::radius() const; -template double BoundingBoxBase::radius() const; +template double BoundingBoxBase::radius() const; template double BoundingBox3Base::radius() const { @@ -183,7 +184,7 @@ template double BoundingBox3Base::radius() const double z = this->max(2) - this->min(2); return 0.5 * sqrt(x*x+y*y+z*z); } -template double BoundingBox3Base::radius() const; +template double BoundingBox3Base::radius() const; template void BoundingBoxBase::offset(coordf_t delta) @@ -193,7 +194,7 @@ BoundingBoxBase::offset(coordf_t delta) this->max += v; } template void BoundingBoxBase::offset(coordf_t delta); -template void BoundingBoxBase::offset(coordf_t delta); +template void BoundingBoxBase::offset(coordf_t delta); template void BoundingBox3Base::offset(coordf_t delta) @@ -202,29 +203,22 @@ BoundingBox3Base::offset(coordf_t delta) this->min -= v; this->max += v; } -template void BoundingBox3Base::offset(coordf_t delta); +template void BoundingBox3Base::offset(coordf_t delta); template PointClass BoundingBoxBase::center() const { - return PointClass( - (this->max(0) + this->min(0))/2, - (this->max(1) + this->min(1))/2 - ); + return (this->min + this->max) / 2; } template Point BoundingBoxBase::center() const; -template Pointf BoundingBoxBase::center() const; +template Vec2d BoundingBoxBase::center() const; template PointClass BoundingBox3Base::center() const { - return PointClass( - (this->max(0) + this->min(0))/2, - (this->max(1) + this->min(1))/2, - (this->max(2) + this->min(2))/2 - ); + return (this->min + this->max) / 2; } -template Pointf3 BoundingBox3Base::center() const; +template Vec3d BoundingBox3Base::center() const; template coordf_t BoundingBox3Base::max_size() const @@ -232,7 +226,7 @@ BoundingBox3Base::max_size() const PointClass s = size(); return std::max(s(0), std::max(s(1), s(2))); } -template coordf_t BoundingBox3Base::max_size() const; +template coordf_t BoundingBox3Base::max_size() const; // Align a coordinate to a grid. The coordinate may be negative, // the aligned value will never be bigger than the original one. @@ -255,39 +249,35 @@ void BoundingBox::align_to_grid(const coord_t cell_size) } } -BoundingBoxf3 BoundingBoxf3::transformed(const Transform3f& matrix) const +BoundingBoxf3 BoundingBoxf3::transformed(const Transform3d& matrix) const { - Eigen::Matrix vertices; + typedef Eigen::Matrix Vertices; - vertices(0, 0) = (float)min(0); vertices(1, 0) = (float)min(1); vertices(2, 0) = (float)min(2); - vertices(0, 1) = (float)max(0); vertices(1, 1) = (float)min(1); vertices(2, 1) = (float)min(2); - vertices(0, 2) = (float)max(0); vertices(1, 2) = (float)max(1); vertices(2, 2) = (float)min(2); - vertices(0, 3) = (float)min(0); vertices(1, 3) = (float)max(1); vertices(2, 3) = (float)min(2); - vertices(0, 4) = (float)min(0); vertices(1, 4) = (float)min(1); vertices(2, 4) = (float)max(2); - vertices(0, 5) = (float)max(0); vertices(1, 5) = (float)min(1); vertices(2, 5) = (float)max(2); - vertices(0, 6) = (float)max(0); vertices(1, 6) = (float)max(1); vertices(2, 6) = (float)max(2); - vertices(0, 7) = (float)min(0); vertices(1, 7) = (float)max(1); vertices(2, 7) = (float)max(2); + Vertices src_vertices; + src_vertices(0, 0) = min(0); src_vertices(1, 0) = min(1); src_vertices(2, 0) = min(2); + src_vertices(0, 1) = max(0); src_vertices(1, 1) = min(1); src_vertices(2, 1) = min(2); + src_vertices(0, 2) = max(0); src_vertices(1, 2) = max(1); src_vertices(2, 2) = min(2); + src_vertices(0, 3) = min(0); src_vertices(1, 3) = max(1); src_vertices(2, 3) = min(2); + src_vertices(0, 4) = min(0); src_vertices(1, 4) = min(1); src_vertices(2, 4) = max(2); + src_vertices(0, 5) = max(0); src_vertices(1, 5) = min(1); src_vertices(2, 5) = max(2); + src_vertices(0, 6) = max(0); src_vertices(1, 6) = max(1); src_vertices(2, 6) = max(2); + src_vertices(0, 7) = min(0); src_vertices(1, 7) = max(1); src_vertices(2, 7) = max(2); - Eigen::Matrix transf_vertices = matrix * vertices.colwise().homogeneous(); + Vertices dst_vertices = matrix * src_vertices.colwise().homogeneous(); - float min_x = transf_vertices(0, 0); - float max_x = transf_vertices(0, 0); - float min_y = transf_vertices(1, 0); - float max_y = transf_vertices(1, 0); - float min_z = transf_vertices(2, 0); - float max_z = transf_vertices(2, 0); + Vec3d v_min(dst_vertices(0, 0), dst_vertices(1, 0), dst_vertices(2, 0)); + Vec3d v_max = v_min; for (int i = 1; i < 8; ++i) { - min_x = std::min(min_x, transf_vertices(0, i)); - max_x = std::max(max_x, transf_vertices(0, i)); - min_y = std::min(min_y, transf_vertices(1, i)); - max_y = std::max(max_y, transf_vertices(1, i)); - min_z = std::min(min_z, transf_vertices(2, i)); - max_z = std::max(max_z, transf_vertices(2, i)); + for (int j = 0; j < 3; ++j) + { + v_min(j) = std::min(v_min(j), dst_vertices(j, i)); + v_max(j) = std::max(v_max(j), dst_vertices(j, 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)); + return BoundingBoxf3(v_min, v_max); } } diff --git a/xs/src/libslic3r/BoundingBox.hpp b/xs/src/libslic3r/BoundingBox.hpp index d09658774f..db56c765cf 100644 --- a/xs/src/libslic3r/BoundingBox.hpp +++ b/xs/src/libslic3r/BoundingBox.hpp @@ -7,11 +7,6 @@ namespace Slic3r { -typedef Point Size; -typedef Point3 Size3; -typedef Pointf Sizef; -typedef Pointf3 Sizef3; - template class BoundingBoxBase { @@ -20,23 +15,20 @@ public: PointClass max; bool defined; - BoundingBoxBase() : defined(false) {}; + BoundingBoxBase() : defined(false), min(PointClass::Zero()), max(PointClass::Zero()) {} BoundingBoxBase(const PointClass &pmin, const PointClass &pmax) : min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {} - BoundingBoxBase(const std::vector& points) + BoundingBoxBase(const std::vector& points) : min(PointClass::Zero()), max(PointClass::Zero()) { if (points.empty()) CONFESS("Empty point set supplied to BoundingBoxBase constructor"); typename std::vector::const_iterator it = points.begin(); - this->min(0) = this->max(0) = (*it)(0); - this->min(1) = this->max(1) = (*it)(1); - for (++it; it != points.end(); ++it) - { - this->min(0) = std::min((*it)(0), this->min(0)); - this->min(1) = std::min((*it)(1), this->min(1)); - this->max(0) = std::max((*it)(0), this->max(0)); - this->max(1) = std::max((*it)(1), this->max(1)); + this->min = *it; + this->max = *it; + for (++ it; it != points.end(); ++ it) { + this->min = this->min.cwiseMin(*it); + this->max = this->max.cwiseMax(*it); } this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1)); } @@ -47,7 +39,7 @@ public: PointClass size() const; double radius() const; void translate(coordf_t x, coordf_t y) { assert(this->defined); PointClass v(x, y); this->min += v; this->max += v; } - void translate(const Pointf &v) { this->min += v; this->max += v; } + void translate(const Vec2d &v) { this->min += v; this->max += v; } void offset(coordf_t delta); PointClass center() const; bool contains(const PointClass &point) const { @@ -71,19 +63,17 @@ public: BoundingBoxBase(pmin, pmax) { if (pmin(2) >= pmax(2)) BoundingBoxBase::defined = false; } BoundingBox3Base(const std::vector& points) - : BoundingBoxBase(points) { if (points.empty()) CONFESS("Empty point set supplied to BoundingBox3Base constructor"); - typename std::vector::const_iterator it = points.begin(); - this->min(2) = this->max(2) = (*it)(2); - for (++it; it != points.end(); ++it) - { - this->min(2) = std::min((*it)(2), this->min(2)); - this->max(2) = std::max((*it)(2), this->max(2)); + this->min = *it; + this->max = *it; + for (++ it; it != points.end(); ++ it) { + this->min = this->min.cwiseMin(*it); + this->max = this->max.cwiseMax(*it); } - this->defined &= (this->min(2) < this->max(2)); + this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1)) && (this->min(2) < this->max(2)); } void merge(const PointClass &point); void merge(const std::vector &points); @@ -91,7 +81,7 @@ public: PointClass size() const; double radius() const; void translate(coordf_t x, coordf_t y, coordf_t z) { assert(this->defined); PointClass v(x, y, z); this->min += v; this->max += v; } - void translate(const Pointf3 &v) { this->min += v; this->max += v; } + void translate(const Vec3d &v) { this->min += v; this->max += v; } void offset(coordf_t delta); PointClass center() const; coordf_t max_size() const; @@ -130,30 +120,30 @@ public: friend BoundingBox get_extents_rotated(const Points &points, double angle); }; -class BoundingBox3 : public BoundingBox3Base +class BoundingBox3 : public BoundingBox3Base { public: - BoundingBox3() : BoundingBox3Base() {}; - BoundingBox3(const Point3 &pmin, const Point3 &pmax) : BoundingBox3Base(pmin, pmax) {}; - BoundingBox3(const Points3& points) : BoundingBox3Base(points) {}; + BoundingBox3() : BoundingBox3Base() {}; + BoundingBox3(const Vec3crd &pmin, const Vec3crd &pmax) : BoundingBox3Base(pmin, pmax) {}; + BoundingBox3(const Points3& points) : BoundingBox3Base(points) {}; }; -class BoundingBoxf : public BoundingBoxBase +class BoundingBoxf : public BoundingBoxBase { public: - BoundingBoxf() : BoundingBoxBase() {}; - BoundingBoxf(const Pointf &pmin, const Pointf &pmax) : BoundingBoxBase(pmin, pmax) {}; - BoundingBoxf(const std::vector &points) : BoundingBoxBase(points) {}; + BoundingBoxf() : BoundingBoxBase() {}; + BoundingBoxf(const Vec2d &pmin, const Vec2d &pmax) : BoundingBoxBase(pmin, pmax) {}; + BoundingBoxf(const std::vector &points) : BoundingBoxBase(points) {}; }; -class BoundingBoxf3 : public BoundingBox3Base +class BoundingBoxf3 : public BoundingBox3Base { public: - BoundingBoxf3() : BoundingBox3Base() {}; - BoundingBoxf3(const Pointf3 &pmin, const Pointf3 &pmax) : BoundingBox3Base(pmin, pmax) {}; - BoundingBoxf3(const std::vector &points) : BoundingBox3Base(points) {}; + BoundingBoxf3() : BoundingBox3Base() {}; + BoundingBoxf3(const Vec3d &pmin, const Vec3d &pmax) : BoundingBox3Base(pmin, pmax) {}; + BoundingBoxf3(const std::vector &points) : BoundingBox3Base(points) {}; - BoundingBoxf3 transformed(const Transform3f& matrix) const; + BoundingBoxf3 transformed(const Transform3d& matrix) const; }; template diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index ee9931a610..e3cd14335a 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -49,9 +49,9 @@ enum ConfigOptionType { coPercents = coPercent + coVectorType, // a fraction or an absolute value coFloatOrPercent = 5, - // single 2d point. Currently not used. + // single 2d point (Point2f). Currently not used. coPoint = 6, - // vector of 2d points. Currently used for the definition of the print bed and for the extruder offsets. + // vector of 2d points (Point2f). Currently used for the definition of the print bed and for the extruder offsets. coPoints = coPoint + coVectorType, // single boolean value coBool = 7, @@ -622,11 +622,11 @@ public: } }; -class ConfigOptionPoint : public ConfigOptionSingle +class ConfigOptionPoint : public ConfigOptionSingle { public: - ConfigOptionPoint() : ConfigOptionSingle(Pointf(0,0)) {} - explicit ConfigOptionPoint(const Pointf &value) : ConfigOptionSingle(value) {} + ConfigOptionPoint() : ConfigOptionSingle(Vec2d(0,0)) {} + explicit ConfigOptionPoint(const Vec2d &value) : ConfigOptionSingle(value) {} static ConfigOptionType static_type() { return coPoint; } ConfigOptionType type() const override { return static_type(); } @@ -652,13 +652,13 @@ public: } }; -class ConfigOptionPoints : public ConfigOptionVector +class ConfigOptionPoints : public ConfigOptionVector { public: - ConfigOptionPoints() : ConfigOptionVector() {} - explicit ConfigOptionPoints(size_t n, const Pointf &value) : ConfigOptionVector(n, value) {} - explicit ConfigOptionPoints(std::initializer_list il) : ConfigOptionVector(std::move(il)) {} - explicit ConfigOptionPoints(const std::vector &values) : ConfigOptionVector(values) {} + ConfigOptionPoints() : ConfigOptionVector() {} + explicit ConfigOptionPoints(size_t n, const Vec2d &value) : ConfigOptionVector(n, value) {} + explicit ConfigOptionPoints(std::initializer_list il) : ConfigOptionVector(std::move(il)) {} + explicit ConfigOptionPoints(const std::vector &values) : ConfigOptionVector(values) {} static ConfigOptionType static_type() { return coPoints; } ConfigOptionType type() const override { return static_type(); } @@ -696,7 +696,7 @@ public: std::istringstream is(str); std::string point_str; while (std::getline(is, point_str, ',')) { - Pointf point; + Vec2d point(Vec2d::Zero()); std::istringstream iss(point_str); std::string coord_str; if (std::getline(iss, coord_str, 'x')) { @@ -821,12 +821,7 @@ public: bool deserialize(const std::string &str, bool append = false) override { UNUSED(append); - const t_config_enum_values &enum_keys_map = ConfigOptionEnum::get_enum_values(); - auto it = enum_keys_map.find(str); - if (it == enum_keys_map.end()) - return false; - this->value = static_cast(it->second); - return true; + return from_string(str, this->value); } static bool has(T value) @@ -838,7 +833,7 @@ public: } // Map from an enum name to an enum integer value. - static t_config_enum_names& get_enum_names() + static const t_config_enum_names& get_enum_names() { static t_config_enum_names names; if (names.empty()) { @@ -855,7 +850,17 @@ public: return names; } // Map from an enum name to an enum integer value. - static t_config_enum_values& get_enum_values(); + static const t_config_enum_values& get_enum_values(); + + static bool from_string(const std::string &str, T &value) + { + const t_config_enum_values &enum_keys_map = ConfigOptionEnum::get_enum_values(); + auto it = enum_keys_map.find(str); + if (it == enum_keys_map.end()) + return false; + value = static_cast(it->second); + return true; + } }; // Generic enum configuration value. @@ -900,7 +905,7 @@ public: // What type? bool, int, string etc. ConfigOptionType type = coNone; // Default value of this option. The default value object is owned by ConfigDef, it is released in its destructor. - ConfigOption *default_value = nullptr; + const ConfigOption *default_value = nullptr; // Usually empty. // Special values - "i_enum_open", "f_enum_open" to provide combo box for int or float selection, @@ -958,7 +963,7 @@ public: std::vector enum_labels; // For enums (when type == coEnum). Maps enum_values to enums. // Initialized by ConfigOptionEnum::get_enum_values() - t_config_enum_values *enum_keys_map = nullptr; + const t_config_enum_values *enum_keys_map = nullptr; bool has_enum_value(const std::string &value) const { for (const std::string &v : enum_values) diff --git a/xs/src/libslic3r/ExtrusionEntity.hpp b/xs/src/libslic3r/ExtrusionEntity.hpp index 15363e8eda..90675c04a7 100644 --- a/xs/src/libslic3r/ExtrusionEntity.hpp +++ b/xs/src/libslic3r/ExtrusionEntity.hpp @@ -149,7 +149,7 @@ public: // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. double min_mm3_per_mm() const { return this->mm3_per_mm; } Polyline as_polyline() const { return this->polyline; } - virtual double total_volume() const { return mm3_per_mm * unscale(length()); } + virtual double total_volume() const { return mm3_per_mm * unscale(length()); } private: void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const; diff --git a/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp b/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp index 16ed12fb95..6a37e4369f 100644 --- a/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp +++ b/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp @@ -54,7 +54,7 @@ static std::vector perpendPoints(const coordf_t offset, const size_t b // components that are outside these limits are set to the limits. static inline void trim(Pointfs &pts, coordf_t minX, coordf_t minY, coordf_t maxX, coordf_t maxY) { - for (Pointf &pt : pts) { + for (Vec2d &pt : pts) { pt(0) = clamp(minX, maxX, pt(0)); pt(1) = clamp(minY, maxY, pt(1)); } @@ -66,7 +66,7 @@ static inline Pointfs zip(const std::vector &x, const std::vector 0.9999f && !params.dont_adjust) { distance = this->_adjust_solid_spacing(bounding_box.size()(0), distance); - this->spacing = unscale(distance); + this->spacing = unscale(distance); } Polygons loops = (Polygons)expolygon; diff --git a/xs/src/libslic3r/Fill/FillGyroid.cpp b/xs/src/libslic3r/Fill/FillGyroid.cpp index 003893649b..d6bf03ce65 100644 --- a/xs/src/libslic3r/Fill/FillGyroid.cpp +++ b/xs/src/libslic3r/Fill/FillGyroid.cpp @@ -30,15 +30,15 @@ static inline double f(double x, double z_sin, double z_cos, bool vertical, bool } static inline Polyline make_wave( - const std::vector& one_period, double width, double height, double offset, double scaleFactor, + const std::vector& one_period, double width, double height, double offset, double scaleFactor, double z_cos, double z_sin, bool vertical) { - std::vector points = one_period; + std::vector points = one_period; double period = points.back()(0); points.pop_back(); int n = points.size(); do { - points.emplace_back(Pointf(points[points.size()-n](0) + period, points[points.size()-n](1))); + points.emplace_back(Vec2d(points[points.size()-n](0) + period, points[points.size()-n](1))); } while (points.back()(0) < width); points.back()(0) = width; @@ -55,14 +55,14 @@ static inline Polyline make_wave( return polyline; } -static std::vector make_one_period(double width, double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip) +static std::vector make_one_period(double width, double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip) { - std::vector points; + std::vector points; double dx = M_PI_4; // very coarse spacing to begin with double limit = std::min(2*M_PI, width); for (double x = 0.; x < limit + EPSILON; x += dx) { // so the last point is there too x = std::min(x, limit); - points.emplace_back(Pointf(x,f(x, z_sin,z_cos, vertical, flip))); + points.emplace_back(Vec2d(x,f(x, z_sin,z_cos, vertical, flip))); } // now we will check all internal points and in case some are too far from the line connecting its neighbours, @@ -71,17 +71,19 @@ static std::vector make_one_period(double width, double scaleFactor, dou for (unsigned int i=1;i(scaleFactor) * std::abs(cross2(rp, lp) - cross2(rp - lp, tp)) / lrv.norm(); if (dist_mm > tolerance) { // if the difference from straight line is more than this double x = 0.5f * (points[i-1](0) + points[i](0)); - points.emplace_back(Pointf(x, f(x, z_sin, z_cos, vertical, flip))); + points.emplace_back(Vec2d(x, f(x, z_sin, z_cos, vertical, flip))); x = 0.5f * (points[i+1](0) + points[i](0)); - points.emplace_back(Pointf(x, f(x, z_sin, z_cos, vertical, flip))); - std::sort(points.begin(), points.end()); // we added the points to the end, but need them all in order - --i; // decrement i so we also check the first newly added point + points.emplace_back(Vec2d(x, f(x, z_sin, z_cos, vertical, flip))); + // we added the points to the end, but need them all in order + std::sort(points.begin(), points.end(), [](const Vec2d &lhs, const Vec2d &rhs){ return lhs < rhs; }); + // decrement i so we also check the first newly added point + --i; } } return points; @@ -107,7 +109,7 @@ static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double std::swap(width,height); } - std::vector one_period = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip); // creates one period of the waves, so it doesn't have to be recalculated all the time + std::vector one_period = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip); // creates one period of the waves, so it doesn't have to be recalculated all the time Polylines result; for (double y0 = lower_bound; y0 < upper_bound+EPSILON; y0 += 2*M_PI) // creates odd polylines diff --git a/xs/src/libslic3r/Fill/FillPlanePath.cpp b/xs/src/libslic3r/Fill/FillPlanePath.cpp index bb7d5ac801..615cc6efed 100644 --- a/xs/src/libslic3r/Fill/FillPlanePath.cpp +++ b/xs/src/libslic3r/Fill/FillPlanePath.cpp @@ -86,12 +86,12 @@ Pointfs FillArchimedeanChords::_generate(coord_t min_x, coord_t min_y, coord_t m coordf_t r = 1; Pointfs out; //FIXME Vojtech: If used as a solid infill, there is a gap left at the center. - out.push_back(Pointf(0, 0)); - out.push_back(Pointf(1, 0)); + out.push_back(Vec2d(0, 0)); + out.push_back(Vec2d(1, 0)); while (r < rmax) { theta += 1. / r; r = a + b * theta; - out.push_back(Pointf(r * cos(theta), r * sin(theta))); + out.push_back(Vec2d(r * cos(theta), r * sin(theta))); } return out; } @@ -162,7 +162,7 @@ Pointfs FillHilbertCurve::_generate(coord_t min_x, coord_t min_y, coord_t max_x, line.reserve(sz2); for (size_t i = 0; i < sz2; ++ i) { Point p = hilbert_n_to_xy(i); - line.push_back(Pointf(p(0) + min_x, p(1) + min_y)); + line.push_back(Vec2d(p(0) + min_x, p(1) + min_y)); } return line; } @@ -175,27 +175,27 @@ Pointfs FillOctagramSpiral::_generate(coord_t min_x, coord_t min_y, coord_t max_ coordf_t r = 0; coordf_t r_inc = sqrt(2.); Pointfs out; - out.push_back(Pointf(0, 0)); + out.push_back(Vec2d(0, 0)); while (r < rmax) { r += r_inc; coordf_t rx = r / sqrt(2.); coordf_t r2 = r + rx; - out.push_back(Pointf( r, 0.)); - out.push_back(Pointf( r2, rx)); - out.push_back(Pointf( rx, rx)); - out.push_back(Pointf( rx, r2)); - out.push_back(Pointf(0., r)); - out.push_back(Pointf(-rx, r2)); - out.push_back(Pointf(-rx, rx)); - out.push_back(Pointf(-r2, rx)); - out.push_back(Pointf(-r, 0.)); - out.push_back(Pointf(-r2, -rx)); - out.push_back(Pointf(-rx, -rx)); - out.push_back(Pointf(-rx, -r2)); - out.push_back(Pointf(0., -r)); - out.push_back(Pointf( rx, -r2)); - out.push_back(Pointf( rx, -rx)); - out.push_back(Pointf( r2+r_inc, -rx)); + out.push_back(Vec2d( r, 0.)); + out.push_back(Vec2d( r2, rx)); + out.push_back(Vec2d( rx, rx)); + out.push_back(Vec2d( rx, r2)); + out.push_back(Vec2d(0., r)); + out.push_back(Vec2d(-rx, r2)); + out.push_back(Vec2d(-rx, rx)); + out.push_back(Vec2d(-r2, rx)); + out.push_back(Vec2d(-r, 0.)); + out.push_back(Vec2d(-r2, -rx)); + out.push_back(Vec2d(-rx, -rx)); + out.push_back(Vec2d(-rx, -r2)); + out.push_back(Vec2d(0., -r)); + out.push_back(Vec2d( rx, -r2)); + out.push_back(Vec2d( rx, -rx)); + out.push_back(Vec2d( r2+r_inc, -rx)); } return out; } diff --git a/xs/src/libslic3r/Fill/FillRectilinear.cpp b/xs/src/libslic3r/Fill/FillRectilinear.cpp index 2209d219e4..205eb1b669 100644 --- a/xs/src/libslic3r/Fill/FillRectilinear.cpp +++ b/xs/src/libslic3r/Fill/FillRectilinear.cpp @@ -27,7 +27,7 @@ void FillRectilinear::_fill_surface_single( // define flow spacing according to requested density if (params.density > 0.9999f && !params.dont_adjust) { this->_line_spacing = this->_adjust_solid_spacing(bounding_box.size()(0), this->_line_spacing); - this->spacing = unscale(this->_line_spacing); + this->spacing = unscale(this->_line_spacing); } else { // extend bounding box so that our pattern will be aligned with other layers // Transform the reference point to the rotated coordinate system. diff --git a/xs/src/libslic3r/Fill/FillRectilinear2.cpp b/xs/src/libslic3r/Fill/FillRectilinear2.cpp index f18ab0f62f..65440d0efe 100644 --- a/xs/src/libslic3r/Fill/FillRectilinear2.cpp +++ b/xs/src/libslic3r/Fill/FillRectilinear2.cpp @@ -792,7 +792,7 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP // define flow spacing according to requested density if (params.full_infill() && !params.dont_adjust) { line_spacing = this->_adjust_solid_spacing(bounding_box.size()(0), line_spacing); - this->spacing = unscale(line_spacing); + this->spacing = unscale(line_spacing); } else { // extend bounding box so that our pattern will be aligned with other layers // Transform the reference point to the rotated coordinate system. diff --git a/xs/src/libslic3r/Fill/FillRectilinear3.cpp b/xs/src/libslic3r/Fill/FillRectilinear3.cpp index d80bbfe6e1..8fc129eacb 100644 --- a/xs/src/libslic3r/Fill/FillRectilinear3.cpp +++ b/xs/src/libslic3r/Fill/FillRectilinear3.cpp @@ -217,11 +217,11 @@ Point SegmentIntersection::pos() const const Point &seg_start = poly.points[(this->iSegment == 0) ? poly.points.size() - 1 : this->iSegment - 1]; const Point &seg_end = poly.points[this->iSegment]; // Point, vector of the segment. - const Pointf p1(seg_start.cast()); - const Pointf v1((seg_end - seg_start).cast()); + const Vec2d p1(seg_start.cast()); + const Vec2d v1((seg_end - seg_start).cast()); // Point, vector of this hatching line. - const Pointf p2(line->pos.cast()); - const Pointf v2(line->dir.cast()); + const Vec2d p2(line->pos.cast()); + const Vec2d v2(line->dir.cast()); // Intersect the two rays. double denom = v1(0) * v2(1) - v2(0) * v1(1); Point out; @@ -391,7 +391,7 @@ static bool prepare_infill_hatching_segments( // Full infill, adjust the line spacing to fit an integer number of lines. out.line_spacing = Fill::_adjust_solid_spacing(bounding_box.size()(0), line_spacing); // Report back the adjusted line spacing. - fill_dir_params.spacing = float(unscale(line_spacing)); + fill_dir_params.spacing = unscale(line_spacing); } else { // Extend bounding box so that our pattern will be aligned with the other layers. // Transform the reference point to the rotated coordinate system. diff --git a/xs/src/libslic3r/Format/3mf.cpp b/xs/src/libslic3r/Format/3mf.cpp index cd6f45c704..d054604081 100644 --- a/xs/src/libslic3r/Format/3mf.cpp +++ b/xs/src/libslic3r/Format/3mf.cpp @@ -1485,12 +1485,13 @@ namespace Slic3r { stl_facet& facet = stl.facet_start[i]; for (unsigned int v = 0; v < 3; ++v) { - ::memcpy((void*)&facet.vertex[v].x, (const void*)&geometry.vertices[geometry.triangles[src_start_id + ii + v] * 3], 3 * sizeof(float)); + ::memcpy(facet.vertex[v].data(), (const void*)&geometry.vertices[geometry.triangles[src_start_id + ii + v] * 3], 3 * sizeof(float)); } } 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) @@ -1844,9 +1845,9 @@ namespace Slic3r { for (int i = 0; i < stl.stats.shared_vertices; ++i) { stream << " <" << VERTEX_TAG << " "; - stream << "x=\"" << stl.v_shared[i].x << "\" "; - stream << "y=\"" << stl.v_shared[i].y << "\" "; - stream << "z=\"" << stl.v_shared[i].z << "\" />\n"; + stream << "x=\"" << stl.v_shared[i](0) << "\" "; + stream << "y=\"" << stl.v_shared[i](1) << "\" "; + stream << "z=\"" << stl.v_shared[i](2) << "\" />\n"; } } diff --git a/xs/src/libslic3r/Format/AMF.cpp b/xs/src/libslic3r/Format/AMF.cpp index 1ad0c082aa..81617ad729 100644 --- a/xs/src/libslic3r/Format/AMF.cpp +++ b/xs/src/libslic3r/Format/AMF.cpp @@ -402,10 +402,11 @@ void AMFParserContext::endElement(const char * /* name */) for (size_t i = 0; i < m_volume_facets.size();) { stl_facet &facet = stl.facet_start[i/3]; for (unsigned int v = 0; v < 3; ++ v) - memcpy(&facet.vertex[v].x, &m_object_vertices[m_volume_facets[i ++] * 3], 3 * sizeof(float)); + memcpy(facet.vertex[v].data(), &m_object_vertices[m_volume_facets[i ++] * 3], 3 * sizeof(float)); } stl_get_size(&stl); m_volume->mesh.repair(); + m_volume->calculate_convex_hull(); m_volume_facets.clear(); m_volume = nullptr; break; @@ -760,9 +761,9 @@ bool store_amf(const char *path, Model *model, Print* print, bool export_print_c for (size_t i = 0; i < stl.stats.shared_vertices; ++ i) { stream << " \n"; stream << " \n"; - stream << " " << stl.v_shared[i].x << "\n"; - stream << " " << stl.v_shared[i].y << "\n"; - stream << " " << stl.v_shared[i].z << "\n"; + stream << " " << stl.v_shared[i](0) << "\n"; + stream << " " << stl.v_shared[i](1) << "\n"; + stream << " " << stl.v_shared[i](2) << "\n"; stream << " \n"; stream << " \n"; } diff --git a/xs/src/libslic3r/Format/OBJ.cpp b/xs/src/libslic3r/Format/OBJ.cpp index ea6b5604c4..ee5756083d 100644 --- a/xs/src/libslic3r/Format/OBJ.cpp +++ b/xs/src/libslic3r/Format/OBJ.cpp @@ -57,14 +57,14 @@ bool load_obj(const char *path, Model *model, const char *object_name_in) continue; stl_facet &facet = stl.facet_start[i_face ++]; size_t num_normals = 0; - stl_normal normal = { 0.f }; + stl_normal normal(stl_normal::Zero()); for (unsigned int v = 0; v < 3; ++ v) { const ObjParser::ObjVertex &vertex = data.vertices[i++]; - memcpy(&facet.vertex[v].x, &data.coordinates[vertex.coordIdx*4], 3 * sizeof(float)); + memcpy(facet.vertex[v].data(), &data.coordinates[vertex.coordIdx*4], 3 * sizeof(float)); if (vertex.normalIdx != -1) { - normal.x += data.normals[vertex.normalIdx*3]; - normal.y += data.normals[vertex.normalIdx*3+1]; - normal.z += data.normals[vertex.normalIdx*3+2]; + normal(0) += data.normals[vertex.normalIdx*3]; + normal(1) += data.normals[vertex.normalIdx*3+1]; + normal(2) += data.normals[vertex.normalIdx*3+2]; ++ num_normals; } } @@ -74,33 +74,27 @@ bool load_obj(const char *path, Model *model, const char *object_name_in) facet2.vertex[0] = facet.vertex[0]; facet2.vertex[1] = facet.vertex[2]; const ObjParser::ObjVertex &vertex = data.vertices[i++]; - memcpy(&facet2.vertex[2].x, &data.coordinates[vertex.coordIdx * 4], 3 * sizeof(float)); + memcpy(facet2.vertex[2].data(), &data.coordinates[vertex.coordIdx * 4], 3 * sizeof(float)); if (vertex.normalIdx != -1) { - normal.x += data.normals[vertex.normalIdx*3]; - normal.y += data.normals[vertex.normalIdx*3+1]; - normal.z += data.normals[vertex.normalIdx*3+2]; + normal(0) += data.normals[vertex.normalIdx*3]; + normal(1) += data.normals[vertex.normalIdx*3+1]; + normal(2) += data.normals[vertex.normalIdx*3+2]; ++ num_normals; } if (num_normals == 4) { // Normalize an average normal of a quad. - float len = sqrt(facet.normal.x*facet.normal.x + facet.normal.y*facet.normal.y + facet.normal.z*facet.normal.z); + float len = facet.normal.norm(); if (len > EPSILON) { - normal.x /= len; - normal.y /= len; - normal.z /= len; + normal /= len; facet.normal = normal; facet2.normal = normal; } } } else if (num_normals == 3) { // Normalize an average normal of a triangle. - float len = sqrt(facet.normal.x*facet.normal.x + facet.normal.y*facet.normal.y + facet.normal.z*facet.normal.z); - if (len > EPSILON) { - normal.x /= len; - normal.y /= len; - normal.z /= len; - facet.normal = normal; - } + float len = facet.normal.norm(); + if (len > EPSILON) + facet.normal = normal / len; } } stl_get_size(&stl); diff --git a/xs/src/libslic3r/Format/PRUS.cpp b/xs/src/libslic3r/Format/PRUS.cpp index b817f8305e..3cf3fc075d 100644 --- a/xs/src/libslic3r/Format/PRUS.cpp +++ b/xs/src/libslic3r/Format/PRUS.cpp @@ -166,7 +166,7 @@ bool load_prus(const char *path, Model *model) float trafo[3][4] = { 0 }; double instance_rotation = 0.; double instance_scaling_factor = 1.f; - Pointf instance_offset(0., 0.); + Vec2d instance_offset(0., 0.); bool trafo_set = false; unsigned int group_id = (unsigned int)-1; unsigned int extruder_id = (unsigned int)-1; @@ -260,8 +260,8 @@ bool load_prus(const char *path, Model *model) mesh.repair(); // Transform the model. stl_transform(&stl, &trafo[0][0]); - if (std::abs(stl.stats.min.z) < EPSILON) - stl.stats.min.z = 0.; + if (std::abs(stl.stats.min(2)) < EPSILON) + stl.stats.min(2) = 0.; // Add a mesh to a model. if (mesh.facets_count() > 0) mesh_valid = true; @@ -309,11 +309,11 @@ bool load_prus(const char *path, Model *model) assert(res_normal == 3); int res_outer_loop = line_reader.next_line_scanf(" outer loop"); assert(res_outer_loop == 0); - int res_vertex1 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[0].x, &facet.vertex[0].y, &facet.vertex[0].z); + int res_vertex1 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[0](0), &facet.vertex[0](1), &facet.vertex[0](2)); assert(res_vertex1 == 3); - int res_vertex2 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[1].x, &facet.vertex[1].y, &facet.vertex[1].z); + int res_vertex2 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[1](0), &facet.vertex[1](1), &facet.vertex[1](2)); assert(res_vertex2 == 3); - int res_vertex3 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[2].x, &facet.vertex[2].y, &facet.vertex[2].z); + int res_vertex3 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[2](0), &facet.vertex[2](1), &facet.vertex[2](2)); assert(res_vertex3 == 3); int res_endloop = line_reader.next_line_scanf(" endloop"); assert(res_endloop == 0); @@ -324,9 +324,9 @@ bool load_prus(const char *path, Model *model) break; } // The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition. - if (sscanf(normal_buf[0], "%f", &facet.normal.x) != 1 || - sscanf(normal_buf[1], "%f", &facet.normal.y) != 1 || - sscanf(normal_buf[2], "%f", &facet.normal.z) != 1) { + if (sscanf(normal_buf[0], "%f", &facet.normal(0)) != 1 || + sscanf(normal_buf[1], "%f", &facet.normal(1)) != 1 || + sscanf(normal_buf[2], "%f", &facet.normal(2)) != 1) { // Normal was mangled. Maybe denormals or "not a number" were stored? // Just reset the normal and silently ignore it. memset(&facet.normal, 0, sizeof(facet.normal)); diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index 419299135f..fa18ddf3fc 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -64,7 +64,7 @@ std::string OozePrevention::pre_toolchange(GCode &gcodegen) // move to the nearest standby point if (!this->standby_points.empty()) { // get current position in print coordinates - Pointf3 writer_pos = gcodegen.writer().get_position(); + Vec3d writer_pos = gcodegen.writer().get_position(); Point pos = Point::new_scale(writer_pos(0), writer_pos(1)); // find standby point @@ -74,7 +74,7 @@ std::string OozePrevention::pre_toolchange(GCode &gcodegen) /* We don't call gcodegen.travel_to() because we don't need retraction (it was already triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates of the destination point must not be transformed by origin nor current extruder offset. */ - gcode += gcodegen.writer().travel_to_xy(Pointf::new_unscale(standby_point), + gcode += gcodegen.writer().travel_to_xy(unscale(standby_point), "move to standby position"); } @@ -207,7 +207,7 @@ 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(end_pos.x, end_pos.y)); + gcodegen.writer().travel_to_xy(Vec2d(end_pos.x, end_pos.y)); gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos)); // Prepare a future wipe. @@ -293,7 +293,7 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen) gcodegen.writer().toolchange(current_extruder_id); gcodegen.placeholder_parser().set("current_extruder", current_extruder_id); // A phony move to the end position at the wipe tower. - gcodegen.writer().travel_to_xy(Pointf(m_priming.end_pos.x, m_priming.end_pos.y)); + gcodegen.writer().travel_to_xy(Vec2d(m_priming.end_pos.x, m_priming.end_pos.y)); gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.end_pos)); // Prepare a future wipe. gcodegen.m_wipe.path.points.clear(); @@ -783,7 +783,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) Polygon outer_skirt = Slic3r::Geometry::convex_hull(skirt_points); Polygons skirts; for (unsigned int extruder_id : print.extruders()) { - const Pointf &extruder_offset = print.config.extruder_offset.get_at(extruder_id); + const Vec2d &extruder_offset = print.config.extruder_offset.get_at(extruder_id); Polygon s(outer_skirt); s.translate(Point::new_scale(- extruder_offset(0), - extruder_offset(1))); skirts.emplace_back(std::move(s)); @@ -831,7 +831,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) final_extruder_id = tool_ordering.last_extruder(); assert(final_extruder_id != (unsigned int)-1); } - this->set_origin(unscale(copy(0)), unscale(copy(1))); + this->set_origin(unscale(copy)); if (finished_objects > 0) { // Move to the origin position for the copy we're going to print. // This happens before Z goes down to layer 0 again, so that no collision happens hopefully. @@ -1547,7 +1547,7 @@ void GCode::process_layer( if (m_last_obj_copy != this_object_copy) m_avoid_crossing_perimeters.use_external_mp_once = true; m_last_obj_copy = this_object_copy; - this->set_origin(unscale(copy(0)), unscale(copy(1))); + this->set_origin(unscale(copy)); if (object_by_extruder.support != nullptr && !print_wipe_extrusions) { m_layer = layers[layer_id].support_layer; gcode += this->extrude_support( @@ -1632,7 +1632,7 @@ void GCode::set_extruders(const std::vector &extruder_ids) } } -void GCode::set_origin(const Pointf &pointf) +void GCode::set_origin(const Vec2d &pointf) { // if origin increases (goes towards right), last_pos decreases because it goes towards left const Point translate( @@ -2618,24 +2618,21 @@ std::string GCode::set_extruder(unsigned int extruder_id) } // convert a model-space scaled point into G-code coordinates -Pointf GCode::point_to_gcode(const Point &point) const +Vec2d GCode::point_to_gcode(const Point &point) const { - Pointf extruder_offset = EXTRUDER_CONFIG(extruder_offset); - return Pointf( - unscale(point(0)) + m_origin(0) - extruder_offset(0), - unscale(point(1)) + m_origin(1) - extruder_offset(1)); + Vec2d extruder_offset = EXTRUDER_CONFIG(extruder_offset); + return unscale(point) + m_origin - extruder_offset; } // convert a model-space scaled point into G-code coordinates -Point GCode::gcode_to_point(const Pointf &point) const +Point GCode::gcode_to_point(const Vec2d &point) const { - Pointf extruder_offset = EXTRUDER_CONFIG(extruder_offset); + Vec2d extruder_offset = EXTRUDER_CONFIG(extruder_offset); return Point( scale_(point(0) - m_origin(0) + extruder_offset(0)), scale_(point(1) - m_origin(1) + extruder_offset(1))); } - // Goes through by_region std::vector and returns reference to a subvector of entities, that are to be printed // during infill/perimeter wiping, or normally (depends on wiping_entities parameter) // Returns a reference to member to avoid copying. diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index 4953c39fef..dc8a321354 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -125,6 +125,7 @@ private: class GCode { public: GCode() : + m_origin(Vec2d::Zero()), m_enable_loop_clipping(true), m_enable_cooling_markers(false), m_enable_extrusion_role_markers(false), @@ -152,12 +153,12 @@ public: void do_export(Print *print, const char *path, GCodePreviewData *preview_data = nullptr); // Exported for the helper classes (OozePrevention, Wipe) and for the Perl binding for unit tests. - const Pointf& origin() const { return m_origin; } - void set_origin(const Pointf &pointf); - void set_origin(const coordf_t x, const coordf_t y) { this->set_origin(Pointf(x, y)); } + const Vec2d& origin() const { return m_origin; } + void set_origin(const Vec2d &pointf); + void set_origin(const coordf_t x, const coordf_t y) { this->set_origin(Vec2d(x, y)); } const Point& last_pos() const { return m_last_pos; } - Pointf point_to_gcode(const Point &point) const; - Point gcode_to_point(const Pointf &point) const; + Vec2d point_to_gcode(const Point &point) const; + Point gcode_to_point(const Vec2d &point) const; const FullPrintConfig &config() const { return m_config; } const Layer* layer() const { return m_layer; } GCodeWriter& writer() { return m_writer; } @@ -258,7 +259,7 @@ protected: /* Origin of print coordinates expressed in unscaled G-code coordinates. This affects the input arguments supplied to the extrude*() and travel_to() methods. */ - Pointf m_origin; + Vec2d m_origin; FullPrintConfig m_config; GCodeWriter m_writer; PlaceholderParser m_placeholder_parser; diff --git a/xs/src/libslic3r/GCode/Analyzer.cpp b/xs/src/libslic3r/GCode/Analyzer.cpp index 44e6e9acb9..51d5b1a067 100644 --- a/xs/src/libslic3r/GCode/Analyzer.cpp +++ b/xs/src/libslic3r/GCode/Analyzer.cpp @@ -14,7 +14,7 @@ static const float MMMIN_TO_MMSEC = 1.0f / 60.0f; static const float INCHES_TO_MM = 25.4f; static const float DEFAULT_FEEDRATE = 0.0f; static const unsigned int DEFAULT_EXTRUDER_ID = 0; -static const Slic3r::Pointf3 DEFAULT_START_POSITION = Slic3r::Pointf3(0.0f, 0.0f, 0.0f); +static const Slic3r::Vec3d DEFAULT_START_POSITION = Slic3r::Vec3d(0.0f, 0.0f, 0.0f); static const float DEFAULT_START_EXTRUSION = 0.0f; namespace Slic3r { @@ -71,7 +71,7 @@ bool GCodeAnalyzer::Metadata::operator != (const GCodeAnalyzer::Metadata& other) return false; } -GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Pointf3& start_position, const Pointf3& end_position, float delta_extruder) +GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder) : type(type) , data(extrusion_role, extruder_id, mm3_per_mm, width, height, feedrate) , start_position(start_position) @@ -80,7 +80,7 @@ GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, ExtrusionRole extrusi { } -GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, const GCodeAnalyzer::Metadata& data, const Pointf3& start_position, const Pointf3& end_position, float delta_extruder) +GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, const GCodeAnalyzer::Metadata& data, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder) : type(type) , data(data) , start_position(start_position) @@ -587,12 +587,12 @@ void GCodeAnalyzer::_reset_axes_position() ::memset((void*)m_state.position, 0, Num_Axis * sizeof(float)); } -void GCodeAnalyzer::_set_start_position(const Pointf3& position) +void GCodeAnalyzer::_set_start_position(const Vec3d& position) { m_state.start_position = position; } -const Pointf3& GCodeAnalyzer::_get_start_position() const +const Vec3d& GCodeAnalyzer::_get_start_position() const { return m_state.start_position; } @@ -612,9 +612,9 @@ float GCodeAnalyzer::_get_delta_extrusion() const return _get_axis_position(E) - m_state.start_extrusion; } -Pointf3 GCodeAnalyzer::_get_end_position() const +Vec3d GCodeAnalyzer::_get_end_position() const { - return Pointf3(m_state.position[X], m_state.position[Y], m_state.position[Z]); + return Vec3d(m_state.position[X], m_state.position[Y], m_state.position[Z]); } void GCodeAnalyzer::_store_move(GCodeAnalyzer::GCodeMove::EType type) @@ -673,7 +673,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ Metadata data; float z = FLT_MAX; Polyline polyline; - Pointf3 position(FLT_MAX, FLT_MAX, FLT_MAX); + Vec3d position(FLT_MAX, FLT_MAX, FLT_MAX); float volumetric_rate = FLT_MAX; GCodePreviewData::Range height_range; GCodePreviewData::Range width_range; @@ -742,7 +742,7 @@ void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data) return; Polyline3 polyline; - Pointf3 position(FLT_MAX, FLT_MAX, FLT_MAX); + Vec3d position(FLT_MAX, FLT_MAX, FLT_MAX); GCodePreviewData::Travel::EType type = GCodePreviewData::Travel::Num_Types; GCodePreviewData::Travel::Polyline::EDirection direction = GCodePreviewData::Travel::Polyline::Num_Directions; float feedrate = FLT_MAX; @@ -768,12 +768,12 @@ void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data) polyline = Polyline3(); // add both vertices of the move - polyline.append(Point3(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z()))); - polyline.append(Point3(scale_(move.end_position.x()), scale_(move.end_position.y()), scale_(move.end_position.z()))); + polyline.append(Vec3crd(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z()))); + polyline.append(Vec3crd(scale_(move.end_position.x()), scale_(move.end_position.y()), scale_(move.end_position.z()))); } else // append end vertex of the move to current polyline - polyline.append(Point3(scale_(move.end_position.x()), scale_(move.end_position.y()), scale_(move.end_position.z()))); + polyline.append(Vec3crd(scale_(move.end_position.x()), scale_(move.end_position.y()), scale_(move.end_position.z()))); // update current values position = move.end_position; @@ -804,7 +804,7 @@ void GCodeAnalyzer::_calc_gcode_preview_retractions(GCodePreviewData& preview_da for (const GCodeMove& move : retraction_moves->second) { // store position - Point3 position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z())); + Vec3crd position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z())); preview_data.retraction.positions.emplace_back(position, move.data.width, move.data.height); } } @@ -818,7 +818,7 @@ void GCodeAnalyzer::_calc_gcode_preview_unretractions(GCodePreviewData& preview_ for (const GCodeMove& move : unretraction_moves->second) { // store position - Point3 position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z())); + Vec3crd position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z())); preview_data.unretraction.positions.emplace_back(position, move.data.width, move.data.height); } } diff --git a/xs/src/libslic3r/GCode/Analyzer.hpp b/xs/src/libslic3r/GCode/Analyzer.hpp index 03dbab3383..27a49b8690 100644 --- a/xs/src/libslic3r/GCode/Analyzer.hpp +++ b/xs/src/libslic3r/GCode/Analyzer.hpp @@ -75,12 +75,12 @@ public: EType type; Metadata data; - Pointf3 start_position; - Pointf3 end_position; + Vec3d start_position; + Vec3d end_position; float delta_extruder; - GCodeMove(EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Pointf3& start_position, const Pointf3& end_position, float delta_extruder); - GCodeMove(EType type, const Metadata& data, const Pointf3& start_position, const Pointf3& end_position, float delta_extruder); + GCodeMove(EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder); + GCodeMove(EType type, const Metadata& data, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder); }; typedef std::vector GCodeMovesList; @@ -93,7 +93,7 @@ private: EPositioningType global_positioning_type; EPositioningType e_local_positioning_type; Metadata data; - Pointf3 start_position; + Vec3d start_position = Vec3d::Zero(); float start_extrusion; float position[Num_Axis]; }; @@ -206,15 +206,15 @@ private: // Sets axes position to zero void _reset_axes_position(); - void _set_start_position(const Pointf3& position); - const Pointf3& _get_start_position() const; + void _set_start_position(const Vec3d& position); + const Vec3d& _get_start_position() const; void _set_start_extrusion(float extrusion); float _get_start_extrusion() const; float _get_delta_extrusion() const; // Returns current xyz position (from m_state.position[]) - Pointf3 _get_end_position() const; + Vec3d _get_end_position() const; // Adds a new move with the given data void _store_move(GCodeMove::EType type); diff --git a/xs/src/libslic3r/GCode/CoolingBuffer.cpp b/xs/src/libslic3r/GCode/CoolingBuffer.cpp index 0f361b250d..40ccc7b09f 100644 --- a/xs/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/xs/src/libslic3r/GCode/CoolingBuffer.cpp @@ -23,7 +23,7 @@ CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_gcodegen(gcodegen), m_current_ void CoolingBuffer::reset() { m_current_pos.assign(5, 0.f); - Pointf3 pos = m_gcodegen.writer().get_position(); + Vec3d pos = m_gcodegen.writer().get_position(); m_current_pos[0] = float(pos(0)); m_current_pos[1] = float(pos(1)); m_current_pos[2] = float(pos(2)); diff --git a/xs/src/libslic3r/GCode/PreviewData.cpp b/xs/src/libslic3r/GCode/PreviewData.cpp index 3833bca06c..9cf9716e0a 100644 --- a/xs/src/libslic3r/GCode/PreviewData.cpp +++ b/xs/src/libslic3r/GCode/PreviewData.cpp @@ -226,7 +226,7 @@ void GCodePreviewData::Travel::set_default() const GCodePreviewData::Color GCodePreviewData::Retraction::Default_Color = GCodePreviewData::Color(1.0f, 1.0f, 1.0f, 1.0f); -GCodePreviewData::Retraction::Position::Position(const Point3& position, float width, float height) +GCodePreviewData::Retraction::Position::Position(const Vec3crd& position, float width, float height) : position(position) , width(width) , height(height) diff --git a/xs/src/libslic3r/GCode/PreviewData.hpp b/xs/src/libslic3r/GCode/PreviewData.hpp index ea8ca6d58a..ab74993f55 100644 --- a/xs/src/libslic3r/GCode/PreviewData.hpp +++ b/xs/src/libslic3r/GCode/PreviewData.hpp @@ -151,11 +151,11 @@ public: struct Position { - Point3 position; + Vec3crd position; float width; float height; - Position(const Point3& position, float width, float height); + Position(const Vec3crd& position, float width, float height); }; typedef std::vector PositionsList; diff --git a/xs/src/libslic3r/GCode/PrintExtents.cpp b/xs/src/libslic3r/GCode/PrintExtents.cpp index e1d4e257f6..2ed6077696 100644 --- a/xs/src/libslic3r/GCode/PrintExtents.cpp +++ b/xs/src/libslic3r/GCode/PrintExtents.cpp @@ -32,8 +32,8 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionPath &extrusio BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width)); BoundingBoxf bboxf; if (! empty(bbox)) { - bboxf.min = Pointf::new_unscale(bbox.min); - bboxf.max = Pointf::new_unscale(bbox.max); + bboxf.min = unscale(bbox.min); + bboxf.max = unscale(bbox.max); bboxf.defined = true; } return bboxf; @@ -46,8 +46,8 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionLoop &extrusio bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width))); BoundingBoxf bboxf; if (! empty(bbox)) { - bboxf.min = Pointf::new_unscale(bbox.min); - bboxf.max = Pointf::new_unscale(bbox.max); + bboxf.min = unscale(bbox.min); + bboxf.max = unscale(bbox.max); bboxf.defined = true; } return bboxf; @@ -60,8 +60,8 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionMultiPath &ext bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width))); BoundingBoxf bboxf; if (! empty(bbox)) { - bboxf.min = Pointf::new_unscale(bbox.min); - bboxf.max = Pointf::new_unscale(bbox.max); + bboxf.min = unscale(bbox.min); + bboxf.max = unscale(bbox.max); bboxf.defined = true; } return bboxf; @@ -123,7 +123,7 @@ BoundingBoxf get_print_object_extrusions_extents(const PrintObject &print_object bbox_this.merge(extrusionentity_extents(extrusion_entity)); for (const Point &offset : print_object._shifted_copies) { BoundingBoxf bbox_translated(bbox_this); - bbox_translated.translate(Pointf::new_unscale(offset)); + bbox_translated.translate(unscale(offset)); bbox.merge(bbox_translated); } } @@ -136,8 +136,9 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_ { // 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; + Transform2d trafo = + Eigen::Translation2d(print.config.wipe_tower_x.value, print.config.wipe_tower_y.value) * + Eigen::Rotation2Dd(print.config.wipe_tower_rotation_angle.value); BoundingBoxf bbox; for (const std::vector &tool_changes : print.m_wipe_tower_tool_changes) { @@ -147,19 +148,11 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_ for (size_t i = 1; i < tcr.extrusions.size(); ++ i) { const WipeTower::Extrusion &e = tcr.extrusions[i]; 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 += wipe_tower_pos; - p2.rotate(wipe_tower_angle); - p2 += wipe_tower_pos; - - bbox.merge(p1); - coordf_t radius = 0.5 * e.width; - bbox.min(0) = std::min(bbox.min(0), std::min(p1(0), p2(0)) - radius); - bbox.min(1) = std::min(bbox.min(1), std::min(p1(1), p2(1)) - radius); - bbox.max(0) = std::max(bbox.max(0), std::max(p1(0), p2(0)) + radius); - bbox.max(1) = std::max(bbox.max(1), std::max(p1(1), p2(1)) + radius); + Vec2d delta = 0.5 * Vec2d(e.width, e.width); + Vec2d p1 = trafo * Vec2d((&e - 1)->pos.x, (&e - 1)->pos.y); + Vec2d p2 = trafo * Vec2d(e.pos.x, e.pos.y); + bbox.merge(p1.cwiseMin(p2) - delta); + bbox.merge(p1.cwiseMax(p2) + delta); } } } @@ -176,8 +169,8 @@ BoundingBoxf get_wipe_tower_priming_extrusions_extents(const Print &print) for (size_t i = 1; i < tcr.extrusions.size(); ++ i) { const WipeTower::Extrusion &e = tcr.extrusions[i]; if (e.width > 0) { - Pointf p1((&e - 1)->pos.x, (&e - 1)->pos.y); - Pointf p2(e.pos.x, e.pos.y); + Vec2d p1((&e - 1)->pos.x, (&e - 1)->pos.y); + Vec2d p2(e.pos.x, e.pos.y); bbox.merge(p1); coordf_t radius = 0.5 * e.width; bbox.min(0) = std::min(bbox.min(0), std::min(p1(0), p2(0)) - radius); diff --git a/xs/src/libslic3r/GCodeWriter.cpp b/xs/src/libslic3r/GCodeWriter.cpp index cefbdad2e2..6ef17f4f41 100644 --- a/xs/src/libslic3r/GCodeWriter.cpp +++ b/xs/src/libslic3r/GCodeWriter.cpp @@ -276,7 +276,7 @@ std::string GCodeWriter::set_speed(double F, const std::string &comment, const s return gcode.str(); } -std::string GCodeWriter::travel_to_xy(const Pointf &point, const std::string &comment) +std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &comment) { m_pos(0) = point(0); m_pos(1) = point(1); @@ -290,7 +290,7 @@ std::string GCodeWriter::travel_to_xy(const Pointf &point, const std::string &co return gcode.str(); } -std::string GCodeWriter::travel_to_xyz(const Pointf3 &point, const std::string &comment) +std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &comment) { /* If target Z is lower than current Z but higher than nominal Z we don't perform the Z move but we only move in the XY plane and @@ -299,7 +299,7 @@ std::string GCodeWriter::travel_to_xyz(const Pointf3 &point, const std::string & if (!this->will_move_z(point(2))) { double nominal_z = m_pos(2) - m_lifted; m_lifted = m_lifted - (point(2) - nominal_z); - return this->travel_to_xy(point.xy()); + return this->travel_to_xy(to_2d(point)); } /* In all the other cases, we perform an actual XYZ move and cancel @@ -358,7 +358,7 @@ bool GCodeWriter::will_move_z(double z) const return true; } -std::string GCodeWriter::extrude_to_xy(const Pointf &point, double dE, const std::string &comment) +std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string &comment) { m_pos(0) = point(0); m_pos(1) = point(1); @@ -373,7 +373,7 @@ std::string GCodeWriter::extrude_to_xy(const Pointf &point, double dE, const std return gcode.str(); } -std::string GCodeWriter::extrude_to_xyz(const Pointf3 &point, double dE, const std::string &comment) +std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment) { m_pos = point; m_lifted = 0; diff --git a/xs/src/libslic3r/GCodeWriter.hpp b/xs/src/libslic3r/GCodeWriter.hpp index f706b87689..664b0e3a1d 100644 --- a/xs/src/libslic3r/GCodeWriter.hpp +++ b/xs/src/libslic3r/GCodeWriter.hpp @@ -55,18 +55,18 @@ public: std::string toolchange_prefix() const; std::string toolchange(unsigned int extruder_id); std::string set_speed(double F, const std::string &comment = std::string(), const std::string &cooling_marker = std::string()) const; - std::string travel_to_xy(const Pointf &point, const std::string &comment = std::string()); - std::string travel_to_xyz(const Pointf3 &point, const std::string &comment = std::string()); + std::string travel_to_xy(const Vec2d &point, const std::string &comment = std::string()); + std::string travel_to_xyz(const Vec3d &point, const std::string &comment = std::string()); std::string travel_to_z(double z, const std::string &comment = std::string()); bool will_move_z(double z) const; - std::string extrude_to_xy(const Pointf &point, double dE, const std::string &comment = std::string()); - std::string extrude_to_xyz(const Pointf3 &point, double dE, const std::string &comment = std::string()); + std::string extrude_to_xy(const Vec2d &point, double dE, const std::string &comment = std::string()); + std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string()); std::string retract(bool before_wipe = false); std::string retract_for_toolchange(bool before_wipe = false); std::string unretract(); std::string lift(); std::string unlift(); - Pointf3 get_position() const { return m_pos; } + Vec3d get_position() const { return m_pos; } private: std::vector m_extruders; @@ -81,7 +81,7 @@ private: unsigned int m_last_bed_temperature; bool m_last_bed_temperature_reached; double m_lifted; - Pointf3 m_pos; + Vec3d m_pos = Vec3d::Zero(); std::string _travel_to_z(double z, const std::string &comment); std::string _retract(double length, double restart_extra, const std::string &comment); diff --git a/xs/src/libslic3r/Geometry.cpp b/xs/src/libslic3r/Geometry.cpp index b4813be14a..c978d46b61 100644 --- a/xs/src/libslic3r/Geometry.cpp +++ b/xs/src/libslic3r/Geometry.cpp @@ -345,7 +345,7 @@ linint(double value, double oldmin, double oldmax, double newmin, double newmax) // If the points have the same weight, sort them lexicographically by their positions. struct ArrangeItem { ArrangeItem() {} - Pointf pos; + Vec2d pos; coordf_t weight; bool operator<(const ArrangeItem &other) const { return weight < other.weight || @@ -353,17 +353,17 @@ struct ArrangeItem { } }; -Pointfs arrange(size_t num_parts, const Pointf &part_size, coordf_t gap, const BoundingBoxf* bed_bounding_box) +Pointfs arrange(size_t num_parts, const Vec2d &part_size, coordf_t gap, const BoundingBoxf* bed_bounding_box) { // Use actual part size (the largest) plus separation distance (half on each side) in spacing algorithm. - const Pointf cell_size(part_size(0) + gap, part_size(1) + gap); + const Vec2d cell_size(part_size(0) + gap, part_size(1) + gap); const BoundingBoxf bed_bbox = (bed_bounding_box != NULL && bed_bounding_box->defined) ? *bed_bounding_box : // Bogus bed size, large enough not to trigger the unsufficient bed size error. BoundingBoxf( - Pointf(0, 0), - Pointf(cell_size(0) * num_parts, cell_size(1) * num_parts)); + Vec2d(0, 0), + Vec2d(cell_size(0) * num_parts, cell_size(1) * num_parts)); // This is how many cells we have available into which to put parts. size_t cellw = size_t(floor((bed_bbox.size()(0) + gap) / cell_size(0))); @@ -372,8 +372,8 @@ Pointfs arrange(size_t num_parts, const Pointf &part_size, coordf_t gap, const B CONFESS(PRINTF_ZU " parts won't fit in your print area!\n", num_parts); // Get a bounding box of cellw x cellh cells, centered at the center of the bed. - Pointf cells_size(cellw * cell_size(0) - gap, cellh * cell_size(1) - gap); - Pointf cells_offset(bed_bbox.center() - 0.5 * cells_size); + Vec2d cells_size(cellw * cell_size(0) - gap, cellh * cell_size(1) - gap); + Vec2d cells_offset(bed_bbox.center() - 0.5 * cells_size); BoundingBoxf cells_bb(cells_offset, cells_size + cells_offset); // List of cells, sorted by distance from center. @@ -405,35 +405,35 @@ Pointfs arrange(size_t num_parts, const Pointf &part_size, coordf_t gap, const B Pointfs positions; positions.reserve(num_parts); for (std::vector::const_iterator it = cellsorder.begin(); it != cellsorder.end(); ++ it) - positions.push_back(Pointf(it->pos(0) - 0.5 * part_size(0), it->pos(1) - 0.5 * part_size(1))); + positions.push_back(Vec2d(it->pos(0) - 0.5 * part_size(0), it->pos(1) - 0.5 * part_size(1))); return positions; } #else class ArrangeItem { - public: - Pointf pos; +public: + Vec2d pos = Vec2d::Zero(); size_t index_x, index_y; coordf_t dist; }; class ArrangeItemIndex { - public: +public: coordf_t index; ArrangeItem item; ArrangeItemIndex(coordf_t _index, ArrangeItem _item) : index(_index), item(_item) {}; }; bool -arrange(size_t total_parts, const Pointf &part_size, coordf_t dist, const BoundingBoxf* bb, Pointfs &positions) +arrange(size_t total_parts, const Vec2d &part_size, coordf_t dist, const BoundingBoxf* bb, Pointfs &positions) { positions.clear(); - Pointf part = part_size; + Vec2d part = part_size; // use actual part size (the largest) plus separation distance (half on each side) in spacing algorithm part(0) += dist; part(1) += dist; - Pointf area; + Vec2d area(Vec2d::Zero()); if (bb != NULL && bb->defined) { area = bb->size(); } else { @@ -449,11 +449,11 @@ arrange(size_t total_parts, const Pointf &part_size, coordf_t dist, const Boundi return false; // total space used by cells - Pointf cells(cellw * part(0), cellh * part(1)); + Vec2d cells(cellw * part(0), cellh * part(1)); // bounding box of total space used by cells BoundingBoxf cells_bb; - cells_bb.merge(Pointf(0,0)); // min + cells_bb.merge(Vec2d(0,0)); // min cells_bb.merge(cells); // max // center bounding box to area @@ -533,7 +533,7 @@ arrange(size_t total_parts, const Pointf &part_size, coordf_t dist, const Boundi coordf_t cx = c.item.index_x - lx; coordf_t cy = c.item.index_y - ty; - positions.push_back(Pointf(cx * part(0), cy * part(1))); + positions.push_back(Vec2d(cx * part(0), cy * part(1))); } if (bb != NULL && bb->defined) { diff --git a/xs/src/libslic3r/Geometry.hpp b/xs/src/libslic3r/Geometry.hpp index 95c13fcdb0..1945340077 100644 --- a/xs/src/libslic3r/Geometry.hpp +++ b/xs/src/libslic3r/Geometry.hpp @@ -66,7 +66,7 @@ static inline bool is_ccw(const Polygon &poly) return o == ORIENTATION_CCW; } -inline bool ray_ray_intersection(const Pointf &p1, const Vectorf &v1, const Pointf &p2, const Vectorf &v2, Pointf &res) +inline bool ray_ray_intersection(const Vec2d &p1, const Vec2d &v1, const Vec2d &p2, const Vec2d &v2, Vec2d &res) { double denom = v1(0) * v2(1) - v2(0) * v1(1); if (std::abs(denom) < EPSILON) @@ -77,7 +77,7 @@ inline bool ray_ray_intersection(const Pointf &p1, const Vectorf &v1, const Poin return true; } -inline bool segment_segment_intersection(const Pointf &p1, const Vectorf &v1, const Pointf &p2, const Vectorf &v2, Pointf &res) +inline bool segment_segment_intersection(const Vec2d &p1, const Vec2d &v1, const Vec2d &p2, const Vec2d &v2, Vec2d &res) { double denom = v1(0) * v2(1) - v2(0) * v1(1); if (std::abs(denom) < EPSILON) @@ -123,7 +123,7 @@ void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* ret double linint(double value, double oldmin, double oldmax, double newmin, double newmax); bool arrange( // input - size_t num_parts, const Pointf &part_size, coordf_t gap, const BoundingBoxf* bed_bounding_box, + size_t num_parts, const Vec2d &part_size, coordf_t gap, const BoundingBoxf* bed_bounding_box, // output Pointfs &positions); diff --git a/xs/src/libslic3r/Line.cpp b/xs/src/libslic3r/Line.cpp index 93e888efa9..a29e4ce4ee 100644 --- a/xs/src/libslic3r/Line.cpp +++ b/xs/src/libslic3r/Line.cpp @@ -97,11 +97,11 @@ bool Line::intersection(const Line &l2, Point *intersection) const return false; // not intersecting } -Pointf3 Linef3::intersect_plane(double z) const +Vec3d Linef3::intersect_plane(double z) const { auto v = (this->b - this->a).cast(); double t = (z - this->a(2)) / v(2); - return Pointf3(this->a(0) + v(0) * t, this->a(1) + v(1) * t, z); + return Vec3d(this->a(0) + v(0) * t, this->a(1) + v(1) * t, z); } } diff --git a/xs/src/libslic3r/Line.hpp b/xs/src/libslic3r/Line.hpp index 39c2e1ff1d..5b77a4b612 100644 --- a/xs/src/libslic3r/Line.hpp +++ b/xs/src/libslic3r/Line.hpp @@ -19,7 +19,7 @@ class Line { public: Line() {} - explicit Line(Point _a, Point _b): a(_a), b(_b) {} + Line(const Point& _a, const Point& _b) : a(_a), b(_b) {} explicit operator Lines() const { Lines lines; lines.emplace_back(*this); return lines; } void scale(double factor) { this->a *= factor; this->b *= factor; } void translate(double x, double y) { Vector v(x, y); this->a += v; this->b += v; } @@ -49,45 +49,49 @@ class ThickLine : public Line { public: ThickLine() : a_width(0), b_width(0) {} - ThickLine(Point a, Point b) : Line(a, b), a_width(0), b_width(0) {} - ThickLine(Point a, Point b, double wa, double wb) : Line(a, b), a_width(wa), b_width(wb) {} + ThickLine(const Point& a, const Point& b) : Line(a, b), a_width(0), b_width(0) {} + ThickLine(const Point& a, const Point& b, double wa, double wb) : Line(a, b), a_width(wa), b_width(wb) {} - coordf_t a_width, b_width; + double a_width, b_width; }; class Line3 { public: - Line3() {} - Line3(const Point3& _a, const Point3& _b) : a(_a), b(_b) {} + Line3() : a(Vec3crd::Zero()), b(Vec3crd::Zero()) {} + Line3(const Vec3crd& _a, const Vec3crd& _b) : a(_a), b(_b) {} double length() const { return (this->a - this->b).cast().norm(); } - Vector3 vector() const { return this->b - this->a; } + Vec3crd vector() const { return this->b - this->a; } - Point3 a; - Point3 b; + Vec3crd a; + Vec3crd b; }; class Linef { public: - Linef() {} - explicit Linef(Pointf _a, Pointf _b): a(_a), b(_b) {} + Linef() : a(Vec2d::Zero()), b(Vec2d::Zero()) {} + Linef(const Vec2d& _a, const Vec2d& _b) : a(_a), b(_b) {} - Pointf a; - Pointf b; + Vec2d a; + Vec2d b; }; class Linef3 { public: - Linef3() {} - explicit Linef3(Pointf3 _a, Pointf3 _b): a(_a), b(_b) {} - Pointf3 intersect_plane(double z) const; - void scale(double factor) { this->a *= factor; this->b *= factor; } + Linef3() : a(Vec3d::Zero()), b(Vec3d::Zero()) {} + Linef3(const Vec3d& _a, const Vec3d& _b) : a(_a), b(_b) {} - Pointf3 a; - Pointf3 b; + Vec3d intersect_plane(double z) const; + void scale(double factor) { this->a *= factor; this->b *= factor; } + Vec3d vector() const { return this->b - this->a; } + Vec3d unit_vector() const { return (length() == 0.0) ? Vec3d::Zero() : vector().normalized(); } + double length() const { return vector().norm(); } + + Vec3d a; + Vec3d b; }; } // namespace Slic3r diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index c5b8ad8dfe..d046a8ef28 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -235,15 +235,7 @@ 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) +void Model::center_instances_around_point(const Vec2d &point) { // BoundingBoxf3 bb = this->bounding_box(); BoundingBoxf3 bb; @@ -251,7 +243,7 @@ void Model::center_instances_around_point(const Pointf &point) for (size_t i = 0; i < o->instances.size(); ++ i) bb.merge(o->instance_bounding_box(i, false)); - Pointf shift = point - 0.5 * bb.size().xy() - bb.min.xy(); + Vec2d shift = point - 0.5 * to_2d(bb.size()) - to_2d(bb.min); for (ModelObject *o : this->objects) { for (ModelInstance *i : o->instances) i->offset += shift; @@ -309,8 +301,8 @@ bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) for (size_t i = 0; i < o->instances.size(); ++ i) { // an accurate snug bounding box around the transformed mesh. BoundingBoxf3 bbox(o->instance_bounding_box(i, true)); - instance_sizes.push_back(bbox.size().xy()); - instance_centers.push_back(bbox.center().xy()); + instance_sizes.emplace_back(to_2d(bbox.size())); + instance_centers.emplace_back(to_2d(bbox.center())); } Pointfs positions; @@ -332,7 +324,7 @@ bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) // Duplicate the entire model preserving instance relative positions. void Model::duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb) { - Pointfs model_sizes(copies_num-1, this->bounding_box().size().xy()); + Pointfs model_sizes(copies_num-1, to_2d(this->bounding_box().size())); Pointfs positions; if (! _arrange(model_sizes, dist, bb, positions)) CONFESS("Cannot duplicate part as the resulting objects would not fit on the print bed.\n"); @@ -343,7 +335,7 @@ void Model::duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb) // make a copy of the pointers in order to avoid recursion when appending their copies ModelInstancePtrs instances = o->instances; for (const ModelInstance *i : instances) { - for (const Pointf &pos : positions) { + for (const Vec2d &pos : positions) { ModelInstance *instance = o->add_instance(*i); instance->offset += pos; } @@ -375,7 +367,7 @@ void Model::duplicate_objects_grid(size_t x, size_t y, coordf_t dist) ModelObject* object = this->objects.front(); object->clear_instances(); - Sizef3 size = object->bounding_box().size(); + Vec3d size = object->bounding_box().size(); for (size_t x_copy = 1; x_copy <= x; ++x_copy) { for (size_t y_copy = 1; y_copy <= y; ++y_copy) { @@ -621,54 +613,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(0) *= inst->scaling_factor; - p(1) *= inst->scaling_factor; - p(2) *= inst->scaling_factor; - - // rotate Z - double x = p(0); - double y = p(1); - p(0) = c * x - s * y; - p(1) = s * x + c * y; - - // translate - p(0) += inst->offset(0); - p(1) += inst->offset(1); - - bb.merge(p); - } - } - } - } - } - - return bb; -} - // A mesh containing all transformed instances of this object. TriangleMesh ModelObject::mesh() const { @@ -726,24 +670,22 @@ void ModelObject::center_around_origin() if (! v->modifier) bb.merge(v->mesh.bounding_box()); - // first align to origin on XYZ - Vectorf3 vector(-bb.min(0), -bb.min(1), -bb.min(2)); + // First align to origin on XYZ, then center it on XY. + Vec3d size = bb.size(); + size(2) = 0.; + Vec3d shift3 = - bb.min - 0.5 * size; + // Unaligned vector, for the Rotation2D to work on Visual Studio 2013. + Eigen::Vector2d shift2 = to_2d(shift3); - // then center it on XY - Sizef3 size = bb.size(); - vector(0) -= size(0)/2; - vector(1) -= size(1)/2; - - this->translate(vector); - this->origin_translation += vector; + this->translate(shift3); + this->origin_translation += shift3; if (!this->instances.empty()) { for (ModelInstance *i : this->instances) { // apply rotation and scaling to vector as well before translating instance, // in order to leave final position unaltered - Vectorf v = - vector.xy(); - v.rotate(i->rotation); - i->offset += v * i->scaling_factor; + Eigen::Rotation2Dd rot(i->rotation); + i->offset -= rot * shift2 * i->scaling_factor; } this->invalidate_bounding_box(); } @@ -752,39 +694,38 @@ 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) +void ModelObject::scale(const Vec3d &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->origin_translation = Vec3d::Zero(); this->invalidate_bounding_box(); } 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); + v->m_convex_hull.rotate(angle, axis); } - 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->origin_translation = Vec3d::Zero(); this->invalidate_bounding_box(); } @@ -796,17 +737,22 @@ 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); - invalidate_bounding_box(); + this->origin_translation = Vec3d::Zero(); + this->invalidate_bounding_box(); } 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 = Vec3d::Zero(); this->invalidate_bounding_box(); } @@ -910,45 +856,18 @@ 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; + Transform3d m = Transform3d::Identity(); + m.translate(Vec3d(inst->offset(0), inst->offset(1), 0.0)); + m.rotate(Eigen::AngleAxisd(inst->rotation, Vec3d::UnitZ())); + m.scale(inst->scaling_factor); - 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(0) *= inst->scaling_factor; - p(1) *= inst->scaling_factor; - p(2) *= inst->scaling_factor; - - // rotate Z - double x = p(0); - double y = p(1); - p(0) = c * x - s * y; - p(1) = s * x + c * y; - - // translate - p(0) += inst->offset(0); - p(1) += inst->offset(1); - - bb.merge(p); - } - } + BoundingBoxf3 bb = vol->get_convex_hull().transformed_bounding_box(m); if (print_volume.contains(bb)) inst->print_volume_state = ModelInstance::PVS_Inside; @@ -970,7 +889,7 @@ void ModelObject::print_info() const TriangleMesh mesh = this->raw_mesh(); mesh.check_topology(); BoundingBoxf3 bb = mesh.bounding_box(); - Sizef3 size = bb.size(); + Vec3d size = bb.size(); cout << "size_x = " << size(0) << endl; cout << "size_y = " << size(1) << endl; cout << "size_z = " << size(2) << endl; @@ -1031,6 +950,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. @@ -1082,30 +1011,20 @@ BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh* mes for (int i = 0; i < mesh->stl.stats.number_of_facets; ++ i) { const stl_facet &facet = mesh->stl.facet_start[i]; for (int j = 0; j < 3; ++ j) { - stl_vertex v = facet.vertex[j]; - double xold = v.x; - double yold = v.y; - v.x = float(c * xold - s * yold); - v.y = float(s * xold + c * yold); - bbox.merge(Pointf3(v.x, v.y, v.z)); + const stl_vertex &v = facet.vertex[j]; + bbox.merge(Vec3d(c * v(0) - s * v(1), s * v(0) + c * v(1), v(2))); } } if (! empty(bbox)) { // Scale the bounding box uniformly. if (std::abs(this->scaling_factor - 1.) > EPSILON) { - bbox.min(0) *= float(this->scaling_factor); - bbox.min(1) *= float(this->scaling_factor); - bbox.min(2) *= float(this->scaling_factor); - bbox.max(0) *= float(this->scaling_factor); - bbox.max(1) *= float(this->scaling_factor); - bbox.max(2) *= float(this->scaling_factor); + bbox.min *= this->scaling_factor; + bbox.max *= this->scaling_factor; } // Translate the bounding box. if (! dont_translate) { - bbox.min(0) += float(this->offset(0)); - bbox.min(1) += float(this->offset(1)); - bbox.max(0) += float(this->offset(0)); - bbox.max(1) += float(this->offset(1)); + Eigen::Map(bbox.min.data()) += this->offset; + Eigen::Map(bbox.max.data()) += this->offset; } } return bbox; @@ -1113,10 +1032,11 @@ BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh* mes BoundingBoxf3 ModelInstance::transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate) const { - auto matrix = Transform3f::Identity(); + Transform3d matrix = Transform3d::Identity(); if (!dont_translate) - matrix.translate(Vec3f((float)offset(0), (float)offset(1), 0.0f)); - matrix.rotate(Eigen::AngleAxisf(rotation, Vec3f::UnitZ())); + matrix.translate(Vec3d(offset(0), offset(1), 0.0)); + + matrix.rotate(Eigen::AngleAxisd(rotation, Vec3d::UnitZ())); matrix.scale(scaling_factor); return bbox.transformed(matrix); } diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp index 8dcfff024e..468e6f8337 100644 --- a/xs/src/libslic3r/Model.hpp +++ b/xs/src/libslic3r/Model.hpp @@ -84,7 +84,7 @@ public: center_around_origin() method. Callers might want to apply the same translation to new volumes before adding them to this object in order to preserve alignment when user expects that. */ - Pointf3 origin_translation; + Vec3d origin_translation; Model* get_model() const { return m_model; }; @@ -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; @@ -120,9 +117,9 @@ public: // A snug bounding box around the transformed non-modifier object volumes. BoundingBoxf3 instance_bounding_box(size_t instance_idx, bool dont_translate = false) const; void center_around_origin(); - void translate(const Vectorf3 &vector) { this->translate(vector(0), vector(1), vector(2)); } + void translate(const Vec3d &vector) { this->translate(vector(0), vector(1), vector(2)); } void translate(coordf_t x, coordf_t y, coordf_t z); - void scale(const Pointf3 &versor); + void scale(const Vec3d &versor); void rotate(float angle, const Axis &axis); void transform(const float* matrix3x4); void mirror(const Axis &axis); @@ -138,7 +135,7 @@ public: void print_info() const; private: - ModelObject(Model *model) : layer_height_profile_valid(false), m_model(model), m_bounding_box_valid(false) {} + ModelObject(Model *model) : layer_height_profile_valid(false), m_model(model), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false) {} ModelObject(Model *model, const ModelObject &other, bool copy_volumes = true); ModelObject& operator= (ModelObject other); void swap(ModelObject &other); @@ -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. @@ -213,7 +227,7 @@ public: // Transform3d transform; double rotation; // Rotation around the Z axis, in radians around mesh center point double scaling_factor; - Pointf offset; // in unscaled coordinates + Vec2d offset; // in unscaled coordinates // flag showing the position of this instance with respect to the print volume (set by Print::validate() using ModelObject::check_instances_print_volume_state()) EPrintVolumeState print_volume_state; @@ -235,7 +249,7 @@ private: // Parent object, owning this instance. ModelObject* object; - ModelInstance(ModelObject *object) : rotation(0), scaling_factor(1), object(object), print_volume_state(PVS_Inside) {} + ModelInstance(ModelObject *object) : rotation(0), scaling_factor(1), offset(Vec2d::Zero()), object(object), print_volume_state(PVS_Inside) {} ModelInstance(ModelObject *object, const ModelInstance &other) : rotation(other.rotation), scaling_factor(other.scaling_factor), offset(other.offset), object(object), print_volume_state(PVS_Inside) {} }; @@ -286,9 +300,7 @@ 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 center_instances_around_point(const Vec2d &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; bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL); diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index 67825248a8..db18e67318 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -468,7 +468,7 @@ void applyResult( // appropriately auto off = item.translation(); Radians rot = item.rotation(); - Pointf foff(off.X*SCALING_FACTOR + batch_offset, + Vec2d foff(off.X*SCALING_FACTOR + batch_offset, off.Y*SCALING_FACTOR); // write the tranformation data into the model instance diff --git a/xs/src/libslic3r/MultiPoint.cpp b/xs/src/libslic3r/MultiPoint.cpp index 0be95fd556..f44897a046 100644 --- a/xs/src/libslic3r/MultiPoint.cpp +++ b/xs/src/libslic3r/MultiPoint.cpp @@ -196,7 +196,7 @@ MultiPoint::_douglas_peucker(const Points &points, const double tolerance) void MultiPoint3::translate(double x, double y) { - for (Point3 &p : points) { + for (Vec3crd &p : points) { p(0) += x; p(1) += y; } diff --git a/xs/src/libslic3r/MultiPoint.hpp b/xs/src/libslic3r/MultiPoint.hpp index c5d35050d5..1fef4083b7 100644 --- a/xs/src/libslic3r/MultiPoint.hpp +++ b/xs/src/libslic3r/MultiPoint.hpp @@ -85,7 +85,7 @@ class MultiPoint3 public: Points3 points; - void append(const Point3& point) { this->points.push_back(point); } + void append(const Vec3crd& point) { this->points.push_back(point); } void translate(double x, double y); void translate(const Point& vector); diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index 8e7b33cc8b..de8aeeb2ab 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -243,7 +243,7 @@ void PerimeterGenerator::process() perimeter_spacing / 2; // only apply infill overlap if we actually have one perimeter if (inset > 0) - inset -= scale_(this->config->get_abs_value("infill_overlap", unscale(inset + solid_infill_spacing / 2))); + inset -= scale_(this->config->get_abs_value("infill_overlap", unscale(inset + solid_infill_spacing / 2))); // simplify infill contours according to resolution Polygons pp; for (ExPolygon &ex : last) @@ -420,7 +420,7 @@ static inline ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyli path.polyline.append(line.b); // Convert from spacing to extrusion width based on the extrusion model // of a square extrusion ended with semi circles. - flow.width = unscale(w) + flow.height * (1. - 0.25 * PI); + flow.width = unscale(w) + flow.height * (1. - 0.25 * PI); #ifdef SLIC3R_DEBUG printf(" filling %f gap\n", flow.width); #endif diff --git a/xs/src/libslic3r/Point.cpp b/xs/src/libslic3r/Point.cpp index 10578641e0..ec6c28fe86 100644 --- a/xs/src/libslic3r/Point.cpp +++ b/xs/src/libslic3r/Point.cpp @@ -148,33 +148,11 @@ Point Point::projection_onto(const Line &line) const return ((line.a - *this).cast().squaredNorm() < (line.b - *this).cast().squaredNorm()) ? line.a : line.b; } -std::ostream& operator<<(std::ostream &stm, const Pointf &pointf) +std::ostream& operator<<(std::ostream &stm, const Vec2d &pointf) { return stm << pointf(0) << "," << pointf(1); } -void Pointf::rotate(double angle) -{ - double cur_x = (*this)(0); - double cur_y = (*this)(1); - double s = ::sin(angle); - double c = ::cos(angle); - (*this)(0) = c * cur_x - s * cur_y; - (*this)(1) = c * cur_y + s * cur_x; -} - -void Pointf::rotate(double angle, const Pointf ¢er) -{ - double cur_x = (*this)(0); - double cur_y = (*this)(1); - double s = ::sin(angle); - double c = ::cos(angle); - double dx = cur_x - center(0); - double dy = cur_y - center(1); - (*this)(0) = center(0) + c * dx - s * dy; - (*this)(1) = center(1) + c * dy + s * dx; -} - namespace int128 { int orient(const Vec2crd &p1, const Vec2crd &p2, const Vec2crd &p3) diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp index 3119511750..b969b98390 100644 --- a/xs/src/libslic3r/Point.hpp +++ b/xs/src/libslic3r/Point.hpp @@ -16,19 +16,7 @@ namespace Slic3r { class Line; class MultiPoint; class Point; -class Point3; -class Pointf; -class Pointf3; -typedef Point Vector; -typedef Point3 Vector3; -typedef Pointf Vectorf; -typedef Pointf3 Vectorf3; -typedef std::vector Points; -typedef std::vector PointPtrs; -typedef std::vector PointConstPtrs; -typedef std::vector Points3; -typedef std::vector Pointfs; -typedef std::vector Pointf3s; +typedef Point Vector; // Eigen types, to replace the Slic3r's own types in the future. // Vector types with a fixed point coordinate base type. @@ -43,16 +31,37 @@ typedef Eigen::Matrix Vec3f; typedef Eigen::Matrix Vec2d; typedef Eigen::Matrix Vec3d; +typedef std::vector Points; +typedef std::vector PointPtrs; +typedef std::vector PointConstPtrs; +typedef std::vector Points3; +typedef std::vector Pointfs; +typedef std::vector Pointf3s; + typedef Eigen::Transform Transform2f; typedef Eigen::Transform Transform2d; typedef Eigen::Transform Transform3f; typedef Eigen::Transform Transform3d; +inline bool operator<(const Vec2d &lhs, const Vec2d &rhs) { return lhs(0) < rhs(0) || (lhs(0) == rhs(0) && lhs(1) < rhs(1)); } + inline int64_t cross2(const Vec2i64 &v1, const Vec2i64 &v2) { return v1(0) * v2(1) - v1(1) * v2(0); } inline coord_t cross2(const Vec2crd &v1, const Vec2crd &v2) { return v1(0) * v2(1) - v1(1) * v2(0); } inline float cross2(const Vec2f &v1, const Vec2f &v2) { return v1(0) * v2(1) - v1(1) * v2(0); } inline double cross2(const Vec2d &v1, const Vec2d &v2) { return v1(0) * v2(1) - v1(1) * v2(0); } +inline Vec2crd to_2d(const Vec3crd &pt3) { return Vec2crd(pt3(0), pt3(1)); } +inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); } +inline Vec2f to_2d(const Vec3f &pt3) { return Vec2f (pt3(0), pt3(1)); } +inline Vec2d to_2d(const Vec3d &pt3) { return Vec2d (pt3(0), pt3(1)); } + +inline Vec2d unscale(coord_t x, coord_t y) { return Vec2d(unscale(x), unscale(y)); } +inline Vec2d unscale(const Vec2crd &pt) { return Vec2d(unscale(pt(0)), unscale(pt(1))); } +inline Vec2d unscale(const Vec2d &pt) { return Vec2d(unscale(pt(0)), unscale(pt(1))); } +inline Vec3d unscale(coord_t x, coord_t y, coord_t z) { return Vec3d(unscale(x), unscale(y), unscale(z)); } +inline Vec3d unscale(const Vec3crd &pt) { return Vec3d(unscale(pt(0)), unscale(pt(1)), unscale(pt(2))); } +inline Vec3d unscale(const Vec3d &pt) { return Vec3d(unscale(pt(0)), unscale(pt(1)), unscale(pt(2))); } + inline std::string to_string(const Vec2crd &pt) { return std::string("[") + std::to_string(pt(0)) + ", " + std::to_string(pt(1)) + "]"; } inline std::string to_string(const Vec2d &pt) { return std::string("[") + std::to_string(pt(0)) + ", " + std::to_string(pt(1)) + "]"; } inline std::string to_string(const Vec3crd &pt) { return std::string("[") + std::to_string(pt(0)) + ", " + std::to_string(pt(1)) + ", " + std::to_string(pt(2)) + "]"; } @@ -210,81 +219,7 @@ private: coord_t m_grid_log2; }; -class Point3 : public Vec3crd -{ -public: - typedef coord_t coord_type; - - explicit Point3() { (*this)(0) = (*this)(1) = (*this)(2) = 0; } - explicit Point3(coord_t x, coord_t y, coord_t z) { (*this)(0) = x; (*this)(1) = y; (*this)(2) = z; } - // This constructor allows you to construct Point3 from Eigen expressions - template - Point3(const Eigen::MatrixBase &other) : Vec3crd(other) {} - static Point3 new_scale(coordf_t x, coordf_t y, coordf_t z) { return Point3(coord_t(scale_(x)), coord_t(scale_(y)), coord_t(scale_(z))); } - - // This method allows you to assign Eigen expressions to MyVectorType - template - Point3& operator=(const Eigen::MatrixBase &other) - { - this->Vec3crd::operator=(other); - return *this; - } - - Point xy() const { return Point((*this)(0), (*this)(1)); } -}; - -std::ostream& operator<<(std::ostream &stm, const Pointf &pointf); - -class Pointf : public Vec2d -{ -public: - typedef coordf_t coord_type; - - explicit Pointf() { (*this)(0) = (*this)(1) = 0.; } - explicit Pointf(coordf_t x, coordf_t y) { (*this)(0) = x; (*this)(1) = y; } - // This constructor allows you to construct Pointf from Eigen expressions - template - Pointf(const Eigen::MatrixBase &other) : Vec2d(other) {} - static Pointf new_unscale(coord_t x, coord_t y) { return Pointf(unscale(x), unscale(y)); } - static Pointf new_unscale(const Point &p) { return Pointf(unscale(p(0)), unscale(p(1))); } - - // This method allows you to assign Eigen expressions to MyVectorType - template - Pointf& operator=(const Eigen::MatrixBase &other) - { - this->Vec2d::operator=(other); - return *this; - } - - void rotate(double angle); - void rotate(double angle, const Pointf ¢er); - - bool operator< (const Pointf& rhs) const { return (*this)(0) < rhs(0) || ((*this)(0) == rhs(0) && (*this)(1) < rhs(1)); } -}; - -class Pointf3 : public Vec3d -{ -public: - typedef coordf_t coord_type; - - explicit Pointf3() { (*this)(0) = (*this)(1) = (*this)(2) = 0.; } - explicit Pointf3(coordf_t x, coordf_t y, coordf_t z) { (*this)(0) = x; (*this)(1) = y; (*this)(2) = z; } - // This constructor allows you to construct Pointf from Eigen expressions - template - Pointf3(const Eigen::MatrixBase &other) : Vec3d(other) {} - static Pointf3 new_unscale(coord_t x, coord_t y, coord_t z) { return Pointf3(unscale(x), unscale(y), unscale(z)); } - static Pointf3 new_unscale(const Point3& p) { return Pointf3(unscale(p(0)), unscale(p(1)), unscale(p(2))); } - - // This method allows you to assign Eigen expressions to MyVectorType - template - Pointf3& operator=(const Eigen::MatrixBase &other) - { - this->Vec3d::operator=(other); - return *this; - } - - Pointf xy() const { return Pointf((*this)(0), (*this)(1)); } -}; +std::ostream& operator<<(std::ostream &stm, const Vec2d &pointf); } // namespace Slic3r diff --git a/xs/src/libslic3r/Polygon.cpp b/xs/src/libslic3r/Polygon.cpp index f9c82d198a..14248d84ff 100644 --- a/xs/src/libslic3r/Polygon.cpp +++ b/xs/src/libslic3r/Polygon.cpp @@ -297,10 +297,10 @@ Point Polygon::point_projection(const Point &point) const dmin = d; proj = pt1; } - Pointf v1(coordf_t(pt1(0) - pt0(0)), coordf_t(pt1(1) - pt0(1))); + Vec2d v1(coordf_t(pt1(0) - pt0(0)), coordf_t(pt1(1) - pt0(1))); coordf_t div = v1.squaredNorm(); if (div > 0.) { - Pointf v2(coordf_t(point(0) - pt0(0)), coordf_t(point(1) - pt0(1))); + Vec2d v2(coordf_t(point(0) - pt0(0)), coordf_t(point(1) - pt0(1))); coordf_t t = v1.dot(v2) / div; if (t > 0. && t < 1.) { Point foot(coord_t(floor(coordf_t(pt0(0)) + t * v1(0) + 0.5)), coord_t(floor(coordf_t(pt0(1)) + t * v1(1) + 0.5))); diff --git a/xs/src/libslic3r/Polygon.hpp b/xs/src/libslic3r/Polygon.hpp index 0091846206..54909352ce 100644 --- a/xs/src/libslic3r/Polygon.hpp +++ b/xs/src/libslic3r/Polygon.hpp @@ -24,11 +24,12 @@ public: explicit Polygon(const Points &points): MultiPoint(points) {} Polygon(const Polygon &other) : MultiPoint(other.points) {} Polygon(Polygon &&other) : MultiPoint(std::move(other.points)) {} - static Polygon new_scale(std::vector points) { - Points int_points; - for (auto pt : points) - int_points.push_back(Point::new_scale(pt(0), pt(1))); - return Polygon(int_points); + static Polygon new_scale(const std::vector &points) { + Polygon pgn; + pgn.points.reserve(points.size()); + for (const Vec2d &pt : points) + pgn.points.emplace_back(Point::new_scale(pt(0), pt(1))); + return pgn; } Polygon& operator=(const Polygon &other) { points = other.points; return *this; } Polygon& operator=(Polygon &&other) { points = std::move(other.points); return *this; } diff --git a/xs/src/libslic3r/Polyline.hpp b/xs/src/libslic3r/Polyline.hpp index edd27aaf68..0c934e0748 100644 --- a/xs/src/libslic3r/Polyline.hpp +++ b/xs/src/libslic3r/Polyline.hpp @@ -23,12 +23,11 @@ public: explicit Polyline(const Point &p1, const Point &p2) { points.reserve(2); points.emplace_back(p1); points.emplace_back(p2); } Polyline& operator=(const Polyline &other) { points = other.points; return *this; } Polyline& operator=(Polyline &&other) { points = std::move(other.points); return *this; } - static Polyline new_scale(std::vector points) { + static Polyline new_scale(const std::vector &points) { Polyline pl; - Points int_points; - for (auto pt : points) - int_points.push_back(Point::new_scale(pt(0), pt(1))); - pl.append(int_points); + pl.points.reserve(points.size()); + for (const Vec2d &pt : points) + pl.points.emplace_back(Point::new_scale(pt(0), pt(1))); return pl; } diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index c3f834e33c..9d44cd31e4 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -540,7 +540,7 @@ bool Print::has_skirt() const std::string Print::validate() const { BoundingBox bed_box_2D = get_extents(Polygon::new_scale(config.bed_shape.values)); - BoundingBoxf3 print_volume(Pointf3(unscale(bed_box_2D.min(0)), unscale(bed_box_2D.min(1)), 0.0), Pointf3(unscale(bed_box_2D.max(0)), unscale(bed_box_2D.max(1)), config.max_print_height)); + BoundingBoxf3 print_volume(unscale(bed_box_2D.min(0), bed_box_2D.min(1), 0.0), unscale(bed_box_2D.max(0), bed_box_2D.max(1), scale_(config.max_print_height))); // Allow the objects to protrude below the print bed, only the part of the object above the print bed will be sliced. print_volume.min(2) = -1e10; unsigned int printable_count = 0; @@ -728,7 +728,7 @@ BoundingBox Print::bounding_box() const for (const PrintObject *object : this->objects) for (Point copy : object->_shifted_copies) { bb.merge(copy); - copy += object->size.xy(); + copy += to_2d(object->size); bb.merge(copy); } return bb; @@ -971,7 +971,7 @@ void Print::_make_skirt() this->skirt.append(eloop); if (this->config.min_skirt_length.value > 0) { // The skirt length is limited. Sum the total amount of filament length extruded, in mm. - extruded_length[extruder_idx] += unscale(loop.length()) * extruders_e_per_mm[extruder_idx]; + extruded_length[extruder_idx] += unscale(loop.length()) * extruders_e_per_mm[extruder_idx]; if (extruded_length[extruder_idx] < this->config.min_skirt_length.value) { // Not extruded enough yet with the current extruder. Add another loop. if (i == 1) diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index a9e54933a5..61e77b5720 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -118,7 +118,7 @@ public: // so that next call to make_perimeters() performs a union() before computing loops bool typed_slices; - Point3 size; // XYZ in scaled coordinates + Vec3crd size; // XYZ in scaled coordinates // scaled coordinates to add to copies (to compensate for the alignment // operated when creating the object but still preserving a coherent API @@ -138,13 +138,13 @@ public: const ModelObject* model_object() const { return this->_model_object; } const Points& copies() const { return this->_copies; } - bool add_copy(const Pointf &point); + bool add_copy(const Vec2d &point); bool delete_last_copy(); bool delete_all_copies() { return this->set_copies(Points()); } bool set_copies(const Points &points); bool reload_model_instances(); // since the object is aligned to origin, bounding box coincides with size - BoundingBox bounding_box() const { return BoundingBox(Point(0,0), this->size.xy()); } + BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size)); } // adds region_id, too, if necessary void add_region_volume(unsigned int region_id, int volume_id) { diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index eadba6f41d..6445b2cd6d 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -17,9 +17,51 @@ namespace Slic3r { #define L(s) Slic3r::I18N::translate(s) PrintConfigDef::PrintConfigDef() +{ + this->init_common_params(); + this->init_fff_params(); + this->init_sla_params(); +} + +void PrintConfigDef::init_common_params() { t_optiondef_map &Options = this->options; + ConfigOptionDef* def; + + def = this->add("printer_technology", coEnum); + def->label = L("Printer technology"); + def->tooltip = L("Printer technology"); + def->cli = "printer-technology=s"; + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("FFF"); + def->enum_values.push_back("SLA"); + def->default_value = new ConfigOptionEnum(ptFFF); + + def = this->add("bed_shape", coPoints); + def->label = L("Bed shape"); + def->default_value = new ConfigOptionPoints{ Vec2d(0, 0), Vec2d(200, 0), Vec2d(200, 200), Vec2d(0, 200) }; + def = this->add("layer_height", coFloat); + def->label = L("Layer height"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("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."); + def->sidetext = L("mm"); + def->cli = "layer-height=f"; + def->min = 0; + def->default_value = new ConfigOptionFloat(0.3); + + def = this->add("max_print_height", coFloat); + def->label = L("Max print height"); + def->tooltip = L("Set this to the maximum height that can be reached by your extruder while printing."); + def->sidetext = L("mm"); + def->cli = "max-print-height=f"; + def->default_value = new ConfigOptionFloat(200.0); +} + +void PrintConfigDef::init_fff_params() +{ + t_optiondef_map &Options = this->options; ConfigOptionDef* def; // Maximum extruder temperature, bumped to 1500 to support printing of glass. @@ -33,10 +75,6 @@ PrintConfigDef::PrintConfigDef() def->cli = "avoid-crossing-perimeters!"; def->default_value = new ConfigOptionBool(false); - def = this->add("bed_shape", coPoints); - def->label = L("Bed shape"); - def->default_value = new ConfigOptionPoints { Pointf(0,0), Pointf(200,0), Pointf(200,200), Pointf(0,200) }; - def = this->add("bed_temperature", coInts); def->label = L("Other layers"); def->tooltip = L("Bed temperature for layers after the first one. " @@ -392,7 +430,7 @@ PrintConfigDef::PrintConfigDef() "from the XY coordinate)."); def->sidetext = L("mm"); def->cli = "extruder-offset=s@"; - def->default_value = new ConfigOptionPoints { Pointf(0,0) }; + def->default_value = new ConfigOptionPoints { Vec2d(0,0) }; def = this->add("extrusion_axis", coString); def->label = L("Extrusion axis"); @@ -906,16 +944,6 @@ PrintConfigDef::PrintConfigDef() def->height = 50; def->default_value = new ConfigOptionString(""); - def = this->add("layer_height", coFloat); - def->label = L("Layer height"); - def->category = L("Layers and Perimeters"); - def->tooltip = L("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."); - def->sidetext = L("mm"); - def->cli = "layer-height=f"; - 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 minutes] at 1 minute" @@ -1036,13 +1064,6 @@ PrintConfigDef::PrintConfigDef() def->min = 0; def->default_value = new ConfigOptionFloats { 0. }; - def = this->add("max_print_height", coFloat); - def->label = L("Max print height"); - def->tooltip = L("Set this to the maximum height that can be reached by your extruder while printing."); - def->sidetext = L("mm"); - def->cli = "max-print-height=f"; - def->default_value = new ConfigOptionFloat(200.0); - def = this->add("max_print_speed", coFloat); def->label = L("Max print speed"); def->tooltip = L("When setting other speed settings to 0 Slic3r will autocalculate the optimal speed " @@ -1137,25 +1158,37 @@ 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->cli = "octoprint-apikey=s"; + 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 = "printhost-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->cli = "printhost-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->cli = "octoprint-host=s"; + 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 = "print-host=s"; def->default_value = new ConfigOptionString(""); def = this->add("only_retract_when_crossing_perimeters", coBool); @@ -2121,6 +2154,103 @@ PrintConfigDef::PrintConfigDef() def->default_value = new ConfigOptionFloat(35.); } +void PrintConfigDef::init_sla_params() +{ + t_optiondef_map &Options = this->options; + ConfigOptionDef* def; + + // SLA Printer settings + def = this->add("display_width", coFloat); + def->label = L("Display width"); + def->tooltip = L("Width of the display"); + def->cli = "display-width=f"; + def->min = 1; + def->default_value = new ConfigOptionFloat(150.); + + def = this->add("display_height", coFloat); + def->label = L("Display height"); + def->tooltip = L("Height of the display"); + def->cli = "display-height=f"; + def->min = 1; + def->default_value = new ConfigOptionFloat(100.); + + def = this->add("display_pixels_x", coInt); + def->full_label = L("Number of pixels in"); + def->label = ("X"); + def->tooltip = L("Number of pixels in X"); + def->cli = "display-pixels-x=i"; + def->min = 100; + def->default_value = new ConfigOptionInt(2000); + + def = this->add("display_pixels_y", coInt); + def->label = ("Y"); + def->tooltip = L("Number of pixels in Y"); + def->cli = "display-pixels-y=i"; + def->min = 100; + def->default_value = new ConfigOptionInt(1000); + + def = this->add("printer_correction", coFloats); + def->full_label = L("Printer scaling correction"); + def->tooltip = L("Printer scaling correction"); + def->min = 0; + def->default_value = new ConfigOptionFloats( { 1., 1., 1. } ); + + // SLA Material settings. + def = this->add("initial_layer_height", coFloat); + def->label = L("Initial layer height"); + def->tooltip = L("Initial layer height"); + def->sidetext = L("mm"); + def->cli = "initial-layer-height=f"; + def->min = 0; + def->default_value = new ConfigOptionFloat(0.3); + + def = this->add("exposure_time", coFloat); + def->label = L("Exposure time"); + def->tooltip = L("Exposure time"); + def->sidetext = L("s"); + def->cli = "exposure-time=f"; + def->min = 0; + def->default_value = new ConfigOptionFloat(10); + + def = this->add("initial_exposure_time", coFloat); + def->label = L("Initial exposure time"); + def->tooltip = L("Initial exposure time"); + def->sidetext = L("s"); + def->cli = "initial-exposure-time=f"; + def->min = 0; + def->default_value = new ConfigOptionFloat(15); + + def = this->add("material_correction_printing", coFloats); + def->full_label = L("Correction for expansion when printing"); + def->tooltip = L("Correction for expansion when printing"); + def->min = 0; + def->default_value = new ConfigOptionFloats( { 1. , 1., 1. } ); + + def = this->add("material_correction_curing", coFloats); + def->full_label = L("Correction for expansion after curing"); + def->tooltip = L("Correction for expansion after curing"); + def->min = 0; + def->default_value = new ConfigOptionFloats( { 1. , 1., 1. } ); + + def = this->add("material_notes", coString); + def->label = L("SLA print material notes"); + def->tooltip = L("You can put your notes regarding the SLA print material here."); + def->cli = "material-notes=s"; + def->multiline = true; + def->full_width = true; + def->height = 130; + def->default_value = new ConfigOptionString(""); + + def = this->add("default_sla_material_profile", coString); + def->label = L("Default SLA material profile"); + def->tooltip = L("Default print profile associated with the current printer profile. " + "On selection of the current printer profile, this print profile will be activated."); + def->default_value = new ConfigOptionString(); + + def = this->add("sla_material_settings_id", coString); + def->default_value = new ConfigOptionString(""); +} + void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value) { // handle legacy options @@ -2153,10 +2283,6 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va std::ostringstream oss; oss << "0x0," << p.value(0) << "x0," << p.value(0) << "x" << p.value(1) << ",0x" << p.value(1); 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; @@ -2167,6 +2293,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: @@ -2176,9 +2308,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" }; @@ -2188,7 +2317,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; } @@ -2446,4 +2574,8 @@ StaticPrintConfig::StaticCache PrintConfig::s_c StaticPrintConfig::StaticCache HostConfig::s_cache_HostConfig; StaticPrintConfig::StaticCache FullPrintConfig::s_cache_FullPrintConfig; +StaticPrintConfig::StaticCache SLAMaterialConfig::s_cache_SLAMaterialConfig; +StaticPrintConfig::StaticCache SLAPrinterConfig::s_cache_SLAPrinterConfig; +StaticPrintConfig::StaticCache SLAFullPrintConfig::s_cache_SLAFullPrintConfig; + } diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 074a0e6119..7845c138e0 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -22,11 +22,23 @@ namespace Slic3r { +enum PrinterTechnology +{ + // Fused Filament Fabrication + ptFFF, + // Stereolitography + ptSLA, +}; + enum GCodeFlavor { gcfRepRap, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlin, gcfSailfish, gcfMach3, gcfMachinekit, gcfSmoothie, gcfNoExtrusion, }; +enum PrintHostType { + htOctoPrint, htDuet, +}; + enum InfillPattern { ipRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, @@ -44,7 +56,16 @@ enum FilamentType { ftPLA, ftABS, ftPET, ftHIPS, ftFLEX, ftSCAFF, ftEDGE, ftNGEN, ftPVA }; -template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { + static t_config_enum_values keys_map; + if (keys_map.empty()) { + keys_map["FFF"] = ptFFF; + keys_map["SLA"] = ptSLA; + } + return keys_map; +} + +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { keys_map["reprap"] = gcfRepRap; @@ -61,7 +82,16 @@ template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_ return keys_map; } -template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { +template<> inline const 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 const t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { keys_map["rectilinear"] = ipRectilinear; @@ -81,7 +111,7 @@ template<> inline t_config_enum_values& ConfigOptionEnum::get_enu return keys_map; } -template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { keys_map["rectilinear"] = smpRectilinear; @@ -91,7 +121,7 @@ template<> inline t_config_enum_values& ConfigOptionEnum return keys_map; } -template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { keys_map["random"] = spRandom; @@ -102,7 +132,7 @@ template<> inline t_config_enum_values& ConfigOptionEnum::get_enum return keys_map; } -template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { keys_map["PLA"] = ftPLA; @@ -126,6 +156,11 @@ public: PrintConfigDef(); static void handle_legacy(t_config_option_key &opt_key, std::string &value); + +private: + void init_common_params(); + void init_fff_params(); + void init_sla_params(); }; // The one and only global definition of SLic3r configuration options. @@ -801,18 +836,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); } @@ -844,6 +881,73 @@ protected: } }; +class SLAMaterialConfig : public StaticPrintConfig +{ + STATIC_PRINT_CONFIG_CACHE(SLAMaterialConfig) +public: + ConfigOptionFloat layer_height; + ConfigOptionFloat initial_layer_height; + ConfigOptionFloat exposure_time; + ConfigOptionFloat initial_exposure_time; + ConfigOptionFloats material_correction_printing; + ConfigOptionFloats material_correction_curing; +protected: + void initialize(StaticCacheBase &cache, const char *base_ptr) + { + OPT_PTR(layer_height); + OPT_PTR(initial_layer_height); + OPT_PTR(exposure_time); + OPT_PTR(initial_exposure_time); + OPT_PTR(material_correction_printing); + OPT_PTR(material_correction_curing); + } +}; + +class SLAPrinterConfig : public StaticPrintConfig +{ + STATIC_PRINT_CONFIG_CACHE(SLAPrinterConfig) +public: + ConfigOptionEnum printer_technology; + ConfigOptionPoints bed_shape; + ConfigOptionFloat max_print_height; + ConfigOptionFloat display_width; + ConfigOptionFloat display_height; + ConfigOptionInt display_pixels_x; + ConfigOptionInt display_pixels_y; + ConfigOptionFloats printer_correction; +protected: + void initialize(StaticCacheBase &cache, const char *base_ptr) + { + OPT_PTR(printer_technology); + OPT_PTR(bed_shape); + OPT_PTR(max_print_height); + OPT_PTR(display_width); + OPT_PTR(display_height); + OPT_PTR(display_pixels_x); + OPT_PTR(display_pixels_y); + OPT_PTR(printer_correction); + } +}; + +class SLAFullPrintConfig : public SLAPrinterConfig, public SLAMaterialConfig +{ + STATIC_PRINT_CONFIG_CACHE_DERIVED(SLAFullPrintConfig) + SLAFullPrintConfig() : SLAPrinterConfig(0), SLAMaterialConfig(0) { initialize_cache(); *this = s_cache_SLAFullPrintConfig.defaults(); } + +public: + // Validate the SLAFullPrintConfig. Returns an empty string on success, otherwise an error message is returned. +// std::string validate(); + +protected: + // Protected constructor to be called to initialize ConfigCache::m_default. + SLAFullPrintConfig(int) : SLAPrinterConfig(0), SLAMaterialConfig(0) {} + void initialize(StaticCacheBase &cache, const char *base_ptr) + { + this->SLAPrinterConfig ::initialize(cache, base_ptr); + this->SLAMaterialConfig::initialize(cache, base_ptr); + } +}; + #undef STATIC_PRINT_CONFIG_CACHE #undef STATIC_PRINT_CONFIG_CACHE_BASE #undef STATIC_PRINT_CONFIG_CACHE_DERIVED diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index 8df7111963..d03b994168 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -38,6 +38,7 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, const Bounding typed_slices(false), _print(print), _model_object(model_object), + size(Vec3crd::Zero()), layer_height_profile_valid(false) { // Compute the translation to be applied to our meshes so that we work with smaller coordinates @@ -50,8 +51,7 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, const Bounding // (copies are expressed in G-code coordinates and this translation is not publicly exposed). this->_copies_shift = Point::new_scale(modobj_bbox.min(0), modobj_bbox.min(1)); // Scale the object size and store it - Pointf3 size = modobj_bbox.size(); - this->size = Point3::new_scale(size(0), size(1), size(2)); + this->size = (modobj_bbox.size() * (1. / SCALING_FACTOR)).cast(); } this->reload_model_instances(); @@ -59,7 +59,7 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, const Bounding this->layer_height_profile = model_object->layer_height_profile; } -bool PrintObject::add_copy(const Pointf &point) +bool PrintObject::add_copy(const Vec2d &point) { Points points = this->_copies; points.push_back(Point::new_scale(point(0), point(1))); @@ -1121,7 +1121,7 @@ SlicingParameters PrintObject::slicing_parameters() const { return SlicingParameters::create_from_config( this->print()->config, this->config, - unscale(this->size(2)), this->print()->object_extruders()); + unscale(this->size(2)), this->print()->object_extruders()); } bool PrintObject::update_layer_height_profile(std::vector &layer_height_profile) const @@ -1335,7 +1335,7 @@ std::vector PrintObject::_slice_region(size_t region_id, const std:: // consider the first one this->model_object()->instances.front()->transform_mesh(&mesh, true); // align mesh to Z = 0 (it should be already aligned actually) and apply XY shift - mesh.translate(- float(unscale(this->_copies_shift(0))), - float(unscale(this->_copies_shift(1))), -float(this->model_object()->bounding_box().min(2))); + mesh.translate(- unscale(this->_copies_shift(0)), - unscale(this->_copies_shift(1)), - float(this->model_object()->bounding_box().min(2))); // perform actual slicing TriangleMeshSlicer mslicer(&mesh); mslicer.slice(z, &layers); diff --git a/xs/src/libslic3r/SVG.cpp b/xs/src/libslic3r/SVG.cpp index d3a0eed36b..03f55802ef 100644 --- a/xs/src/libslic3r/SVG.cpp +++ b/xs/src/libslic3r/SVG.cpp @@ -3,7 +3,7 @@ #include -#define COORD(x) ((float)unscale((x))*10) +#define COORD(x) (unscale((x))*10) namespace Slic3r { @@ -58,8 +58,8 @@ SVG::draw(const Line &line, std::string stroke, coordf_t stroke_width) void SVG::draw(const ThickLine &line, const std::string &fill, const std::string &stroke, coordf_t stroke_width) { - Pointf dir(line.b(0)-line.a(0), line.b(1)-line.a(1)); - Pointf perp(-dir(1), dir(0)); + Vec2d dir(line.b(0)-line.a(0), line.b(1)-line.a(1)); + Vec2d perp(-dir(1), dir(0)); coordf_t len = sqrt(perp(0)*perp(0) + perp(1)*perp(1)); coordf_t da = coordf_t(0.5)*line.a_width/len; coordf_t db = coordf_t(0.5)*line.b_width/len; diff --git a/xs/src/libslic3r/Slicing.cpp b/xs/src/libslic3r/Slicing.cpp index d745a803c0..1bc38502b5 100644 --- a/xs/src/libslic3r/Slicing.cpp +++ b/xs/src/libslic3r/Slicing.cpp @@ -561,15 +561,15 @@ int generate_layer_height_texture( void *data, int rows, int cols, bool level_of_detail_2nd_level) { // https://github.com/aschn/gnuplot-colorbrewer - std::vector palette_raw; - palette_raw.push_back(Point3(0x01A, 0x098, 0x050)); - palette_raw.push_back(Point3(0x066, 0x0BD, 0x063)); - palette_raw.push_back(Point3(0x0A6, 0x0D9, 0x06A)); - palette_raw.push_back(Point3(0x0D9, 0x0F1, 0x0EB)); - palette_raw.push_back(Point3(0x0FE, 0x0E6, 0x0EB)); - palette_raw.push_back(Point3(0x0FD, 0x0AE, 0x061)); - palette_raw.push_back(Point3(0x0F4, 0x06D, 0x043)); - palette_raw.push_back(Point3(0x0D7, 0x030, 0x027)); + std::vector palette_raw; + palette_raw.push_back(Vec3crd(0x01A, 0x098, 0x050)); + palette_raw.push_back(Vec3crd(0x066, 0x0BD, 0x063)); + palette_raw.push_back(Vec3crd(0x0A6, 0x0D9, 0x06A)); + palette_raw.push_back(Vec3crd(0x0D9, 0x0F1, 0x0EB)); + palette_raw.push_back(Vec3crd(0x0FE, 0x0E6, 0x0EB)); + palette_raw.push_back(Vec3crd(0x0FD, 0x0AE, 0x061)); + palette_raw.push_back(Vec3crd(0x0F4, 0x06D, 0x043)); + palette_raw.push_back(Vec3crd(0x0D7, 0x030, 0x027)); // Clear the main texture and the 2nd LOD level. // memset(data, 0, rows * cols * (level_of_detail_2nd_level ? 5 : 4)); @@ -600,14 +600,14 @@ int generate_layer_height_texture( int idx1 = clamp(0, int(palette_raw.size() - 1), int(floor(idxf))); int idx2 = std::min(int(palette_raw.size() - 1), idx1 + 1); coordf_t t = idxf - coordf_t(idx1); - const Point3 &color1 = palette_raw[idx1]; - const Point3 &color2 = palette_raw[idx2]; + const Vec3crd &color1 = palette_raw[idx1]; + const Vec3crd &color2 = palette_raw[idx2]; coordf_t z = cell_to_z * coordf_t(cell); assert(z >= lo && z <= hi); // Intensity profile to visualize the layers. coordf_t intensity = cos(M_PI * 0.7 * (mid - z) / h); // Color mapping from layer height to RGB. - Pointf3 color( + Vec3d color( intensity * lerp(coordf_t(color1(0)), coordf_t(color2(0)), t), intensity * lerp(coordf_t(color1(1)), coordf_t(color2(1)), t), intensity * lerp(coordf_t(color1(2)), coordf_t(color2(2)), t)); @@ -636,10 +636,10 @@ int generate_layer_height_texture( int idx1 = clamp(0, int(palette_raw.size() - 1), int(floor(idxf))); int idx2 = std::min(int(palette_raw.size() - 1), idx1 + 1); coordf_t t = idxf - coordf_t(idx1); - const Point3 &color1 = palette_raw[idx1]; - const Point3 &color2 = palette_raw[idx2]; + const Vec3crd &color1 = palette_raw[idx1]; + const Vec3crd &color2 = palette_raw[idx2]; // Color mapping from layer height to RGB. - Pointf3 color( + Vec3d color( lerp(coordf_t(color1(0)), coordf_t(color2(0)), t), lerp(coordf_t(color1(1)), coordf_t(color2(1)), t), lerp(coordf_t(color1(2)), coordf_t(color2(2)), t)); diff --git a/xs/src/libslic3r/SlicingAdaptive.cpp b/xs/src/libslic3r/SlicingAdaptive.cpp index ff0da76365..2ef4aec8c6 100644 --- a/xs/src/libslic3r/SlicingAdaptive.cpp +++ b/xs/src/libslic3r/SlicingAdaptive.cpp @@ -15,8 +15,8 @@ void SlicingAdaptive::clear() std::pair face_z_span(const stl_facet *f) { return std::pair( - std::min(std::min(f->vertex[0].z, f->vertex[1].z), f->vertex[2].z), - std::max(std::max(f->vertex[0].z, f->vertex[1].z), f->vertex[2].z)); + std::min(std::min(f->vertex[0](2), f->vertex[1](2)), f->vertex[2](2)), + std::max(std::max(f->vertex[0](2), f->vertex[1](2)), f->vertex[2](2))); } void SlicingAdaptive::prepare() @@ -40,7 +40,7 @@ void SlicingAdaptive::prepare() // 3) Generate Z components of the facet normals. m_face_normal_z.assign(m_faces.size(), 0.f); for (size_t iface = 0; iface < m_faces.size(); ++ iface) - m_face_normal_z[iface] = m_faces[iface]->normal.z; + m_face_normal_z[iface] = m_faces[iface]->normal(2); } float SlicingAdaptive::cusp_height(float z, float cusp_value, int ¤t_facet) diff --git a/xs/src/libslic3r/SupportMaterial.cpp b/xs/src/libslic3r/SupportMaterial.cpp index 98f41a6639..4b7e789764 100644 --- a/xs/src/libslic3r/SupportMaterial.cpp +++ b/xs/src/libslic3r/SupportMaterial.cpp @@ -2057,8 +2057,8 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const const Point &p1 = *(it-1); const Point &p2 = *it; // Intersection of a ray (p1, p2) with a circle placed at center_last, with radius of circle_distance. - const Pointf v_seg(coordf_t(p2(0)) - coordf_t(p1(0)), coordf_t(p2(1)) - coordf_t(p1(1))); - const Pointf v_cntr(coordf_t(p1(0) - center_last(0)), coordf_t(p1(1) - center_last(1))); + const Vec2d v_seg(coordf_t(p2(0)) - coordf_t(p1(0)), coordf_t(p2(1)) - coordf_t(p1(1))); + const Vec2d v_cntr(coordf_t(p1(0) - center_last(0)), coordf_t(p1(1) - center_last(1))); coordf_t a = v_seg.squaredNorm(); coordf_t b = 2. * v_seg.dot(v_cntr); coordf_t c = v_cntr.squaredNorm() - circle_distance * circle_distance; diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index 03d05beb2c..8c432c8671 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 @@ -30,13 +35,7 @@ namespace Slic3r { -TriangleMesh::TriangleMesh() - : repaired(false) -{ - stl_initialize(&this->stl); -} - -TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& facets ) +TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& facets ) : repaired(false) { stl_initialize(&this->stl); @@ -51,51 +50,22 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& fa for (int i = 0; i < stl.stats.number_of_facets; i++) { stl_facet facet; - - const Pointf3& ref_f1 = points[facets[i](0)]; - facet.vertex[0].x = ref_f1(0); - facet.vertex[0].y = ref_f1(1); - facet.vertex[0].z = ref_f1(2); - - const Pointf3& ref_f2 = points[facets[i](1)]; - facet.vertex[1].x = ref_f2(0); - facet.vertex[1].y = ref_f2(1); - facet.vertex[1].z = ref_f2(2); - - const Pointf3& ref_f3 = points[facets[i](2)]; - facet.vertex[2].x = ref_f3(0); - facet.vertex[2].y = ref_f3(1); - facet.vertex[2].z = ref_f3(2); - + facet.vertex[0] = points[facets[i](0)].cast(); + facet.vertex[1] = points[facets[i](1)].cast(); + facet.vertex[2] = points[facets[i](2)].cast(); facet.extra[0] = 0; facet.extra[1] = 0; - float normal[3]; + stl_normal normal; stl_calculate_normal(normal, &facet); stl_normalize_vector(normal); - facet.normal.x = normal[0]; - facet.normal.y = normal[1]; - facet.normal.z = normal[2]; + facet.normal = normal; stl.facet_start[i] = facet; } stl_get_size(&stl); } -TriangleMesh::TriangleMesh(const TriangleMesh &other) : - repaired(false) -{ - stl_initialize(&this->stl); - *this = other; -} - -TriangleMesh::TriangleMesh(TriangleMesh &&other) : - repaired(false) -{ - stl_initialize(&this->stl); - this->swap(other); -} - TriangleMesh& TriangleMesh::operator=(const TriangleMesh &other) { stl_close(&this->stl); @@ -123,42 +93,8 @@ TriangleMesh& TriangleMesh::operator=(const TriangleMesh &other) return *this; } -TriangleMesh& TriangleMesh::operator=(TriangleMesh &&other) +void TriangleMesh::repair() { - this->swap(other); - return *this; -} - -void -TriangleMesh::swap(TriangleMesh &other) -{ - std::swap(this->stl, other.stl); - std::swap(this->repaired, other.repaired); -} - -TriangleMesh::~TriangleMesh() { - stl_close(&this->stl); -} - -void -TriangleMesh::ReadSTLFile(const char* input_file) { - stl_open(&stl, input_file); -} - -void -TriangleMesh::write_ascii(const char* output_file) -{ - stl_write_ascii(&this->stl, output_file, ""); -} - -void -TriangleMesh::write_binary(const char* output_file) -{ - stl_write_binary(&this->stl, output_file, ""); -} - -void -TriangleMesh::repair() { if (this->repaired) return; // admesh fails when repairing empty meshes @@ -255,13 +191,7 @@ void TriangleMesh::check_topology() } } -bool TriangleMesh::is_manifold() const -{ - return this->stl.stats.connected_facets_3_edge == this->stl.stats.number_of_facets; -} - -void -TriangleMesh::reset_repair_stats() { +void TriangleMesh::reset_repair_stats() { this->stl.stats.degenerate_facets = 0; this->stl.stats.edges_fixed = 0; this->stl.stats.facets_removed = 0; @@ -271,8 +201,7 @@ TriangleMesh::reset_repair_stats() { this->stl.stats.normals_fixed = 0; } -bool -TriangleMesh::needed_repair() const +bool TriangleMesh::needed_repair() const { return this->stl.stats.degenerate_facets > 0 || this->stl.stats.edges_fixed > 0 @@ -282,14 +211,8 @@ TriangleMesh::needed_repair() const || this->stl.stats.backwards_edges > 0; } -size_t -TriangleMesh::facets_count() const +void TriangleMesh::WriteOBJFile(char* output_file) { - return this->stl.stats.number_of_facets; -} - -void -TriangleMesh::WriteOBJFile(char* output_file) { stl_generate_shared_vertices(&stl); stl_write_obj(&stl, output_file); } @@ -300,13 +223,9 @@ void TriangleMesh::scale(float factor) stl_invalidate_shared_vertices(&this->stl); } -void TriangleMesh::scale(const Pointf3 &versor) +void TriangleMesh::scale(const Vec3d &versor) { - float fversor[3]; - fversor[0] = versor(0); - fversor[1] = versor(1); - fversor[2] = versor(2); - stl_scale_versor(&this->stl, fversor); + stl_scale_versor(&this->stl, versor.cast()); stl_invalidate_shared_vertices(&this->stl); } @@ -336,21 +255,6 @@ void TriangleMesh::rotate(float angle, const Axis &axis) stl_invalidate_shared_vertices(&this->stl); } -void TriangleMesh::rotate_x(float angle) -{ - this->rotate(angle, X); -} - -void TriangleMesh::rotate_y(float angle) -{ - this->rotate(angle, Y); -} - -void TriangleMesh::rotate_z(float angle) -{ - this->rotate(angle, Z); -} - void TriangleMesh::mirror(const Axis &axis) { if (axis == X) { @@ -363,21 +267,6 @@ void TriangleMesh::mirror(const Axis &axis) stl_invalidate_shared_vertices(&this->stl); } -void TriangleMesh::mirror_x() -{ - this->mirror(X); -} - -void TriangleMesh::mirror_y() -{ - this->mirror(Y); -} - -void TriangleMesh::mirror_z() -{ - this->mirror(Z); -} - void TriangleMesh::transform(const float* matrix3x4) { if (matrix3x4 == nullptr) @@ -390,10 +279,9 @@ void TriangleMesh::transform(const float* matrix3x4) void TriangleMesh::align_to_origin() { this->translate( - -(this->stl.stats.min.x), - -(this->stl.stats.min.y), - -(this->stl.stats.min.z) - ); + - this->stl.stats.min(0), + - this->stl.stats.min(1), + - this->stl.stats.min(2)); } void TriangleMesh::rotate(double angle, Point* center) @@ -476,14 +364,14 @@ size_t TriangleMesh::number_of_patches() const return num_bodies; } -TriangleMeshPtrs -TriangleMesh::split() const +TriangleMeshPtrs TriangleMesh::split() const { - TriangleMeshPtrs meshes; - std::set seen_facets; + TriangleMeshPtrs meshes; + std::vector facet_visited(this->stl.stats.number_of_facets, false); // we need neighbors - if (!this->repaired) CONFESS("split() requires repair()"); + if (!this->repaired) + CONFESS("split() requires repair()"); // loop while we have remaining facets for (;;) { @@ -491,25 +379,26 @@ TriangleMesh::split() const std::queue facet_queue; std::deque facets; for (int facet_idx = 0; facet_idx < this->stl.stats.number_of_facets; facet_idx++) { - if (seen_facets.find(facet_idx) == seen_facets.end()) { + if (! facet_visited[facet_idx]) { // if facet was not seen put it into queue and start searching facet_queue.push(facet_idx); break; } } - if (facet_queue.empty()) break; - - while (!facet_queue.empty()) { + if (facet_queue.empty()) + break; + + while (! facet_queue.empty()) { int facet_idx = facet_queue.front(); facet_queue.pop(); - if (seen_facets.find(facet_idx) != seen_facets.end()) continue; - facets.emplace_back(facet_idx); - for (int j = 0; j <= 2; j++) { - facet_queue.push(this->stl.neighbors_start[facet_idx].neighbor[j]); + if (! facet_visited[facet_idx]) { + facets.emplace_back(facet_idx); + for (int j = 0; j < 3; ++ j) + facet_queue.push(this->stl.neighbors_start[facet_idx].neighbor[j]); + facet_visited[facet_idx] = true; } - seen_facets.insert(facet_idx); } - + TriangleMesh* mesh = new TriangleMesh; meshes.emplace_back(mesh); mesh->stl.stats.type = inmemory; @@ -518,19 +407,17 @@ TriangleMesh::split() const stl_clear_error(&mesh->stl); stl_allocate(&mesh->stl); - int first = 1; - for (std::deque::const_iterator facet = facets.begin(); facet != facets.end(); ++facet) { + bool first = true; + for (std::deque::const_iterator facet = facets.begin(); facet != facets.end(); ++ facet) { mesh->stl.facet_start[facet - facets.begin()] = this->stl.facet_start[*facet]; stl_facet_stats(&mesh->stl, this->stl.facet_start[*facet], first); - first = 0; } } return meshes; } -void -TriangleMesh::merge(const TriangleMesh &mesh) +void TriangleMesh::merge(const TriangleMesh &mesh) { // reset stats and metadata int number_of_facets = this->stl.stats.number_of_facets; @@ -561,9 +448,9 @@ ExPolygons TriangleMesh::horizontal_projection() const stl_facet* facet = &this->stl.facet_start[i]; Polygon p; p.points.resize(3); - p.points[0] = Point::new_scale(facet->vertex[0].x, facet->vertex[0].y); - p.points[1] = Point::new_scale(facet->vertex[1].x, facet->vertex[1].y); - p.points[2] = Point::new_scale(facet->vertex[2].x, facet->vertex[2].y); + p.points[0] = Point::new_scale(facet->vertex[0](0), facet->vertex[0](1)); + p.points[1] = Point::new_scale(facet->vertex[1](0), facet->vertex[1](1)); + p.points[2] = Point::new_scale(facet->vertex[2](0), facet->vertex[2](1)); p.make_counter_clockwise(); // do this after scaling, as winding order might change while doing that pp.emplace_back(p); } @@ -578,28 +465,142 @@ Polygon TriangleMesh::convex_hull() Points pp; pp.reserve(this->stl.stats.shared_vertices); for (int i = 0; i < this->stl.stats.shared_vertices; ++ i) { - stl_vertex* v = &this->stl.v_shared[i]; - pp.emplace_back(Point::new_scale(v->x, v->y)); + const stl_vertex &v = this->stl.v_shared[i]; + pp.emplace_back(Point::new_scale(v(0), v(1))); } return Slic3r::Geometry::convex_hull(pp); } -BoundingBoxf3 -TriangleMesh::bounding_box() const +BoundingBoxf3 TriangleMesh::bounding_box() const { BoundingBoxf3 bb; bb.defined = true; - bb.min(0) = this->stl.stats.min.x; - bb.min(1) = this->stl.stats.min.y; - bb.min(2) = this->stl.stats.min.z; - bb.max(0) = this->stl.stats.max.x; - bb.max(1) = this->stl.stats.max.y; - bb.max(2) = this->stl.stats.max.z; + bb.min = this->stl.stats.min.cast(); + bb.max = this->stl.stats.max.cast(); return bb; } -void -TriangleMesh::require_shared_vertices() +BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d& t) 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::MatrixXd 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) = (double)(*vertex_ptr)(0); + src_vertices(1, i) = (double)(*vertex_ptr)(1); + src_vertices(2, i) = (double)(*vertex_ptr)(2); + 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) = (double)facet_ptr->vertex[i](0); + src_vertices(1, v_id) = (double)facet_ptr->vertex[i](1); + src_vertices(2, v_id) = (double)facet_ptr->vertex[i](2); + } + facet_ptr += 1; + ++v_id; + } + } + + if (!has_shared && (stl.stats.shared_vertices > 0)) + stl_invalidate_shared_vertices(&stl); + + Eigen::MatrixXd dst_vertices(3, vertices_count); + dst_vertices = t * src_vertices.colwise().homogeneous(); + + Vec3d v_min(dst_vertices(0, 0), dst_vertices(1, 0), dst_vertices(2, 0)); + Vec3d v_max = v_min; + + for (int i = 1; i < vertices_count; ++i) + { + for (int j = 0; j < 3; ++j) + { + v_min(j) = std::min(v_min(j), dst_vertices(j, i)); + v_max(j) = std::max(v_max(j), dst_vertices(j, i)); + } + } + + return BoundingBoxf3(v_min, v_max); +} + +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(0), v(1), v(2)); + } + + 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 dst_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(); + dst_vertices.emplace_back(coords[0], coords[1], coords[2]); + } + unsigned int size = (unsigned int)dst_vertices.size(); + facets.emplace_back(size - 3, size - 2, size - 1); + } + + TriangleMesh output_mesh(dst_vertices, facets); + output_mesh.repair(); + return output_mesh; +} + +void TriangleMesh::require_shared_vertices() { BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - start"; if (!this->repaired) @@ -619,11 +620,8 @@ TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) : facets_edges.assign(_mesh->stl.stats.number_of_facets * 3, -1); v_scaled_shared.assign(_mesh->stl.v_shared, _mesh->stl.v_shared + _mesh->stl.stats.shared_vertices); // Scale the copied vertices. - for (int i = 0; i < this->mesh->stl.stats.shared_vertices; ++ i) { - this->v_scaled_shared[i].x /= float(SCALING_FACTOR); - this->v_scaled_shared[i].y /= float(SCALING_FACTOR); - this->v_scaled_shared[i].z /= float(SCALING_FACTOR); - } + for (int i = 0; i < this->mesh->stl.stats.shared_vertices; ++ i) + this->v_scaled_shared[i] *= float(1. / SCALING_FACTOR); // Create a mapping from triangle edge into face. struct EdgeToFace { @@ -697,8 +695,7 @@ TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) : } } -void -TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) const +void TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) const { BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice"; @@ -779,14 +776,14 @@ void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vectormesh->stl.facet_start[facet_idx]; // find facet extents - const float min_z = fminf(facet.vertex[0].z, fminf(facet.vertex[1].z, facet.vertex[2].z)); - const float max_z = fmaxf(facet.vertex[0].z, fmaxf(facet.vertex[1].z, facet.vertex[2].z)); + const float min_z = fminf(facet.vertex[0](2), fminf(facet.vertex[1](2), facet.vertex[2](2))); + const float max_z = fmaxf(facet.vertex[0](2), fmaxf(facet.vertex[1](2), facet.vertex[2](2))); #ifdef SLIC3R_DEBUG printf("\n==> FACET %d (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n", facet_idx, - facet.vertex[0].x, facet.vertex[0].y, facet.vertex[0].z, - facet.vertex[1].x, facet.vertex[1].y, facet.vertex[1].z, - facet.vertex[2].x, facet.vertex[2].y, facet.vertex[2].z); + facet.vertex[0].x, facet.vertex[0].y, facet.vertex[0](2), + facet.vertex[1].x, facet.vertex[1].y, facet.vertex[1](2), + facet.vertex[2].x, facet.vertex[2].y, facet.vertex[2](2)); printf("z: min = %.2f, max = %.2f\n", min_z, max_z); #endif @@ -806,18 +803,18 @@ void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vectormesh->stl.v_indices[facet_idx].vertex; - const bool reverse = this->mesh->stl.facet_start[facet_idx].normal.z < 0; + const bool reverse = this->mesh->stl.facet_start[facet_idx].normal(2) < 0; for (int j = 0; j < 3; ++ j) { int a_id = vertices[j % 3]; int b_id = vertices[(j+1) % 3]; if (reverse) std::swap(a_id, b_id); - const stl_vertex *a = &this->v_scaled_shared[a_id]; - const stl_vertex *b = &this->v_scaled_shared[b_id]; - il.a(0) = a->x; - il.a(1) = a->y; - il.b(0) = b->x; - il.b(1) = b->y; + const stl_vertex &a = this->v_scaled_shared[a_id]; + const stl_vertex &b = this->v_scaled_shared[b_id]; + il.a(0) = a(0); + il.a(1) = a(1); + il.b(0) = b(0); + il.b(1) = b(1); il.a_id = a_id; il.b_id = b_id; (*lines)[layer_idx].emplace_back(il); @@ -863,66 +860,63 @@ bool TriangleMeshSlicer::slice_facet( // Reorder vertices so that the first one is the one with lowest Z. // This is needed to get all intersection lines in a consistent order // (external on the right of the line) - int i = (facet.vertex[1].z == min_z) ? 1 : ((facet.vertex[2].z == min_z) ? 2 : 0); + int i = (facet.vertex[1](2) == min_z) ? 1 : ((facet.vertex[2](2) == min_z) ? 2 : 0); for (int j = i; j - i < 3; ++ j) { // loop through facet edges int edge_id = this->facets_edges[facet_idx * 3 + (j % 3)]; const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex; int a_id = vertices[j % 3]; int b_id = vertices[(j+1) % 3]; - const stl_vertex *a = &this->v_scaled_shared[a_id]; - const stl_vertex *b = &this->v_scaled_shared[b_id]; + const stl_vertex &a = this->v_scaled_shared[a_id]; + const stl_vertex &b = this->v_scaled_shared[b_id]; // Is edge or face aligned with the cutting plane? - if (a->z == slice_z && b->z == slice_z) { + if (a(2) == slice_z && b(2) == slice_z) { // Edge is horizontal and belongs to the current layer. const stl_vertex &v0 = this->v_scaled_shared[vertices[0]]; const stl_vertex &v1 = this->v_scaled_shared[vertices[1]]; const stl_vertex &v2 = this->v_scaled_shared[vertices[2]]; + bool swap = false; if (min_z == max_z) { // All three vertices are aligned with slice_z. line_out->edge_type = feHorizontal; - if (this->mesh->stl.facet_start[facet_idx].normal.z < 0) { + if (this->mesh->stl.facet_start[facet_idx].normal(2) < 0) { // If normal points downwards this is a bottom horizontal facet so we reverse its point order. - std::swap(a, b); - std::swap(a_id, b_id); + swap = true; } - } else if (v0.z < slice_z || v1.z < slice_z || v2.z < slice_z) { + } else if (v0(2) < slice_z || v1(2) < slice_z || v2(2) < slice_z) { // Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane. line_out->edge_type = feTop; - std::swap(a, b); - std::swap(a_id, b_id); + swap = true; } else { // Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane. line_out->edge_type = feBottom; } - line_out->a(0) = a->x; - line_out->a(1) = a->y; - line_out->b(0) = b->x; - line_out->b(1) = b->y; - line_out->a_id = a_id; - line_out->b_id = b_id; + line_out->a = to_2d(swap ? b : a).cast(); + line_out->b = to_2d(swap ? a : b).cast(); + line_out->a_id = swap ? b_id : a_id; + line_out->b_id = swap ? a_id : b_id; return true; } - if (a->z == slice_z) { + if (a(2) == slice_z) { // Only point a alings with the cutting plane. points_on_layer[num_points_on_layer ++] = num_points; IntersectionPoint &point = points[num_points ++]; - point(0) = a->x; - point(1) = a->y; + point(0) = a(0); + point(1) = a(1); point.point_id = a_id; - } else if (b->z == slice_z) { + } else if (b(2) == slice_z) { // Only point b alings with the cutting plane. points_on_layer[num_points_on_layer ++] = num_points; IntersectionPoint &point = points[num_points ++]; - point(0) = b->x; - point(1) = b->y; + point(0) = b(0); + point(1) = b(1); point.point_id = b_id; - } else if ((a->z < slice_z && b->z > slice_z) || (b->z < slice_z && a->z > slice_z)) { + } else if ((a(2) < slice_z && b(2) > slice_z) || (b(2) < slice_z && a(2) > slice_z)) { // A general case. The face edge intersects the cutting plane. Calculate the intersection point. IntersectionPoint &point = points[num_points ++]; - point(0) = b->x + (a->x - b->x) * (slice_z - b->z) / (a->z - b->z); - point(1) = b->y + (a->y - b->y) * (slice_z - b->z) / (a->z - b->z); + point(0) = b(0) + (a(0) - b(0)) * (slice_z - b(2)) / (a(2) - b(2)); + point(1) = b(1) + (a(1) - b(1)) * (slice_z - b(2)) / (a(2) - b(2)); point.edge_id = edge_id; } } @@ -1389,8 +1383,8 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) stl_facet* facet = &this->mesh->stl.facet_start[facet_idx]; // find facet extents - float min_z = std::min(facet->vertex[0].z, std::min(facet->vertex[1].z, facet->vertex[2].z)); - float max_z = std::max(facet->vertex[0].z, std::max(facet->vertex[1].z, facet->vertex[2].z)); + float min_z = std::min(facet->vertex[0](2), std::min(facet->vertex[1](2), facet->vertex[2](2))); + float max_z = std::max(facet->vertex[0](2), std::max(facet->vertex[1](2), facet->vertex[2](2))); // intersect facet with cutting plane IntersectionLine line; @@ -1417,47 +1411,47 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) // look for the vertex on whose side of the slicing plane there are no other vertices int isolated_vertex; - if ( (facet->vertex[0].z > z) == (facet->vertex[1].z > z) ) { + if ( (facet->vertex[0](2) > z) == (facet->vertex[1](2) > z) ) { isolated_vertex = 2; - } else if ( (facet->vertex[1].z > z) == (facet->vertex[2].z > z) ) { + } else if ( (facet->vertex[1](2) > z) == (facet->vertex[2](2) > z) ) { isolated_vertex = 0; } else { isolated_vertex = 1; } // get vertices starting from the isolated one - stl_vertex* v0 = &facet->vertex[isolated_vertex]; - stl_vertex* v1 = &facet->vertex[(isolated_vertex+1) % 3]; - stl_vertex* v2 = &facet->vertex[(isolated_vertex+2) % 3]; + const stl_vertex &v0 = facet->vertex[isolated_vertex]; + const stl_vertex &v1 = facet->vertex[(isolated_vertex+1) % 3]; + const stl_vertex &v2 = facet->vertex[(isolated_vertex+2) % 3]; // intersect v0-v1 and v2-v0 with cutting plane and make new vertices stl_vertex v0v1, v2v0; - v0v1.x = v1->x + (v0->x - v1->x) * (z - v1->z) / (v0->z - v1->z); - v0v1.y = v1->y + (v0->y - v1->y) * (z - v1->z) / (v0->z - v1->z); - v0v1.z = z; - v2v0.x = v2->x + (v0->x - v2->x) * (z - v2->z) / (v0->z - v2->z); - v2v0.y = v2->y + (v0->y - v2->y) * (z - v2->z) / (v0->z - v2->z); - v2v0.z = z; + v0v1(0) = v1(0) + (v0(0) - v1(0)) * (z - v1(2)) / (v0(2) - v1(2)); + v0v1(1) = v1(1) + (v0(1) - v1(1)) * (z - v1(2)) / (v0(2) - v1(2)); + v0v1(2) = z; + v2v0(0) = v2(0) + (v0(0) - v2(0)) * (z - v2(2)) / (v0(2) - v2(2)); + v2v0(1) = v2(1) + (v0(1) - v2(1)) * (z - v2(2)) / (v0(2) - v2(2)); + v2v0(2) = z; // build the triangular facet stl_facet triangle; triangle.normal = facet->normal; - triangle.vertex[0] = *v0; + triangle.vertex[0] = v0; triangle.vertex[1] = v0v1; triangle.vertex[2] = v2v0; // build the facets forming a quadrilateral on the other side stl_facet quadrilateral[2]; quadrilateral[0].normal = facet->normal; - quadrilateral[0].vertex[0] = *v1; - quadrilateral[0].vertex[1] = *v2; + quadrilateral[0].vertex[0] = v1; + quadrilateral[0].vertex[1] = v2; quadrilateral[0].vertex[2] = v0v1; quadrilateral[1].normal = facet->normal; - quadrilateral[1].vertex[0] = *v2; + quadrilateral[1].vertex[0] = v2; quadrilateral[1].vertex[1] = v2v0; quadrilateral[1].vertex[2] = v0v1; - if (v0->z > z) { + if (v0(2) > z) { if (upper != NULL) stl_add_facet(&upper->stl, &triangle); if (lower != NULL) { stl_add_facet(&lower->stl, &quadrilateral[0]); @@ -1489,13 +1483,11 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) Polygon p = *polygon; p.reverse(); stl_facet facet; - facet.normal.x = 0; - facet.normal.y = 0; - facet.normal.z = -1; + facet.normal = stl_normal(0, 0, -1.f); for (size_t i = 0; i <= 2; ++i) { - facet.vertex[i].x = unscale(p.points[i](0)); - facet.vertex[i].y = unscale(p.points[i](1)); - facet.vertex[i].z = z; + facet.vertex[i](0) = unscale(p.points[i](0)); + facet.vertex[i](1) = unscale(p.points[i](1)); + facet.vertex[i](2) = z; } stl_add_facet(&upper->stl, &facet); } @@ -1515,13 +1507,11 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) // convert triangles to facets and append them to mesh for (Polygons::const_iterator polygon = triangles.begin(); polygon != triangles.end(); ++polygon) { stl_facet facet; - facet.normal.x = 0; - facet.normal.y = 0; - facet.normal.z = 1; + facet.normal = stl_normal(0, 0, 1.f); for (size_t i = 0; i <= 2; ++i) { - facet.vertex[i].x = unscale(polygon->points[i](0)); - facet.vertex[i].y = unscale(polygon->points[i](1)); - facet.vertex[i].z = z; + facet.vertex[i](0) = unscale(polygon->points[i](0)); + facet.vertex[i](1) = unscale(polygon->points[i](1)); + facet.vertex[i](2) = z; } stl_add_facet(&lower->stl, &facet); } @@ -1534,19 +1524,19 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) // Generate the vertex list for a cube solid of arbitrary size in X/Y/Z. TriangleMesh make_cube(double x, double y, double z) { - Pointf3 pv[8] = { - Pointf3(x, y, 0), Pointf3(x, 0, 0), Pointf3(0, 0, 0), - Pointf3(0, y, 0), Pointf3(x, y, z), Pointf3(0, y, z), - Pointf3(0, 0, z), Pointf3(x, 0, z) + Vec3d pv[8] = { + Vec3d(x, y, 0), Vec3d(x, 0, 0), Vec3d(0, 0, 0), + Vec3d(0, y, 0), Vec3d(x, y, z), Vec3d(0, y, z), + Vec3d(0, 0, z), Vec3d(x, 0, z) }; - Point3 fv[12] = { - Point3(0, 1, 2), Point3(0, 2, 3), Point3(4, 5, 6), - Point3(4, 6, 7), Point3(0, 4, 7), Point3(0, 7, 1), - Point3(1, 7, 6), Point3(1, 6, 2), Point3(2, 6, 5), - Point3(2, 5, 3), Point3(4, 0, 3), Point3(4, 3, 5) + Vec3crd fv[12] = { + Vec3crd(0, 1, 2), Vec3crd(0, 2, 3), Vec3crd(4, 5, 6), + Vec3crd(4, 6, 7), Vec3crd(0, 4, 7), Vec3crd(0, 7, 1), + Vec3crd(1, 7, 6), Vec3crd(1, 6, 2), Vec3crd(2, 6, 5), + Vec3crd(2, 5, 3), Vec3crd(4, 0, 3), Vec3crd(4, 3, 5) }; - std::vector facets(&fv[0], &fv[0]+12); + std::vector facets(&fv[0], &fv[0]+12); Pointf3s vertices(&pv[0], &pv[0]+8); TriangleMesh mesh(vertices ,facets); @@ -1558,11 +1548,11 @@ TriangleMesh make_cube(double x, double y, double z) { // Default is 360 sides, angle fa is in radians. TriangleMesh make_cylinder(double r, double h, double fa) { Pointf3s vertices; - std::vector facets; + std::vector facets; // 2 special vertices, top and bottom center, rest are relative to this - vertices.emplace_back(Pointf3(0.0, 0.0, 0.0)); - vertices.emplace_back(Pointf3(0.0, 0.0, h)); + vertices.emplace_back(Vec3d(0.0, 0.0, 0.0)); + vertices.emplace_back(Vec3d(0.0, 0.0, h)); // adjust via rounding to get an even multiple for any provided angle. double angle = (2*PI / floor(2*PI / fa)); @@ -1572,24 +1562,23 @@ TriangleMesh make_cylinder(double r, double h, double fa) { // top and bottom. // Special case: Last line shares 2 vertices with the first line. unsigned id = vertices.size() - 1; - vertices.emplace_back(Pointf3(sin(0) * r , cos(0) * r, 0)); - vertices.emplace_back(Pointf3(sin(0) * r , cos(0) * r, h)); + vertices.emplace_back(Vec3d(sin(0) * r , cos(0) * r, 0)); + vertices.emplace_back(Vec3d(sin(0) * r , cos(0) * r, h)); for (double i = 0; i < 2*PI; i+=angle) { - Pointf p(0, r); - p.rotate(i); - vertices.emplace_back(Pointf3(p(0), p(1), 0.)); - vertices.emplace_back(Pointf3(p(0), p(1), h)); + Vec2d p = Eigen::Rotation2Dd(i) * Eigen::Vector2d(0, r); + vertices.emplace_back(Vec3d(p(0), p(1), 0.)); + vertices.emplace_back(Vec3d(p(0), p(1), h)); id = vertices.size() - 1; - facets.emplace_back(Point3( 0, id - 1, id - 3)); // top - facets.emplace_back(Point3(id, 1, id - 2)); // bottom - facets.emplace_back(Point3(id, id - 2, id - 3)); // upper-right of side - facets.emplace_back(Point3(id, id - 3, id - 1)); // bottom-left of side + facets.emplace_back(Vec3crd( 0, id - 1, id - 3)); // top + facets.emplace_back(Vec3crd(id, 1, id - 2)); // bottom + facets.emplace_back(Vec3crd(id, id - 2, id - 3)); // upper-right of side + facets.emplace_back(Vec3crd(id, id - 3, id - 1)); // bottom-left of side } // Connect the last set of vertices with the first. - facets.emplace_back(Point3( 2, 0, id - 1)); - facets.emplace_back(Point3( 1, 3, id)); - facets.emplace_back(Point3(id, 3, 2)); - facets.emplace_back(Point3(id, 2, id - 1)); + facets.emplace_back(Vec3crd( 2, 0, id - 1)); + facets.emplace_back(Vec3crd( 1, 3, id)); + facets.emplace_back(Vec3crd(id, 3, 2)); + facets.emplace_back(Vec3crd(id, 2, id - 1)); TriangleMesh mesh(vertices, facets); return mesh; @@ -1600,7 +1589,7 @@ TriangleMesh make_cylinder(double r, double h, double fa) { // Default angle is 1 degree. TriangleMesh make_sphere(double rho, double fa) { Pointf3s vertices; - std::vector facets; + std::vector facets; // Algorithm: // Add points one-by-one to the sphere grid and form facets using relative coordinates. @@ -1619,17 +1608,16 @@ TriangleMesh make_sphere(double rho, double fa) { // special case: first ring connects to 0,0,0 // insert and form facets. - vertices.emplace_back(Pointf3(0.0, 0.0, -rho)); + vertices.emplace_back(Vec3d(0.0, 0.0, -rho)); size_t id = vertices.size(); for (size_t i = 0; i < ring.size(); i++) { // Fixed scaling const double z = -rho + increment*rho*2.0; // radius of the circle for this step. const double r = sqrt(abs(rho*rho - z*z)); - Pointf b(0, r); - b.rotate(ring[i]); - vertices.emplace_back(Pointf3(b(0), b(1), z)); - facets.emplace_back((i == 0) ? Point3(1, 0, ring.size()) : Point3(id, 0, id - 1)); + Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); + vertices.emplace_back(Vec3d(b(0), b(1), z)); + facets.emplace_back((i == 0) ? Vec3crd(1, 0, ring.size()) : Vec3crd(id, 0, id - 1)); ++ id; } @@ -1639,16 +1627,15 @@ TriangleMesh make_sphere(double rho, double fa) { const double r = sqrt(abs(rho*rho - z*z)); for (size_t i = 0; i < ring.size(); i++) { - Pointf b(0, r); - b.rotate(ring[i]); - vertices.emplace_back(Pointf3(b(0), b(1), z)); + Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); + vertices.emplace_back(Vec3d(b(0), b(1), z)); if (i == 0) { // wrap around - facets.emplace_back(Point3(id + ring.size() - 1 , id, id - 1)); - facets.emplace_back(Point3(id, id - ring.size(), id - 1)); + facets.emplace_back(Vec3crd(id + ring.size() - 1 , id, id - 1)); + facets.emplace_back(Vec3crd(id, id - ring.size(), id - 1)); } else { - facets.emplace_back(Point3(id , id - ring.size(), (id - 1) - ring.size())); - facets.emplace_back(Point3(id, id - 1 - ring.size() , id - 1)); + facets.emplace_back(Vec3crd(id , id - ring.size(), (id - 1) - ring.size())); + facets.emplace_back(Vec3crd(id, id - 1 - ring.size() , id - 1)); } id++; } @@ -1657,13 +1644,13 @@ TriangleMesh make_sphere(double rho, double fa) { // special case: last ring connects to 0,0,rho*2.0 // only form facets. - vertices.emplace_back(Pointf3(0.0, 0.0, rho)); + vertices.emplace_back(Vec3d(0.0, 0.0, rho)); for (size_t i = 0; i < ring.size(); i++) { if (i == 0) { // third vertex is on the other side of the ring. - facets.emplace_back(Point3(id, id - ring.size(), id - 1)); + facets.emplace_back(Vec3crd(id, id - ring.size(), id - 1)); } else { - facets.emplace_back(Point3(id, id - ring.size() + i, id - ring.size() + (i - 1))); + facets.emplace_back(Vec3crd(id, id - ring.size() + i, id - ring.size() + (i - 1))); } } id++; diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp index c700784a51..e4d377a9d0 100644 --- a/xs/src/libslic3r/TriangleMesh.hpp +++ b/xs/src/libslic3r/TriangleMesh.hpp @@ -20,33 +20,33 @@ typedef std::vector TriangleMeshPtrs; class TriangleMesh { public: - TriangleMesh(); - TriangleMesh(const Pointf3s &points, const std::vector &facets); - TriangleMesh(const TriangleMesh &other); - TriangleMesh(TriangleMesh &&other); + TriangleMesh() : repaired(false) { stl_initialize(&this->stl); } + TriangleMesh(const Pointf3s &points, const std::vector &facets); + TriangleMesh(const TriangleMesh &other) : repaired(false) { stl_initialize(&this->stl); *this = other; } + TriangleMesh(TriangleMesh &&other) : repaired(false) { stl_initialize(&this->stl); this->swap(other); } + ~TriangleMesh() { stl_close(&this->stl); } TriangleMesh& operator=(const TriangleMesh &other); - TriangleMesh& operator=(TriangleMesh &&other); - void swap(TriangleMesh &other); - ~TriangleMesh(); - void ReadSTLFile(const char* input_file); - void write_ascii(const char* output_file); - void write_binary(const char* output_file); + TriangleMesh& operator=(TriangleMesh &&other) { this->swap(other); return *this; } + void swap(TriangleMesh &other) { std::swap(this->stl, other.stl); std::swap(this->repaired, other.repaired); } + void ReadSTLFile(const char* input_file) { stl_open(&stl, input_file); } + void write_ascii(const char* output_file) { stl_write_ascii(&this->stl, output_file, ""); } + void write_binary(const char* output_file) { stl_write_binary(&this->stl, output_file, ""); } void repair(); float volume(); void check_topology(); - bool is_manifold() const; + bool is_manifold() const { return this->stl.stats.connected_facets_3_edge == this->stl.stats.number_of_facets; } void WriteOBJFile(char* output_file); void scale(float factor); - void scale(const Pointf3 &versor); + void scale(const Vec3d &versor); void translate(float x, float y, float z); void rotate(float angle, const Axis &axis); - void rotate_x(float angle); - void rotate_y(float angle); - void rotate_z(float angle); + void rotate_x(float angle) { this->rotate(angle, X); } + void rotate_y(float angle) { this->rotate(angle, Y); } + void rotate_z(float angle) { this->rotate(angle, Z); } void mirror(const Axis &axis); - void mirror_x(); - void mirror_y(); - void mirror_z(); + void mirror_x() { this->mirror(X); } + void mirror_y() { this->mirror(Y); } + void mirror_z() { this->mirror(Z); } void transform(const float* matrix3x4); void align_to_origin(); void rotate(double angle, Point* center); @@ -55,9 +55,13 @@ public: ExPolygons horizontal_projection() const; Polygon convex_hull(); BoundingBoxf3 bounding_box() const; + // Returns the bbox of this TriangleMesh transformed by the given transformation + BoundingBoxf3 transformed_bounding_box(const Transform3d& t) 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; + size_t facets_count() const { return this->stl.stats.number_of_facets; } // Returns true, if there are two and more connected patches in the mesh. // Returns false, if one or zero connected patch is in the mesh. @@ -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/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index 862ce2e516..6fea64cd12 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -45,7 +45,6 @@ typedef double coordf_t; //FIXME Better to use an inline function with an explicit return type. //inline coord_t scale_(coordf_t v) { return coord_t(floor(v / SCALING_FACTOR + 0.5f)); } #define scale_(val) ((val) / SCALING_FACTOR) -#define unscale(val) ((val) * SCALING_FACTOR) #define SCALED_EPSILON scale_(EPSILON) /* Implementation of CONFESS("foo"): */ #ifdef _MSC_VER @@ -102,6 +101,9 @@ inline std::string debug_out_path(const char *name, ...) namespace Slic3r { +template +inline T unscale(Q v) { return T(v) * T(SCALING_FACTOR); } + enum Axis { X=0, Y, Z, E, F, NUM_AXES }; template diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index dbaf35ae47..5e09187551 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -41,9 +41,8 @@ REGISTER_CLASS(BoundingBoxf, "Geometry::BoundingBoxf"); REGISTER_CLASS(BoundingBoxf3, "Geometry::BoundingBoxf3"); REGISTER_CLASS(BridgeDetector, "BridgeDetector"); REGISTER_CLASS(Point, "Point"); -REGISTER_CLASS(Point3, "Point3"); -REGISTER_CLASS(Pointf, "Pointf"); -REGISTER_CLASS(Pointf3, "Pointf3"); +__REGISTER_CLASS(Vec2d, "Pointf"); +__REGISTER_CLASS(Vec3d, "Pointf3"); REGISTER_CLASS(DynamicPrintConfig, "Config"); REGISTER_CLASS(StaticPrintConfig, "Config::Static"); REGISTER_CLASS(PrintObjectConfig, "Config::PrintObject"); @@ -64,9 +63,9 @@ 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"); SV* ConfigBase__as_hash(ConfigBase* THIS) { @@ -133,7 +132,7 @@ SV* ConfigOption_to_SV(const ConfigOption &opt, const ConfigOptionDef &def) auto optv = static_cast(&opt); AV* av = newAV(); av_fill(av, optv->values.size()-1); - for (const Pointf &v : optv->values) + for (const Vec2d &v : optv->values) av_store(av, &v - optv->values.data(), perl_to_SV_clone_ref(v)); return newRV_noinc((SV*)av); } @@ -263,14 +262,14 @@ bool ConfigBase__set(ConfigBase* THIS, const t_config_option_key &opt_key, SV* v return from_SV_check(value, &static_cast(opt)->value); case coPoints: { - std::vector &values = static_cast(opt)->values; + std::vector &values = static_cast(opt)->values; AV* av = (AV*)SvRV(value); const size_t len = av_len(av)+1; values.clear(); values.reserve(len); for (size_t i = 0; i < len; i++) { SV** elem = av_fetch(av, i, 0); - Pointf point; + Vec2d point(Vec2d::Zero()); if (elem == NULL || !from_SV_check(*elem, &point)) return false; values.emplace_back(point); } @@ -509,7 +508,7 @@ void from_SV_check(SV* point_sv, Point* point) } } -SV* to_SV_pureperl(const Pointf* point) +SV* to_SV_pureperl(const Vec2d* point) { AV* av = newAV(); av_fill(av, 1); @@ -518,23 +517,23 @@ SV* to_SV_pureperl(const Pointf* point) return newRV_noinc((SV*)av); } -bool from_SV(SV* point_sv, Pointf* point) +bool from_SV(SV* point_sv, Vec2d* point) { AV* point_av = (AV*)SvRV(point_sv); SV* sv_x = *av_fetch(point_av, 0, 0); SV* sv_y = *av_fetch(point_av, 1, 0); if (!looks_like_number(sv_x) || !looks_like_number(sv_y)) return false; - *point = Pointf(SvNV(sv_x), SvNV(sv_y)); + *point = Vec2d(SvNV(sv_x), SvNV(sv_y)); return true; } -bool from_SV_check(SV* point_sv, Pointf* point) +bool from_SV_check(SV* point_sv, Vec2d* point) { if (sv_isobject(point_sv) && (SvTYPE(SvRV(point_sv)) == SVt_PVMG)) { if (!sv_isa(point_sv, perl_class_name(point)) && !sv_isa(point_sv, perl_class_name_ref(point))) CONFESS("Not a valid %s object (got %s)", perl_class_name(point), HvNAME(SvSTASH(SvRV(point_sv)))); - *point = *(Pointf*)SvIV((SV*)SvRV( point_sv )); + *point = *(Vec2d*)SvIV((SV*)SvRV( point_sv )); return true; } else { return from_SV(point_sv, point); 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 0000000000..f46d421274 Binary files /dev/null and b/xs/src/qhull/html/normal_voronoi_knauss_oesterle.jpg differ 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 0000000000..08be18c8a5 Binary files /dev/null and b/xs/src/qhull/html/qh--4d.gif differ diff --git a/xs/src/qhull/html/qh--cone.gif b/xs/src/qhull/html/qh--cone.gif new file mode 100644 index 0000000000..78470ee8f0 Binary files /dev/null and b/xs/src/qhull/html/qh--cone.gif differ diff --git a/xs/src/qhull/html/qh--dt.gif b/xs/src/qhull/html/qh--dt.gif new file mode 100644 index 0000000000..b6d4e26727 Binary files /dev/null and b/xs/src/qhull/html/qh--dt.gif differ diff --git a/xs/src/qhull/html/qh--geom.gif b/xs/src/qhull/html/qh--geom.gif new file mode 100644 index 0000000000..9c70b54991 Binary files /dev/null and b/xs/src/qhull/html/qh--geom.gif differ diff --git a/xs/src/qhull/html/qh--half.gif b/xs/src/qhull/html/qh--half.gif new file mode 100644 index 0000000000..80a4d0883b Binary files /dev/null and b/xs/src/qhull/html/qh--half.gif differ diff --git a/xs/src/qhull/html/qh--rand.gif b/xs/src/qhull/html/qh--rand.gif new file mode 100644 index 0000000000..4d4e4daee8 Binary files /dev/null and b/xs/src/qhull/html/qh--rand.gif differ diff --git a/xs/src/qhull/html/qh-code.htm b/xs/src/qhull/html/qh-code.htm new file mode 100644 index 0000000000..fff7faddb5 --- /dev/null +++ b/xs/src/qhull/html/qh-code.htm @@ -0,0 +1,1062 @@ + + + + +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	m_on_move = nullptr;
    +	Vec2d		m_shift = Vec2d::Zero();
    +	Vec2d		m_pos = Vec2d::Zero();
    +	std::function	m_on_move = nullptr;
     
    -	Point		to_pixels(Pointf point);
    -	Pointf		to_units(Point point);
    +	Point		to_pixels(Vec2d point);
    +	Vec2d		to_units(Point point);
     	void		repaint();
     	void		mouse_event(wxMouseEvent event);
    -	void		set_pos(Pointf pos);
    +	void		set_pos(Vec2d pos);
     
     public:
     	Bed_2D(wxWindow* parent) 
    @@ -41,7 +41,7 @@ public:
     	}
     	~Bed_2D(){}
     
    -	std::vector		m_bed_shape;
    +	std::vector		m_bed_shape;
     		
     };
     
    diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp
    index 36952d230e..c9607ccf9c 100644
    --- a/xs/src/slic3r/GUI/3DScene.cpp
    +++ b/xs/src/slic3r/GUI/3DScene.cpp
    @@ -39,7 +39,7 @@ void GLIndexedVertexArray::load_mesh_flat_shading(const TriangleMesh &mesh)
         for (int i = 0; i < mesh.stl.stats.number_of_facets; ++ i) {
             const stl_facet &facet = mesh.stl.facet_start[i];
             for (int j = 0; j < 3; ++ j)
    -            this->push_geometry(facet.vertex[j].x, facet.vertex[j].y, facet.vertex[j].z, facet.normal.x, facet.normal.y, facet.normal.z);
    +            this->push_geometry(facet.vertex[j](0), facet.vertex[j](1), facet.vertex[j](2), facet.normal(0), facet.normal(1), facet.normal(2));
         }
     }
     
    @@ -55,7 +55,7 @@ void GLIndexedVertexArray::load_mesh_full_shading(const TriangleMesh &mesh)
         for (int i = 0; i < mesh.stl.stats.number_of_facets; ++i) {
             const stl_facet &facet = mesh.stl.facet_start[i];
             for (int j = 0; j < 3; ++j)
    -            this->push_geometry(facet.vertex[j].x, facet.vertex[j].y, facet.vertex[j].z, facet.normal.x, facet.normal.y, facet.normal.z);
    +            this->push_geometry(facet.vertex[j](0), facet.vertex[j](1), facet.vertex[j](2), facet.normal(0), facet.normal(1), facet.normal(2));
     
             this->push_triangle(vertices_count, vertices_count + 1, vertices_count + 2);
             vertices_count += 3;
    @@ -195,9 +195,12 @@ const float GLVolume::OUTSIDE_COLOR[4] = { 0.0f, 0.38f, 0.8f, 1.0f };
     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_origin(0, 0, 0)
    +    , 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)
    +    , m_convex_hull(nullptr)
         , composite_id(-1)
         , select_group_id(-1)
         , drag_group_id(-1)
    @@ -214,8 +217,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 = Transform3f::Identity();
    -
         color[0] = r;
         color[1] = g;
         color[2] = b;
    @@ -252,51 +253,86 @@ void GLVolume::set_render_color()
             set_render_color(color, 4);
     }
     
    -const Pointf3& GLVolume::get_origin() const
    +const Vec3d& GLVolume::get_origin() const
     {
         return m_origin;
     }
     
    -void GLVolume::set_origin(const Pointf3& origin)
    +float GLVolume::get_angle_z()
     {
    -    m_origin = origin;
    -    m_dirty = true;
    +    return m_angle_z;
    +}
    +
    +void GLVolume::set_origin(const Vec3d& origin)
    +{
    +    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 Transform3f& GLVolume::world_matrix() const
    +void GLVolume::set_convex_hull(const TriangleMesh& convex_hull)
     {
    -    if (m_dirty)
    -    {
    -        m_world_mat = Transform3f::Identity();
    -        m_world_mat.translate(Vec3f(m_origin(0), m_origin(1), 0));
    -        m_world_mat.rotate(Eigen::AngleAxisf(m_angle_z, Vec3f::UnitZ()));
    -        m_world_mat.scale(m_scale_factor);
    -        m_dirty = false;
    -    }
    +    m_convex_hull = &convex_hull;
    +}
     
    -    return m_world_mat;
    +Transform3f GLVolume::world_matrix() const
    +{
    +    Transform3f matrix = Transform3f::Identity();
    +    matrix.translate(Vec3f((float)m_origin(0), (float)m_origin(1), (float)m_origin(2)));
    +    matrix.rotate(Eigen::AngleAxisf(m_angle_z, Vec3f::UnitZ()));
    +    matrix.scale(m_scale_factor);
    +    return matrix;
     }
     
     BoundingBoxf3 GLVolume::transformed_bounding_box() const
     {
    -    if (m_dirty)
    -        m_transformed_bounding_box = bounding_box.transformed(world_matrix());
    +    if (m_transformed_bounding_box_dirty)
    +    {
    +        m_transformed_bounding_box = bounding_box.transformed(world_matrix().cast());
    +        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 != nullptr) && (m_convex_hull->stl.stats.number_of_facets > 0))
    +            m_transformed_convex_hull_bounding_box = m_convex_hull->transformed_bounding_box(world_matrix().cast());
    +        else
    +            m_transformed_convex_hull_bounding_box = bounding_box.transformed(world_matrix().cast());
    +
    +        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;
    @@ -623,13 +659,14 @@ 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;
                 }
                 v.is_modifier = model_volume->modifier;
                 v.shader_outside_printer_detection_enabled = !model_volume->modifier;
    -            v.set_origin(Pointf3(instance->offset(0), instance->offset(1), 0.0));
    +            v.set_origin(Vec3d(instance->offset(0), instance->offset(1), 0.0));
                 v.set_angle_z(instance->rotation);
                 v.set_scale_factor(instance->scaling_factor);
             }
    @@ -660,16 +697,16 @@ int GLVolumeCollection::load_wipe_tower_preview(
             // 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;
    +        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]));
    +            points.push_back(Vec3d(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]));
    +            facets.push_back(Vec3crd(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
    @@ -680,7 +717,7 @@ int GLVolumeCollection::load_wipe_tower_preview(
                 tooth_mesh.translate(min_width, 0.f, 0.f);
             }
     
    -        mesh.scale(Pointf3(width/(n*min_width), 1.f, height)); // Scaling to proper width
    +        mesh.scale(Vec3d(width/(n*min_width), 1.f, height)); // Scaling to proper width
         }
         else
             mesh = make_cube(width, depth, height);
    @@ -700,7 +737,7 @@ int GLVolumeCollection::load_wipe_tower_preview(
         else
             v.indexed_vertex_array.load_mesh_flat_shading(mesh);
     
    -    v.set_origin(Pointf3(pos_x, pos_y, 0.));
    +    v.set_origin(Vec3d(pos_x, pos_y, 0.));
     
         // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry().
         v.bounding_box = v.indexed_vertex_array.bounding_box();
    @@ -786,7 +823,7 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M
             return false;
     
         BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values));
    -    BoundingBoxf3 print_volume(Pointf3(unscale(bed_box_2D.min(0)), unscale(bed_box_2D.min(1)), 0.0), Pointf3(unscale(bed_box_2D.max(0)), unscale(bed_box_2D.max(1)), config->opt_float("max_print_height")));
    +    BoundingBoxf3 print_volume(Vec3d(unscale(bed_box_2D.min(0)), unscale(bed_box_2D.min(1)), 0.0), Vec3d(unscale(bed_box_2D.max(0)), unscale(bed_box_2D.max(1)), config->opt_float("max_print_height")));
         // Allow the objects to protrude below the print bed
         print_volume.min(2) = -1e10;
     
    @@ -795,9 +832,9 @@ 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_bounding_box();
    +            const BoundingBoxf3& bb = volume->transformed_convex_hull_bounding_box();
                 bool contained = print_volume.contains(bb);
                 all_contained &= contained;
     
    @@ -951,8 +988,8 @@ static void thick_lines_to_indexed_vertex_array(
         // right, left, top, bottom
         int     idx_prev[4]      = { -1, -1, -1, -1 };
         double  bottom_z_prev    = 0.;
    -    Pointf  b1_prev;
    -    Vectorf v_prev;
    +    Vec2d  b1_prev(Vec2d::Zero());
    +    Vec2d v_prev(Vec2d::Zero());
         int     idx_initial[4]   = { -1, -1, -1, -1 };
         double  width_initial    = 0.;
         double  bottom_z_initial = 0.0;
    @@ -962,7 +999,7 @@ static void thick_lines_to_indexed_vertex_array(
         for (size_t ii = 0; ii < lines_end; ++ ii) {
             size_t i = (ii == lines.size()) ? 0 : ii;
             const Line &line = lines[i];
    -        double len = unscale(line.length());
    +        double len = unscale(line.length());
             double inv_len = 1.0 / len;
             double bottom_z = top_z - heights[i];
             double middle_z = 0.5 * (top_z + bottom_z);
    @@ -972,28 +1009,28 @@ static void thick_lines_to_indexed_vertex_array(
             bool is_last = (ii == lines_end - 1);
             bool is_closing = closed && is_last;
     
    -        Vectorf v = Vectorf::new_unscale(line.vector());
    +        Vec2d v = unscale(line.vector());
             v *= inv_len;
     
    -        Pointf a = Pointf::new_unscale(line.a);
    -        Pointf b = Pointf::new_unscale(line.b);
    -        Pointf a1 = a;
    -        Pointf a2 = a;
    -        Pointf b1 = b;
    -        Pointf b2 = b;
    +        Vec2d a = unscale(line.a);
    +        Vec2d b = unscale(line.b);
    +        Vec2d a1 = a;
    +        Vec2d a2 = a;
    +        Vec2d b1 = b;
    +        Vec2d b2 = b;
             {
                 double dist = 0.5 * width;  // scaled
                 double dx = dist * v(0);
                 double dy = dist * v(1);
    -            a1 += Vectorf(+dy, -dx);
    -            a2 += Vectorf(-dy, +dx);
    -            b1 += Vectorf(+dy, -dx);
    -            b2 += Vectorf(-dy, +dx);
    +            a1 += Vec2d(+dy, -dx);
    +            a2 += Vec2d(-dy, +dx);
    +            b1 += Vec2d(+dy, -dx);
    +            b2 += Vec2d(-dy, +dx);
             }
     
             // calculate new XY normals
             Vector n = line.normal();
    -        Vectorf3 xy_right_normal = Vectorf3::new_unscale(n(0), n(1), 0);
    +        Vec3d xy_right_normal = unscale(n(0), n(1), 0);
             xy_right_normal *= inv_len;
     
             int idx_a[4];
    @@ -1063,7 +1100,7 @@ static void thick_lines_to_indexed_vertex_array(
                     {
                         // Create a sharp corner with an overshot and average the left / right normals.
                         // At the crease angle of 45 degrees, the overshot at the corner will be less than (1-1/cos(PI/8)) = 8.2% over an arc.
    -                    Pointf intersection;
    +                    Vec2d intersection(Vec2d::Zero());
                         Geometry::ray_ray_intersection(b1_prev, v_prev, a1, v, intersection);
                         a1 = intersection;
                         a2 = 2. * a - intersection;
    @@ -1196,15 +1233,15 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines,
         int      idx_initial[4] = { -1, -1, -1, -1 };
         int      idx_prev[4] = { -1, -1, -1, -1 };
         double   z_prev = 0.0;
    -    Vectorf3 n_right_prev;
    -    Vectorf3 n_top_prev;
    -    Vectorf3 unit_v_prev;
    +    Vec3d n_right_prev = Vec3d::Zero();
    +    Vec3d n_top_prev = Vec3d::Zero();
    +    Vec3d unit_v_prev = Vec3d::Zero();
         double   width_initial = 0.0;
     
         // new vertices around the line endpoints
         // left, right, top, bottom
    -    Pointf3 a[4];
    -    Pointf3 b[4];
    +    Vec3d a[4] = { Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() };
    +    Vec3d b[4] = { Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() };
     
         // loop once more in case of closed loops
         size_t lines_end = closed ? (lines.size() + 1) : lines.size();
    @@ -1216,17 +1253,17 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines,
             double height = heights[i];
             double width = widths[i];
     
    -        Vectorf3 unit_v = Vectorf3::new_unscale(line.vector()).normalized();
    +        Vec3d unit_v = unscale(line.vector()).normalized();
     
    -        Vectorf3 n_top;
    -        Vectorf3 n_right;
    -        Vectorf3 unit_positive_z(0.0, 0.0, 1.0);
    +        Vec3d n_top = Vec3d::Zero();
    +        Vec3d n_right = Vec3d::Zero();
    +        Vec3d unit_positive_z(0.0, 0.0, 1.0);
     
             if ((line.a(0) == line.b(0)) && (line.a(1) == line.b(1)))
             {
                 // vertical segment
    -            n_right = (line.a(2) < line.b(2)) ? Vectorf3(-1.0, 0.0, 0.0) : Vectorf3(1.0, 0.0, 0.0);
    -            n_top = Vectorf3(0.0, 1.0, 0.0);
    +            n_right = (line.a(2) < line.b(2)) ? Vec3d(-1.0, 0.0, 0.0) : Vec3d(1.0, 0.0, 0.0);
    +            n_top = Vec3d(0.0, 1.0, 0.0);
             }
             else
             {
    @@ -1235,10 +1272,10 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines,
                 n_top = n_right.cross(unit_v).normalized();
             }
     
    -        Vectorf3 rl_displacement = 0.5 * width * n_right;
    -        Vectorf3 tb_displacement = 0.5 * height * n_top;
    -        Pointf3 l_a = Pointf3::new_unscale(line.a);
    -        Pointf3 l_b = Pointf3::new_unscale(line.b);
    +        Vec3d rl_displacement = 0.5 * width * n_right;
    +        Vec3d tb_displacement = 0.5 * height * n_top;
    +        Vec3d l_a = unscale(line.a);
    +        Vec3d l_b = unscale(line.b);
     
             a[RIGHT] = l_a + rl_displacement;
             a[LEFT] = l_a - rl_displacement;
    @@ -1249,8 +1286,8 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines,
             b[TOP] = l_b + tb_displacement;
             b[BOTTOM] = l_b - tb_displacement;
     
    -        Vectorf3 n_bottom = -n_top;
    -        Vectorf3 n_left = -n_right;
    +        Vec3d n_bottom = -n_top;
    +        Vec3d n_left = -n_right;
     
             int idx_a[4];
             int idx_b[4];
    @@ -1316,9 +1353,9 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines,
                     // At the crease angle of 45 degrees, the overshot at the corner will be less than (1-1/cos(PI/8)) = 8.2% over an arc.
     
                     // averages normals
    -                Vectorf3 average_n_right = 0.5 * (n_right + n_right_prev).normalized();
    -                Vectorf3 average_n_left = -average_n_right;
    -                Vectorf3 average_rl_displacement = 0.5 * width * average_n_right;
    +                Vec3d average_n_right = 0.5 * (n_right + n_right_prev).normalized();
    +                Vec3d average_n_left = -average_n_right;
    +                Vec3d average_rl_displacement = 0.5 * width * average_n_right;
     
                     // updates vertices around a
                     a[RIGHT] = l_a + average_rl_displacement;
    @@ -1441,14 +1478,14 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines,
     #undef BOTTOM
     }
     
    -static void point_to_indexed_vertex_array(const Point3& point,
    +static void point_to_indexed_vertex_array(const Vec3crd& point,
         double width,
         double height,
         GLIndexedVertexArray& volume)
     {
         // builds a double piramid, with vertices on the local axes, around the point
     
    -    Pointf3 center = Pointf3::new_unscale(point);
    +    Vec3d center = unscale(point);
     
         double scale_factor = 1.0;
         double w = scale_factor * width;
    @@ -1462,13 +1499,13 @@ static void point_to_indexed_vertex_array(const Point3& point,
             idxs[i] = idx_last + i;
         }
     
    -    Vectorf3 displacement_x(w, 0.0, 0.0);
    -    Vectorf3 displacement_y(0.0, w, 0.0);
    -    Vectorf3 displacement_z(0.0, 0.0, h);
    +    Vec3d displacement_x(w, 0.0, 0.0);
    +    Vec3d displacement_y(0.0, w, 0.0);
    +    Vec3d displacement_z(0.0, 0.0, h);
     
    -    Vectorf3 unit_x(1.0, 0.0, 0.0);
    -    Vectorf3 unit_y(0.0, 1.0, 0.0);
    -    Vectorf3 unit_z(0.0, 0.0, 1.0);
    +    Vec3d unit_x(1.0, 0.0, 0.0);
    +    Vec3d unit_y(0.0, 1.0, 0.0);
    +    Vec3d unit_z(0.0, 0.0, 1.0);
     
         // vertices
         volume.push_geometry(center - displacement_x, -unit_x); // idxs[0]
    @@ -1511,7 +1548,7 @@ void _3DScene::thick_lines_to_verts(const Lines3& lines,
         thick_lines_to_indexed_vertex_array(lines, widths, heights, closed, volume.indexed_vertex_array);
     }
     
    -static void thick_point_to_verts(const Point3& point,
    +static void thick_point_to_verts(const Vec3crd& point,
         double width,
         double height,
         GLVolume& volume)
    @@ -1617,7 +1654,7 @@ void _3DScene::polyline3_to_verts(const Polyline3& polyline, double width, doubl
         thick_lines_to_verts(lines, widths, heights, false, volume);
     }
     
    -void _3DScene::point3_to_verts(const Point3& point, double width, double height, GLVolume& volume)
    +void _3DScene::point3_to_verts(const Vec3crd& point, double width, double height, GLVolume& volume)
     {
         thick_point_to_verts(point, width, height, volume);
     }
    @@ -1814,6 +1851,11 @@ void _3DScene::enable_gizmos(wxGLCanvas* canvas, bool enable)
         s_canvas_mgr.enable_gizmos(canvas, enable);
     }
     
    +void _3DScene::enable_toolbar(wxGLCanvas* canvas, bool enable)
    +{
    +    s_canvas_mgr.enable_toolbar(canvas, enable);
    +}
    +
     void _3DScene::enable_shader(wxGLCanvas* canvas, bool enable)
     {
         s_canvas_mgr.enable_shader(canvas, enable);
    @@ -1834,6 +1876,16 @@ void _3DScene::allow_multisample(wxGLCanvas* canvas, bool allow)
         s_canvas_mgr.allow_multisample(canvas, allow);
     }
     
    +void _3DScene::enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable)
    +{
    +    s_canvas_mgr.enable_toolbar_item(canvas, name, enable);
    +}
    +
    +bool _3DScene::is_toolbar_item_pressed(wxGLCanvas* canvas, const std::string& name)
    +{
    +    return s_canvas_mgr.is_toolbar_item_pressed(canvas, name);
    +}
    +
     void _3DScene::zoom_to_bed(wxGLCanvas* canvas)
     {
         s_canvas_mgr.zoom_to_bed(canvas);
    @@ -1969,6 +2021,56 @@ void _3DScene::register_on_update_geometry_info_callback(wxGLCanvas* canvas, voi
         s_canvas_mgr.register_on_update_geometry_info_callback(canvas, callback);
     }
     
    +void _3DScene::register_action_add_callback(wxGLCanvas* canvas, void* callback)
    +{
    +    s_canvas_mgr.register_action_add_callback(canvas, callback);
    +}
    +
    +void _3DScene::register_action_delete_callback(wxGLCanvas* canvas, void* callback)
    +{
    +    s_canvas_mgr.register_action_delete_callback(canvas, callback);
    +}
    +
    +void _3DScene::register_action_deleteall_callback(wxGLCanvas* canvas, void* callback)
    +{
    +    s_canvas_mgr.register_action_deleteall_callback(canvas, callback);
    +}
    +
    +void _3DScene::register_action_arrange_callback(wxGLCanvas* canvas, void* callback)
    +{
    +    s_canvas_mgr.register_action_arrange_callback(canvas, callback);
    +}
    +
    +void _3DScene::register_action_more_callback(wxGLCanvas* canvas, void* callback)
    +{
    +    s_canvas_mgr.register_action_more_callback(canvas, callback);
    +}
    +
    +void _3DScene::register_action_fewer_callback(wxGLCanvas* canvas, void* callback)
    +{
    +    s_canvas_mgr.register_action_fewer_callback(canvas, callback);
    +}
    +
    +void _3DScene::register_action_split_callback(wxGLCanvas* canvas, void* callback)
    +{
    +    s_canvas_mgr.register_action_split_callback(canvas, callback);
    +}
    +
    +void _3DScene::register_action_cut_callback(wxGLCanvas* canvas, void* callback)
    +{
    +    s_canvas_mgr.register_action_cut_callback(canvas, callback);
    +}
    +
    +void _3DScene::register_action_settings_callback(wxGLCanvas* canvas, void* callback)
    +{
    +    s_canvas_mgr.register_action_settings_callback(canvas, callback);
    +}
    +
    +void _3DScene::register_action_layersediting_callback(wxGLCanvas* canvas, void* callback)
    +{
    +    s_canvas_mgr.register_action_layersediting_callback(canvas, callback);
    +}
    +
     static inline int hex_digit_to_int(const char c)
     {
         return 
    diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp
    index 41839a3404..b4aa6ce955 100644
    --- a/xs/src/slic3r/GUI/3DScene.hpp
    +++ b/xs/src/slic3r/GUI/3DScene.hpp
    @@ -119,7 +119,7 @@ public:
             push_geometry(float(x), float(y), float(z), float(nx), float(ny), float(nz));
         }
     
    -    inline void push_geometry(const Pointf3& p, const Vectorf3& n) {
    +    inline void push_geometry(const Vec3d& p, const Vec3d& n) {
             push_geometry(p(0), p(1), p(2), n(0), n(1), n(2));
         }
     
    @@ -255,17 +255,21 @@ public:
     
     private:
         // Offset of the volume to be rendered.
    -    Pointf3               m_origin;
    +    Vec3d                 m_origin;
         // Rotation around Z axis of the volume to be rendered.
         float                 m_angle_z;
         // Scale factor of the volume to be rendered.
         float                 m_scale_factor;
    -    // World matrix of the volume to be rendered.
    -    mutable Transform3f   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;
    +    // 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.
    +    mutable bool          m_transformed_convex_hull_bounding_box_dirty;
     
     public:
     
    @@ -319,17 +323,20 @@ public:
         // Sets render color in dependence of current state
         void set_render_color();
     
    -    const Pointf3& get_origin() const;
    -    void set_origin(const Pointf3& origin);
    +    float get_angle_z();
    +    const Vec3d& get_origin() const;
    +    void set_origin(const Vec3d& 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 Transform3f&  world_matrix() const;
    +    Transform3f         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(); }
    @@ -497,11 +504,15 @@ public:
         static void enable_picking(wxGLCanvas* canvas, bool enable);
         static void enable_moving(wxGLCanvas* canvas, bool enable);
         static void enable_gizmos(wxGLCanvas* canvas, bool enable);
    +    static void enable_toolbar(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 enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable);
    +    static bool is_toolbar_item_pressed(wxGLCanvas* canvas, const std::string& name);
    +
         static void zoom_to_bed(wxGLCanvas* canvas);
         static void zoom_to_volumes(wxGLCanvas* canvas);
         static void select_view(wxGLCanvas* canvas, const std::string& direction);
    @@ -534,6 +545,17 @@ public:
         static void register_on_gizmo_rotate_callback(wxGLCanvas* canvas, void* callback);
         static void register_on_update_geometry_info_callback(wxGLCanvas* canvas, void* callback);
     
    +    static void register_action_add_callback(wxGLCanvas* canvas, void* callback);
    +    static void register_action_delete_callback(wxGLCanvas* canvas, void* callback);
    +    static void register_action_deleteall_callback(wxGLCanvas* canvas, void* callback);
    +    static void register_action_arrange_callback(wxGLCanvas* canvas, void* callback);
    +    static void register_action_more_callback(wxGLCanvas* canvas, void* callback);
    +    static void register_action_fewer_callback(wxGLCanvas* canvas, void* callback);
    +    static void register_action_split_callback(wxGLCanvas* canvas, void* callback);
    +    static void register_action_cut_callback(wxGLCanvas* canvas, void* callback);
    +    static void register_action_settings_callback(wxGLCanvas* canvas, void* callback);
    +    static void register_action_layersediting_callback(wxGLCanvas* canvas, void* callback);
    +
         static std::vector load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector instance_idxs);
         static std::vector load_object(wxGLCanvas* canvas, const Model* model, int obj_idx);
     
    @@ -553,7 +575,7 @@ public:
         static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GLVolume& volume);
         static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GLVolume& volume);
         static void polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume);
    -    static void point3_to_verts(const Point3& point, double width, double height, GLVolume& volume);
    +    static void point3_to_verts(const Vec3crd& point, double width, double height, GLVolume& volume);
     };
     
     }
    diff --git a/xs/src/slic3r/GUI/AppConfig.cpp b/xs/src/slic3r/GUI/AppConfig.cpp
    index 2a33cd7331..0f77b1953c 100644
    --- a/xs/src/slic3r/GUI/AppConfig.cpp
    +++ b/xs/src/slic3r/GUI/AppConfig.cpp
    @@ -233,6 +233,7 @@ void AppConfig::reset_selections()
         if (it != m_storage.end()) {
             it->second.erase("print");
             it->second.erase("filament");
    +        it->second.erase("sla_material");
             it->second.erase("printer");
             m_dirty = true;
         }
    diff --git a/xs/src/slic3r/GUI/BedShapeDialog.cpp b/xs/src/slic3r/GUI/BedShapeDialog.cpp
    index f021e1b96a..e04f2b3700 100644
    --- a/xs/src/slic3r/GUI/BedShapeDialog.cpp
    +++ b/xs/src/slic3r/GUI/BedShapeDialog.cpp
    @@ -48,14 +48,14 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt)
     	auto optgroup = init_shape_options_page(_(L("Rectangular")));
     		ConfigOptionDef def;
     		def.type = coPoints;
    -		def.default_value = new ConfigOptionPoints{ Pointf(200, 200) };
    +		def.default_value = new ConfigOptionPoints{ Vec2d(200, 200) };
     		def.label = L("Size");
     		def.tooltip = L("Size in X and Y of the rectangular plate.");
     		Option option(def, "rect_size");
     		optgroup->append_single_option_line(option);
     
     		def.type = coPoints;
    -		def.default_value = new ConfigOptionPoints{ Pointf(0, 0) };
    +		def.default_value = new ConfigOptionPoints{ Vec2d(0, 0) };
     		def.label = L("Origin");
     		def.tooltip = L("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle.");
     		option = Option(def, "rect_origin");
    @@ -159,11 +159,11 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points)
                     y_max = std::max(y_max, pt(1));
                 }
     
    -            auto origin = new ConfigOptionPoints{ Pointf(-x_min, -y_min) };
    +            auto origin = new ConfigOptionPoints{ Vec2d(-x_min, -y_min) };
     
     			m_shape_options_book->SetSelection(SHAPE_RECTANGULAR);
     			auto optgroup = m_optgroups[SHAPE_RECTANGULAR];
    -			optgroup->set_value("rect_size", new ConfigOptionPoints{ Pointf(x_max - x_min, y_max - y_min) });//[x_max - x_min, y_max - y_min]);
    +			optgroup->set_value("rect_size", new ConfigOptionPoints{ Vec2d(x_max - x_min, y_max - y_min) });//[x_max - x_min, y_max - y_min]);
     			optgroup->set_value("rect_origin", origin);
     			update_shape();
     			return;
    @@ -195,7 +195,7 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points)
     			// all vertices are equidistant to center
     			m_shape_options_book->SetSelection(SHAPE_CIRCULAR);
     			auto optgroup = m_optgroups[SHAPE_CIRCULAR];
    -			boost::any ret = wxNumberFormatter::ToString(unscale(avg_dist * 2), 0);
    +			boost::any ret = wxNumberFormatter::ToString(unscale(avg_dist * 2), 0);
      			optgroup->set_value("diameter", ret);
     			update_shape();
     			return;
    @@ -206,8 +206,8 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points)
     		// Invalid polygon.Revert to default bed dimensions.
     		m_shape_options_book->SetSelection(SHAPE_RECTANGULAR);
     		auto optgroup = m_optgroups[SHAPE_RECTANGULAR];
    -		optgroup->set_value("rect_size", new ConfigOptionPoints{ Pointf(200, 200) });
    -		optgroup->set_value("rect_origin", new ConfigOptionPoints{ Pointf(0, 0) });
    +		optgroup->set_value("rect_size", new ConfigOptionPoints{ Vec2d(200, 200) });
    +		optgroup->set_value("rect_origin", new ConfigOptionPoints{ Vec2d(0, 0) });
     		update_shape();
     		return;
     	}
    @@ -230,13 +230,15 @@ void BedShapePanel::update_shape()
     {
     	auto page_idx = m_shape_options_book->GetSelection();
     	if (page_idx == SHAPE_RECTANGULAR) {
    -		Pointf rect_size, rect_origin;
    +		Vec2d rect_size(Vec2d::Zero());
    +		Vec2d rect_origin(Vec2d::Zero());
     		try{
    -			rect_size = boost::any_cast(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_size")); }
    +			rect_size = boost::any_cast(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_size")); }
     		catch (const std::exception &e){
    -			return;}
    +			return;
    +		}
     		try{
    -			rect_origin = boost::any_cast(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_origin"));
    +			rect_origin = boost::any_cast(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_origin"));
     		}
     		catch (const std::exception &e){
     			return;}
    @@ -257,10 +259,10 @@ void BedShapePanel::update_shape()
     		x1 -= dx;
     		y0 -= dy;
     		y1 -= dy;
    -		m_canvas->m_bed_shape = {	Pointf(x0, y0),
    -									Pointf(x1, y0),
    -									Pointf(x1, y1),
    -									Pointf(x0, y1)};
    +		m_canvas->m_bed_shape = {	Vec2d(x0, y0),
    +									Vec2d(x1, y0),
    +									Vec2d(x1, y1),
    +									Vec2d(x0, y1)};
     	} 
     	else if(page_idx == SHAPE_CIRCULAR) {
     		double diameter;
    @@ -274,10 +276,10 @@ void BedShapePanel::update_shape()
     		auto r = diameter / 2;
     		auto twopi = 2 * PI;
     		auto edges = 60;
    -		std::vector points;
    +		std::vector points;
     		for (size_t i = 1; i <= 60; ++i){
     			auto angle = i * twopi / edges;
    -			points.push_back(Pointf(r*cos(angle), r*sin(angle)));
    +			points.push_back(Vec2d(r*cos(angle), r*sin(angle)));
     		}
     		m_canvas->m_bed_shape = points;
     	}
    @@ -330,9 +332,9 @@ void BedShapePanel::load_stl()
     	}
     
     	auto polygon = expolygons[0].contour;
    -	std::vector points;
    +	std::vector points;
     	for (auto pt : polygon.points)
    -		points.push_back(Pointf::new_unscale(pt));
    +		points.push_back(unscale(pt));
     	m_canvas->m_bed_shape = points;
     	update_preview();
     }
    diff --git a/xs/src/slic3r/GUI/BedShapeDialog.hpp b/xs/src/slic3r/GUI/BedShapeDialog.hpp
    index 5ff4880637..d8ba5a9128 100644
    --- a/xs/src/slic3r/GUI/BedShapeDialog.hpp
    +++ b/xs/src/slic3r/GUI/BedShapeDialog.hpp
    @@ -34,7 +34,7 @@ public:
     	void		load_stl();
     	
     	// Returns the resulting bed shape polygon. This value will be stored to the ini file.
    -	std::vector	GetValue() { return m_canvas->m_bed_shape; }
    +	std::vector	GetValue() { return m_canvas->m_bed_shape; }
     };
     
     class BedShapeDialog : public wxDialog
    @@ -46,7 +46,7 @@ public:
     	~BedShapeDialog(){  }
     
     	void		build_dialog(ConfigOptionPoints* default_pt);
    -	std::vector	GetValue() { return m_panel->GetValue(); }
    +	std::vector	GetValue() { return m_panel->GetValue(); }
     };
     
     } // GUI
    diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp
    index 942b134400..4043a44572 100644
    --- a/xs/src/slic3r/GUI/Field.cpp
    +++ b/xs/src/slic3r/GUI/Field.cpp
    @@ -132,7 +132,11 @@ namespace Slic3r { namespace GUI {
     				break;
     			}
     			double val;
    -			str.ToCDouble(&val);
    +			if(!str.ToCDouble(&val))
    +			{
    +				show_error(m_parent, _(L("Input value contains incorrect symbol(s).\nUse, please, only digits")));
    +				set_value(double_to_string(val), true);
    +			}
     			if (m_opt.min > val || val > m_opt.max)
     			{
     				show_error(m_parent, _(L("Input value is out of range")));
    @@ -274,7 +278,7 @@ void CheckBox::BUILD() {
     
     	bool check_value =	m_opt.type == coBool ? 
     						m_opt.default_value->getBool() : m_opt.type == coBools ? 
    -						static_cast(m_opt.default_value)->get_at(m_opt_idx) : 
    +						static_cast(m_opt.default_value)->get_at(m_opt_idx) : 
         					false;
     
     	auto temp = new wxCheckBox(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size); 
    @@ -330,9 +334,7 @@ void SpinCtrl::BUILD() {
     		break;
     	}
     
    -	const int min_val = m_opt_id == "standby_temperature_delta" ? 
    -						-500 : m_opt.min > 0 ? 
    -						m_opt.min : 0;
    +    const int min_val = m_opt.min == INT_MIN ? 0: m_opt.min;
     	const int max_val = m_opt.max < 2147483647 ? m_opt.max : 2147483647;
     
     	auto temp = new wxSpinCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size,
    @@ -558,8 +560,11 @@ boost::any& Choice::get_value()
     // 	boost::any m_value;
     	wxString ret_str = static_cast(window)->GetValue();	
     
    -	if (m_opt_id == "support")
    -		return m_value = boost::any(ret_str);//ret_str;
    +	// options from right panel
    +	std::vector  right_panel_options{ "support", "scale_unit" };
    +	for (auto rp_option: right_panel_options)
    +		if (m_opt_id == rp_option)
    +			return m_value = boost::any(ret_str);
     
     	if (m_opt.type != coEnum)
     		/*m_value = */get_value_by_opt_type(ret_str);
    @@ -586,6 +591,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;
    @@ -597,7 +604,7 @@ void ColourPicker::BUILD()
     	if (m_opt.height >= 0) size.SetHeight(m_opt.height);
     	if (m_opt.width >= 0) size.SetWidth(m_opt.width);
     
    -	wxString clr(static_cast(m_opt.default_value)->get_at(m_opt_idx));
    +	wxString clr(static_cast(m_opt.default_value)->get_at(m_opt_idx));
     	auto temp = new wxColourPickerCtrl(m_parent, wxID_ANY, clr, wxDefaultPosition, size);
     		
     	// 	// recast as a wxWindow to fit the calling convention
    @@ -629,7 +636,7 @@ void PointCtrl::BUILD()
     	// 
     	wxSize field_size(40, -1);
     
    -	auto default_pt = static_cast(m_opt.default_value)->values.at(0);
    +	auto default_pt = static_cast(m_opt.default_value)->values.at(0);
     	double val = default_pt(0);
     	wxString X = val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None);
     	val = default_pt(1);
    @@ -653,7 +660,7 @@ void PointCtrl::BUILD()
     	y_textctrl->SetToolTip(get_tooltip_text(X+", "+Y));
     }
     
    -void PointCtrl::set_value(const Pointf& value, bool change_event)
    +void PointCtrl::set_value(const Vec2d& value, bool change_event)
     {
     	m_disable_change_event = !change_event;
     
    @@ -667,8 +674,8 @@ void PointCtrl::set_value(const Pointf& value, bool change_event)
     
     void PointCtrl::set_value(const boost::any& value, bool change_event)
     {
    -	Pointf pt;
    -	const Pointf *ptf = boost::any_cast(&value);
    +	Vec2d pt(Vec2d::Zero());
    +	const Vec2d *ptf = boost::any_cast(&value);
     	if (!ptf)
     	{
     		ConfigOptionPoints* pts = boost::any_cast(value);
    @@ -681,13 +688,10 @@ void PointCtrl::set_value(const boost::any& value, bool change_event)
     
     boost::any& PointCtrl::get_value()
     {
    -	Pointf ret_point;
    -	double val;
    -	x_textctrl->GetValue().ToDouble(&val);
    -	ret_point(0) = val;
    -	y_textctrl->GetValue().ToDouble(&val);
    -	ret_point(1) = val;
    -	return m_value = ret_point;
    +	double x, y;
    +	x_textctrl->GetValue().ToDouble(&x);
    +	y_textctrl->GetValue().ToDouble(&y);
    +	return m_value = Vec2d(x, y);
     }
     
     void StaticText::BUILD()
    @@ -696,7 +700,7 @@ void StaticText::BUILD()
     	if (m_opt.height >= 0) size.SetHeight(m_opt.height);
     	if (m_opt.width >= 0) size.SetWidth(m_opt.width);
     
    -	wxString legend(static_cast(m_opt.default_value)->value);
    +	wxString legend(static_cast(m_opt.default_value)->value);
     	auto temp = new wxStaticText(m_parent, wxID_ANY, legend, wxDefaultPosition, size);
     	temp->SetFont(bold_font());
     
    @@ -706,6 +710,69 @@ void StaticText::BUILD()
     	temp->SetToolTip(get_tooltip_text(legend));
     }
     
    +void SliderCtrl::BUILD()
    +{
    +	auto size = wxSize(wxDefaultSize);
    +	if (m_opt.height >= 0) size.SetHeight(m_opt.height);
    +	if (m_opt.width >= 0) size.SetWidth(m_opt.width);
    +
    +	auto temp = new wxBoxSizer(wxHORIZONTAL);
    +
    +	auto def_val = static_cast(m_opt.default_value)->value;
    +	auto min = m_opt.min == INT_MIN ? 0 : m_opt.min;
    +	auto max = m_opt.max == INT_MAX ? 100 : m_opt.max;
    +
    +	m_slider = new wxSlider(m_parent, wxID_ANY, def_val * m_scale,
    +							min * m_scale, max * m_scale,
    +							wxDefaultPosition, size);
    + 	wxSize field_size(40, -1);
    +
    +	m_textctrl = new wxTextCtrl(m_parent, wxID_ANY, wxString::Format("%d", m_slider->GetValue()/m_scale), 
    +								wxDefaultPosition, field_size);
    +
    +	temp->Add(m_slider, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL, 0);
    +	temp->Add(m_textctrl, 0, wxALIGN_CENTER_VERTICAL, 0);
    +
    +	m_slider->Bind(wxEVT_SLIDER, ([this](wxCommandEvent e) {
    +		if (!m_disable_change_event){
    +			int val = boost::any_cast(get_value());
    +			m_textctrl->SetLabel(wxString::Format("%d", val));
    +			on_change_field();
    +		}
    +	}), m_slider->GetId());
    +
    +	m_textctrl->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) {
    +		std::string value = e.GetString().utf8_str().data();
    +		if (is_matched(value, "^-?\\d+(\\.\\d*)?$")){
    +			m_disable_change_event = true;
    +			m_slider->SetValue(stoi(value)*m_scale);
    +			m_disable_change_event = false;
    +			on_change_field();
    +		}
    +	}), m_textctrl->GetId());
    +
    +	m_sizer = dynamic_cast(temp);
    +}
    +
    +void SliderCtrl::set_value(const boost::any& value, bool change_event)
    +{
    +	m_disable_change_event = !change_event;
    +
    +	m_slider->SetValue(boost::any_cast(value)*m_scale);
    +	int val = boost::any_cast(get_value());
    +	m_textctrl->SetLabel(wxString::Format("%d", val));
    +
    +	m_disable_change_event = false;
    +}
    +
    +boost::any& SliderCtrl::get_value()
    +{
    +// 	int ret_val;
    +// 	x_textctrl->GetValue().ToDouble(&val);
    +	return m_value = int(m_slider->GetValue()/m_scale);
    +}
    +
    +
     } // GUI
     } // Slic3r
     
    diff --git a/xs/src/slic3r/GUI/Field.hpp b/xs/src/slic3r/GUI/Field.hpp
    index db8d2a4085..e305f1e530 100644
    --- a/xs/src/slic3r/GUI/Field.hpp
    +++ b/xs/src/slic3r/GUI/Field.hpp
    @@ -38,6 +38,7 @@ wxString double_to_string(double const value);
     
     class MyButton : public wxButton
     {
    +    bool hidden = false; // never show button if it's hidden ones
     public:
     	MyButton() {}
     	MyButton(wxWindow* parent, wxWindowID id, const wxString& label = wxEmptyString,
    @@ -52,6 +53,12 @@ public:
     	// overridden from wxWindow base class
     	virtual bool
     		AcceptsFocusFromKeyboard() const { return false; }
    +
    +    virtual bool Show(bool show = true) override {
    +        if (!show)
    +            hidden = true;
    +        return wxButton::Show(!hidden);
    +	}
     };
     
     class Field {
    @@ -189,6 +196,10 @@ public:
     		return false;
     	}
     
    +	void	set_side_text_ptr(wxStaticText* side_text) {
    +		m_side_text = side_text;
    +    }
    +
     protected:
     	MyButton*			m_Undo_btn = nullptr;
     	// Bitmap and Tooltip text for m_Undo_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one.
    @@ -203,6 +214,8 @@ protected:
     	// Color for Label. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one.
     	const wxColour*		m_label_color = nullptr;
     
    +	wxStaticText*		m_side_text = nullptr;
    +
     	// current value
     	boost::any			m_value;
     
    @@ -214,7 +227,7 @@ protected:
     inline bool is_bad_field(const t_field& obj) { return obj->getSizer() == nullptr && obj->getWindow() == nullptr; }
     
     /// Covenience function to determine whether this field is a valid window field.
    -inline bool is_window_field(const t_field& obj) { return !is_bad_field(obj) && obj->getWindow() != nullptr; }
    +inline bool is_window_field(const t_field& obj) { return !is_bad_field(obj) && obj->getWindow() != nullptr && obj->getSizer() == nullptr; }
     
     /// Covenience function to determine whether this field is a valid sizer field.
     inline bool is_sizer_field(const t_field& obj) { return !is_bad_field(obj) && obj->getSizer() != nullptr; }
    @@ -372,7 +385,7 @@ public:
     
     	void			BUILD()  override;
     
    -	void			set_value(const Pointf& value, bool change_event = false);
    +	void			set_value(const Vec2d& value, bool change_event = false);
     	void			set_value(const boost::any& value, bool change_event = false);
     	boost::any&		get_value() override;
     
    @@ -408,11 +421,44 @@ public:
     
     	boost::any&		get_value()override { return m_value; }
     
    -	void			enable() override { dynamic_cast(window)->Enable(); };
    -	void			disable() override{ dynamic_cast(window)->Disable(); };
    +	void			enable() override { dynamic_cast(window)->Enable(); };
    +	void			disable() override{ dynamic_cast(window)->Disable(); };
     	wxWindow*		getWindow() override { return window; }
     };
     
    +class SliderCtrl : public Field {
    +	using Field::Field;
    +public:
    +	SliderCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
    +	SliderCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
    +	~SliderCtrl() {}
    +
    +	wxSizer*		m_sizer{ nullptr };
    +	wxTextCtrl*		m_textctrl{ nullptr };
    +	wxSlider*		m_slider{ nullptr };
    +
    +	int				m_scale = 10;
    +
    +	void			BUILD()  override;
    +
    +	void			set_value(const int value, bool change_event = false);
    +	void			set_value(const boost::any& value, bool change_event = false);
    +	boost::any&		get_value() override;
    +
    +	void			enable() override {
    +		m_slider->Enable();
    +		m_textctrl->Enable();
    +		m_textctrl->SetEditable(true);
    +	}
    +	void			disable() override{
    +		m_slider->Disable();
    +		m_textctrl->Disable();
    +		m_textctrl->SetEditable(false);
    +	}
    +	wxSizer*		getSizer() override { return m_sizer; }
    +	wxWindow*		getWindow() override { return dynamic_cast(m_slider); }
    +};
    +
     } // GUI
     } // Slic3r
     
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    index d380c4bfc0..14c3bffa72 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    @@ -71,8 +71,8 @@ bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool
         if (generate_tex_coords)
             m_tex_coords = std::vector(t_size, 0.0f);
     
    -    float min_x = (float)unscale(triangles[0].points[0](0));
    -    float min_y = (float)unscale(triangles[0].points[0](1));
    +    float min_x = unscale(triangles[0].points[0](0));
    +    float min_y = unscale(triangles[0].points[0](1));
         float max_x = min_x;
         float max_y = min_y;
     
    @@ -83,8 +83,8 @@ bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool
             for (unsigned int v = 0; v < 3; ++v)
             {
                 const Point& p = t.points[v];
    -            float x = (float)unscale(p(0));
    -            float y = (float)unscale(p(1));
    +            float x = unscale(p(0));
    +            float y = unscale(p(1));
     
                 m_vertices[v_coord++] = x;
                 m_vertices[v_coord++] = y;
    @@ -137,11 +137,11 @@ bool GeometryBuffer::set_from_lines(const Lines& lines, float z)
         unsigned int coord = 0;
         for (const Line& l : lines)
         {
    -        m_vertices[coord++] = (float)unscale(l.a(0));
    -        m_vertices[coord++] = (float)unscale(l.a(1));
    +        m_vertices[coord++] = unscale(l.a(0));
    +        m_vertices[coord++] = unscale(l.a(1));
             m_vertices[coord++] = z;
    -        m_vertices[coord++] = (float)unscale(l.b(0));
    -        m_vertices[coord++] = (float)unscale(l.b(1));
    +        m_vertices[coord++] = unscale(l.b(0));
    +        m_vertices[coord++] = unscale(l.b(1));
             m_vertices[coord++] = z;
         }
     
    @@ -317,7 +317,7 @@ bool GLCanvas3D::Bed::set_shape(const Pointfs& shape)
         _calc_bounding_box();
     
         ExPolygon poly;
    -    for (const Pointf& p : m_shape)
    +    for (const Vec2d& p : m_shape)
         {
             poly.contour.append(Point(scale_(p(0)), scale_(p(1))));
         }
    @@ -373,9 +373,9 @@ void GLCanvas3D::Bed::render(float theta) const
     void GLCanvas3D::Bed::_calc_bounding_box()
     {
         m_bounding_box = BoundingBoxf3();
    -    for (const Pointf& p : m_shape)
    +    for (const Vec2d& p : m_shape)
         {
    -        m_bounding_box.merge(Pointf3(p(0), p(1), 0.0));
    +        m_bounding_box.merge(Vec3d(p(0), p(1), 0.0));
         }
     }
     
    @@ -507,6 +507,7 @@ void GLCanvas3D::Bed::_render_prusa(float theta) const
         if (triangles_vcount > 0)
         {
             ::glEnable(GL_DEPTH_TEST);
    +        ::glDepthMask(GL_FALSE);
     
             ::glEnable(GL_BLEND);
             ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    @@ -535,6 +536,7 @@ void GLCanvas3D::Bed::_render_prusa(float theta) const
             ::glDisable(GL_TEXTURE_2D);
     
             ::glDisable(GL_BLEND);
    +        ::glDepthMask(GL_TRUE);
         }
     }
     
    @@ -591,7 +593,7 @@ bool GLCanvas3D::Bed::_are_equal(const Pointfs& bed_1, const Pointfs& bed_2)
     }
     
     GLCanvas3D::Axes::Axes()
    -    : length(0.0f)
    +    : origin(0, 0, 0), length(0.0f)
     {
     }
     
    @@ -1040,7 +1042,7 @@ void GLCanvas3D::LayersEditing::_render_profile(const PrintObject& print_object,
         // Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region.
         layer_height_max *= 1.12;
     
    -    coordf_t max_z = unscale(print_object.size(2));
    +    double max_z = unscale(print_object.size(2));
         double layer_height = dynamic_cast(print_object.config.option("layer_height"))->value;
         float l = bar_rect.get_left();
         float w = bar_rect.get_right() - l;
    @@ -1062,7 +1064,7 @@ void GLCanvas3D::LayersEditing::_render_profile(const PrintObject& print_object,
         const ModelObject* model_object = print_object.model_object();
         if (model_object->layer_height_profile_valid)
         {
    -        const std::vector& profile = model_object->layer_height_profile;
    +        const std::vector& profile = model_object->layer_height_profile;
     
             ::glColor3f(0.0f, 0.0f, 1.0f);
             ::glBegin(GL_LINE_STRIP);
    @@ -1075,11 +1077,12 @@ void GLCanvas3D::LayersEditing::_render_profile(const PrintObject& print_object,
     }
     
     const Point GLCanvas3D::Mouse::Drag::Invalid_2D_Point(INT_MAX, INT_MAX);
    -const Pointf3 GLCanvas3D::Mouse::Drag::Invalid_3D_Point(DBL_MAX, DBL_MAX, DBL_MAX);
    +const Vec3d GLCanvas3D::Mouse::Drag::Invalid_3D_Point(DBL_MAX, DBL_MAX, DBL_MAX);
     
     GLCanvas3D::Mouse::Drag::Drag()
         : start_position_2D(Invalid_2D_Point)
         , start_position_3D(Invalid_3D_Point)
    +    , volume_center_offset(0, 0, 0)
         , move_with_shift(false)
         , move_volume_idx(-1)
         , gizmo_volume_idx(-1)
    @@ -1128,9 +1131,13 @@ GLCanvas3D::Gizmos::~Gizmos()
         _reset();
     }
     
    -bool GLCanvas3D::Gizmos::init()
    +bool GLCanvas3D::Gizmos::init(GLCanvas3D& parent)
     {
    -    GLGizmoBase* gizmo = new GLGizmoScale;
    +#if ENABLE_GIZMOS_3D
    +    GLGizmoBase* gizmo = new GLGizmoScale3D(parent);
    +#else
    +    GLGizmoBase* gizmo = new GLGizmoScale(parent);
    +#endif // ENABLE_GIZMOS_3D
         if (gizmo == nullptr)
             return false;
     
    @@ -1139,7 +1146,11 @@ bool GLCanvas3D::Gizmos::init()
     
         m_gizmos.insert(GizmosMap::value_type(Scale, gizmo));
     
    -    gizmo = new GLGizmoRotate;
    +#if ENABLE_GIZMOS_3D
    +    gizmo = new GLGizmoRotate3D(parent);
    +#else
    +    gizmo = new GLGizmoRotate(parent, GLGizmoRotate::Z);
    +#endif // ENABLE_GIZMOS_3D
         if (gizmo == nullptr)
         {
             _reset();
    @@ -1167,7 +1178,7 @@ void GLCanvas3D::Gizmos::set_enabled(bool enable)
         m_enabled = enable;
     }
     
    -void GLCanvas3D::Gizmos::update_hover_state(const GLCanvas3D& canvas, const Pointf& mouse_pos)
    +void GLCanvas3D::Gizmos::update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos)
     {
         if (!m_enabled)
             return;
    @@ -1186,14 +1197,14 @@ void GLCanvas3D::Gizmos::update_hover_state(const GLCanvas3D& canvas, const Poin
             // we currently use circular icons for gizmo, so we check the radius
             if (it->second->get_state() != GLGizmoBase::On)
             {
    -            bool inside = (mouse_pos - Pointf(OverlayOffsetX + half_tex_size, top_y + half_tex_size)).norm() < half_tex_size;
    +            bool inside = (mouse_pos - Vec2d(OverlayOffsetX + half_tex_size, top_y + half_tex_size)).norm() < half_tex_size;
                 it->second->set_state(inside ? GLGizmoBase::Hover : GLGizmoBase::Off);
             }
             top_y += (tex_size + OverlayGapY);
         }
     }
     
    -void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Pointf& mouse_pos)
    +void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos)
     {
         if (!m_enabled)
             return;
    @@ -1210,7 +1221,7 @@ void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Poi
             float half_tex_size = 0.5f * tex_size;
     
             // we currently use circular icons for gizmo, so we check the radius
    -        if ((mouse_pos - Pointf(OverlayOffsetX + half_tex_size, top_y + half_tex_size)).norm() < half_tex_size)
    +        if ((mouse_pos - Vec2d(OverlayOffsetX + half_tex_size, top_y + half_tex_size)).norm() < half_tex_size)
             {
                 if ((it->second->get_state() == GLGizmoBase::On))
                 {
    @@ -1259,7 +1270,7 @@ void GLCanvas3D::Gizmos::set_hover_id(int id)
         }
     }
     
    -bool GLCanvas3D::Gizmos::overlay_contains_mouse(const GLCanvas3D& canvas, const Pointf& mouse_pos) const
    +bool GLCanvas3D::Gizmos::overlay_contains_mouse(const GLCanvas3D& canvas, const Vec2d& mouse_pos) const
     {
         if (!m_enabled)
             return false;
    @@ -1276,7 +1287,7 @@ bool GLCanvas3D::Gizmos::overlay_contains_mouse(const GLCanvas3D& canvas, const
             float half_tex_size = 0.5f * tex_size;
     
             // we currently use circular icons for gizmo, so we check the radius
    -        if ((mouse_pos - Pointf(OverlayOffsetX + half_tex_size, top_y + half_tex_size)).norm() < half_tex_size)
    +        if ((mouse_pos - Vec2d(OverlayOffsetX + half_tex_size, top_y + half_tex_size)).norm() < half_tex_size)
                 return true;
     
             top_y += (tex_size + OverlayGapY);
    @@ -1294,14 +1305,14 @@ bool GLCanvas3D::Gizmos::grabber_contains_mouse() const
         return (curr != nullptr) ? (curr->get_hover_id() != -1) : false;
     }
     
    -void GLCanvas3D::Gizmos::update(const Pointf& mouse_pos)
    +void GLCanvas3D::Gizmos::update(const Linef3& mouse_ray)
     {
         if (!m_enabled)
             return;
     
         GLGizmoBase* curr = _get_current();
         if (curr != nullptr)
    -        curr->update(mouse_pos);
    +        curr->update(mouse_ray);
     }
     
     void GLCanvas3D::Gizmos::refresh()
    @@ -1355,7 +1366,11 @@ float GLCanvas3D::Gizmos::get_scale() const
             return 1.0f;
     
         GizmosMap::const_iterator it = m_gizmos.find(Scale);
    +#if ENABLE_GIZMOS_3D
    +    return (it != m_gizmos.end()) ? reinterpret_cast(it->second)->get_scale_x() : 1.0f;
    +#else
         return (it != m_gizmos.end()) ? reinterpret_cast(it->second)->get_scale() : 1.0f;
    +#endif // ENABLE_GIZMOS_3D
     }
     
     void GLCanvas3D::Gizmos::set_scale(float scale)
    @@ -1365,7 +1380,11 @@ void GLCanvas3D::Gizmos::set_scale(float scale)
     
         GizmosMap::const_iterator it = m_gizmos.find(Scale);
         if (it != m_gizmos.end())
    +#if ENABLE_GIZMOS_3D
    +        reinterpret_cast(it->second)->set_scale(scale);
    +#else
             reinterpret_cast(it->second)->set_scale(scale);
    +#endif // ENABLE_GIZMOS_3D
     }
     
     float GLCanvas3D::Gizmos::get_angle_z() const
    @@ -1374,7 +1393,11 @@ float GLCanvas3D::Gizmos::get_angle_z() const
             return 0.0f;
     
         GizmosMap::const_iterator it = m_gizmos.find(Rotate);
    -    return (it != m_gizmos.end()) ? reinterpret_cast(it->second)->get_angle_z() : 0.0f;
    +#if ENABLE_GIZMOS_3D
    +    return (it != m_gizmos.end()) ? reinterpret_cast(it->second)->get_angle_z() : 0.0f;
    +#else
    +    return (it != m_gizmos.end()) ? reinterpret_cast(it->second)->get_angle() : 0.0f;
    +#endif // ENABLE_GIZMOS_3D
     }
     
     void GLCanvas3D::Gizmos::set_angle_z(float angle_z)
    @@ -1384,25 +1407,20 @@ void GLCanvas3D::Gizmos::set_angle_z(float angle_z)
     
         GizmosMap::const_iterator it = m_gizmos.find(Rotate);
         if (it != m_gizmos.end())
    -        reinterpret_cast(it->second)->set_angle_z(angle_z);
    +#if ENABLE_GIZMOS_3D
    +reinterpret_cast(it->second)->set_angle_z(angle_z);
    +#else
    +        reinterpret_cast(it->second)->set_angle(angle_z);
    +#endif // ENABLE_GIZMOS_3D
     }
     
    -void GLCanvas3D::Gizmos::render(const GLCanvas3D& canvas, const BoundingBoxf3& box) const
    +void GLCanvas3D::Gizmos::render_current_gizmo(const BoundingBoxf3& box) const
     {
         if (!m_enabled)
             return;
     
    -    ::glDisable(GL_DEPTH_TEST);
    -
         if (box.radius() > 0.0)
             _render_current_gizmo(box);
    -
    -    ::glPushMatrix();
    -    ::glLoadIdentity();
    -
    -    _render_overlay(canvas);
    -
    -    ::glPopMatrix();
     }
     
     void GLCanvas3D::Gizmos::render_current_gizmo_for_picking_pass(const BoundingBoxf3& box) const
    @@ -1410,13 +1428,26 @@ void GLCanvas3D::Gizmos::render_current_gizmo_for_picking_pass(const BoundingBox
         if (!m_enabled)
             return;
     
    -    ::glDisable(GL_DEPTH_TEST);
    -
         GLGizmoBase* curr = _get_current();
         if (curr != nullptr)
             curr->render_for_picking(box);
     }
     
    +void GLCanvas3D::Gizmos::render_overlay(const GLCanvas3D& canvas) const
    +{
    +    if (!m_enabled)
    +        return;
    +
    +    ::glDisable(GL_DEPTH_TEST);
    +
    +    ::glPushMatrix();
    +    ::glLoadIdentity();
    +
    +    _render_overlay(canvas);
    +
    +    ::glPopMatrix();
    +}
    +
     void GLCanvas3D::Gizmos::_reset()
     {
         for (GizmosMap::value_type& gizmo : m_gizmos)
    @@ -1473,6 +1504,13 @@ float GLCanvas3D::Gizmos::_get_total_overlay_height() const
     const unsigned char GLCanvas3D::WarningTexture::Background_Color[3] = { 9, 91, 134 };
     const unsigned char GLCanvas3D::WarningTexture::Opacity = 255;
     
    +GLCanvas3D::WarningTexture::WarningTexture()
    +    : GUI::GLTexture()
    +    , m_original_width(0)
    +    , m_original_height(0)
    +{
    +}
    +
     bool GLCanvas3D::WarningTexture::generate(const std::string& msg)
     {
         reset();
    @@ -1482,13 +1520,21 @@ bool GLCanvas3D::WarningTexture::generate(const std::string& msg)
     
         wxMemoryDC memDC;
         // select default font
    -    memDC.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
    +    wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
    +    font.MakeLarger();
    +    font.MakeBold();
    +    memDC.SetFont(font);
     
         // calculates texture size
         wxCoord w, h;
         memDC.GetTextExtent(msg, &w, &h);
    -    m_width = (int)w;
    -    m_height = (int)h;
    +
    +    int pow_of_two_size = next_highest_power_of_2((int)std::max(w, h));
    +
    +    m_original_width = (int)w;
    +    m_original_height = (int)h;
    +    m_width = pow_of_two_size;
    +    m_height = pow_of_two_size;
     
         // generates bitmap
         wxBitmap bitmap(m_width, m_height);
    @@ -1540,10 +1586,51 @@ bool GLCanvas3D::WarningTexture::generate(const std::string& msg)
         return true;
     }
     
    +void GLCanvas3D::WarningTexture::render(const GLCanvas3D& canvas) const
    +{
    +    if ((m_id > 0) && (m_original_width > 0) && (m_original_height > 0) && (m_width > 0) && (m_height > 0))
    +    {
    +        ::glDisable(GL_DEPTH_TEST);
    +        ::glPushMatrix();
    +        ::glLoadIdentity();
    +
    +        const Size& cnv_size = canvas.get_canvas_size();
    +        float zoom = canvas.get_camera_zoom();
    +        float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
    +        float left = (-0.5f * (float)m_original_width) * inv_zoom;
    +        float top = (-0.5f * (float)cnv_size.get_height() + (float)m_original_height + 2.0f) * inv_zoom;
    +        float right = left + (float)m_original_width * inv_zoom;
    +        float bottom = top - (float)m_original_height * inv_zoom;
    +
    +        float uv_left = 0.0f;
    +        float uv_top = 0.0f;
    +        float uv_right = (float)m_original_width / (float)m_width;
    +        float uv_bottom = (float)m_original_height / (float)m_height;
    +
    +        GLTexture::Quad_UVs uvs;
    +        uvs.left_top = { uv_left, uv_top };
    +        uvs.left_bottom = { uv_left, uv_bottom };
    +        uvs.right_bottom = { uv_right, uv_bottom };
    +        uvs.right_top = { uv_right, uv_top };
    +
    +        GLTexture::render_sub_texture(m_id, left, right, bottom, top, uvs);
    +
    +        ::glPopMatrix();
    +        ::glEnable(GL_DEPTH_TEST);
    +    }
    +}
    +
     const unsigned char GLCanvas3D::LegendTexture::Squares_Border_Color[3] = { 64, 64, 64 };
     const unsigned char GLCanvas3D::LegendTexture::Background_Color[3] = { 9, 91, 134 };
     const unsigned char GLCanvas3D::LegendTexture::Opacity = 255;
     
    +GLCanvas3D::LegendTexture::LegendTexture()
    +    : GUI::GLTexture()
    +    , m_original_width(0)
    +    , m_original_height(0)
    +{
    +}
    +
     bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, const std::vector& tool_colors)
     {
         reset();
    @@ -1576,10 +1663,15 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c
             max_text_height = std::max(max_text_height, (int)h);
         }
     
    -    m_width = std::max(2 * Px_Border + title_width, 2 * (Px_Border + Px_Square_Contour) + Px_Square + Px_Text_Offset + max_text_width);
    -    m_height = 2 * (Px_Border + Px_Square_Contour) + title_height + Px_Title_Offset + items_count * Px_Square;
    +    m_original_width = std::max(2 * Px_Border + title_width, 2 * (Px_Border + Px_Square_Contour) + Px_Square + Px_Text_Offset + max_text_width);
    +    m_original_height = 2 * (Px_Border + Px_Square_Contour) + title_height + Px_Title_Offset + items_count * Px_Square;
         if (items_count > 1)
    -        m_height += (items_count - 1) * Px_Square_Contour;
    +        m_original_height += (items_count - 1) * Px_Square_Contour;
    +
    +    int pow_of_two_size = next_highest_power_of_2(std::max(m_original_width, m_original_height));
    +
    +    m_width = pow_of_two_size;
    +    m_height = pow_of_two_size;
     
         // generates bitmap
         wxBitmap bitmap(m_width, m_height);
    @@ -1688,6 +1780,40 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c
         return true;
     }
     
    +void GLCanvas3D::LegendTexture::render(const GLCanvas3D& canvas) const
    +{
    +    if ((m_id > 0) && (m_original_width > 0) && (m_original_height > 0) && (m_width > 0) && (m_height > 0))
    +    {
    +        ::glDisable(GL_DEPTH_TEST);
    +        ::glPushMatrix();
    +        ::glLoadIdentity();
    +
    +        const Size& cnv_size = canvas.get_canvas_size();
    +        float zoom = canvas.get_camera_zoom();
    +        float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
    +        float left = (-0.5f * (float)cnv_size.get_width()) * inv_zoom;
    +        float top = (0.5f * (float)cnv_size.get_height()) * inv_zoom;
    +        float right = left + (float)m_original_width * inv_zoom;
    +        float bottom = top - (float)m_original_height * inv_zoom;
    +
    +        float uv_left = 0.0f;
    +        float uv_top = 0.0f;
    +        float uv_right = (float)m_original_width / (float)m_width;
    +        float uv_bottom = (float)m_original_height / (float)m_height;
    +
    +        GLTexture::Quad_UVs uvs;
    +        uvs.left_top = { uv_left, uv_top };
    +        uvs.left_bottom = { uv_left, uv_bottom };
    +        uvs.right_bottom = { uv_right, uv_bottom };
    +        uvs.right_top = { uv_right, uv_top };
    +
    +        GLTexture::render_sub_texture(m_id, left, right, bottom, top, uvs);
    +
    +        ::glPopMatrix();
    +        ::glEnable(GL_DEPTH_TEST);
    +    }
    +}
    +
     GLGizmoBase* GLCanvas3D::Gizmos::_get_current() const
     {
         GizmosMap::const_iterator it = m_gizmos.find(m_current);
    @@ -1698,6 +1824,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas)
         : m_canvas(canvas)
         , m_context(nullptr)
         , m_timer(nullptr)
    +    , m_toolbar(*this)
         , m_config(nullptr)
         , m_print(nullptr)
         , m_model(nullptr)
    @@ -1707,6 +1834,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas)
         , m_force_zoom_to_bed_enabled(false)
         , m_apply_zoom_to_volumes_filter(false)
         , m_hover_volume_id(-1)
    +    , m_toolbar_action_running(false)
         , m_warning_texture_enabled(false)
         , m_legend_texture_enabled(false)
         , m_picking_enabled(false)
    @@ -1810,7 +1938,10 @@ bool GLCanvas3D::init(bool useVBOs, bool use_legacy_opengl)
         if (!m_volumes.empty())
             m_volumes.finalize_geometry(m_use_VBOs);
     
    -    if (m_gizmos.is_enabled() && !m_gizmos.init())
    +    if (m_gizmos.is_enabled() && !m_gizmos.init(*this))
    +        return false;
    +
    +    if (!_init_toolbar())
             return false;
     
         m_initialized = true;
    @@ -1950,7 +2081,7 @@ void GLCanvas3D::set_bed_shape(const Pointfs& 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);
    +    m_axes.origin = Vec3d(0.0, 0.0, (double)GROUND_Z);
         set_axes_length(0.3f * (float)m_bed.get_bounding_box().max_size());
     
         if (new_shape)
    @@ -1969,8 +2100,8 @@ void GLCanvas3D::set_auto_bed_shape()
     {
         // draw a default square bed around object center
         const BoundingBoxf3& bbox = volumes_bounding_box();
    -    coordf_t max_size = bbox.max_size();
    -    const Pointf3 center = bbox.center();
    +    double max_size = bbox.max_size();
    +    const Vec3d center = bbox.center();
     
         Pointfs bed_shape;
         bed_shape.reserve(4);
    @@ -1982,7 +2113,7 @@ void GLCanvas3D::set_auto_bed_shape()
         set_bed_shape(bed_shape);
     
         // Set the origin for painting of the coordinate system axes.
    -    m_axes.origin = Pointf3(center(0), center(1), (coordf_t)GROUND_Z);
    +    m_axes.origin = Vec3d(center(0), center(1), (double)GROUND_Z);
     }
     
     void GLCanvas3D::set_axes_length(float length)
    @@ -2076,6 +2207,11 @@ void GLCanvas3D::enable_gizmos(bool enable)
         m_gizmos.set_enabled(enable);
     }
     
    +void GLCanvas3D::enable_toolbar(bool enable)
    +{
    +    m_toolbar.set_enabled(enable);
    +}
    +
     void GLCanvas3D::enable_shader(bool enable)
     {
         m_shader_enabled = enable;
    @@ -2096,6 +2232,19 @@ void GLCanvas3D::allow_multisample(bool allow)
         m_multisample_allowed = allow;
     }
     
    +void GLCanvas3D::enable_toolbar_item(const std::string& name, bool enable)
    +{
    +    if (enable)
    +        m_toolbar.enable_item(name);
    +    else
    +        m_toolbar.disable_item(name);
    +}
    +
    +bool GLCanvas3D::is_toolbar_item_pressed(const std::string& name) const
    +{
    +    return m_toolbar.is_item_pressed(name);
    +}
    +
     void GLCanvas3D::zoom_to_bed()
     {
         _zoom_to_bounding_box(m_bed.get_bounding_box());
    @@ -2205,26 +2354,34 @@ void GLCanvas3D::render()
         float theta = m_camera.get_theta();
         bool is_custom_bed = m_bed.is_custom();
     
    +    // picking pass
         _picking_pass();
    +
    +    // draw scene
         _render_background();
    -    // untextured bed needs to be rendered before objects
    -    if (is_custom_bed)
    +
    +    if (is_custom_bed) // untextured bed needs to be rendered before objects
         {
             _render_bed(theta);
             // disable depth testing so that axes are not covered by ground
             _render_axes(false);
         }
         _render_objects();
    -    // textured bed needs to be rendered after objects
    -    if (!is_custom_bed)
    +
    +    if (!is_custom_bed) // textured bed needs to be rendered after objects
         {
             _render_axes(true);
             _render_bed(theta);
         }
    +
    +    _render_current_gizmo();
         _render_cutting_plane();
    +
    +    // draw overlays
    +    _render_gizmos_overlay();
         _render_warning_texture();
         _render_legend_texture();
    -    _render_gizmo();
    +    _render_toolbar();
         _render_layer_editing_overlay();
     
         m_canvas->SwapBuffers();
    @@ -2311,7 +2468,7 @@ void GLCanvas3D::reload_scene(bool force)
             if ((extruders_count > 1) && semm && wt && !co)
             {
                 // Height of a print (Show at least a slab)
    -            coordf_t height = std::max(m_model->bounding_box().max(2), 10.0);
    +            double height = std::max(m_model->bounding_box().max(2), 10.0);
     
                 float x = dynamic_cast(m_config->option("wipe_tower_x"))->value;
                 float y = dynamic_cast(m_config->option("wipe_tower_y"))->value;
    @@ -2527,6 +2684,66 @@ void GLCanvas3D::register_on_update_geometry_info_callback(void* callback)
             m_on_update_geometry_info_callback.register_callback(callback);
     }
     
    +void GLCanvas3D::register_action_add_callback(void* callback)
    +{
    +    if (callback != nullptr)
    +        m_action_add_callback.register_callback(callback);
    +}
    +
    +void GLCanvas3D::register_action_delete_callback(void* callback)
    +{
    +    if (callback != nullptr)
    +        m_action_delete_callback.register_callback(callback);
    +}
    +
    +void GLCanvas3D::register_action_deleteall_callback(void* callback)
    +{
    +    if (callback != nullptr)
    +        m_action_deleteall_callback.register_callback(callback);
    +}
    +
    +void GLCanvas3D::register_action_arrange_callback(void* callback)
    +{
    +    if (callback != nullptr)
    +        m_action_arrange_callback.register_callback(callback);
    +}
    +
    +void GLCanvas3D::register_action_more_callback(void* callback)
    +{
    +    if (callback != nullptr)
    +        m_action_more_callback.register_callback(callback);
    +}
    +
    +void GLCanvas3D::register_action_fewer_callback(void* callback)
    +{
    +    if (callback != nullptr)
    +        m_action_fewer_callback.register_callback(callback);
    +}
    +
    +void GLCanvas3D::register_action_split_callback(void* callback)
    +{
    +    if (callback != nullptr)
    +        m_action_split_callback.register_callback(callback);
    +}
    +
    +void GLCanvas3D::register_action_cut_callback(void* callback)
    +{
    +    if (callback != nullptr)
    +        m_action_cut_callback.register_callback(callback);
    +}
    +
    +void GLCanvas3D::register_action_settings_callback(void* callback)
    +{
    +    if (callback != nullptr)
    +        m_action_settings_callback.register_callback(callback);
    +}
    +
    +void GLCanvas3D::register_action_layersediting_callback(void* callback)
    +{
    +    if (callback != nullptr)
    +        m_action_layersediting_callback.register_callback(callback);
    +}
    +
     void GLCanvas3D::bind_event_handlers()
     {
         if (m_canvas != nullptr)
    @@ -2704,6 +2921,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
         int layer_editing_object_idx = is_layers_editing_enabled() ? selected_object_idx : -1;
         m_layers_editing.last_object_id = layer_editing_object_idx;
         bool gizmos_overlay_contains_mouse = m_gizmos.overlay_contains_mouse(*this, m_mouse.position);
    +    int toolbar_contains_mouse = m_toolbar.contains_mouse(m_mouse.position);
     
         if (evt.Entering())
         {
    @@ -2714,15 +2932,20 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
     
             m_mouse.set_start_position_2D_as_invalid();
     #endif
    -    } 
    +    }
         else if (evt.Leaving())
         {
             // to remove hover on objects when the mouse goes out of this canvas
    -        m_mouse.position = Pointf(-1.0, -1.0);
    +        m_mouse.position = Vec2d(-1.0, -1.0);
             m_dirty = true;
         }
         else if (evt.LeftDClick() && (m_hover_volume_id != -1))
             m_on_double_click_callback.call();
    +    else if (evt.LeftDClick() && (toolbar_contains_mouse != -1))
    +    {
    +        m_toolbar_action_running = true;
    +        m_toolbar.do_action((unsigned int)toolbar_contains_mouse);
    +    }
         else if (evt.LeftDown() || evt.RightDown())
         {
             // If user pressed left or right button we first check whether this happened
    @@ -2761,6 +2984,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                 m_mouse.drag.gizmo_volume_idx = _get_first_selected_volume_id(selected_object_idx);
                 m_dirty = true;
             }
    +        else if (toolbar_contains_mouse != -1)
    +        {
    +            m_toolbar_action_running = true;
    +            m_toolbar.do_action((unsigned int)toolbar_contains_mouse);
    +        }
             else
             {
                 // Select volume in this 3D canvas.
    @@ -2799,7 +3027,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                     {
                         // The mouse_to_3d gets the Z coordinate from the Z buffer at the screen coordinate pos x, y,
                         // an converts the screen space coordinate to unscaled object space.
    -                    Pointf3 pos3d = (volume_idx == -1) ? Pointf3(DBL_MAX, DBL_MAX, DBL_MAX) : _mouse_to_3d(pos);
    +                    Vec3d pos3d = (volume_idx == -1) ? Vec3d(DBL_MAX, DBL_MAX, DBL_MAX) : _mouse_to_3d(pos);
     
                         // Only accept the initial position, if it is inside the volume bounding box.
                         BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box();
    @@ -2817,9 +3045,16 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                     }
                     else if (evt.RightDown())
                     {
    -                    // if right clicking on volume, propagate event through callback
    -                    if (m_volumes.volumes[volume_idx]->hover)
    -                        m_on_right_click_callback.call(pos(0), pos(1));
    +                    // forces a frame render to ensure that m_hover_volume_id is updated even when the user right clicks while
    +                    // the context menu is already shown, ensuring it to disappear if the mouse is outside any volume
    +                    m_mouse.position = Vec2d((double)pos(0), (double)pos(1));
    +                    render();
    +                    if (m_hover_volume_id != -1)
    +                    {
    +                        // if right clicking on volume, propagate event through callback (shows context menu)
    +                        if (m_volumes.volumes[volume_idx]->hover)
    +                            m_on_right_click_callback.call(pos(0), pos(1));
    +                    }
                     }
                 }
             }
    @@ -2831,7 +3066,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
             // Get new position at the same Z of the initial click point.
             float z0 = 0.0f;
             float z1 = 1.0f;
    -        Pointf3 cur_pos = Linef3(_mouse_to_3d(pos, &z0), _mouse_to_3d(pos, &z1)).intersect_plane(m_mouse.drag.start_position_3D(2));
    +        Vec3d cur_pos = Linef3(_mouse_to_3d(pos, &z0), _mouse_to_3d(pos, &z1)).intersect_plane(m_mouse.drag.start_position_3D(2));
     
             // Clip the new position, so the object center remains close to the bed.
             cur_pos += m_mouse.drag.volume_center_offset;
    @@ -2839,13 +3074,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
             if (!m_bed.contains(cur_pos2))
             {
                 Point ip = m_bed.point_projection(cur_pos2);
    -            cur_pos(0) = unscale(ip(0));
    -            cur_pos(1) = unscale(ip(1));
    +            cur_pos(0) = unscale(ip(0));
    +            cur_pos(1) = unscale(ip(1));
             }
             cur_pos -= m_mouse.drag.volume_center_offset;
     
             // Calculate the translation vector.
    -        Vectorf3 vector = cur_pos - m_mouse.drag.start_position_3D;
    +        Vec3d vector = cur_pos - m_mouse.drag.start_position_3D;
             // Get the volume being dragged.
             GLVolume* volume = m_volumes.volumes[m_mouse.drag.move_volume_idx];
             // Get all volumes belonging to the same group, if any.
    @@ -2867,7 +3102,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
     
             // Apply new temporary volume origin and ignore Z.
             for (GLVolume* v : volumes)
    -            v->set_origin(v->get_origin() + Vectorf3(vector(0), vector(1), 0.0));
    +            v->set_origin(v->get_origin() + Vec3d(vector(0), vector(1), 0.0));
     
             m_mouse.drag.start_position_3D = cur_pos;
             m_gizmos.refresh();
    @@ -2876,10 +3111,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
         }
         else if (evt.Dragging() && m_gizmos.is_dragging())
         {
    -        m_mouse.dragging = true;
    +        m_canvas->CaptureMouse();
     
    -        const Pointf3& cur_pos = _mouse_to_bed_3d(pos);
    -        m_gizmos.update(Pointf(cur_pos(0), cur_pos(1)));
    +        m_mouse.dragging = true;
    +        m_gizmos.update(mouse_ray(pos));
     
             std::vector volumes;
             if (m_mouse.drag.gizmo_volume_idx != -1)
    @@ -2931,8 +3166,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                 {
                     bb.merge(volume->transformed_bounding_box());
                 }
    -            const Pointf3& size = bb.size();
    +            const Vec3d& size = bb.size();
                 m_on_update_geometry_info_callback.call(size(0), size(1), size(2), m_gizmos.get_scale());
    +            update_scale_values(size, m_gizmos.get_scale());
    +            update_rotation_value(volumes[0]->get_angle_z(), "z");
             }
     
             if ((m_gizmos.get_current_type() != Gizmos::Rotate) && (volumes.size() > 1))
    @@ -2954,7 +3191,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                 // if dragging over blank area with left button, rotate
                 if (m_mouse.is_start_position_3D_defined())
                 {
    -                const Pointf3& orig = m_mouse.drag.start_position_3D;
    +                const Vec3d& orig = m_mouse.drag.start_position_3D;
                     m_camera.phi += (((float)pos(0) - (float)orig(0)) * TRACKBALLSIZE);
                     m_camera.set_theta(m_camera.get_theta() - ((float)pos(1) - (float)orig(1)) * TRACKBALLSIZE);
     
    @@ -2962,7 +3199,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
     
                     m_dirty = true;
                 }
    -            m_mouse.drag.start_position_3D = Pointf3((coordf_t)pos(0), (coordf_t)pos(1), 0.0);
    +            m_mouse.drag.start_position_3D = Vec3d((double)pos(0), (double)pos(1), 0.0);
             }
             else if (evt.MiddleIsDown() || evt.RightIsDown())
             {
    @@ -2971,8 +3208,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                 {
                     // get point in model space at Z = 0
                     float z = 0.0f;
    -                const Pointf3& cur_pos = _mouse_to_3d(pos, &z);
    -                Pointf3 orig = _mouse_to_3d(m_mouse.drag.start_position_2D, &z);
    +                const Vec3d& cur_pos = _mouse_to_3d(pos, &z);
    +                Vec3d orig = _mouse_to_3d(m_mouse.drag.start_position_2D, &z);
                     m_camera.target += orig - cur_pos;
     
                     m_on_viewport_changed_callback.call();
    @@ -3011,11 +3248,15 @@ 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())
    +        else if (evt.LeftUp() && !m_mouse.dragging && (m_hover_volume_id == -1) && !gizmos_overlay_contains_mouse && !m_gizmos.is_dragging() && !is_layers_editing_enabled())
             {
                 // deselect and propagate event through callback
    -            if (m_picking_enabled)
    +            if (m_picking_enabled && !m_toolbar_action_running)
                 {
                     deselect_volumes();
                     _on_select(-1);
    @@ -3029,6 +3270,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                 case Gizmos::Scale:
                 {
                     m_on_gizmo_scale_uniformly_callback.call((double)m_gizmos.get_scale());
    +                Slic3r::GUI::update_settings_value();
                     break;
                 }
                 case Gizmos::Rotate:
    @@ -3047,11 +3289,15 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
             m_mouse.set_start_position_3D_as_invalid();
             m_mouse.set_start_position_2D_as_invalid();
             m_mouse.dragging = false;
    +        m_toolbar_action_running = false;
             m_dirty = true;
    +
    +        if (m_canvas->HasCapture())
    +            m_canvas->ReleaseMouse();
         }
         else if (evt.Moving())
         {
    -        m_mouse.position = Pointf((coordf_t)pos(0), (coordf_t)pos(1));
    +        m_mouse.position = Vec2d((double)pos(0), (double)pos(1));
             // Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor hovers over.
             if (m_picking_enabled)
                 m_dirty = true;
    @@ -3113,6 +3359,12 @@ void GLCanvas3D::reset_legend_texture()
         m_legend_texture.reset();
     }
     
    +void GLCanvas3D::set_tooltip(const std::string& tooltip)
    +{
    +    if (m_canvas != nullptr)
    +        m_canvas->SetToolTip(tooltip);
    +}
    +
     bool GLCanvas3D::_is_shown_on_screen() const
     {
         return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false;
    @@ -3124,6 +3376,119 @@ void GLCanvas3D::_force_zoom_to_bed()
         m_force_zoom_to_bed_enabled = false;
     }
     
    +bool GLCanvas3D::_init_toolbar()
    +{
    +    if (!m_toolbar.is_enabled())
    +        return true;
    +
    +    if (!m_toolbar.init("toolbar.png", 36, 1, 1))
    +    {
    +        // unable to init the toolbar texture, disable it
    +        m_toolbar.set_enabled(false);
    +        return true;
    +    }
    +
    +//    m_toolbar.set_layout_type(GLToolbar::Layout::Vertical);
    +    m_toolbar.set_layout_type(GLToolbar::Layout::Horizontal);
    +    m_toolbar.set_separator_size(5);
    +    m_toolbar.set_gap_size(2);
    +
    +    GLToolbarItem::Data item;
    +
    +    item.name = "add";
    +    item.tooltip = GUI::L_str("Add...");
    +    item.sprite_id = 0;
    +    item.is_toggable = false;
    +    item.action_callback = &m_action_add_callback;
    +    if (!m_toolbar.add_item(item))
    +        return false;
    +
    +    item.name = "delete";
    +    item.tooltip = GUI::L_str("Delete");
    +    item.sprite_id = 1;
    +    item.is_toggable = false;
    +    item.action_callback = &m_action_delete_callback;
    +    if (!m_toolbar.add_item(item))
    +        return false;
    +
    +    item.name = "deleteall";
    +    item.tooltip = GUI::L_str("Delete all");
    +    item.sprite_id = 2;
    +    item.is_toggable = false;
    +    item.action_callback = &m_action_deleteall_callback;
    +    if (!m_toolbar.add_item(item))
    +        return false;
    +
    +    item.name = "arrange";
    +    item.tooltip = GUI::L_str("Arrange");
    +    item.sprite_id = 3;
    +    item.is_toggable = false;
    +    item.action_callback = &m_action_arrange_callback;
    +    if (!m_toolbar.add_item(item))
    +        return false;
    +
    +    if (!m_toolbar.add_separator())
    +        return false;
    +
    +    item.name = "more";
    +    item.tooltip = GUI::L_str("Add instance");
    +    item.sprite_id = 4;
    +    item.is_toggable = false;
    +    item.action_callback = &m_action_more_callback;
    +    if (!m_toolbar.add_item(item))
    +        return false;
    +
    +    item.name = "fewer";
    +    item.tooltip = GUI::L_str("Remove instance");
    +    item.sprite_id = 5;
    +    item.is_toggable = false;
    +    item.action_callback = &m_action_fewer_callback;
    +    if (!m_toolbar.add_item(item))
    +        return false;
    +
    +    if (!m_toolbar.add_separator())
    +        return false;
    +
    +    item.name = "split";
    +    item.tooltip = GUI::L_str("Split");
    +    item.sprite_id = 6;
    +    item.is_toggable = false;
    +    item.action_callback = &m_action_split_callback;
    +    if (!m_toolbar.add_item(item))
    +        return false;
    +
    +    item.name = "cut";
    +    item.tooltip = GUI::L_str("Cut...");
    +    item.sprite_id = 7;
    +    item.is_toggable = false;
    +    item.action_callback = &m_action_cut_callback;
    +    if (!m_toolbar.add_item(item))
    +        return false;
    +
    +    if (!m_toolbar.add_separator())
    +        return false;
    +
    +    item.name = "settings";
    +    item.tooltip = GUI::L_str("Settings...");
    +    item.sprite_id = 8;
    +    item.is_toggable = false;
    +    item.action_callback = &m_action_settings_callback;
    +    if (!m_toolbar.add_item(item))
    +        return false;
    +
    +    item.name = "layersediting";
    +    item.tooltip = GUI::L_str("Layers editing");
    +    item.sprite_id = 9;
    +    item.is_toggable = true;
    +    item.action_callback = &m_action_layersediting_callback;
    +    if (!m_toolbar.add_item(item))
    +        return false;
    +
    +    enable_toolbar_item("add", true);
    +
    +    return true;
    +}
    +
     void GLCanvas3D::_resize(unsigned int w, unsigned int h)
     {
         if ((m_canvas == nullptr) && (m_context == nullptr))
    @@ -3231,13 +3596,13 @@ BoundingBoxf3 GLCanvas3D::_selected_volumes_bounding_box() const
                 bb.merge(volume->bounding_box);
             }
     
    -        bb = bb.transformed(selected_volumes[0]->world_matrix());
    +        bb = bb.transformed(selected_volumes[0]->world_matrix().cast());
         }
         else
         {
             for (const GLVolume* volume : selected_volumes)
             {
    -            bb.merge(volume->transformed_bounding_box());
    +            bb.merge(volume->transformed_convex_hull_bounding_box());
             }
         }
     
    @@ -3277,16 +3642,16 @@ float GLCanvas3D::_get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) co
         ::glGetFloatv(GL_MODELVIEW_MATRIX, matrix);
     
         // camera axes
    -    Pointf3 right((coordf_t)matrix[0], (coordf_t)matrix[4], (coordf_t)matrix[8]);
    -    Pointf3 up((coordf_t)matrix[1], (coordf_t)matrix[5], (coordf_t)matrix[9]);
    -    Pointf3 forward((coordf_t)matrix[2], (coordf_t)matrix[6], (coordf_t)matrix[10]);
    +    Vec3d right((double)matrix[0], (double)matrix[4], (double)matrix[8]);
    +    Vec3d up((double)matrix[1], (double)matrix[5], (double)matrix[9]);
    +    Vec3d forward((double)matrix[2], (double)matrix[6], (double)matrix[10]);
     
    -    Pointf3 bb_min = bbox.min;
    -    Pointf3 bb_max = bbox.max;
    -    Pointf3 bb_center = bbox.center();
    +    Vec3d bb_min = bbox.min;
    +    Vec3d bb_max = bbox.max;
    +    Vec3d bb_center = bbox.center();
     
         // bbox vertices in world space
    -    std::vector vertices;
    +    std::vector vertices;
         vertices.reserve(8);
         vertices.push_back(bb_min);
         vertices.emplace_back(bb_max(0), bb_min(1), bb_min(2));
    @@ -3297,21 +3662,21 @@ float GLCanvas3D::_get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) co
         vertices.push_back(bb_max);
         vertices.emplace_back(bb_min(0), bb_max(1), bb_max(2));
     
    -    coordf_t max_x = 0.0;
    -    coordf_t max_y = 0.0;
    +    double max_x = 0.0;
    +    double max_y = 0.0;
     
         // margin factor to give some empty space around the bbox
    -    coordf_t margin_factor = 1.25;
    +    double margin_factor = 1.25;
     
    -    for (const Pointf3 v : vertices)
    +    for (const Vec3d v : vertices)
         {
             // project vertex on the plane perpendicular to camera forward axis
    -        Pointf3 pos(v(0) - bb_center(0), v(1) - bb_center(1), v(2) - bb_center(2));
    -        Pointf3 proj_on_plane = pos - pos.dot(forward) * forward;
    +        Vec3d pos(v(0) - bb_center(0), v(1) - bb_center(1), v(2) - bb_center(2));
    +        Vec3d proj_on_plane = pos - pos.dot(forward) * forward;
     
             // calculates vertex coordinate along camera xy axes
    -        coordf_t x_on_plane = proj_on_plane.dot(right);
    -        coordf_t y_on_plane = proj_on_plane.dot(up);
    +        double x_on_plane = proj_on_plane.dot(right);
    +        double y_on_plane = proj_on_plane.dot(up);
     
             max_x = std::max(max_x, margin_factor * std::abs(x_on_plane));
             max_y = std::max(max_y, margin_factor * std::abs(y_on_plane));
    @@ -3324,7 +3689,7 @@ float GLCanvas3D::_get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) co
         max_y *= 2.0;
     
         const Size& cnv_size = get_canvas_size();
    -    return (float)std::min((coordf_t)cnv_size.get_width() / max_x, (coordf_t)cnv_size.get_height() / max_y);
    +    return (float)std::min((double)cnv_size.get_width() / max_x, (double)cnv_size.get_height() / max_y);
     }
     
     void GLCanvas3D::_deregister_callbacks()
    @@ -3347,6 +3712,17 @@ void GLCanvas3D::_deregister_callbacks()
         m_on_gizmo_scale_uniformly_callback.deregister_callback();
         m_on_gizmo_rotate_callback.deregister_callback();
         m_on_update_geometry_info_callback.deregister_callback();
    +
    +    m_action_add_callback.deregister_callback();
    +    m_action_delete_callback.deregister_callback();
    +    m_action_deleteall_callback.deregister_callback();
    +    m_action_arrange_callback.deregister_callback();
    +    m_action_more_callback.deregister_callback();
    +    m_action_fewer_callback.deregister_callback();
    +    m_action_split_callback.deregister_callback();
    +    m_action_cut_callback.deregister_callback();
    +    m_action_settings_callback.deregister_callback();
    +    m_action_layersediting_callback.deregister_callback();
     }
     
     void GLCanvas3D::_mark_volumes_for_layer_height() const
    @@ -3389,15 +3765,15 @@ void GLCanvas3D::_camera_tranform() const
         ::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;
    +    Vec3d neg_target = - m_camera.target;
         ::glTranslatef((GLfloat)neg_target(0), (GLfloat)neg_target(1), (GLfloat)neg_target(2));
     }
     
     void GLCanvas3D::_picking_pass() const
     {
    -    const Pointf& pos = m_mouse.position;
    +    const Vec2d& pos = m_mouse.position;
     
    -    if (m_picking_enabled && !m_mouse.dragging && (pos != Pointf(DBL_MAX, DBL_MAX)))
    +    if (m_picking_enabled && !m_mouse.dragging && (pos != Vec2d(DBL_MAX, DBL_MAX)))
         {
             // Render the object for picking.
             // FIXME This cannot possibly work in a multi - sampled context as the color gets mangled by the anti - aliasing.
    @@ -3458,6 +3834,8 @@ void GLCanvas3D::_picking_pass() const
                 m_gizmos.update_hover_state(*this, pos);
             else
                 m_gizmos.reset_all_states();
    +
    +        m_toolbar.update_hover_state(pos);
         }
     }
     
    @@ -3511,6 +3889,7 @@ void GLCanvas3D::_render_objects() const
             return;
     
         ::glEnable(GL_LIGHTING);
    +    ::glEnable(GL_DEPTH_TEST);
     
         if (!m_shader_enabled)
             _render_volumes(false);
    @@ -3562,32 +3941,7 @@ void GLCanvas3D::_render_warning_texture() const
         if (!m_warning_texture_enabled)
             return;
     
    -    // If the warning texture has not been loaded into the GPU, do it now.
    -    unsigned int tex_id = m_warning_texture.get_id();
    -    if (tex_id > 0)
    -    {
    -        int w = m_warning_texture.get_width();
    -        int h = m_warning_texture.get_height();
    -        if ((w > 0) && (h > 0))
    -        {
    -            ::glDisable(GL_DEPTH_TEST);
    -            ::glPushMatrix();
    -            ::glLoadIdentity();
    -
    -            const Size& cnv_size = get_canvas_size();
    -            float zoom = get_camera_zoom();
    -            float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
    -            float l = (-0.5f * (float)w) * inv_zoom;
    -            float t = (-0.5f * (float)cnv_size.get_height() + (float)h) * inv_zoom;
    -            float r = l + (float)w * inv_zoom;
    -            float b = t - (float)h * inv_zoom;
    -
    -            GLTexture::render_texture(tex_id, l, r, b, t);
    -
    -            ::glPopMatrix();
    -            ::glEnable(GL_DEPTH_TEST);
    -        }
    -    }
    +    m_warning_texture.render(*this);
     }
     
     void GLCanvas3D::_render_legend_texture() const
    @@ -3595,32 +3949,7 @@ void GLCanvas3D::_render_legend_texture() const
         if (!m_legend_texture_enabled)
             return;
     
    -    // If the legend texture has not been loaded into the GPU, do it now.
    -    unsigned int tex_id = m_legend_texture.get_id();
    -    if (tex_id > 0)
    -    {
    -        int w = m_legend_texture.get_width();
    -        int h = m_legend_texture.get_height();
    -        if ((w > 0) && (h > 0))
    -        {
    -            ::glDisable(GL_DEPTH_TEST);
    -            ::glPushMatrix();
    -            ::glLoadIdentity();
    -
    -            const Size& cnv_size = get_canvas_size();
    -            float zoom = get_camera_zoom();
    -            float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
    -            float l = (-0.5f * (float)cnv_size.get_width()) * inv_zoom;
    -            float t = (0.5f * (float)cnv_size.get_height()) * inv_zoom;
    -            float r = l + (float)w * inv_zoom;
    -            float b = t - (float)h * inv_zoom;
    -
    -            GLTexture::render_texture(tex_id, l, r, b, t);
    -
    -            ::glPopMatrix();
    -            ::glEnable(GL_DEPTH_TEST);
    -        }
    -    }
    +    m_legend_texture.render(*this);
     }
     
     void GLCanvas3D::_render_layer_editing_overlay() const
    @@ -3702,9 +4031,20 @@ void GLCanvas3D::_render_volumes(bool fake_colors) const
             ::glDisable(GL_LIGHTING);
     }
     
    -void GLCanvas3D::_render_gizmo() const
    +void GLCanvas3D::_render_current_gizmo() const
     {
    -    m_gizmos.render(*this, _selected_volumes_bounding_box());
    +    m_gizmos.render_current_gizmo(_selected_volumes_bounding_box());
    +}
    +
    +void GLCanvas3D::_render_gizmos_overlay() const
    +{
    +    m_gizmos.render_overlay(*this);
    +}
    +
    +void GLCanvas3D::_render_toolbar() const
    +{
    +    _resize_toolbar();
    +    m_toolbar.render();
     }
     
     float GLCanvas3D::_get_layers_editing_cursor_z_relative() const
    @@ -3730,7 +4070,7 @@ void GLCanvas3D::_perform_layer_editing_action(wxMouseEvent* evt)
         {
             const Rect& rect = LayersEditing::get_bar_rect_screen(*this);
             float b = rect.get_bottom();
    -        m_layers_editing.last_z = unscale(selected_obj->size(2)) * (b - evt->GetY() - 1.0f) / (b - rect.get_top());
    +        m_layers_editing.last_z = unscale(selected_obj->size(2)) * (b - evt->GetY() - 1.0f) / (b - rect.get_top());
             m_layers_editing.last_action = evt->ShiftDown() ? (evt->RightIsDown() ? 3 : 2) : (evt->RightIsDown() ? 0 : 1);
         }
     
    @@ -3760,10 +4100,10 @@ void GLCanvas3D::_perform_layer_editing_action(wxMouseEvent* evt)
         _start_timer();
     }
     
    -Pointf3 GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, float* z)
    +Vec3d GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, float* z)
     {
         if (m_canvas == nullptr)
    -        return Pointf3(DBL_MAX, DBL_MAX, DBL_MAX);
    +        return Vec3d(DBL_MAX, DBL_MAX, DBL_MAX);
     
         _camera_tranform();
     
    @@ -3783,14 +4123,19 @@ Pointf3 GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, float* z)
     
         GLdouble out_x, out_y, out_z;
         ::gluUnProject((GLdouble)mouse_pos(0), (GLdouble)y, (GLdouble)mouse_z, modelview_matrix, projection_matrix, viewport, &out_x, &out_y, &out_z);
    -    return Pointf3((coordf_t)out_x, (coordf_t)out_y, (coordf_t)out_z);
    +    return Vec3d((double)out_x, (double)out_y, (double)out_z);
     }
     
    -Pointf3 GLCanvas3D::_mouse_to_bed_3d(const Point& mouse_pos)
    +Vec3d GLCanvas3D::_mouse_to_bed_3d(const Point& mouse_pos)
    +{
    +    return mouse_ray(mouse_pos).intersect_plane(0.0);
    +}
    +
    +Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos)
     {
         float z0 = 0.0f;
         float z1 = 1.0f;
    -    return Linef3(_mouse_to_3d(mouse_pos, &z0), _mouse_to_3d(mouse_pos, &z1)).intersect_plane(0.0);
    +    return Linef3(_mouse_to_3d(mouse_pos, &z0), _mouse_to_3d(mouse_pos, &z1));
     }
     
     void GLCanvas3D::_start_timer()
    @@ -4501,7 +4846,7 @@ bool GLCanvas3D::_travel_paths_by_type(const GCodePreviewData& preview_data)
             TypesList::iterator type = std::find(types.begin(), types.end(), Type(polyline.type));
             if (type != types.end())
             {
    -            type->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min(2)));
    +            type->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min(2)));
                 type->volume->offsets.push_back(type->volume->indexed_vertex_array.quad_indices.size());
                 type->volume->offsets.push_back(type->volume->indexed_vertex_array.triangle_indices.size());
     
    @@ -4567,7 +4912,7 @@ bool GLCanvas3D::_travel_paths_by_feedrate(const GCodePreviewData& preview_data)
             FeedratesList::iterator feedrate = std::find(feedrates.begin(), feedrates.end(), Feedrate(polyline.feedrate));
             if (feedrate != feedrates.end())
             {
    -            feedrate->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min(2)));
    +            feedrate->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min(2)));
                 feedrate->volume->offsets.push_back(feedrate->volume->indexed_vertex_array.quad_indices.size());
                 feedrate->volume->offsets.push_back(feedrate->volume->indexed_vertex_array.triangle_indices.size());
     
    @@ -4633,7 +4978,7 @@ bool GLCanvas3D::_travel_paths_by_tool(const GCodePreviewData& preview_data, con
             ToolsList::iterator tool = std::find(tools.begin(), tools.end(), Tool(polyline.extruder_id));
             if (tool != tools.end())
             {
    -            tool->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min(2)));
    +            tool->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min(2)));
                 tool->volume->offsets.push_back(tool->volume->indexed_vertex_array.quad_indices.size());
                 tool->volume->offsets.push_back(tool->volume->indexed_vertex_array.triangle_indices.size());
     
    @@ -4662,7 +5007,7 @@ void GLCanvas3D::_load_gcode_retractions(const GCodePreviewData& preview_data)
     
             for (const GCodePreviewData::Retraction::Position& position : copy)
             {
    -            volume->print_zs.push_back(unscale(position.position(2)));
    +            volume->print_zs.push_back(unscale(position.position(2)));
                 volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size());
                 volume->offsets.push_back(volume->indexed_vertex_array.triangle_indices.size());
     
    @@ -4693,7 +5038,7 @@ void GLCanvas3D::_load_gcode_unretractions(const GCodePreviewData& preview_data)
     
             for (const GCodePreviewData::Retraction::Position& position : copy)
             {
    -            volume->print_zs.push_back(unscale(position.position(2)));
    +            volume->print_zs.push_back(unscale(position.position(2)));
                 volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size());
                 volume->offsets.push_back(volume->indexed_vertex_array.triangle_indices.size());
     
    @@ -4733,7 +5078,7 @@ void GLCanvas3D::_load_shells()
         }
     
         // adds wipe tower's volume
    -    coordf_t max_z = m_print->objects[0]->model_object()->get_model()->bounding_box().max(2);
    +    double max_z = m_print->objects[0]->model_object()->get_model()->bounding_box().max(2);
         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) {
    @@ -4806,8 +5151,8 @@ 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;
    +    static const double tolerance_x = 0.05;
    +    static const double tolerance_y = 0.05;
     
         BoundingBoxf3 print_volume;
         if (m_config != nullptr)
    @@ -4816,7 +5161,7 @@ void GLCanvas3D::_update_toolpath_volumes_outside_state()
             if (opt != nullptr)
             {
                 BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values));
    -            print_volume = BoundingBoxf3(Pointf3(unscale(bed_box_2D.min(0)) - tolerance_x, unscale(bed_box_2D.min(1)) - tolerance_y, 0.0), Pointf3(unscale(bed_box_2D.max(0)) + tolerance_x, unscale(bed_box_2D.max(1)) + tolerance_y, m_config->opt_float("max_print_height")));
    +            print_volume = BoundingBoxf3(Vec3d(unscale(bed_box_2D.min(0)) - tolerance_x, unscale(bed_box_2D.min(1)) - tolerance_y, 0.0), Vec3d(unscale(bed_box_2D.max(0)) + tolerance_x, unscale(bed_box_2D.max(1)) + tolerance_y, m_config->opt_float("max_print_height")));
                 // Allow the objects to protrude below the print bed
                 print_volume.min(2) = -1e10;
             }
    @@ -4849,7 +5194,7 @@ void GLCanvas3D::_on_move(const std::vector& volume_idxs)
     
         std::set done;  // prevent moving instances twice
         bool object_moved = false;
    -    Pointf3 wipe_tower_origin(0.0, 0.0, 0.0);
    +    Vec3d wipe_tower_origin(0.0, 0.0, 0.0);
         for (int volume_idx : volume_idxs)
         {
             GLVolume* volume = m_volumes.volumes[volume_idx];
    @@ -4868,8 +5213,8 @@ void GLCanvas3D::_on_move(const std::vector& volume_idxs)
             {
                 // Move a regular object.
                 ModelObject* model_object = m_model->objects[obj_idx];
    -            const Pointf3& origin = volume->get_origin();
    -            model_object->instances[instance_idx]->offset = Pointf(origin(0), origin(1));
    +            const Vec3d& origin = volume->get_origin();
    +            model_object->instances[instance_idx]->offset = Vec2d(origin(0), origin(1));
                 model_object->invalidate_bounding_box();
                 object_moved = true;
             }
    @@ -4881,7 +5226,7 @@ void GLCanvas3D::_on_move(const std::vector& volume_idxs)
         if (object_moved)
             m_on_instance_moved_callback.call();
     
    -    if (wipe_tower_origin != Pointf3(0.0, 0.0, 0.0))
    +    if (wipe_tower_origin != Vec3d(0.0, 0.0, 0.0))
             m_on_wipe_tower_moved_callback.call(wipe_tower_origin(0), wipe_tower_origin(1));
     }
     
    @@ -4958,5 +5303,36 @@ bool GLCanvas3D::_is_any_volume_outside() const
         return false;
     }
     
    +void GLCanvas3D::_resize_toolbar() const
    +{
    +    Size cnv_size = get_canvas_size();
    +    float zoom = get_camera_zoom();
    +    float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
    +
    +    switch (m_toolbar.get_layout_type())
    +    {
    +    default:
    +    case GLToolbar::Layout::Horizontal:
    +    {
    +        // centers the toolbar on the top edge of the 3d scene
    +        unsigned int toolbar_width = m_toolbar.get_width();
    +        float top = (0.5f * (float)cnv_size.get_height() - 2.0f) * inv_zoom;
    +        float left = -0.5f * (float)toolbar_width * inv_zoom;
    +        m_toolbar.set_position(top, left);
    +        break;
    +    }
    +    case GLToolbar::Layout::Vertical:
    +    {
    +        // centers the toolbar on the right edge of the 3d scene
    +        unsigned int toolbar_width = m_toolbar.get_width();
    +        unsigned int toolbar_height = m_toolbar.get_height();
    +        float top = 0.5f * (float)toolbar_height * inv_zoom;
    +        float left = (0.5f * (float)cnv_size.get_width() - toolbar_width - 2.0f) * inv_zoom;
    +        m_toolbar.set_position(top, left);
    +        break;
    +    }
    +    }
    +}
    +
     } // namespace GUI
     } // namespace Slic3r
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp
    index ae20882fc6..2334fe0927 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp
    @@ -2,7 +2,7 @@
     #define slic3r_GLCanvas3D_hpp_
     
     #include "../../slic3r/GUI/3DScene.hpp"
    -#include "../../slic3r/GUI/GLTexture.hpp"
    +#include "../../slic3r/GUI/GLToolbar.hpp"
     
     class wxTimer;
     class wxSizeEvent;
    @@ -120,7 +120,7 @@ public:
             float zoom;
             float phi;
     //        float distance;
    -        Pointf3 target;
    +        Vec3d target;
     
         private:
             float m_theta;
    @@ -185,7 +185,7 @@ public:
     
         struct Axes
         {
    -        Pointf3 origin;
    +        Vec3d origin;
             float length;
     
             Axes();
    @@ -299,11 +299,11 @@ public:
             struct Drag
             {
                 static const Point Invalid_2D_Point;
    -            static const Pointf3 Invalid_3D_Point;
    +            static const Vec3d Invalid_3D_Point;
     
                 Point start_position_2D;
    -            Pointf3 start_position_3D;
    -            Vectorf3 volume_center_offset;
    +            Vec3d start_position_3D;
    +            Vec3d volume_center_offset;
     
                 bool move_with_shift;
                 int move_volume_idx;
    @@ -314,7 +314,7 @@ public:
             };
     
             bool dragging;
    -        Pointf position;
    +        Vec2d position;
             Drag drag;
     
             Mouse();
    @@ -352,20 +352,20 @@ public:
             Gizmos();
             ~Gizmos();
     
    -        bool init();
    +        bool init(GLCanvas3D& parent);
     
             bool is_enabled() const;
             void set_enabled(bool enable);
     
    -        void update_hover_state(const GLCanvas3D& canvas, const Pointf& mouse_pos);
    -        void update_on_off_state(const GLCanvas3D& canvas, const Pointf& mouse_pos);
    +        void update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos);
    +        void update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos);
             void reset_all_states();
     
             void set_hover_id(int id);
     
    -        bool overlay_contains_mouse(const GLCanvas3D& canvas, const Pointf& mouse_pos) const;
    +        bool overlay_contains_mouse(const GLCanvas3D& canvas, const Vec2d& mouse_pos) const;
             bool grabber_contains_mouse() const;
    -        void update(const Pointf& mouse_pos);
    +        void update(const Linef3& mouse_ray);
             void refresh();
     
             EType get_current_type() const;
    @@ -382,8 +382,9 @@ public:
             float get_angle_z() const;
             void set_angle_z(float angle_z);
     
    -        void render(const GLCanvas3D& canvas, const BoundingBoxf3& box) const;
    +        void render_current_gizmo(const BoundingBoxf3& box) const;
             void render_current_gizmo_for_picking_pass(const BoundingBoxf3& box) const;
    +        void render_overlay(const GLCanvas3D& canvas) const;
     
         private:
             void _reset();
    @@ -400,8 +401,15 @@ public:
             static const unsigned char Background_Color[3];
             static const unsigned char Opacity;
     
    +        int m_original_width;
    +        int m_original_height;
    +
         public:
    +        WarningTexture();
    +
             bool generate(const std::string& msg);
    +
    +        void render(const GLCanvas3D& canvas) const;
         };
     
         class LegendTexture : public GUI::GLTexture
    @@ -415,8 +423,15 @@ public:
             static const unsigned char Background_Color[3];
             static const unsigned char Opacity;
     
    +        int m_original_width;
    +        int m_original_height;
    +
         public:
    +        LegendTexture();
    +
             bool generate(const GCodePreviewData& preview_data, const std::vector& tool_colors);
    +
    +        void render(const GLCanvas3D& canvas) const;
         };
     
     private:
    @@ -433,6 +448,7 @@ private:
         Shader m_shader;
         Mouse m_mouse;
         mutable Gizmos m_gizmos;
    +    mutable GLToolbar m_toolbar;
     
         mutable GLVolumeCollection m_volumes;
         DynamicPrintConfig* m_config;
    @@ -445,6 +461,7 @@ private:
         bool m_force_zoom_to_bed_enabled;
         bool m_apply_zoom_to_volumes_filter;
         mutable int m_hover_volume_id;
    +    bool m_toolbar_action_running;
         bool m_warning_texture_enabled;
         bool m_legend_texture_enabled;
         bool m_picking_enabled;
    @@ -482,6 +499,17 @@ private:
         PerlCallback m_on_gizmo_rotate_callback;
         PerlCallback m_on_update_geometry_info_callback;
     
    +    PerlCallback m_action_add_callback;
    +    PerlCallback m_action_delete_callback;
    +    PerlCallback m_action_deleteall_callback;
    +    PerlCallback m_action_arrange_callback;
    +    PerlCallback m_action_more_callback;
    +    PerlCallback m_action_fewer_callback;
    +    PerlCallback m_action_split_callback;
    +    PerlCallback m_action_cut_callback;
    +    PerlCallback m_action_settings_callback;
    +    PerlCallback m_action_layersediting_callback;
    +
     public:
         GLCanvas3D(wxGLCanvas* canvas);
         ~GLCanvas3D();
    @@ -539,11 +567,15 @@ public:
         void enable_picking(bool enable);
         void enable_moving(bool enable);
         void enable_gizmos(bool enable);
    +    void enable_toolbar(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 enable_toolbar_item(const std::string& name, bool enable);
    +    bool is_toolbar_item_pressed(const std::string& name) const;
    +
         void zoom_to_bed();
         void zoom_to_volumes();
         void select_view(const std::string& direction);
    @@ -584,6 +616,17 @@ public:
         void register_on_gizmo_rotate_callback(void* callback);
         void register_on_update_geometry_info_callback(void* callback);
     
    +    void register_action_add_callback(void* callback);
    +    void register_action_delete_callback(void* callback);
    +    void register_action_deleteall_callback(void* callback);
    +    void register_action_arrange_callback(void* callback);
    +    void register_action_more_callback(void* callback);
    +    void register_action_fewer_callback(void* callback);
    +    void register_action_split_callback(void* callback);
    +    void register_action_cut_callback(void* callback);
    +    void register_action_settings_callback(void* callback);
    +    void register_action_layersediting_callback(void* callback);
    +
         void bind_event_handlers();
         void unbind_event_handlers();
     
    @@ -601,10 +644,14 @@ public:
     
         void reset_legend_texture();
     
    +    void set_tooltip(const std::string& tooltip);
    +
     private:
         bool _is_shown_on_screen() const;
         void _force_zoom_to_bed();
     
    +    bool _init_toolbar();
    +
         void _resize(unsigned int w, unsigned int h);
     
         BoundingBoxf3 _max_bounding_box() const;
    @@ -629,17 +676,22 @@ 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_current_gizmo() const;
    +    void _render_gizmos_overlay() const;
    +    void _render_toolbar() const;
     
         float _get_layers_editing_cursor_z_relative() const;
         void _perform_layer_editing_action(wxMouseEvent* evt = nullptr);
     
         // Convert the screen space coordinate to an object space coordinate.
         // If the Z screen space coordinate is not provided, a depth buffer value is substituted.
    -    Pointf3 _mouse_to_3d(const Point& mouse_pos, float* z = nullptr);
    +    Vec3d _mouse_to_3d(const Point& mouse_pos, float* z = nullptr);
     
         // Convert the screen space coordinate to world coordinate on the bed.
    -    Pointf3 _mouse_to_bed_3d(const Point& mouse_pos);
    +    Vec3d _mouse_to_bed_3d(const Point& mouse_pos);
    +
    +    // Returns the view ray line, in world coordinate, at the given mouse position.
    +    Linef3 mouse_ray(const Point& mouse_pos);
     
         void _start_timer();
         void _stop_timer();
    @@ -687,6 +739,8 @@ private:
     
         bool _is_any_volume_outside() const;
     
    +    void _resize_toolbar() 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 5249f6dc4f..57a49d7ab4 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp
    @@ -404,6 +404,13 @@ void GLCanvas3DManager::enable_gizmos(wxGLCanvas* canvas, bool enable)
             it->second->enable_gizmos(enable);
     }
     
    +void GLCanvas3DManager::enable_toolbar(wxGLCanvas* canvas, bool enable)
    +{
    +    CanvasesMap::iterator it = _get_canvas(canvas);
    +    if (it != m_canvases.end())
    +        it->second->enable_toolbar(enable);
    +}
    +
     void GLCanvas3DManager::enable_shader(wxGLCanvas* canvas, bool enable)
     {
         CanvasesMap::iterator it = _get_canvas(canvas);
    @@ -432,6 +439,19 @@ void GLCanvas3DManager::allow_multisample(wxGLCanvas* canvas, bool allow)
             it->second->allow_multisample(allow);
     }
     
    +void GLCanvas3DManager::enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable)
    +{
    +    CanvasesMap::iterator it = _get_canvas(canvas);
    +    if (it != m_canvases.end())
    +        it->second->enable_toolbar_item(name, enable);
    +}
    +
    +bool GLCanvas3DManager::is_toolbar_item_pressed(wxGLCanvas* canvas, const std::string& name) const
    +{
    +    CanvasesMap::const_iterator it = _get_canvas(canvas);
    +    return (it != m_canvases.end()) ? it->second->is_toolbar_item_pressed(name) : false;
    +}
    +
     void GLCanvas3DManager::zoom_to_bed(wxGLCanvas* canvas)
     {
         CanvasesMap::iterator it = _get_canvas(canvas);
    @@ -675,6 +695,76 @@ void GLCanvas3DManager::register_on_update_geometry_info_callback(wxGLCanvas* ca
             it->second->register_on_update_geometry_info_callback(callback);
     }
     
    +void GLCanvas3DManager::register_action_add_callback(wxGLCanvas* canvas, void* callback)
    +{
    +    CanvasesMap::iterator it = _get_canvas(canvas);
    +    if (it != m_canvases.end())
    +        it->second->register_action_add_callback(callback);
    +}
    +
    +void GLCanvas3DManager::register_action_delete_callback(wxGLCanvas* canvas, void* callback)
    +{
    +    CanvasesMap::iterator it = _get_canvas(canvas);
    +    if (it != m_canvases.end())
    +        it->second->register_action_delete_callback(callback);
    +}
    +
    +void GLCanvas3DManager::register_action_deleteall_callback(wxGLCanvas* canvas, void* callback)
    +{
    +    CanvasesMap::iterator it = _get_canvas(canvas);
    +    if (it != m_canvases.end())
    +        it->second->register_action_deleteall_callback(callback);
    +}
    +
    +void GLCanvas3DManager::register_action_arrange_callback(wxGLCanvas* canvas, void* callback)
    +{
    +    CanvasesMap::iterator it = _get_canvas(canvas);
    +    if (it != m_canvases.end())
    +        it->second->register_action_arrange_callback(callback);
    +}
    +
    +void GLCanvas3DManager::register_action_more_callback(wxGLCanvas* canvas, void* callback)
    +{
    +    CanvasesMap::iterator it = _get_canvas(canvas);
    +    if (it != m_canvases.end())
    +        it->second->register_action_more_callback(callback);
    +}
    +
    +void GLCanvas3DManager::register_action_fewer_callback(wxGLCanvas* canvas, void* callback)
    +{
    +    CanvasesMap::iterator it = _get_canvas(canvas);
    +    if (it != m_canvases.end())
    +        it->second->register_action_fewer_callback(callback);
    +}
    +
    +void GLCanvas3DManager::register_action_split_callback(wxGLCanvas* canvas, void* callback)
    +{
    +    CanvasesMap::iterator it = _get_canvas(canvas);
    +    if (it != m_canvases.end())
    +        it->second->register_action_split_callback(callback);
    +}
    +
    +void GLCanvas3DManager::register_action_cut_callback(wxGLCanvas* canvas, void* callback)
    +{
    +    CanvasesMap::iterator it = _get_canvas(canvas);
    +    if (it != m_canvases.end())
    +        it->second->register_action_cut_callback(callback);
    +}
    +
    +void GLCanvas3DManager::register_action_settings_callback(wxGLCanvas* canvas, void* callback)
    +{
    +    CanvasesMap::iterator it = _get_canvas(canvas);
    +    if (it != m_canvases.end())
    +        it->second->register_action_settings_callback(callback);
    +}
    +
    +void GLCanvas3DManager::register_action_layersediting_callback(wxGLCanvas* canvas, void* callback)
    +{
    +    CanvasesMap::iterator it = _get_canvas(canvas);
    +    if (it != m_canvases.end())
    +        it->second->register_action_layersediting_callback(callback);
    +}
    +
     GLCanvas3DManager::CanvasesMap::iterator GLCanvas3DManager::_get_canvas(wxGLCanvas* canvas)
     {
         return (canvas == nullptr) ? m_canvases.end() : m_canvases.find(canvas);
    diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp
    index 55c42fda69..f7705a56c6 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp
    @@ -110,11 +110,15 @@ public:
         void enable_picking(wxGLCanvas* canvas, bool enable);
         void enable_moving(wxGLCanvas* canvas, bool enable);
         void enable_gizmos(wxGLCanvas* canvas, bool enable);
    +    void enable_toolbar(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 enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable);
    +    bool is_toolbar_item_pressed(wxGLCanvas* canvas, const std::string& name) const;
    +
         void zoom_to_bed(wxGLCanvas* canvas);
         void zoom_to_volumes(wxGLCanvas* canvas);
         void select_view(wxGLCanvas* canvas, const std::string& direction);
    @@ -157,6 +161,17 @@ public:
         void register_on_gizmo_rotate_callback(wxGLCanvas* canvas, void* callback);
         void register_on_update_geometry_info_callback(wxGLCanvas* canvas, void* callback);
     
    +    void register_action_add_callback(wxGLCanvas* canvas, void* callback);
    +    void register_action_delete_callback(wxGLCanvas* canvas, void* callback);
    +    void register_action_deleteall_callback(wxGLCanvas* canvas, void* callback);
    +    void register_action_arrange_callback(wxGLCanvas* canvas, void* callback);
    +    void register_action_more_callback(wxGLCanvas* canvas, void* callback);
    +    void register_action_fewer_callback(wxGLCanvas* canvas, void* callback);
    +    void register_action_split_callback(wxGLCanvas* canvas, void* callback);
    +    void register_action_cut_callback(wxGLCanvas* canvas, void* callback);
    +    void register_action_settings_callback(wxGLCanvas* canvas, void* callback);
    +    void register_action_layersediting_callback(wxGLCanvas* canvas, void* callback);
    +
     private:
         CanvasesMap::iterator _get_canvas(wxGLCanvas* canvas);
         CanvasesMap::const_iterator _get_canvas(wxGLCanvas* canvas) const;
    diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
    index f4f947d9c2..39ba440c3c 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.cpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.cpp
    @@ -1,23 +1,34 @@
     #include "GLGizmo.hpp"
     
     #include "../../libslic3r/Utils.hpp"
    -#include "../../libslic3r/BoundingBox.hpp"
    +#include "../../slic3r/GUI/GLCanvas3D.hpp"
    +
    +#include 
     
     #include 
     
     #include 
     
    +static const float DEFAULT_BASE_COLOR[3] = { 0.625f, 0.625f, 0.625f };
    +static const float DEFAULT_DRAG_COLOR[3] = { 1.0f, 1.0f, 1.0f };
    +static const float DEFAULT_HIGHLIGHT_COLOR[3] = { 1.0f, 0.38f, 0.0f };
    +
    +static const float RED[3] = { 1.0f, 0.0f, 0.0f };
    +static const float GREEN[3] = { 0.0f, 1.0f, 0.0f };
    +static const float BLUE[3] = { 0.0f, 0.0f, 1.0f };
    +
     namespace Slic3r {
     namespace GUI {
     
     const float GLGizmoBase::Grabber::HalfSize = 2.0f;
    -const float GLGizmoBase::Grabber::HoverOffset = 0.5f;
    -const float GLGizmoBase::BaseColor[3] = { 1.0f, 1.0f, 1.0f };
    -const float GLGizmoBase::HighlightColor[3] = { 1.0f, 0.38f, 0.0f };
    +const float GLGizmoBase::Grabber::DraggingScaleFactor = 1.25f;
     
     GLGizmoBase::Grabber::Grabber()
    -    : center(Pointf(0.0, 0.0))
    +    : center(0.0, 0.0, 0.0)
    +    , angle_x(0.0f)
    +    , angle_y(0.0f)
         , angle_z(0.0f)
    +    , dragging(false)
     {
         color[0] = 1.0f;
         color[1] = 1.0f;
    @@ -26,18 +37,92 @@ GLGizmoBase::Grabber::Grabber()
     
     void GLGizmoBase::Grabber::render(bool hover) const
     {
    -    float min_x = -HalfSize;
    -    float max_x = +HalfSize;
    -    float min_y = -HalfSize;
    -    float max_y = +HalfSize;
    +    float render_color[3];
    +    if (hover)
    +    {
    +        render_color[0] = 1.0f - color[0];
    +        render_color[1] = 1.0f - color[1];
    +        render_color[2] = 1.0f - color[2];
    +    }
    +    else
    +        ::memcpy((void*)render_color, (const void*)color, 3 * sizeof(float));
     
    -    ::glColor3f((GLfloat)color[0], (GLfloat)color[1], (GLfloat)color[2]);
    +#if ENABLE_GIZMOS_3D
    +    render(render_color, true);
    +#else
    +    render(render_color);
    +#endif // ENABLE_GIZMOS_3D
    +}
    +
    +#if ENABLE_GIZMOS_3D
    +void GLGizmoBase::Grabber::render(const float* render_color, bool use_lighting) const
    +#else
    +void GLGizmoBase::Grabber::render(const float* render_color) const
    +#endif // ENABLE_GIZMOS_3D
    +{
    +    float half_size = dragging ? HalfSize * DraggingScaleFactor : HalfSize;
    +#if ENABLE_GIZMOS_3D
    +    if (use_lighting)
    +        ::glEnable(GL_LIGHTING);
    +#else
    +    float min_x = -half_size;
    +    float max_x = +half_size;
    +    float min_y = -half_size;
    +    float max_y = +half_size;
    +#endif // !ENABLE_GIZMOS_3D
    +
    +    ::glColor3f((GLfloat)render_color[0], (GLfloat)render_color[1], (GLfloat)render_color[2]);
     
    -    float angle_z_in_deg = angle_z * 180.0f / (float)PI;
         ::glPushMatrix();
    -    ::glTranslatef((GLfloat)center(0), (GLfloat)center(1), 0.0f);
    -    ::glRotatef((GLfloat)angle_z_in_deg, 0.0f, 0.0f, 1.0f);
    +    ::glTranslatef((GLfloat)center(0), (GLfloat)center(1), (GLfloat)center(2));
     
    +    float rad_to_deg = 180.0f / (GLfloat)PI;
    +    ::glRotatef((GLfloat)angle_x * rad_to_deg, 1.0f, 0.0f, 0.0f);
    +    ::glRotatef((GLfloat)angle_y * rad_to_deg, 0.0f, 1.0f, 0.0f);
    +    ::glRotatef((GLfloat)angle_z * rad_to_deg, 0.0f, 0.0f, 1.0f);
    +
    +#if ENABLE_GIZMOS_3D
    +    // face min x
    +    ::glPushMatrix();
    +    ::glTranslatef(-(GLfloat)half_size, 0.0f, 0.0f);
    +    ::glRotatef(-90.0f, 0.0f, 1.0f, 0.0f);
    +    render_face(half_size);
    +    ::glPopMatrix();
    +
    +    // face max x
    +    ::glPushMatrix();
    +    ::glTranslatef((GLfloat)half_size, 0.0f, 0.0f);
    +    ::glRotatef(90.0f, 0.0f, 1.0f, 0.0f);
    +    render_face(half_size);
    +    ::glPopMatrix();
    +
    +    // face min y
    +    ::glPushMatrix();
    +    ::glTranslatef(0.0f, -(GLfloat)half_size, 0.0f);
    +    ::glRotatef(90.0f, 1.0f, 0.0f, 0.0f);
    +    render_face(half_size);
    +    ::glPopMatrix();
    +
    +    // face max y
    +    ::glPushMatrix();
    +    ::glTranslatef(0.0f, (GLfloat)half_size, 0.0f);
    +    ::glRotatef(-90.0f, 1.0f, 0.0f, 0.0f);
    +    render_face(half_size);
    +    ::glPopMatrix();
    +
    +    // face min z
    +    ::glPushMatrix();
    +    ::glTranslatef(0.0f, 0.0f, -(GLfloat)half_size);
    +    ::glRotatef(180.0f, 1.0f, 0.0f, 0.0f);
    +    render_face(half_size);
    +    ::glPopMatrix();
    +
    +    // face max z
    +    ::glPushMatrix();
    +    ::glTranslatef(0.0f, 0.0f, (GLfloat)half_size);
    +    render_face(half_size);
    +    ::glPopMatrix();
    +#else
         ::glDisable(GL_CULL_FACE);
         ::glBegin(GL_TRIANGLES);
         ::glVertex3f((GLfloat)min_x, (GLfloat)min_y, 0.0f);
    @@ -48,121 +133,93 @@ void GLGizmoBase::Grabber::render(bool hover) const
         ::glVertex3f((GLfloat)min_x, (GLfloat)min_y, 0.0f);
         ::glEnd();
         ::glEnable(GL_CULL_FACE);
    -
    -    if (hover)
    -    {
    -        min_x -= HoverOffset;
    -        max_x += HoverOffset;
    -        min_y -= HoverOffset;
    -        max_y += HoverOffset;
    -
    -        ::glBegin(GL_LINE_LOOP);
    -        ::glVertex3f((GLfloat)min_x, (GLfloat)min_y, 0.0f);
    -        ::glVertex3f((GLfloat)max_x, (GLfloat)min_y, 0.0f);
    -        ::glVertex3f((GLfloat)max_x, (GLfloat)max_y, 0.0f);
    -        ::glVertex3f((GLfloat)min_x, (GLfloat)max_y, 0.0f);
    -        ::glEnd();
    -    }
    +#endif // ENABLE_GIZMOS_3D
     
         ::glPopMatrix();
    +
    +#if ENABLE_GIZMOS_3D
    +    if (use_lighting)
    +        ::glDisable(GL_LIGHTING);
    +#endif // ENABLE_GIZMOS_3D
     }
     
    -GLGizmoBase::GLGizmoBase()
    -    : m_state(Off)
    +#if ENABLE_GIZMOS_3D
    +void GLGizmoBase::Grabber::render_face(float half_size) const
    +{
    +    ::glBegin(GL_TRIANGLES);
    +    ::glNormal3f(0.0f, 0.0f, 1.0f);
    +    ::glVertex3f(-(GLfloat)half_size, -(GLfloat)half_size, 0.0f);
    +    ::glVertex3f((GLfloat)half_size, -(GLfloat)half_size, 0.0f);
    +    ::glVertex3f((GLfloat)half_size, (GLfloat)half_size, 0.0f);
    +    ::glVertex3f((GLfloat)half_size, (GLfloat)half_size, 0.0f);
    +    ::glVertex3f(-(GLfloat)half_size, (GLfloat)half_size, 0.0f);
    +    ::glVertex3f(-(GLfloat)half_size, -(GLfloat)half_size, 0.0f);
    +    ::glEnd();
    +}
    +#endif // ENABLE_GIZMOS_3D
    +
    +GLGizmoBase::GLGizmoBase(GLCanvas3D& parent)
    +    : m_parent(parent)
    +    , m_group_id(-1)
    +    , m_state(Off)
         , m_hover_id(-1)
    +    , m_is_container(false)
     {
    -}
    -
    -GLGizmoBase::~GLGizmoBase()
    -{
    -}
    -
    -bool GLGizmoBase::init()
    -{
    -    return on_init();
    -}
    -
    -GLGizmoBase::EState GLGizmoBase::get_state() const
    -{
    -    return m_state;
    -}
    -
    -void GLGizmoBase::set_state(GLGizmoBase::EState state)
    -{
    -    m_state = state;
    -    on_set_state();
    -}
    -
    -unsigned int GLGizmoBase::get_texture_id() const
    -{
    -    return m_textures[m_state].get_id();
    -}
    -
    -int GLGizmoBase::get_textures_size() const
    -{
    -    return m_textures[Off].get_width();
    -}
    -
    -int GLGizmoBase::get_hover_id() const
    -{
    -    return m_hover_id;
    +    ::memcpy((void*)m_base_color, (const void*)DEFAULT_BASE_COLOR, 3 * sizeof(float));
    +    ::memcpy((void*)m_drag_color, (const void*)DEFAULT_DRAG_COLOR, 3 * sizeof(float));
    +    ::memcpy((void*)m_highlight_color, (const void*)DEFAULT_HIGHLIGHT_COLOR, 3 * sizeof(float));
     }
     
     void GLGizmoBase::set_hover_id(int id)
     {
    -    if (id < (int)m_grabbers.size())
    +    if (m_is_container || (id < (int)m_grabbers.size()))
    +    {
             m_hover_id = id;
    +        on_set_hover_id();
    +    }
    +}
    +
    +void GLGizmoBase::set_highlight_color(const float* color)
    +{
    +    if (color != nullptr)
    +        ::memcpy((void*)m_highlight_color, (const void*)color, 3 * sizeof(float));
     }
     
     void GLGizmoBase::start_dragging()
     {
    +    for (int i = 0; i < (int)m_grabbers.size(); ++i)
    +    {
    +        m_grabbers[i].dragging = (m_hover_id == i);
    +    }
    +
         on_start_dragging();
     }
     
     void GLGizmoBase::stop_dragging()
     {
    +    set_tooltip("");
    +
    +    for (int i = 0; i < (int)m_grabbers.size(); ++i)
    +    {
    +        m_grabbers[i].dragging = false;
    +    }
    +
         on_stop_dragging();
     }
     
    -void GLGizmoBase::update(const Pointf& mouse_pos)
    +void GLGizmoBase::update(const Linef3& mouse_ray)
     {
         if (m_hover_id != -1)
    -        on_update(mouse_pos);
    +        on_update(mouse_ray);
     }
     
    -void GLGizmoBase::refresh()
    +float GLGizmoBase::picking_color_component(unsigned int id) const
     {
    -    on_refresh();
    -}
    +    int color = 254 - (int)id;
    +    if (m_group_id > -1)
    +        color -= m_group_id;
     
    -void GLGizmoBase::render(const BoundingBoxf3& box) const
    -{
    -    on_render(box);
    -}
    -
    -void GLGizmoBase::render_for_picking(const BoundingBoxf3& box) const
    -{
    -    on_render_for_picking(box);
    -}
    -
    -void GLGizmoBase::on_set_state()
    -{
    -    // do nothing
    -}
    -
    -void GLGizmoBase::on_start_dragging()
    -{
    -    // do nothing
    -}
    -
    -void GLGizmoBase::on_stop_dragging()
    -{
    -    // do nothing
    -}
    -
    -void GLGizmoBase::on_refresh()
    -{
    -    // do nothing
    +    return (float)color / 255.0f;
     }
     
     void GLGizmoBase::render_grabbers() const
    @@ -173,41 +230,394 @@ void GLGizmoBase::render_grabbers() const
         }
     }
     
    +void GLGizmoBase::render_grabbers_for_picking() const
    +{
    +    for (int i = 0; i < (int)m_grabbers.size(); ++i)
    +    {
    +        m_grabbers[i].render_for_picking();
    +    }
    +}
    +
    +void GLGizmoBase::set_tooltip(const std::string& tooltip) const
    +{
    +    m_parent.set_tooltip(tooltip);
    +}
    +
    +std::string GLGizmoBase::format(float value, unsigned int decimals) const
    +{
    +    char buf[1024];
    +    ::sprintf(buf, "%.*f", decimals, value);
    +    return buf;
    +}
    +
     const float GLGizmoRotate::Offset = 5.0f;
     const unsigned int GLGizmoRotate::CircleResolution = 64;
     const unsigned int GLGizmoRotate::AngleResolution = 64;
    -const unsigned int GLGizmoRotate::ScaleStepsCount = 60;
    +const unsigned int GLGizmoRotate::ScaleStepsCount = 72;
     const float GLGizmoRotate::ScaleStepRad = 2.0f * (float)PI / GLGizmoRotate::ScaleStepsCount;
    -const unsigned int GLGizmoRotate::ScaleLongEvery = 5;
    +const unsigned int GLGizmoRotate::ScaleLongEvery = 2;
     const float GLGizmoRotate::ScaleLongTooth = 2.0f;
     const float GLGizmoRotate::ScaleShortTooth = 1.0f;
     const unsigned int GLGizmoRotate::SnapRegionsCount = 8;
     const float GLGizmoRotate::GrabberOffset = 5.0f;
     
    -GLGizmoRotate::GLGizmoRotate()
    -    : GLGizmoBase()
    -    , m_angle_z(0.0f)
    -    , m_center(Pointf(0.0, 0.0))
    +GLGizmoRotate::GLGizmoRotate(GLCanvas3D& parent, GLGizmoRotate::Axis axis)
    +    : GLGizmoBase(parent)
    +    , m_axis(axis)
    +    , m_angle(0.0f)
    +    , m_center(0.0, 0.0, 0.0)
         , m_radius(0.0f)
    -    , m_keep_radius(false)
    +    , m_keep_initial_values(false)
     {
     }
     
    -float GLGizmoRotate::get_angle_z() const
    +void GLGizmoRotate::set_angle(float angle)
     {
    -    return m_angle_z;
    -}
    +    if (std::abs(angle - 2.0f * PI) < EPSILON)
    +        angle = 0.0f;
     
    -void GLGizmoRotate::set_angle_z(float angle_z)
    -{
    -    if (std::abs(angle_z - 2.0f * PI) < EPSILON)
    -        angle_z = 0.0f;
    -
    -    m_angle_z = angle_z;
    +    m_angle = angle;
     }
     
     bool GLGizmoRotate::on_init()
     {
    +#if !ENABLE_GIZMOS_3D
    +    std::string path = resources_dir() + "/icons/overlay/";
    +
    +    std::string filename = path + "rotate_off.png";
    +    if (!m_textures[Off].load_from_file(filename, false))
    +        return false;
    +    
    +    filename = path + "rotate_hover.png";
    +    if (!m_textures[Hover].load_from_file(filename, false))
    +        return false;
    +    
    +    filename = path + "rotate_on.png";
    +    if (!m_textures[On].load_from_file(filename, false))
    +        return false;
    +#endif // !ENABLE_GIZMOS_3D
    +
    +    m_grabbers.push_back(Grabber());
    +
    +    return true;
    +}
    +
    +void GLGizmoRotate::on_update(const Linef3& mouse_ray)
    +{ 
    +    Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(mouse_ray));
    +
    +    Vec2d orig_dir = Vec2d::UnitX();
    +    Vec2d new_dir = mouse_pos.normalized();
    +
    +    double theta = ::acos(clamp(-1.0, 1.0, new_dir.dot(orig_dir)));
    +    if (cross2(orig_dir, new_dir) < 0.0)
    +        theta = 2.0 * (double)PI - theta;
    +
    +    double len = mouse_pos.norm();
    +
    +    // snap to snap region
    +    double in_radius = (double)m_radius / 3.0;
    +    double out_radius = 2.0 * (double)in_radius;
    +    if ((in_radius <= len) && (len <= out_radius))
    +    {
    +        double step = 2.0 * (double)PI / (double)SnapRegionsCount;
    +        theta = step * (double)std::round(theta / step);
    +    }
    +    else
    +    {
    +        // snap to scale
    +        in_radius = (double)m_radius;
    +        out_radius = in_radius + (double)ScaleLongTooth;
    +        if ((in_radius <= len) && (len <= out_radius))
    +        {
    +            double step = 2.0 * (double)PI / (double)ScaleStepsCount;
    +            theta = step * (double)std::round(theta / step);
    +        }
    +    }
    +
    +    if (theta == 2.0 * (double)PI)
    +        theta = 0.0;
    +
    +    m_angle = (float)theta;
    +}
    +
    +void GLGizmoRotate::on_render(const BoundingBoxf3& box) const
    +{
    +    if (m_grabbers[0].dragging)
    +        set_tooltip(format(m_angle * 180.0f / (float)PI, 4));
    +
    +#if ENABLE_GIZMOS_3D
    +    ::glEnable(GL_DEPTH_TEST);
    +#else
    +    ::glDisable(GL_DEPTH_TEST);
    +#endif // ENABLE_GIZMOS_3D
    +
    +    if (!m_keep_initial_values)
    +    {
    +        m_center = box.center();
    +#if !ENABLE_GIZMOS_3D
    +        const Vec3d& size = box.size();
    +        m_center(2) = 0.0;
    +#endif // !ENABLE_GIZMOS_3D
    +
    +#if ENABLE_GIZMOS_3D
    +        m_radius = Offset + box.radius();
    +#else
    +        m_radius = Offset + ::sqrt(sqr(0.5f * (float)size(0)) + sqr(0.5f * (float)size(1)));
    +#endif // ENABLE_GIZMOS_3D
    +        m_keep_initial_values = true;
    +    }
    +
    +    ::glPushMatrix();
    +    transform_to_local();
    +
    +#if ENABLE_GIZMOS_3D
    +    ::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f);
    +    ::glColor3fv((m_hover_id != -1) ? m_drag_color : m_highlight_color);
    +#else
    +    ::glLineWidth(2.0f);
    +    ::glColor3fv(m_drag_color);
    +#endif // ENABLE_GIZMOS_3D
    +
    +    render_circle();
    +#if ENABLE_GIZMOS_3D
    +    if (m_hover_id != -1)
    +    {
    +#endif // ENABLE_GIZMOS_3D
    +        render_scale();
    +        render_snap_radii();
    +        render_reference_radius();
    +#if ENABLE_GIZMOS_3D
    +    }
    +#endif // ENABLE_GIZMOS_3D
    +
    +    ::glColor3fv(m_highlight_color);
    +#if ENABLE_GIZMOS_3D
    +    if (m_hover_id != -1)
    +#endif // ENABLE_GIZMOS_3D
    +        render_angle();
    +
    +    render_grabber();
    +
    +    ::glPopMatrix();
    +}
    +
    +void GLGizmoRotate::on_render_for_picking(const BoundingBoxf3& box) const
    +{
    +    ::glDisable(GL_DEPTH_TEST);
    +
    +    ::glPushMatrix();
    +    transform_to_local();
    +
    +    m_grabbers[0].color[0] = 1.0f;
    +    m_grabbers[0].color[1] = 1.0f;
    +    m_grabbers[0].color[2] = picking_color_component(0);
    +
    +    render_grabbers_for_picking();
    +
    +    ::glPopMatrix();
    +}
    +
    +void GLGizmoRotate::render_circle() const
    +{
    +    ::glBegin(GL_LINE_LOOP);
    +    for (unsigned int i = 0; i < ScaleStepsCount; ++i)
    +    {
    +        float angle = (float)i * ScaleStepRad;
    +        float x = ::cos(angle) * m_radius;
    +        float y = ::sin(angle) * m_radius;
    +        float z = 0.0f;
    +        ::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z);
    +    }
    +    ::glEnd();
    +}
    +
    +void GLGizmoRotate::render_scale() const
    +{
    +    float out_radius_long = m_radius + ScaleLongTooth;
    +    float out_radius_short = m_radius + ScaleShortTooth;
    +
    +    ::glBegin(GL_LINES);
    +    for (unsigned int i = 0; i < ScaleStepsCount; ++i)
    +    {
    +        float angle = (float)i * ScaleStepRad;
    +        float cosa = ::cos(angle);
    +        float sina = ::sin(angle);
    +        float in_x = cosa * m_radius;
    +        float in_y = sina * m_radius;
    +        float in_z = 0.0f;
    +        float out_x = (i % ScaleLongEvery == 0) ? cosa * out_radius_long : cosa * out_radius_short;
    +        float out_y = (i % ScaleLongEvery == 0) ? sina * out_radius_long : sina * out_radius_short;
    +        float out_z = 0.0f;
    +        ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, (GLfloat)in_z);
    +        ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, (GLfloat)out_z);
    +    }
    +    ::glEnd();
    +}
    +
    +void GLGizmoRotate::render_snap_radii() const
    +{
    +    float step = 2.0f * (float)PI / (float)SnapRegionsCount;
    +
    +    float in_radius = m_radius / 3.0f;
    +    float out_radius = 2.0f * in_radius;
    +
    +    ::glBegin(GL_LINES);
    +    for (unsigned int i = 0; i < SnapRegionsCount; ++i)
    +    {
    +        float angle = (float)i * step;
    +        float cosa = ::cos(angle);
    +        float sina = ::sin(angle);
    +        float in_x = cosa * in_radius;
    +        float in_y = sina * in_radius;
    +        float in_z = 0.0f;
    +        float out_x = cosa * out_radius;
    +        float out_y = sina * out_radius;
    +        float out_z = 0.0f;
    +        ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, (GLfloat)in_z);
    +        ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, (GLfloat)out_z);
    +    }
    +    ::glEnd();
    +}
    +
    +void GLGizmoRotate::render_reference_radius() const
    +{
    +    ::glBegin(GL_LINES);
    +    ::glVertex3f(0.0f, 0.0f, 0.0f);
    +    ::glVertex3f((GLfloat)(m_radius + GrabberOffset), 0.0f, 0.0f);
    +    ::glEnd();
    +}
    +
    +void GLGizmoRotate::render_angle() const
    +{
    +    float step_angle = m_angle / AngleResolution;
    +    float ex_radius = m_radius + GrabberOffset;
    +
    +    ::glBegin(GL_LINE_STRIP);
    +    for (unsigned int i = 0; i <= AngleResolution; ++i)
    +    {
    +        float angle = (float)i * step_angle;
    +        float x = ::cos(angle) * ex_radius;
    +        float y = ::sin(angle) * ex_radius;
    +        float z = 0.0f;
    +        ::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z);
    +    }
    +    ::glEnd();
    +}
    +
    +void GLGizmoRotate::render_grabber() const
    +{
    +    float grabber_radius = m_radius + GrabberOffset;
    +    m_grabbers[0].center = Vec3d(::cos(m_angle) * grabber_radius, ::sin(m_angle) * grabber_radius, 0.0);
    +    m_grabbers[0].angle_z = m_angle;
    +
    +#if ENABLE_GIZMOS_3D
    +    ::glColor3fv((m_hover_id != -1) ? m_drag_color : m_highlight_color);
    +#else
    +    ::glColor3fv(m_drag_color);
    +#endif // ENABLE_GIZMOS_3D
    +
    +    ::glBegin(GL_LINES);
    +    ::glVertex3f(0.0f, 0.0f, 0.0f);
    +    ::glVertex3f((GLfloat)m_grabbers[0].center(0), (GLfloat)m_grabbers[0].center(1), (GLfloat)m_grabbers[0].center(2));
    +    ::glEnd();
    +
    +    ::memcpy((void*)m_grabbers[0].color, (const void*)m_highlight_color, 3 * sizeof(float));
    +    render_grabbers();
    +}
    +
    +void GLGizmoRotate::transform_to_local() const
    +{
    +    ::glTranslatef((GLfloat)m_center(0), (GLfloat)m_center(1), (GLfloat)m_center(2));
    +
    +    switch (m_axis)
    +    {
    +    case X:
    +    {
    +        ::glRotatef(90.0f, 0.0f, 1.0f, 0.0f);
    +        ::glRotatef(90.0f, 0.0f, 0.0f, 1.0f);
    +        break;
    +    }
    +    case Y:
    +    {
    +        ::glRotatef(-90.0f, 1.0f, 0.0f, 0.0f);
    +        ::glRotatef(180.0f, 0.0f, 0.0f, 1.0f);
    +        break;
    +    }
    +    default:
    +    case Z:
    +    {
    +        // no rotation
    +        break;
    +    }
    +    }
    +}
    +
    +Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray) const
    +{
    +    double half_pi = 0.5 * (double)PI;
    +
    +    Transform3d m = Transform3d::Identity();
    +
    +    switch (m_axis)
    +    {
    +    case X:
    +    {
    +        m.rotate(Eigen::AngleAxisd(-half_pi, Vec3d::UnitZ()));
    +        m.rotate(Eigen::AngleAxisd(-half_pi, Vec3d::UnitY()));
    +        break;
    +    }
    +    case Y:
    +    {
    +        m.rotate(Eigen::AngleAxisd(-(double)PI, Vec3d::UnitZ()));
    +        m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitX()));
    +        break;
    +    }
    +    default:
    +    case Z:
    +    {
    +        // no rotation applied
    +        break;
    +    }
    +    }
    +
    +    m.translate(-m_center);
    +
    +    Eigen::Matrix world_ray;
    +    Eigen::Matrix local_ray;
    +    world_ray(0, 0) = mouse_ray.a(0);
    +    world_ray(1, 0) = mouse_ray.a(1);
    +    world_ray(2, 0) = mouse_ray.a(2);
    +    world_ray(0, 1) = mouse_ray.b(0);
    +    world_ray(1, 1) = mouse_ray.b(1);
    +    world_ray(2, 1) = mouse_ray.b(2);
    +    local_ray = m * world_ray.colwise().homogeneous();
    +
    +    return Linef3(Vec3d(local_ray(0, 0), local_ray(1, 0), local_ray(2, 0)), Vec3d(local_ray(0, 1), local_ray(1, 1), local_ray(2, 1))).intersect_plane(0.0);
    +}
    +
    +GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent)
    +    : GLGizmoBase(parent)
    +    , m_x(parent, GLGizmoRotate::X)
    +    , m_y(parent, GLGizmoRotate::Y)
    +    , m_z(parent, GLGizmoRotate::Z)
    +{
    +    m_is_container = true;
    +
    +    m_x.set_group_id(0);
    +    m_y.set_group_id(1);
    +    m_z.set_group_id(2);
    +}
    +
    +bool GLGizmoRotate3D::on_init()
    +{
    +    if (!m_x.init() || !m_y.init() || !m_z.init())
    +        return false;
    +
    +    m_x.set_highlight_color(RED);
    +    m_y.set_highlight_color(GREEN);
    +    m_z.set_highlight_color(BLUE);
    +
         std::string path = resources_dir() + "/icons/overlay/";
     
         std::string filename = path + "rotate_off.png";
    @@ -222,194 +632,82 @@ bool GLGizmoRotate::on_init()
         if (!m_textures[On].load_from_file(filename, false))
             return false;
     
    -    m_grabbers.push_back(Grabber());
    -
         return true;
     }
     
    -void GLGizmoRotate::on_set_state()
    +void GLGizmoRotate3D::on_start_dragging()
     {
    -    m_keep_radius = (m_state == On) ? false : true;
    -}
    -
    -void GLGizmoRotate::on_update(const Pointf& mouse_pos)
    -{
    -    Vectorf orig_dir(1.0, 0.0);
    -    Vectorf new_dir = (mouse_pos - m_center).normalized();
    -    coordf_t theta = ::acos(clamp(-1.0, 1.0, new_dir.dot(orig_dir)));
    -    if (cross2(orig_dir, new_dir) < 0.0)
    -        theta = 2.0 * (coordf_t)PI - theta;
    -
    -    // snap
    -    if ((mouse_pos - m_center).norm() < 2.0 * (double)m_radius / 3.0)
    +    switch (m_hover_id)
         {
    -        coordf_t step = 2.0 * (coordf_t)PI / (coordf_t)SnapRegionsCount;
    -        theta = step * (coordf_t)std::round(theta / step);
    -    }
    -
    -    if (theta == 2.0 * (coordf_t)PI)
    -        theta = 0.0;
    -
    -    m_angle_z = (float)theta;
    -}
    -
    -void GLGizmoRotate::on_refresh()
    -{
    -    m_keep_radius = false;
    -}
    -
    -void GLGizmoRotate::on_render(const BoundingBoxf3& box) const
    -{
    -    ::glDisable(GL_DEPTH_TEST);
    -
    -    const Pointf size = box.size().xy();
    -    m_center = box.center().xy();
    -    if (!m_keep_radius)
    +    case 0:
         {
    -        m_radius = Offset + ::sqrt(sqr(0.5f * size(0)) + sqr(0.5f * size(1)));
    -        m_keep_radius = true;
    +        m_x.start_dragging();
    +        break;
         }
    -
    -    ::glLineWidth(2.0f);
    -    ::glColor3fv(BaseColor);
    -
    -    _render_circle();
    -    _render_scale();
    -    _render_snap_radii();
    -    _render_reference_radius();
    -
    -    ::glColor3fv(HighlightColor);
    -    _render_angle_z();
    -    _render_grabber();
    -}
    -
    -void GLGizmoRotate::on_render_for_picking(const BoundingBoxf3& box) const
    -{
    -    ::glDisable(GL_DEPTH_TEST);
    -
    -    m_grabbers[0].color[0] = 1.0f;
    -    m_grabbers[0].color[1] = 1.0f;
    -    m_grabbers[0].color[2] = 254.0f / 255.0f;
    -    render_grabbers();
    -}
    -
    -void GLGizmoRotate::_render_circle() const
    -{
    -    ::glBegin(GL_LINE_LOOP);
    -    for (unsigned int i = 0; i < ScaleStepsCount; ++i)
    +    case 1:
         {
    -        float angle = (float)i * ScaleStepRad;
    -        float x = m_center(0) + ::cos(angle) * m_radius;
    -        float y = m_center(1) + ::sin(angle) * m_radius;
    -        ::glVertex3f((GLfloat)x, (GLfloat)y, 0.0f);
    +        m_y.start_dragging();
    +        break;
         }
    -    ::glEnd();
    -}
    -
    -void GLGizmoRotate::_render_scale() const
    -{
    -    float out_radius_long = m_radius + ScaleLongTooth;
    -    float out_radius_short = m_radius + ScaleShortTooth;
    -
    -    ::glBegin(GL_LINES);
    -    for (unsigned int i = 0; i < ScaleStepsCount; ++i)
    +    case 2:
         {
    -        float angle = (float)i * ScaleStepRad;
    -        float cosa = ::cos(angle);
    -        float sina = ::sin(angle);
    -        float in_x = m_center(0) + cosa * m_radius;
    -        float in_y = m_center(1) + sina * m_radius;
    -        float out_x = (i % ScaleLongEvery == 0) ? m_center(0) + cosa * out_radius_long : m_center(0) + cosa * out_radius_short;
    -        float out_y = (i % ScaleLongEvery == 0) ? m_center(1) + sina * out_radius_long : m_center(1) + sina * out_radius_short;
    -        ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, 0.0f);
    -        ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, 0.0f);
    +        m_z.start_dragging();
    +        break;
         }
    -    ::glEnd();
    -}
    -
    -void GLGizmoRotate::_render_snap_radii() const
    -{
    -    float step = 2.0f * (float)PI / (float)SnapRegionsCount;
    -
    -    float in_radius = m_radius / 3.0f;
    -    float out_radius = 2.0f * in_radius;
    -
    -    ::glBegin(GL_LINES);
    -    for (unsigned int i = 0; i < SnapRegionsCount; ++i)
    +    default:
         {
    -        float angle = (float)i * step;
    -        float cosa = ::cos(angle);
    -        float sina = ::sin(angle);
    -        float in_x = m_center(0) + cosa * in_radius;
    -        float in_y = m_center(1) + sina * in_radius;
    -        float out_x = m_center(0) + cosa * out_radius;
    -        float out_y = m_center(1) + sina * out_radius;
    -        ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, 0.0f);
    -        ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, 0.0f);
    +        break;
    +    }
         }
    -    ::glEnd();
     }
     
    -void GLGizmoRotate::_render_reference_radius() const
    +void GLGizmoRotate3D::on_stop_dragging()
     {
    -    ::glBegin(GL_LINES);
    -    ::glVertex3f((GLfloat)m_center(0), (GLfloat)m_center(1), 0.0f);
    -    ::glVertex3f((GLfloat)m_center(0) + m_radius + GrabberOffset, (GLfloat)m_center(1), 0.0f);
    -    ::glEnd();
    -}
    -
    -void GLGizmoRotate::_render_angle_z() const
    -{
    -    float step_angle = m_angle_z / AngleResolution;
    -    float ex_radius = m_radius + GrabberOffset;
    -
    -    ::glBegin(GL_LINE_STRIP);
    -    for (unsigned int i = 0; i <= AngleResolution; ++i)
    +    switch (m_hover_id)
         {
    -        float angle = (float)i * step_angle;
    -        float x = m_center(0) + ::cos(angle) * ex_radius;
    -        float y = m_center(1) + ::sin(angle) * ex_radius;
    -        ::glVertex3f((GLfloat)x, (GLfloat)y, 0.0f);
    +    case 0:
    +    {
    +        m_x.stop_dragging();
    +        break;
    +    }
    +    case 1:
    +    {
    +        m_y.stop_dragging();
    +        break;
    +    }
    +    case 2:
    +    {
    +        m_z.stop_dragging();
    +        break;
    +    }
    +    default:
    +    {
    +        break;
    +    }
         }
    -    ::glEnd();
     }
     
    -void GLGizmoRotate::_render_grabber() const
    +void GLGizmoRotate3D::on_render(const BoundingBoxf3& box) const
     {
    -    float grabber_radius = m_radius + GrabberOffset;
    -    m_grabbers[0].center(0) = m_center(0) + ::cos(m_angle_z) * grabber_radius;
    -    m_grabbers[0].center(1) = m_center(1) + ::sin(m_angle_z) * grabber_radius;
    -    m_grabbers[0].angle_z = m_angle_z;
    +    if ((m_hover_id == -1) || (m_hover_id == 0))
    +        m_x.render(box);
     
    -    ::glColor3fv(BaseColor);
    -    ::glBegin(GL_LINES);
    -    ::glVertex3f((GLfloat)m_center(0), (GLfloat)m_center(1), 0.0f);
    -    ::glVertex3f((GLfloat)m_grabbers[0].center(0), (GLfloat)m_grabbers[0].center(1), 0.0f);
    -    ::glEnd();
    +    if ((m_hover_id == -1) || (m_hover_id == 1))
    +        m_y.render(box);
     
    -    ::memcpy((void*)m_grabbers[0].color, (const void*)HighlightColor, 3 * sizeof(float));
    -    render_grabbers();
    +    if ((m_hover_id == -1) || (m_hover_id == 2))
    +        m_z.render(box);
     }
     
     const float GLGizmoScale::Offset = 5.0f;
     
    -GLGizmoScale::GLGizmoScale()
    -    : GLGizmoBase()
    +GLGizmoScale::GLGizmoScale(GLCanvas3D& parent)
    +    : GLGizmoBase(parent)
         , m_scale(1.0f)
         , m_starting_scale(1.0f)
     {
     }
     
    -float GLGizmoScale::get_scale() const
    -{
    -    return m_scale;
    -}
    -
    -void GLGizmoScale::set_scale(float scale)
    -{
    -    m_starting_scale = scale;
    -}
    -
     bool GLGizmoScale::on_init()
     {
         std::string path = resources_dir() + "/icons/overlay/";
    @@ -437,40 +735,41 @@ bool GLGizmoScale::on_init()
     void GLGizmoScale::on_start_dragging()
     {
         if (m_hover_id != -1)
    -        m_starting_drag_position = m_grabbers[m_hover_id].center;
    +        m_starting_drag_position = to_2d(m_grabbers[m_hover_id].center);
     }
     
    -void GLGizmoScale::on_update(const Pointf& mouse_pos)
    +void GLGizmoScale::on_update(const Linef3& mouse_ray)
     {
    -    Pointf center(0.5 * (m_grabbers[1].center(0) + m_grabbers[0].center(0)), 0.5 * (m_grabbers[3].center(1) + m_grabbers[0].center(1)));
    +    Vec2d mouse_pos = to_2d(mouse_ray.intersect_plane(0.0));
    +    Vec2d center(0.5 * (m_grabbers[1].center(0) + m_grabbers[0].center(0)), 0.5 * (m_grabbers[3].center(1) + m_grabbers[0].center(1)));
     
    -    coordf_t orig_len = (m_starting_drag_position - center).norm();
    -    coordf_t new_len = (mouse_pos - center).norm();
    -    coordf_t ratio = (orig_len != 0.0) ? new_len / orig_len : 1.0;
    +    double orig_len = (m_starting_drag_position - center).norm();
    +    double new_len = (mouse_pos - center).norm();
    +    double ratio = (orig_len != 0.0) ? new_len / orig_len : 1.0;
     
         m_scale = m_starting_scale * (float)ratio;
     }
     
     void GLGizmoScale::on_render(const BoundingBoxf3& box) const
     {
    +    if (m_grabbers[0].dragging || m_grabbers[1].dragging || m_grabbers[2].dragging || m_grabbers[3].dragging)
    +        set_tooltip(format(100.0f * m_scale, 4) + "%");
    +
         ::glDisable(GL_DEPTH_TEST);
     
    -    coordf_t min_x = box.min(0) - (coordf_t)Offset;
    -    coordf_t max_x = box.max(0) + (coordf_t)Offset;
    -    coordf_t min_y = box.min(1) - (coordf_t)Offset;
    -    coordf_t max_y = box.max(1) + (coordf_t)Offset;
    +    double min_x = box.min(0) - (double)Offset;
    +    double max_x = box.max(0) + (double)Offset;
    +    double min_y = box.min(1) - (double)Offset;
    +    double max_y = box.max(1) + (double)Offset;
     
    -    m_grabbers[0].center(0) = min_x;
    -    m_grabbers[0].center(1) = min_y;
    -    m_grabbers[1].center(0) = max_x;
    -    m_grabbers[1].center(1) = min_y;
    -    m_grabbers[2].center(0) = max_x;
    -    m_grabbers[2].center(1) = max_y;
    -    m_grabbers[3].center(0) = min_x;
    -    m_grabbers[3].center(1) = max_y;
    +    m_grabbers[0].center = Vec3d(min_x, min_y, 0.0);
    +    m_grabbers[1].center = Vec3d(max_x, min_y, 0.0);
    +    m_grabbers[2].center = Vec3d(max_x, max_y, 0.0);
    +    m_grabbers[3].center = Vec3d(min_x, max_y, 0.0);
     
         ::glLineWidth(2.0f);
    -    ::glColor3fv(BaseColor);
    +    ::glColor3fv(m_drag_color);
    +
         // draw outline
         ::glBegin(GL_LINE_LOOP);
         for (unsigned int i = 0; i < 4; ++i)
    @@ -482,24 +781,388 @@ void GLGizmoScale::on_render(const BoundingBoxf3& box) const
         // draw grabbers
         for (unsigned int i = 0; i < 4; ++i)
         {
    -        ::memcpy((void*)m_grabbers[i].color, (const void*)HighlightColor, 3 * sizeof(float));
    +        ::memcpy((void*)m_grabbers[i].color, (const void*)m_highlight_color, 3 * sizeof(float));
         }
         render_grabbers();
     }
     
     void GLGizmoScale::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 < 4; ++i)
         {
             m_grabbers[i].color[0] = 1.0f;
             m_grabbers[i].color[1] = 1.0f;
    -        m_grabbers[i].color[2] = (254.0f - (float)i) * INV_255;
    +        m_grabbers[i].color[2] = picking_color_component(i);
         }
    -    render_grabbers();
    +    render_grabbers_for_picking();
    +}
    +
    +const float GLGizmoScale3D::Offset = 5.0f;
    +
    +GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent)
    +    : GLGizmoBase(parent)
    +    , m_scale_x(1.0f)
    +    , m_scale_y(1.0f)
    +    , m_scale_z(1.0f)
    +    , m_starting_scale_x(1.0f)
    +    , m_starting_scale_y(1.0f)
    +    , m_starting_scale_z(1.0f)
    +{
    +}
    +
    +bool GLGizmoScale3D::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;
    +
    +    for (int i = 0; i < 10; ++i)
    +    {
    +        m_grabbers.push_back(Grabber());
    +    }
    +
    +    float half_pi = 0.5f * (float)PI;
    +
    +    // x axis
    +    m_grabbers[0].angle_y = half_pi;
    +    m_grabbers[1].angle_y = half_pi;
    +
    +    // y axis
    +    m_grabbers[2].angle_x = half_pi;
    +    m_grabbers[3].angle_x = half_pi;
    +
    +    return true;
    +}
    +
    +void GLGizmoScale3D::on_start_dragging()
    +{
    +    if (m_hover_id != -1)
    +    {
    +        m_starting_drag_position = m_grabbers[m_hover_id].center;
    +        m_starting_center = m_box.center();
    +    }
    +}
    +
    +void GLGizmoScale3D::on_update(const Linef3& mouse_ray)
    +{
    +    if ((m_hover_id == 0) || (m_hover_id == 1))
    +        do_scale_x(mouse_ray);
    +    else if ((m_hover_id == 2) || (m_hover_id == 3))
    +        do_scale_y(mouse_ray);
    +    else if ((m_hover_id == 4) || (m_hover_id == 5))
    +        do_scale_z(mouse_ray);
    +    else if (m_hover_id >= 6)
    +        do_scale_uniform(mouse_ray);
    +}
    +
    +void GLGizmoScale3D::on_render(const BoundingBoxf3& box) const
    +{
    +    if (m_grabbers[0].dragging || m_grabbers[1].dragging)
    +        set_tooltip("X: " + format(100.0f * m_scale_x, 4) + "%");
    +    else if (m_grabbers[2].dragging || m_grabbers[3].dragging)
    +        set_tooltip("Y: " + format(100.0f * m_scale_y, 4) + "%");
    +    else if (m_grabbers[4].dragging || m_grabbers[5].dragging)
    +        set_tooltip("Z: " + format(100.0f * m_scale_z, 4) + "%");
    +    else if (m_grabbers[6].dragging || m_grabbers[7].dragging || m_grabbers[8].dragging || m_grabbers[9].dragging)
    +    {
    +        std::string tooltip = "X: " + format(100.0f * m_scale_x, 4) + "%\n";
    +        tooltip += "Y: " + format(100.0f * m_scale_y, 4) + "%\n";
    +        tooltip += "Z: " + format(100.0f * m_scale_z, 4) + "%";
    +        set_tooltip(tooltip);
    +    }
    +
    +    ::glEnable(GL_DEPTH_TEST);
    +
    +    Vec3d offset_vec = (double)Offset * Vec3d::Ones();
    +
    +    m_box = BoundingBoxf3(box.min - offset_vec, box.max + offset_vec);
    +    const Vec3d& center = m_box.center();
    +
    +    // x axis
    +    m_grabbers[0].center = Vec3d(m_box.min(0), center(1), center(2));
    +    m_grabbers[1].center = Vec3d(m_box.max(0), center(1), center(2));
    +    ::memcpy((void*)m_grabbers[0].color, (const void*)RED, 3 * sizeof(float));
    +    ::memcpy((void*)m_grabbers[1].color, (const void*)RED, 3 * sizeof(float));
    +
    +    // y axis
    +    m_grabbers[2].center = Vec3d(center(0), m_box.min(1), center(2));
    +    m_grabbers[3].center = Vec3d(center(0), m_box.max(1), center(2));
    +    ::memcpy((void*)m_grabbers[2].color, (const void*)GREEN, 3 * sizeof(float));
    +    ::memcpy((void*)m_grabbers[3].color, (const void*)GREEN, 3 * sizeof(float));
    +
    +    // z axis
    +    m_grabbers[4].center = Vec3d(center(0), center(1), m_box.min(2));
    +    m_grabbers[5].center = Vec3d(center(0), center(1), m_box.max(2));
    +    ::memcpy((void*)m_grabbers[4].color, (const void*)BLUE, 3 * sizeof(float));
    +    ::memcpy((void*)m_grabbers[5].color, (const void*)BLUE, 3 * sizeof(float));
    +
    +    // uniform
    +    m_grabbers[6].center = Vec3d(m_box.min(0), m_box.min(1), m_box.min(2));
    +    m_grabbers[7].center = Vec3d(m_box.max(0), m_box.min(1), m_box.min(2));
    +    m_grabbers[8].center = Vec3d(m_box.max(0), m_box.max(1), m_box.min(2));
    +    m_grabbers[9].center = Vec3d(m_box.min(0), m_box.max(1), m_box.min(2));
    +    for (int i = 6; i < 10; ++i)
    +    {
    +        ::memcpy((void*)m_grabbers[i].color, (const void*)m_highlight_color, 3 * sizeof(float));
    +    }
    +
    +    ::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f);
    +
    +    if (m_hover_id == -1)
    +    {
    +        ::glColor3fv(m_base_color);
    +        // draw box
    +        render_box();
    +        // draw grabbers
    +        render_grabbers();
    +    }
    +    else if ((m_hover_id == 0) || (m_hover_id == 1))
    +    {
    +        ::glColor3fv(m_grabbers[0].color);
    +        // draw connection
    +        render_grabbers_connection(0, 1);
    +        // draw grabbers
    +        m_grabbers[0].render(true);
    +        m_grabbers[1].render(true);
    +    }
    +    else if ((m_hover_id == 2) || (m_hover_id == 3))
    +    {
    +        ::glColor3fv(m_grabbers[2].color);
    +        // draw connection
    +        render_grabbers_connection(2, 3);
    +        // draw grabbers
    +        m_grabbers[2].render(true);
    +        m_grabbers[3].render(true);
    +    }
    +    else if ((m_hover_id == 4) || (m_hover_id == 5))
    +    {
    +        ::glColor3fv(m_grabbers[4].color);
    +        // draw connection
    +        render_grabbers_connection(4, 5);
    +        // draw grabbers
    +        m_grabbers[4].render(true);
    +        m_grabbers[5].render(true);
    +    }
    +    else if (m_hover_id >= 6)
    +    {
    +        ::glColor3fv(m_drag_color);
    +        // draw box
    +        render_box();
    +        // draw grabbers
    +        for (int i = 6; i < 10; ++i)
    +        {
    +            m_grabbers[i].render(true);
    +        }
    +    }
    +}
    +
    +void GLGizmoScale3D::on_render_for_picking(const BoundingBoxf3& box) const
    +{
    +    ::glDisable(GL_DEPTH_TEST);
    +
    +    for (unsigned int i = 0; i < (unsigned int)m_grabbers.size(); ++i)
    +    {
    +        m_grabbers[i].color[0] = 1.0f;
    +        m_grabbers[i].color[1] = 1.0f;
    +        m_grabbers[i].color[2] = picking_color_component(i);
    +    }
    +
    +    render_grabbers_for_picking();
    +}
    +
    +void GLGizmoScale3D::render_box() const
    +{
    +    // bottom face
    +    ::glBegin(GL_LINE_LOOP);
    +    ::glVertex3f((GLfloat)m_box.min(0), (GLfloat)m_box.min(1), (GLfloat)m_box.min(2));
    +    ::glVertex3f((GLfloat)m_box.min(0), (GLfloat)m_box.max(1), (GLfloat)m_box.min(2));
    +    ::glVertex3f((GLfloat)m_box.max(0), (GLfloat)m_box.max(1), (GLfloat)m_box.min(2));
    +    ::glVertex3f((GLfloat)m_box.max(0), (GLfloat)m_box.min(1), (GLfloat)m_box.min(2));
    +    ::glEnd();
    +
    +    // top face
    +    ::glBegin(GL_LINE_LOOP);
    +    ::glVertex3f((GLfloat)m_box.min(0), (GLfloat)m_box.min(1), (GLfloat)m_box.max(2));
    +    ::glVertex3f((GLfloat)m_box.min(0), (GLfloat)m_box.max(1), (GLfloat)m_box.max(2));
    +    ::glVertex3f((GLfloat)m_box.max(0), (GLfloat)m_box.max(1), (GLfloat)m_box.max(2));
    +    ::glVertex3f((GLfloat)m_box.max(0), (GLfloat)m_box.min(1), (GLfloat)m_box.max(2));
    +    ::glEnd();
    +
    +    // vertical edges
    +    ::glBegin(GL_LINES);
    +    ::glVertex3f((GLfloat)m_box.min(0), (GLfloat)m_box.min(1), (GLfloat)m_box.min(2)); ::glVertex3f((GLfloat)m_box.min(0), (GLfloat)m_box.min(1), (GLfloat)m_box.max(2));
    +    ::glVertex3f((GLfloat)m_box.min(0), (GLfloat)m_box.max(1), (GLfloat)m_box.min(2)); ::glVertex3f((GLfloat)m_box.min(0), (GLfloat)m_box.max(1), (GLfloat)m_box.max(2));
    +    ::glVertex3f((GLfloat)m_box.max(0), (GLfloat)m_box.max(1), (GLfloat)m_box.min(2)); ::glVertex3f((GLfloat)m_box.max(0), (GLfloat)m_box.max(1), (GLfloat)m_box.max(2));
    +    ::glVertex3f((GLfloat)m_box.max(0), (GLfloat)m_box.min(1), (GLfloat)m_box.min(2)); ::glVertex3f((GLfloat)m_box.max(0), (GLfloat)m_box.min(1), (GLfloat)m_box.max(2));
    +    ::glEnd();
    +}
    +
    +void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2) const
    +{
    +    unsigned int grabbers_count = (unsigned int)m_grabbers.size();
    +    if ((id_1 < grabbers_count) && (id_2 < grabbers_count))
    +    {
    +        ::glBegin(GL_LINES);
    +        ::glVertex3f((GLfloat)m_grabbers[id_1].center(0), (GLfloat)m_grabbers[id_1].center(1), (GLfloat)m_grabbers[id_1].center(2));
    +        ::glVertex3f((GLfloat)m_grabbers[id_2].center(0), (GLfloat)m_grabbers[id_2].center(1), (GLfloat)m_grabbers[id_2].center(2));
    +        ::glEnd();
    +    }
    +}
    +
    +Linef3 transform(const Linef3& line, const Transform3d& t)
    +{
    +    Eigen::Matrix world_line;
    +    Eigen::Matrix local_line;
    +    world_line(0, 0) = line.a(0);
    +    world_line(1, 0) = line.a(1);
    +    world_line(2, 0) = line.a(2);
    +    world_line(0, 1) = line.b(0);
    +    world_line(1, 1) = line.b(1);
    +    world_line(2, 1) = line.b(2);
    +    local_line = t * world_line.colwise().homogeneous();
    +
    +    return Linef3(Vec3d(local_line(0, 0), local_line(1, 0), local_line(2, 0)), Vec3d(local_line(0, 1), local_line(1, 1), local_line(2, 1)));
    +}
    +
    +void GLGizmoScale3D::do_scale_x(const Linef3& mouse_ray)
    +{
    +    double ratio = calc_ratio(1, mouse_ray, m_starting_center);
    +
    +    if (ratio > 0.0)
    +        m_scale_x = m_starting_scale_x * (float)ratio;
    +}
    +
    +void GLGizmoScale3D::do_scale_y(const Linef3& mouse_ray)
    +{
    +    double ratio = calc_ratio(2, mouse_ray, m_starting_center);
    +
    +    if (ratio > 0.0)
    +        m_scale_x = m_starting_scale_y * (float)ratio; // << this is temporary
    +//        m_scale_y = m_starting_scale_y * (float)ratio;
    +}
    +
    +void GLGizmoScale3D::do_scale_z(const Linef3& mouse_ray)
    +{
    +    double ratio = calc_ratio(1, mouse_ray, m_starting_center);
    +
    +    if (ratio > 0.0)
    +        m_scale_x = m_starting_scale_z * (float)ratio; // << this is temporary
    +//        m_scale_z = m_starting_scale_z * (float)ratio;
    +}
    +
    +void GLGizmoScale3D::do_scale_uniform(const Linef3& mouse_ray)
    +{
    +    Vec3d center = m_starting_center;
    +    center(2) = m_box.min(2);
    +    double ratio = calc_ratio(0, mouse_ray, center);
    +
    +    if (ratio > 0.0)
    +    {
    +        m_scale_x = m_starting_scale_x * (float)ratio;
    +        m_scale_y = m_starting_scale_y * (float)ratio;
    +        m_scale_z = m_starting_scale_z * (float)ratio;
    +    }
    +}
    +
    +double GLGizmoScale3D::calc_ratio(unsigned int preferred_plane_id, const Linef3& mouse_ray, const Vec3d& center) const
    +{
    +    double ratio = 0.0;
    +
    +    Vec3d starting_vec = m_starting_drag_position - center;
    +    double len_starting_vec = starting_vec.norm();
    +    if (len_starting_vec == 0.0)
    +        return ratio;
    +
    +    Vec3d starting_vec_dir = starting_vec.normalized();
    +    Vec3d mouse_dir = mouse_ray.unit_vector();
    +    unsigned int plane_id = preferred_plane_id;
    +
    +    // 1st try to see if the mouse direction is close enough to the preferred plane normal
    +    double dot_to_normal = 0.0;
    +    switch (plane_id)
    +    {
    +    case 0:
    +    {
    +        dot_to_normal = std::abs(mouse_dir.dot(Vec3d::UnitZ()));
    +        break;
    +    }
    +    case 1:
    +    {
    +        dot_to_normal = std::abs(mouse_dir.dot(-Vec3d::UnitY()));
    +        break;
    +    }
    +    case 2:
    +    {
    +        dot_to_normal = std::abs(mouse_dir.dot(Vec3d::UnitX()));
    +        break;
    +    }
    +    }
    +
    +    if (dot_to_normal < 0.1)
    +    {
    +        // if not, select the plane who's normal is closest to the mouse direction
    +
    +        typedef std::map ProjsMap;
    +        ProjsMap projs_map;
    +
    +        projs_map.insert(ProjsMap::value_type(std::abs(mouse_dir.dot(Vec3d::UnitZ())), 0));  // plane xy
    +        projs_map.insert(ProjsMap::value_type(std::abs(mouse_dir.dot(-Vec3d::UnitY())), 1)); // plane xz
    +        projs_map.insert(ProjsMap::value_type(std::abs(mouse_dir.dot(Vec3d::UnitX())), 2));  // plane yz
    +        plane_id = projs_map.rbegin()->second;
    +    }
    +
    +    switch (plane_id)
    +    {
    +    case 0:
    +    {
    +        // calculates the intersection of the mouse ray with the plane parallel to plane XY and passing through the given center
    +        Transform3d m = Transform3d::Identity();
    +        m.translate(-center);
    +        Vec2d mouse_pos_2d = to_2d(transform(mouse_ray, m).intersect_plane(0.0));
    +
    +        // ratio is given by the projection of the calculated intersection on the starting vector divided by the starting vector length
    +        ratio = starting_vec_dir.dot(Vec3d(mouse_pos_2d(0), mouse_pos_2d(1), 0.0)) / len_starting_vec;
    +        break;
    +    }
    +    case 1:
    +    {
    +        // calculates the intersection of the mouse ray with the plane parallel to plane XZ and passing through the given center
    +        Transform3d m = Transform3d::Identity();
    +        m.rotate(Eigen::AngleAxisd(-0.5 * (double)PI, Vec3d::UnitX()));
    +        m.translate(-center);
    +        Vec2d mouse_pos_2d = to_2d(transform(mouse_ray, m).intersect_plane(0.0));
    +
    +        // ratio is given by the projection of the calculated intersection on the starting vector divided by the starting vector length
    +        ratio = starting_vec_dir.dot(Vec3d(mouse_pos_2d(0), 0.0, mouse_pos_2d(1))) / len_starting_vec;
    +        break;
    +    }
    +    case 2:
    +    {
    +        // calculates the intersection of the mouse ray with the plane parallel to plane YZ and passing through the given center
    +        Transform3d m = Transform3d::Identity();
    +        m.rotate(Eigen::AngleAxisd(-0.5f * (double)PI, Vec3d::UnitY()));
    +        m.translate(-center);
    +        Vec2d mouse_pos_2d = to_2d(transform(mouse_ray, m).intersect_plane(0.0));
    +
    +        // ratio is given by the projection of the calculated intersection on the starting vector divided by the starting vector length
    +        ratio = starting_vec_dir.dot(Vec3d(0.0, mouse_pos_2d(1), -mouse_pos_2d(0))) / len_starting_vec;
    +        break;
    +    }
    +    }
    +
    +    return ratio;
     }
     
     } // namespace GUI
    diff --git a/xs/src/slic3r/GUI/GLGizmo.hpp b/xs/src/slic3r/GUI/GLGizmo.hpp
    index 506b3972e7..50bb333e5c 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.hpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.hpp
    @@ -3,33 +3,54 @@
     
     #include "../../slic3r/GUI/GLTexture.hpp"
     #include "../../libslic3r/Point.hpp"
    +#include "../../libslic3r/BoundingBox.hpp"
     
     #include 
     
    +#define ENABLE_GIZMOS_3D 1
    +
     namespace Slic3r {
     
     class BoundingBoxf3;
    -class Pointf3;
    +class Linef3;
     
     namespace GUI {
     
    +class GLCanvas3D;
    +
     class GLGizmoBase
     {
     protected:
    -    static const float BaseColor[3];
    -    static const float HighlightColor[3];
    -
         struct Grabber
         {
             static const float HalfSize;
    -        static const float HoverOffset;
    +        static const float DraggingScaleFactor;
     
    -        Pointf center;
    +        Vec3d center;
    +        float angle_x;
    +        float angle_y;
             float angle_z;
             float color[3];
    +        bool dragging;
     
             Grabber();
    +
             void render(bool hover) const;
    +#if ENABLE_GIZMOS_3D
    +        void render_for_picking() const { render(color, false); }
    +#else
    +        void render_for_picking() const { render(color); }
    +#endif // ENABLE_GIZMOS_3D
    +
    +    private:
    +#if ENABLE_GIZMOS_3D
    +        void render(const float* render_color, bool use_lighting) const;
    +#else
    +        void render(const float* render_color) const;
    +#endif // ENABLE_GIZMOS_3D
    +#if ENABLE_GIZMOS_3D
    +        void render_face(float half_size) const;
    +#endif // ENABLE_GIZMOS_3D
         };
     
     public:
    @@ -42,46 +63,64 @@ public:
         };
     
     protected:
    +    GLCanvas3D& m_parent;
    +
    +    int m_group_id;
         EState m_state;
         // textures are assumed to be square and all with the same size in pixels, no internal check is done
         GLTexture m_textures[Num_States];
         int m_hover_id;
    +    float m_base_color[3];
    +    float m_drag_color[3];
    +    float m_highlight_color[3];
         mutable std::vector m_grabbers;
    +    bool m_is_container;
     
     public:
    -    GLGizmoBase();
    -    virtual ~GLGizmoBase();
    +    explicit GLGizmoBase(GLCanvas3D& parent);
    +    virtual ~GLGizmoBase() {}
     
    -    bool init();
    +    bool init() { return on_init(); }
     
    -    EState get_state() const;
    -    void set_state(EState state);
    +    int get_group_id() const { return m_group_id; }
    +    void set_group_id(int id) { m_group_id = id; }
     
    -    unsigned int get_texture_id() const;
    -    int get_textures_size() const;
    +    EState get_state() const { return m_state; }
    +    void set_state(EState state) { m_state = state; on_set_state(); }
     
    -    int get_hover_id() const;
    +    unsigned int get_texture_id() const { return m_textures[m_state].get_id(); }
    +    int get_textures_size() const { return m_textures[Off].get_width(); }
    +
    +    int get_hover_id() const { return m_hover_id; }
         void set_hover_id(int id);
     
    +    void set_highlight_color(const float* color);
    +
         void start_dragging();
         void stop_dragging();
    -    void update(const Pointf& mouse_pos);
    -    void refresh();
    +    void update(const Linef3& mouse_ray);
    +    void refresh() { on_refresh(); }
     
    -    void render(const BoundingBoxf3& box) const;
    -    void render_for_picking(const BoundingBoxf3& box) const;
    +    void render(const BoundingBoxf3& box) const { on_render(box); }
    +    void render_for_picking(const BoundingBoxf3& box) const { on_render_for_picking(box); }
     
     protected:
         virtual bool on_init() = 0;
    -    virtual void on_set_state();
    -    virtual void on_start_dragging();
    -    virtual void on_stop_dragging();
    -    virtual void on_update(const Pointf& mouse_pos) = 0;
    -    virtual void on_refresh();
    +    virtual void on_set_state() {}
    +    virtual void on_set_hover_id() {}
    +    virtual void on_start_dragging() {}
    +    virtual void on_stop_dragging() {}
    +    virtual void on_update(const Linef3& mouse_ray) = 0;
    +    virtual void on_refresh() {}
         virtual void on_render(const BoundingBoxf3& box) const = 0;
         virtual void on_render_for_picking(const BoundingBoxf3& box) const = 0;
     
    +    float picking_color_component(unsigned int id) const;
         void render_grabbers() const;
    +    void render_grabbers_for_picking() const;
    +
    +    void set_tooltip(const std::string& tooltip) const;
    +    std::string format(float value, unsigned int decimals) const;
     };
     
     class GLGizmoRotate : public GLGizmoBase
    @@ -97,33 +136,102 @@ class GLGizmoRotate : public GLGizmoBase
         static const unsigned int SnapRegionsCount;
         static const float GrabberOffset;
     
    -    float m_angle_z;
    +public:
    +    enum Axis : unsigned char
    +    {
    +        X,
    +        Y,
    +        Z
    +    };
     
    -    mutable Pointf m_center;
    +private:
    +    Axis m_axis;
    +    float m_angle;
    +
    +    mutable Vec3d m_center;
         mutable float m_radius;
    -    mutable bool m_keep_radius;
    +    mutable bool m_keep_initial_values;
     
     public:
    -    GLGizmoRotate();
    +    GLGizmoRotate(GLCanvas3D& parent, Axis axis);
     
    -    float get_angle_z() const;
    -    void set_angle_z(float angle_z);
    +    float get_angle() const { return m_angle; }
    +    void set_angle(float angle);
     
     protected:
         virtual bool on_init();
    -    virtual void on_set_state();
    -    virtual void on_update(const Pointf& mouse_pos);
    -    virtual void on_refresh();
    +    virtual void on_set_state() { m_keep_initial_values = (m_state == On) ? false : true; }
    +    virtual void on_update(const Linef3& mouse_ray);
    +    virtual void on_refresh() { m_keep_initial_values = false; }
         virtual void on_render(const BoundingBoxf3& box) const;
         virtual void on_render_for_picking(const BoundingBoxf3& box) const;
     
     private:
    -    void _render_circle() const;
    -    void _render_scale() const;
    -    void _render_snap_radii() const;
    -    void _render_reference_radius() const;
    -    void _render_angle_z() const;
    -    void _render_grabber() const;
    +    void render_circle() const;
    +    void render_scale() const;
    +    void render_snap_radii() const;
    +    void render_reference_radius() const;
    +    void render_angle() const;
    +    void render_grabber() const;
    +
    +    void transform_to_local() const;
    +    // returns the intersection of the mouse ray with the plane perpendicular to the gizmo axis, in local coordinate
    +    Vec3d mouse_position_in_local_plane(const Linef3& mouse_ray) const;
    +};
    +
    +class GLGizmoRotate3D : public GLGizmoBase
    +{
    +    GLGizmoRotate m_x;
    +    GLGizmoRotate m_y;
    +    GLGizmoRotate m_z;
    +
    +public:
    +    explicit GLGizmoRotate3D(GLCanvas3D& parent);
    +
    +    float get_angle_x() const { return m_x.get_angle(); }
    +    void set_angle_x(float angle) { m_x.set_angle(angle); }
    +
    +    float get_angle_y() const { return m_y.get_angle(); }
    +    void set_angle_y(float angle) { m_y.set_angle(angle); }
    +
    +    float get_angle_z() const { return m_z.get_angle(); }
    +    void set_angle_z(float angle) { m_z.set_angle(angle); }
    +
    +protected:
    +    virtual bool on_init();
    +    virtual void on_set_state()
    +    {
    +        m_x.set_state(m_state);
    +        m_y.set_state(m_state);
    +        m_z.set_state(m_state);
    +    }
    +    virtual void on_set_hover_id()
    +    {
    +        m_x.set_hover_id(m_hover_id == 0 ? 0 : -1);
    +        m_y.set_hover_id(m_hover_id == 1 ? 0 : -1);
    +        m_z.set_hover_id(m_hover_id == 2 ? 0 : -1);
    +    }
    +    virtual void on_start_dragging();
    +    virtual void on_stop_dragging();
    +    virtual void on_update(const Linef3& mouse_ray)
    +    {
    +        m_x.update(mouse_ray);
    +        m_y.update(mouse_ray);
    +        m_z.update(mouse_ray);
    +    }
    +    virtual void on_refresh()
    +    {
    +        m_x.refresh();
    +        m_y.refresh();
    +        m_z.refresh();
    +    }
    +    virtual void on_render(const BoundingBoxf3& box) const;
    +    virtual void on_render_for_picking(const BoundingBoxf3& box) const
    +    {
    +        m_x.render_for_picking(box);
    +        m_y.render_for_picking(box);
    +        m_z.render_for_picking(box);
    +    }
     };
     
     class GLGizmoScale : public GLGizmoBase
    @@ -133,22 +241,77 @@ class GLGizmoScale : public GLGizmoBase
         float m_scale;
         float m_starting_scale;
     
    -    Pointf m_starting_drag_position;
    +    Vec2d m_starting_drag_position;
     
     public:
    -    GLGizmoScale();
    +    explicit GLGizmoScale(GLCanvas3D& parent);
     
    -    float get_scale() const;
    -    void set_scale(float scale);
    +    float get_scale() const { return m_scale; }
    +    void set_scale(float scale) { m_starting_scale = scale; }
     
     protected:
         virtual bool on_init();
         virtual void on_start_dragging();
    -    virtual void on_update(const Pointf& mouse_pos);
    +    virtual void on_update(const Linef3& mouse_ray);
         virtual void on_render(const BoundingBoxf3& box) const;
         virtual void on_render_for_picking(const BoundingBoxf3& box) const;
     };
     
    +class GLGizmoScale3D : public GLGizmoBase
    +{
    +    static const float Offset;
    +
    +    mutable BoundingBoxf3 m_box;
    +
    +    float m_scale_x;
    +    float m_scale_y;
    +    float m_scale_z;
    +
    +    float m_starting_scale_x;
    +    float m_starting_scale_y;
    +    float m_starting_scale_z;
    +
    +    Vec3d m_starting_drag_position;
    +    Vec3d m_starting_center;
    +
    +public:
    +    explicit GLGizmoScale3D(GLCanvas3D& parent);
    +
    +    float get_scale_x() const { return m_scale_x; }
    +    void set_scale_x(float scale) { m_starting_scale_x = scale; }
    +
    +    float get_scale_y() const { return m_scale_y; }
    +    void set_scale_y(float scale) { m_starting_scale_y = scale; }
    +
    +    float get_scale_z() const { return m_scale_z; }
    +    void set_scale_z(float scale) { m_starting_scale_z = scale; }
    +
    +    void set_scale(float scale)
    +    {
    +        m_starting_scale_x = scale;
    +        m_starting_scale_y = scale;
    +        m_starting_scale_z = scale;
    +    }
    +
    +protected:
    +    virtual bool on_init();
    +    virtual void on_start_dragging();
    +    virtual void on_update(const Linef3& mouse_ray);
    +    virtual void on_render(const BoundingBoxf3& box) const;
    +    virtual void on_render_for_picking(const BoundingBoxf3& box) const;
    +
    +private:
    +    void render_box() const;
    +    void render_grabbers_connection(unsigned int id_1, unsigned int id_2) const;
    +
    +    void do_scale_x(const Linef3& mouse_ray);
    +    void do_scale_y(const Linef3& mouse_ray);
    +    void do_scale_z(const Linef3& mouse_ray);
    +    void do_scale_uniform(const Linef3& mouse_ray);
    +
    +    double calc_ratio(unsigned int preferred_plane_id, const Linef3& mouse_ray, const Vec3d& center) const;
    +};
    +
     } // namespace GUI
     } // namespace Slic3r
     
    diff --git a/xs/src/slic3r/GUI/GLTexture.cpp b/xs/src/slic3r/GUI/GLTexture.cpp
    index 18c9f5dea0..235e3d93b6 100644
    --- a/xs/src/slic3r/GUI/GLTexture.cpp
    +++ b/xs/src/slic3r/GUI/GLTexture.cpp
    @@ -12,6 +12,8 @@
     namespace Slic3r {
     namespace GUI {
     
    +GLTexture::Quad_UVs GLTexture::FullTextureUVs = { { 0.0f, 1.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f }, { 0.0f, 0.0f } };
    +
     GLTexture::GLTexture()
         : m_id(0)
         , m_width(0)
    @@ -128,6 +130,11 @@ const std::string& GLTexture::get_source() const
     }
     
     void GLTexture::render_texture(unsigned int tex_id, float left, float right, float bottom, float top)
    +{
    +    render_sub_texture(tex_id, left, right, bottom, top, FullTextureUVs);
    +}
    +
    +void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const GLTexture::Quad_UVs& uvs)
     {
         ::glEnable(GL_BLEND);
         ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    @@ -138,10 +145,10 @@ void GLTexture::render_texture(unsigned int tex_id, float left, float right, flo
         ::glBindTexture(GL_TEXTURE_2D, (GLuint)tex_id);
     
         ::glBegin(GL_QUADS);
    -    ::glTexCoord2f(0.0f, 1.0f); ::glVertex2f(left, bottom);
    -    ::glTexCoord2f(1.0f, 1.0f); ::glVertex2f(right, bottom);
    -    ::glTexCoord2f(1.0f, 0.0f); ::glVertex2f(right, top);
    -    ::glTexCoord2f(0.0f, 0.0f); ::glVertex2f(left, top);
    +    ::glTexCoord2f(uvs.left_bottom.u, uvs.left_bottom.v); ::glVertex2f(left, bottom);
    +    ::glTexCoord2f(uvs.right_bottom.u, uvs.right_bottom.v); ::glVertex2f(right, bottom);
    +    ::glTexCoord2f(uvs.right_top.u, uvs.right_top.v); ::glVertex2f(right, top);
    +    ::glTexCoord2f(uvs.left_top.u, uvs.left_top.v); ::glVertex2f(left, top);
         ::glEnd();
     
         ::glBindTexture(GL_TEXTURE_2D, 0);
    diff --git a/xs/src/slic3r/GUI/GLTexture.hpp b/xs/src/slic3r/GUI/GLTexture.hpp
    index 3113fcab20..e027bd152f 100644
    --- a/xs/src/slic3r/GUI/GLTexture.hpp
    +++ b/xs/src/slic3r/GUI/GLTexture.hpp
    @@ -10,6 +10,23 @@ namespace GUI {
     
         class GLTexture
         {
    +    public:
    +        struct UV
    +        {
    +            float u;
    +            float v;
    +        };
    +
    +        struct Quad_UVs
    +        {
    +            UV left_bottom;
    +            UV right_bottom;
    +            UV right_top;
    +            UV left_top;
    +        };
    +
    +        static Quad_UVs FullTextureUVs;
    +
         protected:
             unsigned int m_id;
             int m_width;
    @@ -30,6 +47,7 @@ namespace GUI {
             const std::string& get_source() const;
     
             static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top);
    +        static void render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const Quad_UVs& uvs);
     
         protected:
             unsigned int _generate_mipmaps(wxImage& image);
    diff --git a/xs/src/slic3r/GUI/GLToolbar.cpp b/xs/src/slic3r/GUI/GLToolbar.cpp
    new file mode 100644
    index 0000000000..388868b12f
    --- /dev/null
    +++ b/xs/src/slic3r/GUI/GLToolbar.cpp
    @@ -0,0 +1,722 @@
    +#include "../../libslic3r/Point.hpp"
    +#include "GLToolbar.hpp"
    +
    +#include "../../libslic3r/libslic3r.h"
    +#include "../../slic3r/GUI/GLCanvas3D.hpp"
    +
    +#include 
    +
    +#include 
    +#include 
    +#include 
    +
    +namespace Slic3r {
    +namespace GUI {
    +
    +GLToolbarItem::Data::Data()
    +    : name("")
    +    , tooltip("")
    +    , sprite_id(-1)
    +    , is_toggable(false)
    +    , action_callback(nullptr)
    +{
    +}
    +
    +GLToolbarItem::GLToolbarItem(GLToolbarItem::EType type, const GLToolbarItem::Data& data)
    +    : m_type(type)
    +    , m_state(Disabled)
    +    , m_data(data)
    +{
    +}
    +
    +GLToolbarItem::EState GLToolbarItem::get_state() const
    +{
    +    return m_state;
    +}
    +
    +void GLToolbarItem::set_state(GLToolbarItem::EState state)
    +{
    +    m_state = state;
    +}
    +
    +const std::string& GLToolbarItem::get_name() const
    +{
    +    return m_data.name;
    +}
    +
    +const std::string& GLToolbarItem::get_tooltip() const
    +{
    +    return m_data.tooltip;
    +}
    +
    +void GLToolbarItem::do_action()
    +{
    +    if (m_data.action_callback != nullptr)
    +        m_data.action_callback->call();
    +}
    +
    +bool GLToolbarItem::is_enabled() const
    +{
    +    return m_state != Disabled;
    +}
    +
    +bool GLToolbarItem::is_hovered() const
    +{
    +    return (m_state == Hover) || (m_state == HoverPressed);
    +}
    +
    +bool GLToolbarItem::is_pressed() const
    +{
    +    return (m_state == Pressed) || (m_state == HoverPressed);
    +}
    +
    +bool GLToolbarItem::is_toggable() const
    +{
    +    return m_data.is_toggable;
    +}
    +
    +bool GLToolbarItem::is_separator() const
    +{
    +    return m_type == Separator;
    +}
    +
    +void GLToolbarItem::render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const
    +{
    +    GLTexture::render_sub_texture(tex_id, left, right, bottom, top, get_uvs(texture_size, border_size, icon_size, gap_size));
    +}
    +
    +GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const
    +{
    +    GLTexture::Quad_UVs uvs;
    +
    +    float inv_texture_size = (texture_size != 0) ? 1.0f / (float)texture_size : 0.0f;
    +
    +    float scaled_icon_size = (float)icon_size * inv_texture_size;
    +    float scaled_border_size = (float)border_size * inv_texture_size;
    +    float scaled_gap_size = (float)gap_size * inv_texture_size;
    +    float stride = scaled_icon_size + scaled_gap_size;
    +
    +    float left = scaled_border_size + (float)m_state * stride;
    +    float right = left + scaled_icon_size;
    +    float top = scaled_border_size + (float)m_data.sprite_id * stride;
    +    float bottom = top + scaled_icon_size;
    +
    +    uvs.left_top = { left, top };
    +    uvs.left_bottom = { left, bottom };
    +    uvs.right_bottom = { right, bottom };
    +    uvs.right_top = { right, top };
    +    
    +    return uvs;
    +}
    +
    +GLToolbar::ItemsIconsTexture::ItemsIconsTexture()
    +    : items_icon_size(0)
    +    , items_icon_border_size(0)
    +    , items_icon_gap_size(0)
    +{
    +}
    +
    +GLToolbar::Layout::Layout()
    +    : type(Horizontal)
    +    , top(0.0f)
    +    , left(0.0f)
    +    , separator_size(0.0f)
    +    , gap_size(0.0f)
    +{
    +}
    +
    +GLToolbar::GLToolbar(GLCanvas3D& parent)
    +    : m_parent(parent)
    +    , m_enabled(false)
    +{
    +}
    +
    +bool GLToolbar::init(const std::string& icons_texture_filename, unsigned int items_icon_size, unsigned int items_icon_border_size, unsigned int items_icon_gap_size)
    +{
    +    std::string path = resources_dir() + "/icons/";
    +    bool res = !icons_texture_filename.empty() && m_icons_texture.texture.load_from_file(path + icons_texture_filename, false);
    +    if (res)
    +    {
    +        m_icons_texture.items_icon_size = items_icon_size;
    +        m_icons_texture.items_icon_border_size = items_icon_border_size;
    +        m_icons_texture.items_icon_gap_size = items_icon_gap_size;
    +    }
    +
    +    return res;
    +}
    +
    +GLToolbar::Layout::Type GLToolbar::get_layout_type() const
    +{
    +    return m_layout.type;
    +}
    +
    +void GLToolbar::set_layout_type(GLToolbar::Layout::Type type)
    +{
    +    m_layout.type = type;
    +}
    +
    +void GLToolbar::set_position(float top, float left)
    +{
    +    m_layout.top = top;
    +    m_layout.left = left;
    +}
    +
    +void GLToolbar::set_separator_size(float size)
    +{
    +    m_layout.separator_size = size;
    +}
    +
    +void GLToolbar::set_gap_size(float size)
    +{
    +    m_layout.gap_size = size;
    +}
    +
    +bool GLToolbar::is_enabled() const
    +{
    +    return m_enabled;
    +}
    +
    +void GLToolbar::set_enabled(bool enable)
    +{
    +    m_enabled = true;
    +}
    +
    +bool GLToolbar::add_item(const GLToolbarItem::Data& data)
    +{
    +    GLToolbarItem* item = new GLToolbarItem(GLToolbarItem::Action, data);
    +    if (item == nullptr)
    +        return false;
    +
    +    m_items.push_back(item);
    +    return true;
    +}
    +
    +bool GLToolbar::add_separator()
    +{
    +    GLToolbarItem::Data data;
    +    GLToolbarItem* item = new GLToolbarItem(GLToolbarItem::Separator, data);
    +    if (item == nullptr)
    +        return false;
    +
    +    m_items.push_back(item);
    +    return true;
    +}
    +
    +float GLToolbar::get_width() const
    +{
    +    switch (m_layout.type)
    +    {
    +    default:
    +    case Layout::Horizontal:
    +    {
    +        return get_width_horizontal();
    +    }
    +    case Layout::Vertical:
    +    {
    +        return get_width_vertical();
    +    }
    +    }
    +}
    +
    +float GLToolbar::get_height() const
    +{
    +    switch (m_layout.type)
    +    {
    +    default:
    +    case Layout::Horizontal:
    +    {
    +        return get_height_horizontal();
    +    }
    +    case Layout::Vertical:
    +    {
    +        return get_height_vertical();
    +    }
    +    }
    +}
    +
    +void GLToolbar::enable_item(const std::string& name)
    +{
    +    for (GLToolbarItem* item : m_items)
    +    {
    +        if ((item->get_name() == name) && (item->get_state() == GLToolbarItem::Disabled))
    +        {
    +            item->set_state(GLToolbarItem::Normal);
    +            return;
    +        }
    +    }
    +}
    +
    +void GLToolbar::disable_item(const std::string& name)
    +{
    +    for (GLToolbarItem* item : m_items)
    +    {
    +        if (item->get_name() == name)
    +        {
    +            item->set_state(GLToolbarItem::Disabled);
    +            return;
    +        }
    +    }
    +}
    +
    +bool GLToolbar::is_item_pressed(const std::string& name) const
    +{
    +    for (GLToolbarItem* item : m_items)
    +    {
    +        if (item->get_name() == name)
    +            return item->is_pressed();
    +    }
    +
    +    return false;
    +}
    +
    +void GLToolbar::update_hover_state(const Vec2d& mouse_pos)
    +{
    +    if (!m_enabled)
    +        return;
    +
    +    switch (m_layout.type)
    +    {
    +    default:
    +    case Layout::Horizontal:
    +    {
    +        update_hover_state_horizontal(mouse_pos);
    +        break;
    +    }
    +    case Layout::Vertical:
    +    {
    +        update_hover_state_vertical(mouse_pos);
    +        break;
    +    }
    +    }
    +}
    +
    +int GLToolbar::contains_mouse(const Vec2d& mouse_pos) const
    +{
    +    if (!m_enabled)
    +        return -1;
    +
    +    switch (m_layout.type)
    +    {
    +    default:
    +    case Layout::Horizontal:
    +    {
    +        return contains_mouse_horizontal(mouse_pos);
    +    }
    +    case Layout::Vertical:
    +    {
    +        return contains_mouse_vertical(mouse_pos);
    +    }
    +    }
    +}
    +
    +void GLToolbar::do_action(unsigned int item_id)
    +{
    +    if (item_id < (unsigned int)m_items.size())
    +    {
    +        GLToolbarItem* item = m_items[item_id];
    +        if ((item != nullptr) && !item->is_separator() && item->is_hovered())
    +        {
    +            if (item->is_toggable())
    +            {
    +                GLToolbarItem::EState state = item->get_state();
    +                if (state == GLToolbarItem::Hover)
    +                    item->set_state(GLToolbarItem::HoverPressed);
    +                else if (state == GLToolbarItem::HoverPressed)
    +                    item->set_state(GLToolbarItem::Hover);
    +
    +                m_parent.render();
    +                item->do_action();
    +            }
    +            else
    +            {
    +                item->set_state(GLToolbarItem::HoverPressed);
    +                m_parent.render();
    +                item->do_action();
    +                if (item->get_state() != GLToolbarItem::Disabled)
    +                {
    +                    // the item may get disabled during the action, if not, set it back to hover state
    +                    item->set_state(GLToolbarItem::Hover);
    +                    m_parent.render();
    +                }
    +            }
    +        }
    +    }
    +}
    +
    +void GLToolbar::render() const
    +{
    +    if (!m_enabled || m_items.empty())
    +        return;
    +
    +    ::glDisable(GL_DEPTH_TEST);
    +
    +    ::glPushMatrix();
    +    ::glLoadIdentity();
    +
    +    switch (m_layout.type)
    +    {
    +    default:
    +    case Layout::Horizontal:
    +    {
    +        render_horizontal();
    +        break;
    +    }
    +    case Layout::Vertical:
    +    {
    +        render_vertical();
    +        break;
    +    }
    +    }
    +
    +    ::glPopMatrix();
    +}
    +
    +float GLToolbar::get_width_horizontal() const
    +{
    +    return get_main_size();
    +}
    +
    +float GLToolbar::get_width_vertical() const
    +{
    +    return m_icons_texture.items_icon_size;
    +}
    +
    +float GLToolbar::get_height_horizontal() const
    +{
    +    return m_icons_texture.items_icon_size;
    +}
    +
    +float GLToolbar::get_height_vertical() const
    +{
    +    return get_main_size();
    +}
    +
    +float GLToolbar::get_main_size() const
    +{
    +    float size = 0.0f;
    +    for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i)
    +    {
    +        if (m_items[i]->is_separator())
    +            size += m_layout.separator_size;
    +        else
    +            size += (float)m_icons_texture.items_icon_size;
    +    }
    +
    +    if (m_items.size() > 1)
    +        size += ((float)m_items.size() - 1.0f) * m_layout.gap_size;
    +
    +    return size;
    +}
    +
    +void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos)
    +{
    +    float zoom = m_parent.get_camera_zoom();
    +    float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
    +
    +    Size cnv_size = m_parent.get_canvas_size();
    +    Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom);
    +
    +    float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom;
    +    float scaled_separator_size = m_layout.separator_size * inv_zoom;
    +    float scaled_gap_size = m_layout.gap_size * inv_zoom;
    +
    +    float separator_stride = scaled_separator_size + scaled_gap_size;
    +    float icon_stride = scaled_icons_size + scaled_gap_size;
    +
    +    float left = m_layout.left;
    +    float top = m_layout.top;
    +
    +    std::string tooltip = "";
    +        
    +    for (GLToolbarItem* item : m_items)
    +    {
    +        if (item->is_separator())
    +            left += separator_stride;
    +        else
    +        {
    +            float right = left + scaled_icons_size;
    +            float bottom = top - scaled_icons_size;
    +
    +            GLToolbarItem::EState state = item->get_state();
    +            bool inside = (left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top);
    +
    +            switch (state)
    +            {
    +            case GLToolbarItem::Normal:
    +            {
    +                if (inside)
    +                    item->set_state(GLToolbarItem::Hover);
    +
    +                break;
    +            }
    +            case GLToolbarItem::Hover:
    +            {
    +                if (inside)
    +                    tooltip = item->get_tooltip();
    +                else
    +                    item->set_state(GLToolbarItem::Normal);
    +                
    +                break;
    +            }
    +            case GLToolbarItem::Pressed:
    +            {
    +                if (inside)
    +                    item->set_state(GLToolbarItem::HoverPressed);
    +                
    +                break;
    +            }
    +            case GLToolbarItem::HoverPressed:
    +            {
    +                if (inside)
    +                    tooltip = item->get_tooltip();
    +                else
    +                    item->set_state(GLToolbarItem::Pressed);
    +                
    +                break;
    +            }
    +            default:
    +            case GLToolbarItem::Disabled:
    +            {
    +                break;
    +            }
    +            }
    +
    +            left += icon_stride;
    +        }
    +    }
    +
    +    m_parent.set_tooltip(tooltip);
    +}
    +
    +void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos)
    +{
    +    float zoom = m_parent.get_camera_zoom();
    +    float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
    +
    +    Size cnv_size = m_parent.get_canvas_size();
    +    Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom);
    +
    +    float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom;
    +    float scaled_separator_size = m_layout.separator_size * inv_zoom;
    +    float scaled_gap_size = m_layout.gap_size * inv_zoom;
    +
    +    float separator_stride = scaled_separator_size + scaled_gap_size;
    +    float icon_stride = scaled_icons_size + scaled_gap_size;
    +
    +    float left = m_layout.left;
    +    float top = m_layout.top;
    +
    +    std::string tooltip = "";
    +
    +    for (GLToolbarItem* item : m_items)
    +    {
    +        if (item->is_separator())
    +            top -= separator_stride;
    +        else
    +        {
    +            float right = left + scaled_icons_size;
    +            float bottom = top - scaled_icons_size;
    +
    +            GLToolbarItem::EState state = item->get_state();
    +            bool inside = (left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top);
    +
    +            switch (state)
    +            {
    +            case GLToolbarItem::Normal:
    +            {
    +                if (inside)
    +                    item->set_state(GLToolbarItem::Hover);
    +
    +                break;
    +            }
    +            case GLToolbarItem::Hover:
    +            {
    +                if (inside)
    +                    tooltip = item->get_tooltip();
    +                else
    +                    item->set_state(GLToolbarItem::Normal);
    +
    +                break;
    +            }
    +            case GLToolbarItem::Pressed:
    +            {
    +                if (inside)
    +                    item->set_state(GLToolbarItem::HoverPressed);
    +
    +                break;
    +            }
    +            case GLToolbarItem::HoverPressed:
    +            {
    +                if (inside)
    +                    tooltip = item->get_tooltip();
    +                else
    +                    item->set_state(GLToolbarItem::Pressed);
    +
    +                break;
    +            }
    +            default:
    +            case GLToolbarItem::Disabled:
    +            {
    +                break;
    +            }
    +            }
    +
    +            top -= icon_stride;
    +        }
    +    }
    +
    +    m_parent.set_tooltip(tooltip);
    +}
    +
    +int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos) const
    +{
    +    float zoom = m_parent.get_camera_zoom();
    +    float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
    +
    +    Size cnv_size = m_parent.get_canvas_size();
    +    Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom);
    +
    +    float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom;
    +    float scaled_separator_size = m_layout.separator_size * inv_zoom;
    +    float scaled_gap_size = m_layout.gap_size * inv_zoom;
    +
    +    float separator_stride = scaled_separator_size + scaled_gap_size;
    +    float icon_stride = scaled_icons_size + scaled_gap_size;
    +
    +    float left = m_layout.left;
    +    float top = m_layout.top;
    +
    +    int id = -1;
    +    
    +    for (GLToolbarItem* item : m_items)
    +    {
    +        ++id;
    +        
    +        if (item->is_separator())
    +            left += separator_stride;
    +        else
    +        {
    +            float right = left + scaled_icons_size;
    +            float bottom = top - scaled_icons_size;
    +            
    +            if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top))
    +                return id;
    +            
    +            left += icon_stride;
    +        }
    +    }
    +    
    +    return -1;
    +}
    +
    +int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos) const
    +{
    +    float zoom = m_parent.get_camera_zoom();
    +    float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
    +
    +    Size cnv_size = m_parent.get_canvas_size();
    +    Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom);
    +
    +    float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom;
    +    float scaled_separator_size = m_layout.separator_size * inv_zoom;
    +    float scaled_gap_size = m_layout.gap_size * inv_zoom;
    +
    +    float separator_stride = scaled_separator_size + scaled_gap_size;
    +    float icon_stride = scaled_icons_size + scaled_gap_size;
    +
    +    float left = m_layout.left;
    +    float top = m_layout.top;
    +
    +    int id = -1;
    +
    +    for (GLToolbarItem* item : m_items)
    +    {
    +        ++id;
    +
    +        if (item->is_separator())
    +            top -= separator_stride;
    +        else
    +        {
    +            float right = left + scaled_icons_size;
    +            float bottom = top - scaled_icons_size;
    +
    +            if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top))
    +                return id;
    +
    +            top -= icon_stride;
    +        }
    +    }
    +
    +    return -1;
    +}
    +
    +void GLToolbar::render_horizontal() const
    +{
    +    unsigned int tex_id = m_icons_texture.texture.get_id();
    +    int tex_size = m_icons_texture.texture.get_width();
    +
    +    if ((tex_id == 0) || (tex_size <= 0))
    +        return;
    +
    +    float zoom = m_parent.get_camera_zoom();
    +    float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
    +
    +    float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom;
    +    float scaled_separator_size = m_layout.separator_size * inv_zoom;
    +    float scaled_gap_size = m_layout.gap_size * inv_zoom;
    +
    +    float separator_stride = scaled_separator_size + scaled_gap_size;
    +    float icon_stride = scaled_icons_size + scaled_gap_size;
    +
    +    float left = m_layout.left;
    +    float top = m_layout.top;
    +
    +    // renders icons
    +    for (const GLToolbarItem* item : m_items)
    +    {
    +        if (item->is_separator())
    +            left += separator_stride;
    +        else
    +        {
    +            item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_size, m_icons_texture.items_icon_border_size, m_icons_texture.items_icon_size, m_icons_texture.items_icon_gap_size);
    +            left += icon_stride;
    +        }
    +    }
    +}
    +
    +void GLToolbar::render_vertical() const
    +{
    +    unsigned int tex_id = m_icons_texture.texture.get_id();
    +    int tex_size = m_icons_texture.texture.get_width();
    +
    +    if ((tex_id == 0) || (tex_size <= 0))
    +        return;
    +
    +    float zoom = m_parent.get_camera_zoom();
    +    float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
    +
    +    float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom;
    +    float scaled_separator_size = m_layout.separator_size * inv_zoom;
    +    float scaled_gap_size = m_layout.gap_size * inv_zoom;
    +
    +    float separator_stride = scaled_separator_size + scaled_gap_size;
    +    float icon_stride = scaled_icons_size + scaled_gap_size;
    +
    +    float left = m_layout.left;
    +    float top = m_layout.top;
    +
    +    // renders icons
    +    for (const GLToolbarItem* item : m_items)
    +    {
    +        if (item->is_separator())
    +            top -= separator_stride;
    +        else
    +        {
    +            item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_size, m_icons_texture.items_icon_border_size, m_icons_texture.items_icon_size, m_icons_texture.items_icon_gap_size);
    +            top -= icon_stride;
    +        }
    +    }
    +}
    +
    +} // namespace GUI
    +} // namespace Slic3r
    diff --git a/xs/src/slic3r/GUI/GLToolbar.hpp b/xs/src/slic3r/GUI/GLToolbar.hpp
    new file mode 100644
    index 0000000000..c1501b10c2
    --- /dev/null
    +++ b/xs/src/slic3r/GUI/GLToolbar.hpp
    @@ -0,0 +1,175 @@
    +#ifndef slic3r_GLToolbar_hpp_
    +#define slic3r_GLToolbar_hpp_
    +
    +#include "../../slic3r/GUI/GLTexture.hpp"
    +#include "../../libslic3r/Utils.hpp"
    +
    +#include 
    +#include 
    +
    +namespace Slic3r {
    +namespace GUI {
    +
    +class GLCanvas3D;
    +
    +class GLToolbarItem
    +{
    +public:
    +    enum EType : unsigned char
    +    {
    +        Action,
    +        Separator,
    +        Num_Types
    +    };
    +
    +    enum EState : unsigned char
    +    {
    +        Normal,
    +        Pressed,
    +        Disabled,
    +        Hover,
    +        HoverPressed,
    +        Num_States
    +    };
    +
    +    struct Data
    +    {
    +        std::string name;
    +        std::string tooltip;
    +        unsigned int sprite_id;
    +        bool is_toggable;
    +        PerlCallback* action_callback;
    +
    +        Data();
    +    };
    +
    +private:
    +    EType m_type;
    +    EState m_state;
    +    Data m_data;
    +
    +public:
    +    GLToolbarItem(EType type, const Data& data);
    +
    +    EState get_state() const;
    +    void set_state(EState state);
    +
    +    const std::string& get_name() const;
    +    const std::string& get_tooltip() const;
    +
    +    void do_action();
    +
    +    bool is_enabled() const;
    +    bool is_hovered() const;
    +    bool is_pressed() const;
    +
    +    bool is_toggable() const;
    +    bool is_separator() const;
    +
    +    void render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const;
    +
    +private:
    +    GLTexture::Quad_UVs get_uvs(unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const;
    +};
    +
    +class GLToolbar
    +{
    +public:
    +    // items icon textures are assumed to be square and all with the same size in pixels, no internal check is done
    +    // icons are layed-out into the texture starting from the top-left corner in the same order as enum GLToolbarItem::EState
    +    // from left to right
    +    struct ItemsIconsTexture
    +    {
    +        GLTexture texture;
    +        // size of the square icons, in pixels
    +        unsigned int items_icon_size;
    +        // distance from the border, in pixels
    +        unsigned int items_icon_border_size;
    +        // distance between two adjacent icons (to avoid filtering artifacts), in pixels
    +        unsigned int items_icon_gap_size;
    +
    +        ItemsIconsTexture();
    +    };
    +
    +    struct Layout
    +    {
    +        enum Type : unsigned char
    +        {
    +            Horizontal,
    +            Vertical,
    +            Num_Types
    +        };
    +
    +        Type type;
    +        float top;
    +        float left;
    +        float separator_size;
    +        float gap_size;
    +
    +        Layout();
    +    };
    +
    +private:
    +    typedef std::vector ItemsList;
    +
    +    GLCanvas3D& m_parent;
    +    bool m_enabled;
    +    ItemsIconsTexture m_icons_texture;
    +    Layout m_layout;
    +
    +    ItemsList m_items;
    +
    +public:
    +    explicit GLToolbar(GLCanvas3D& parent);
    +
    +    bool init(const std::string& icons_texture_filename, unsigned int items_icon_size, unsigned int items_icon_border_size, unsigned int items_icon_gap_size);
    +    
    +    Layout::Type get_layout_type() const;
    +    void set_layout_type(Layout::Type type);
    +
    +    void set_position(float top, float left);
    +    void set_separator_size(float size);
    +    void set_gap_size(float size);
    +
    +    bool is_enabled() const;
    +    void set_enabled(bool enable);
    +
    +    bool add_item(const GLToolbarItem::Data& data);
    +    bool add_separator();
    +
    +    float get_width() const;
    +    float get_height() const;
    +
    +    void enable_item(const std::string& name);
    +    void disable_item(const std::string& name);
    +
    +    bool is_item_pressed(const std::string& name) const;
    +
    +    void update_hover_state(const Vec2d& mouse_pos);
    +
    +    // returns the id of the item under the given mouse position or -1 if none
    +    int contains_mouse(const Vec2d& mouse_pos) const;
    +
    +    void do_action(unsigned int item_id);
    +
    +    void render() const;
    +
    +private:
    +    float get_width_horizontal() const;
    +    float get_width_vertical() const;
    +    float get_height_horizontal() const;
    +    float get_height_vertical() const;
    +    float get_main_size() const;
    +    void update_hover_state_horizontal(const Vec2d& mouse_pos);
    +    void update_hover_state_vertical(const Vec2d& mouse_pos);
    +    int contains_mouse_horizontal(const Vec2d& mouse_pos) const;
    +    int contains_mouse_vertical(const Vec2d& mouse_pos) const;
    +
    +    void render_horizontal() const;
    +    void render_vertical() const;
    +};
    +
    +} // namespace GUI
    +} // namespace Slic3r
    +
    +#endif // slic3r_GLToolbar_hpp_
    diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp
    index e5dca948c9..9e28de04a5 100644
    --- a/xs/src/slic3r/GUI/GUI.cpp
    +++ b/xs/src/slic3r/GUI/GUI.cpp
    @@ -38,6 +38,8 @@
     #include 
     #include 
     #include 
    +#include 
    +#include 
     
     #include "wxExtensions.hpp"
     
    @@ -53,12 +55,15 @@
     #include "PresetBundle.hpp"
     #include "UpdateDialogs.hpp"
     #include "FirmwareDialog.hpp"
    +#include "GUI_ObjectParts.hpp"
     
     #include "../Utils/PresetUpdater.hpp"
     #include "../Config/Snapshot.hpp"
     
     #include "3DScene.hpp"
     #include "libslic3r/I18N.hpp"
    +#include "Model.hpp"
    +#include "LambdaObjectDialog.hpp"
     
     namespace Slic3r { namespace GUI {
     
    @@ -123,10 +128,26 @@ wxLocale*	g_wxLocale;
     wxFont		g_small_font;
     wxFont		g_bold_font;
     
    -std::shared_ptr	m_optgroup;
    -double m_brim_width = 0.0;
    +std::vector > m_optgroups;
    +double		m_brim_width = 0.0;
    +size_t		m_label_width = 100;
     wxButton*	g_wiping_dialog_button = nullptr;
     
    +//showed/hided controls according to the view mode
    +wxWindow	*g_right_panel = nullptr;
    +wxBoxSizer	*g_frequently_changed_parameters_sizer = nullptr;
    +wxBoxSizer	*g_expert_mode_part_sizer = nullptr;
    +wxBoxSizer	*g_scrolled_window_sizer = nullptr;
    +wxBoxSizer	*g_object_list_sizer = nullptr;
    +wxButton	*g_btn_export_gcode = nullptr;
    +wxButton	*g_btn_export_stl = nullptr;
    +wxButton	*g_btn_reslice = nullptr;
    +wxButton	*g_btn_print = nullptr;
    +wxButton	*g_btn_send_gcode = nullptr;
    +wxStaticBitmap	*g_manifold_warning_icon = nullptr;
    +bool		g_show_print_info = false;
    +bool		g_show_manifold_warning_icon = false;
    +
     static void init_label_colours()
     {
     	auto luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
    @@ -206,6 +227,39 @@ void set_3DScene(_3DScene *scene)
     	g_3DScene = scene;
     }
     
    +void set_objects_from_perl(	wxWindow* parent, wxBoxSizer *frequently_changed_parameters_sizer,
    +							wxBoxSizer *expert_mode_part_sizer, wxBoxSizer *scrolled_window_sizer,
    +							wxButton *btn_export_gcode,
    +							wxButton *btn_export_stl, wxButton *btn_reslice, 
    +							wxButton *btn_print, wxButton *btn_send_gcode,
    +							wxStaticBitmap *manifold_warning_icon)
    +{
    +	g_right_panel = parent;
    +	g_frequently_changed_parameters_sizer = frequently_changed_parameters_sizer;
    +	g_expert_mode_part_sizer = expert_mode_part_sizer;
    +	g_scrolled_window_sizer = scrolled_window_sizer;
    +	g_btn_export_gcode = btn_export_gcode;
    +	g_btn_export_stl = btn_export_stl;
    +	g_btn_reslice = btn_reslice;
    +	g_btn_print = btn_print;
    +	g_btn_send_gcode = btn_send_gcode;
    +	g_manifold_warning_icon = manifold_warning_icon;
    +}
    +
    +void set_show_print_info(bool show)
    +{
    +	g_show_print_info = show;
    +}
    +
    +void set_show_manifold_warning_icon(bool show)
    +{
    +	g_show_manifold_warning_icon = show;
    +}
    +
    +void set_objects_list_sizer(wxBoxSizer *objects_list_sizer){
    +	g_object_list_sizer = objects_list_sizer;
    +}
    +
     std::vector& get_tabs_list()
     {
     	return g_tabs_list;
    @@ -332,10 +386,21 @@ enum ConfigMenuIDs {
     	ConfigMenuTakeSnapshot,
     	ConfigMenuUpdate,
     	ConfigMenuPreferences,
    +	ConfigMenuModeSimple,
    +	ConfigMenuModeExpert,
     	ConfigMenuLanguage,
     	ConfigMenuFlashFirmware,
     	ConfigMenuCnt,
     };
    +	
    +ConfigMenuIDs get_view_mode()
    +{
    +	if (!g_AppConfig->has("view_mode"))
    +		return ConfigMenuModeSimple;
    +
    +	const auto mode = g_AppConfig->get("view_mode");
    +	return mode == "expert" ? ConfigMenuModeExpert : ConfigMenuModeSimple;
    +}
     
     static wxString dots("…", wxConvUTF8);
     
    @@ -353,8 +418,15 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l
     // 	local_menu->Append(config_id_base + ConfigMenuUpdate, 		_(L("Check for updates")), 					_(L("Check for configuration updates")));
        	local_menu->AppendSeparator();
        	local_menu->Append(config_id_base + ConfigMenuPreferences, 	_(L("Preferences"))+dots+"\tCtrl+,", 		_(L("Application preferences")));
    -   	local_menu->Append(config_id_base + ConfigMenuLanguage, 	_(L("Change Application Language")));
    +	local_menu->AppendSeparator();
    +	auto mode_menu = new wxMenu();
    +	mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple,	_(L("&Simple")),					_(L("Simple View Mode")));
    +	mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeExpert,	_(L("&Expert")),					_(L("Expert View Mode")));
    +	mode_menu->Check(config_id_base + get_view_mode(), true);
    +	local_menu->AppendSubMenu(mode_menu,						_(L("&Mode")), 								_(L("Slic3r View Mode")));
        	local_menu->AppendSeparator();
    +	local_menu->Append(config_id_base + ConfigMenuLanguage,		_(L("Change Application Language")));
    +	local_menu->AppendSeparator();
     	local_menu->Append(config_id_base + ConfigMenuFlashFirmware, _(L("Flash printer firmware")), _(L("Upload a firmware image into an Arduino based printer")));
     	// TODO: for when we're able to flash dictionaries
     	// local_menu->Append(config_id_base + FirmwareMenuDict,  _(L("Flash language file")),    _(L("Upload a language dictionary file into a Prusa printer")));
    @@ -421,6 +493,13 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l
     			break;
     		}
     	});
    +	mode_menu->Bind(wxEVT_MENU, [config_id_base](wxEvent& event) {
    +		std::string mode =	event.GetId() - config_id_base == ConfigMenuModeExpert ?
    +							"expert" : "simple";
    +		g_AppConfig->set("view_mode", mode);
    +		g_AppConfig->save();
    +		update_mode();
    +	});
     	menu->Append(local_menu, _(L("&Configuration")));
     }
     
    @@ -429,6 +508,26 @@ void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_languag
         add_config_menu(menu, event_preferences_changed, event_language_change);
     }
     
    +void open_model(wxWindow *parent, wxArrayString& input_files){
    +	t_file_wild_card vec_FILE_WILDCARDS = get_file_wild_card();
    +	std::vector file_types = { "known", "stl", "obj", "amf", "3mf", "prusa" };
    +	wxString MODEL_WILDCARD;
    +	for (auto file_type : file_types)
    +		MODEL_WILDCARD += vec_FILE_WILDCARDS.at(file_type) + "|";
    +
    +	auto dlg_title = _(L("Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):"));
    +	auto dialog = new wxFileDialog(parent /*? parent : GetTopWindow(g_wxMainFrame)*/, dlg_title, 
    +		g_AppConfig->get_last_dir(), "",
    +		MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
    +	if (dialog->ShowModal() != wxID_OK) {
    +		dialog->Destroy();
    +		return ;
    +	}
    +	
    +	dialog->GetPaths(input_files);
    +	dialog->Destroy();
    +}
    +
     // This is called when closing the application, when loading a config file or when starting the config wizard
     // to notify the user whether he is aware that some preset changes will be lost.
     bool check_unsaved_changes()
    @@ -496,20 +595,33 @@ void open_preferences_dialog(int event_preferences)
     void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed)
     {	
     	update_label_colours_from_appconfig();
    -	add_created_tab(new TabPrint	(g_wxTabPanel, no_controller));
    -	add_created_tab(new TabFilament	(g_wxTabPanel, no_controller));
    -	add_created_tab(new TabPrinter	(g_wxTabPanel, no_controller));
    -	for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++ i) {
    -		Tab *tab = dynamic_cast(g_wxTabPanel->GetPage(i));
    -		if (! tab)
    -			continue;
    -		tab->set_event_value_change(wxEventType(event_value_change));
    -		tab->set_event_presets_changed(wxEventType(event_presets_changed));
    -	}
    +	add_created_tab(new TabPrint	    (g_wxTabPanel, no_controller), event_value_change, event_presets_changed);
    +	add_created_tab(new TabFilament	    (g_wxTabPanel, no_controller), event_value_change, event_presets_changed);
    +	add_created_tab(new TabSLAMaterial  (g_wxTabPanel, no_controller), event_value_change, event_presets_changed);
    +	add_created_tab(new TabPrinter	    (g_wxTabPanel, no_controller), event_value_change, event_presets_changed);
    +}
    +
    +std::vector preset_tabs = {
    +    { "print",        nullptr, ptFFF },
    +    { "filament",     nullptr, ptFFF },
    +    { "sla_material", nullptr, ptSLA }
    +};
    +const std::vector& get_preset_tabs() {
    +    return preset_tabs;
    +}
    +
    +Tab* get_tab(const std::string& name)
    +{
    +    std::vector::iterator it = std::find_if(preset_tabs.begin(), preset_tabs.end(),
    +                                                       [name](PresetTab& tab){ return name == tab.name; });
    +    return it != preset_tabs.end() ? it->panel : nullptr;
     }
     
     TabIface* get_preset_tab_iface(char *name)
     {
    +    Tab* tab = get_tab(name);
    +    if (tab) return new TabIface(tab);
    +
     	for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++ i) {
     		Tab *tab = dynamic_cast(g_wxTabPanel->GetPage(i));
     		if (! tab)
    @@ -604,14 +716,16 @@ 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:{
     			if (opt_key.compare("bed_shape") == 0){
    -				config.option(opt_key)->values = boost::any_cast>(value);
    +				config.option(opt_key)->values = boost::any_cast>(value);
     				break;
     			}
    -			ConfigOptionPoints* vec_new = new ConfigOptionPoints{ boost::any_cast(value) };
    +			ConfigOptionPoints* vec_new = new ConfigOptionPoints{ boost::any_cast(value) };
     			config.option(opt_key)->set_at(vec_new, opt_index, 0);
     			}
     			break;
    @@ -627,13 +741,28 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt
     	}
     }
     
    -void add_created_tab(Tab* panel)
    +void add_created_tab(Tab* panel, int event_value_change, int event_presets_changed)
     {
     	panel->create_preset_tab(g_PresetBundle);
     
     	// Load the currently selected preset into the GUI, update the preset selection box.
     	panel->load_current_preset();
    -	g_wxTabPanel->AddPage(panel, panel->title());
    +
    +    panel->set_event_value_change(wxEventType(event_value_change));
    +    panel->set_event_presets_changed(wxEventType(event_presets_changed));
    +
    +    const wxString& tab_name = panel->GetName();
    +    bool add_panel = true;
    +
    +    auto it = std::find_if( preset_tabs.begin(), preset_tabs.end(), 
    +                           [tab_name](PresetTab& tab){return tab.name == tab_name; });
    +    if (it != preset_tabs.end()) {
    +        it->panel = panel;
    +        add_panel = it->technology == g_PresetBundle->printers.get_edited_preset().printer_technology();
    +    }
    +
    +    if (add_panel)
    +	    g_wxTabPanel->AddPage(panel, panel->title());
     }
     
     void load_current_presets()
    @@ -723,6 +852,22 @@ unsigned get_colour_approx_luma(const wxColour &colour)
     	));
     }
     
    +wxWindow* get_right_panel(){
    +	return g_right_panel;
    +}
    +
    +wxFrame* get_main_frame() {
    +	return g_wxMainFrame;
    +}
    +
    +wxNotebook * get_tab_panel() {
    +	return g_wxTabPanel;
    +}
    +
    +const size_t& label_width(){
    +	return m_label_width;
    +}
    +
     void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string items, bool initial_value)
     {
         if (comboCtrl == nullptr)
    @@ -791,14 +936,37 @@ wxString from_u8(const std::string &str)
     	return wxString::FromUTF8(str.c_str());
     }
     
    +void add_expert_mode_part(	wxWindow* parent, wxBoxSizer* sizer, 
    +							Model &model,
    +							int event_object_selection_changed,
    +							int event_object_settings_changed,
    +							int event_remove_object, 
    +							int event_update_scene)
    +{
    +	set_event_object_selection_changed(event_object_selection_changed);
    +	set_event_object_settings_changed(event_object_settings_changed);
    +	set_event_remove_object(event_remove_object);
    +	set_event_update_scene(event_update_scene);
    +	set_objects_from_model(model);
    +	init_mesh_icons();
    +
    +// 	wxWindowUpdateLocker noUpdates(parent);
    +
    +// 	add_objects_list(parent, sizer);
    +
    +// 	add_collapsible_panes(parent, sizer);
    +}
     
     void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer)
     {
     	DynamicPrintConfig*	config = &g_PresetBundle->prints.get_edited_preset().config;
    -	m_optgroup = std::make_shared(parent, "", config);
    +	std::shared_ptr optgroup = std::make_shared(parent, "", config);
     	const wxArrayInt& ar = preset_sizer->GetColWidths();
    -	m_optgroup->label_width = ar.IsEmpty() ? 100 : ar.front()-4; // doesn't work
    -	m_optgroup->m_on_change = [config](t_config_option_key opt_key, boost::any value){
    +	m_label_width = ar.IsEmpty() ? 100 : ar.front()-4;
    +	optgroup->label_width = m_label_width;
    +
    +	//Frequently changed parameters
    +	optgroup->m_on_change = [config](t_config_option_key opt_key, boost::any value){
     		TabPrint* tab_print = nullptr;
     		for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++i) {
     			Tab *tab = dynamic_cast(g_wxTabPanel->GetPage(i));
    @@ -813,7 +981,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl
     			return;
     
     		if (opt_key == "fill_density"){
    -			value = m_optgroup->get_config_value(*config, opt_key);
    +			value = m_optgroups[ogFrequentlyChangingParameters]->get_config_value(*config, opt_key);
     			tab_print->set_value(opt_key, value);
     			tab_print->update();
     		}
    @@ -836,14 +1004,14 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl
     			}
     			else{ //(opt_key == "support")
     				const wxString& selection = boost::any_cast(value);
    -				
    +
     				auto support_material = selection == _("None") ? false : true;
     				new_conf.set_key_value("support_material", new ConfigOptionBool(support_material));
     
     				if (selection == _("Everywhere"))
     					new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(false));
     				else if (selection == _("Support on build plate only"))
    -					new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(true));				
    +					new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(true));
     			}
     			tab_print->load_config(new_conf);
     		}
    @@ -851,10 +1019,10 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl
     		tab_print->update_dirty();
     	};
     
    -	Option option = m_optgroup->get_option("fill_density");
    +	Option option = optgroup->get_option("fill_density");
     	option.opt.sidetext = "";
     	option.opt.full_width = true;
    -	m_optgroup->append_single_option_line(option);
    +	optgroup->append_single_option_line(option);
     
     	ConfigOptionDef def;
     
    @@ -873,7 +1041,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl
     	def.default_value = new ConfigOptionStrings { selection };
     	option = Option(def, "support");
     	option.opt.full_width = true;
    -	m_optgroup->append_single_option_line(option);
    +	optgroup->append_single_option_line(option);
     
     	m_brim_width = config->opt_float("brim_width");
     	def.label = L("Brim");
    @@ -882,7 +1050,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl
     	def.gui_type = "";
     	def.default_value = new ConfigOptionBool{ m_brim_width > 0.0 ? true : false };
     	option = Option(def, "brim");
    -	m_optgroup->append_single_option_line(option);
    +	optgroup->append_single_option_line(option);
     
     
         Line line = { "", "" };
    @@ -908,16 +1076,95 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl
     			}));
     			return sizer;
     		};
    -		m_optgroup->append_line(line);
    +		optgroup->append_line(line);
     
    +	sizer->Add(optgroup->sizer, 0, wxEXPAND | wxBOTTOM, 2);
     
    +	m_optgroups.push_back(optgroup);// ogFrequentlyChangingParameters
     
    -	sizer->Add(m_optgroup->sizer, 1, wxEXPAND | wxBOTTOM, 2);
    +	// Object List
    +	add_objects_list(parent, sizer);
    +
    +	// Frequently Object Settings
    +	add_object_settings(parent, sizer);
     }
     
    -ConfigOptionsGroup* get_optgroup()
    +void show_frequently_changed_parameters(bool show)
     {
    -	return m_optgroup.get();
    +	g_frequently_changed_parameters_sizer->Show(show);
    +	if (!show) return;
    +
    +	for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++i) {
    +		Tab *tab = dynamic_cast(g_wxTabPanel->GetPage(i));
    +		if (!tab)
    +			continue;
    +		tab->update_wiping_button_visibility();
    +		break;
    +	}
    +}
    +
    +void show_buttons(bool show)
    +{
    +	g_btn_export_stl->Show(show);
    +	g_btn_reslice->Show(show);
    +	for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++i) {
    +		TabPrinter *tab = dynamic_cast(g_wxTabPanel->GetPage(i));
    +		if (!tab)
    +			continue;
    +        if (g_PresetBundle->printers.get_selected_preset().printer_technology() == ptFFF) {
    +            g_btn_print->Show(show && !tab->m_config->opt_string("serial_port").empty());
    +            g_btn_send_gcode->Show(show && !tab->m_config->opt_string("print_host").empty());
    +        }
    +		break;
    +	}
    +}
    +
    +void show_info_sizer(bool show)
    +{
    +	g_scrolled_window_sizer->Show(static_cast(0), show); 
    +	g_scrolled_window_sizer->Show(1, show && g_show_print_info);
    +	g_manifold_warning_icon->Show(show && g_show_manifold_warning_icon);
    +}
    +
    +void update_mode()
    +{
    +	wxWindowUpdateLocker noUpdates(g_right_panel);
    +
    +	// TODO There is a not the best place of it!
    +	//*** Update style of the "Export G-code" button****
    +	if (g_btn_export_gcode->GetFont() != bold_font()){
    +		g_btn_export_gcode->SetBackgroundColour(wxColour(252, 77, 1));
    +		g_btn_export_gcode->SetFont(bold_font());
    +	}
    +	// ***********************************
    +
    +	ConfigMenuIDs mode = get_view_mode();
    +
    +// 	show_frequently_changed_parameters(mode >= ConfigMenuModeRegular);
    +// 	g_expert_mode_part_sizer->Show(mode == ConfigMenuModeExpert);
    +	g_object_list_sizer->Show(mode == ConfigMenuModeExpert);
    +	show_info_sizer(mode == ConfigMenuModeExpert);
    +	show_buttons(mode == ConfigMenuModeExpert);
    +
    +	// TODO There is a not the best place of it!
    +	// *** Update showing of the collpane_settings
    +// 	show_collpane_settings(mode == ConfigMenuModeExpert);
    +	// *************************
    +	g_right_panel->GetParent()->Layout();
    +	g_right_panel->Layout();
    +}
    +
    +bool is_expert_mode(){
    +	return get_view_mode() == ConfigMenuModeExpert;
    +}
    +
    +ConfigOptionsGroup* get_optgroup(size_t i)
    +{
    +	return m_optgroups[i].get();
    +}
    +
    +std::vector >& get_optgroups() {
    +	return m_optgroups;
     }
     
     wxButton* get_wiping_dialog_button()
    diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp
    index 165288819b..69e83eaabb 100644
    --- a/xs/src/slic3r/GUI/GUI.hpp
    +++ b/xs/src/slic3r/GUI/GUI.hpp
    @@ -3,8 +3,9 @@
     
     #include 
     #include 
    -#include "Config.hpp"
    +#include "PrintConfig.hpp"
     #include "../../libslic3r/Utils.hpp"
    +#include "GUI_ObjectParts.hpp"
     
     #include 
     #include 
    @@ -12,7 +13,6 @@
     class wxApp;
     class wxWindow;
     class wxFrame;
    -class wxFont;
     class wxMenuBar;
     class wxNotebook;
     class wxComboCtrl;
    @@ -24,6 +24,8 @@ class wxBoxSizer;
     class wxFlexGridSizer;
     class wxButton;
     class wxFileDialog;
    +class wxStaticBitmap;
    +class wxFont;
     
     namespace Slic3r { 
     
    @@ -67,19 +69,26 @@ typedef std::map t_file_wild_card;
     inline t_file_wild_card& get_file_wild_card() {
     	static t_file_wild_card FILE_WILDCARDS;
     	if (FILE_WILDCARDS.empty()){
    -		FILE_WILDCARDS["known"] = "Known files (*.stl, *.obj, *.amf, *.xml, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.prusa;*.PRUSA";
    -		FILE_WILDCARDS["stl"] = "STL files (*.stl)|*.stl;*.STL";
    -		FILE_WILDCARDS["obj"] = "OBJ files (*.obj)|*.obj;*.OBJ";
    -        FILE_WILDCARDS["amf"] = "AMF files (*.amf)|*.zip.amf;*.amf;*.AMF;*.xml;*.XML";
    -        FILE_WILDCARDS["3mf"] = "3MF files (*.3mf)|*.3mf;*.3MF;";
    -        FILE_WILDCARDS["prusa"] = "Prusa Control files (*.prusa)|*.prusa;*.PRUSA";
    -		FILE_WILDCARDS["ini"] = "INI files *.ini|*.ini;*.INI";
    +		FILE_WILDCARDS["known"]	= "Known files (*.stl, *.obj, *.amf, *.xml, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.prusa;*.PRUSA";
    +		FILE_WILDCARDS["stl"]	= "STL files (*.stl)|*.stl;*.STL";
    +		FILE_WILDCARDS["obj"]	= "OBJ files (*.obj)|*.obj;*.OBJ";
    +        FILE_WILDCARDS["amf"]	= "AMF files (*.amf)|*.zip.amf;*.amf;*.AMF;*.xml;*.XML";
    +        FILE_WILDCARDS["3mf"]	= "3MF files (*.3mf)|*.3mf;*.3MF;";
    +        FILE_WILDCARDS["prusa"]	= "Prusa Control files (*.prusa)|*.prusa;*.PRUSA";
    +		FILE_WILDCARDS["ini"]	= "INI files *.ini|*.ini;*.INI";
     		FILE_WILDCARDS["gcode"] = "G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC";
    -		FILE_WILDCARDS["svg"] = "SVG files *.svg|*.svg;*.SVG";
    +		FILE_WILDCARDS["svg"]	= "SVG files *.svg|*.svg;*.SVG";
     	}
     	return FILE_WILDCARDS;
     }
     
    +struct PresetTab {
    +    std::string       name;
    +    Tab*              panel;
    +    PrinterTechnology technology;
    +};
    +
    +
     void disable_screensaver();
     void enable_screensaver();
     bool debugged();
    @@ -93,10 +102,26 @@ void set_app_config(AppConfig *app_config);
     void set_preset_bundle(PresetBundle *preset_bundle);
     void set_preset_updater(PresetUpdater *updater);
     void set_3DScene(_3DScene *scene);
    +void set_objects_from_perl(	wxWindow* parent,
    +							wxBoxSizer *frequently_changed_parameters_sizer,
    +							wxBoxSizer *expert_mode_part_sizer,
    +							wxBoxSizer *scrolled_window_sizer,
    +							wxButton *btn_export_gcode,
    +							wxButton *btn_export_stl,
    +							wxButton *btn_reslice,
    +							wxButton *btn_print,
    +							wxButton *btn_send_gcode,
    +							wxStaticBitmap *manifold_warning_icon);
    +void set_show_print_info(bool show);
    +void set_show_manifold_warning_icon(bool show);
    +void set_objects_list_sizer(wxBoxSizer *objects_list_sizer);
     
    -AppConfig*	get_app_config();
    -wxApp*		get_app();
    -PresetBundle* get_preset_bundle();
    +AppConfig*		get_app_config();
    +wxApp*			get_app();
    +PresetBundle*	get_preset_bundle();
    +wxFrame*		get_main_frame();
    +wxNotebook *	get_tab_panel();
    +wxNotebook*		get_tab_panel();
     
     const wxColour& get_label_clr_modified();
     const wxColour& get_label_clr_sys();
    @@ -108,6 +133,14 @@ void set_label_clr_sys(const wxColour& clr);
     const wxFont& small_font();
     const wxFont& bold_font();
     
    +void open_model(wxWindow *parent, wxArrayString& input_files);
    +
    +wxWindow*			get_right_panel();
    +const size_t&		label_width();
    +
    +Tab*         get_tab(const std::string& name);
    +const std::vector& get_preset_tabs();
    +
     extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change);
     
     // This is called when closing the application, when loading a config file or when starting the config wizard
    @@ -130,7 +163,7 @@ void create_preset_tabs(bool no_controller, int event_value_change, int event_pr
     TabIface* get_preset_tab_iface(char *name);
     
     // add it at the end of the tab panel.
    -void add_created_tab(Tab* panel);
    +void add_created_tab(Tab* panel, int event_value_change, int event_presets_changed);
     // Change option value in config
     void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0);
     
    @@ -150,6 +183,8 @@ void save_language();
     void get_installed_languages(wxArrayString & names, wxArrayLong & identifiers);
     // select language from the list of installed languages
     bool select_language(wxArrayString & names, wxArrayLong & identifiers);
    +// update right panel of the Plater according to view mode
    +void update_mode();
     
     std::vector& get_tabs_list();
     bool checked_tab(Tab* tab);
    @@ -169,13 +204,22 @@ wxString	L_str(const std::string &str);
     // Return wxString from std::string in UTF8
     wxString	from_u8(const std::string &str);
     
    -
    +void add_expert_mode_part(	wxWindow* parent, wxBoxSizer* sizer, 
    +							Model &model,
    +							int event_object_selection_changed,
    +							int event_object_settings_changed,
    +							int event_remove_object, 
    +							int event_update_scene);
     void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer);
    +// Update view mode according to selected menu 
    +void update_mode();
    +bool is_expert_mode();
     
     // Callback to trigger a configuration update timer on the Plater.
     static PerlCallback g_on_request_update_callback;
      
    -ConfigOptionsGroup* get_optgroup();
    +ConfigOptionsGroup* get_optgroup(size_t i); 
    +std::vector >& get_optgroups();
     wxButton*			get_wiping_dialog_button();
     
     void add_export_option(wxFileDialog* dlg, const std::string& format);
    diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    new file mode 100644
    index 0000000000..50b1a1f5a3
    --- /dev/null
    +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    @@ -0,0 +1,1644 @@
    +#include "GUI.hpp"
    +#include "OptionsGroup.hpp"
    +#include "PresetBundle.hpp"
    +#include "GUI_ObjectParts.hpp"
    +#include "Model.hpp"
    +#include "wxExtensions.hpp"
    +#include "LambdaObjectDialog.hpp"
    +#include "../../libslic3r/Utils.hpp"
    +
    +#include 
    +#include 
    +#include 
    +#include "Geometry.hpp"
    +#include "slic3r/Utils/FixModelByWin10.hpp"
    +
    +namespace Slic3r
    +{
    +namespace GUI
    +{
    +wxSizer		*m_sizer_object_buttons = nullptr;
    +wxSizer		*m_sizer_part_buttons = nullptr;
    +wxSizer		*m_sizer_object_movers = nullptr;
    +wxDataViewCtrl				*m_objects_ctrl = nullptr;
    +PrusaObjectDataViewModel	*m_objects_model = nullptr;
    +wxCollapsiblePane			*m_collpane_settings = nullptr;
    +
    +wxIcon		m_icon_modifiermesh;
    +wxIcon		m_icon_solidmesh;
    +wxIcon		m_icon_manifold_warning;
    +wxBitmap	m_bmp_cog;
    +wxBitmap	m_bmp_split;
    +
    +wxSlider*	m_mover_x = nullptr;
    +wxSlider*	m_mover_y = nullptr;
    +wxSlider*	m_mover_z = nullptr;
    +wxButton*	m_btn_move_up = nullptr;
    +wxButton*	m_btn_move_down = nullptr;
    +Vec3d		m_move_options;
    +Vec3d		m_last_coords;
    +int			m_selected_object_id = -1;
    +
    +bool		g_prevent_list_events = false;		// We use this flag to avoid circular event handling Select() 
    +												// happens to fire a wxEVT_LIST_ITEM_SELECTED on OSX, whose event handler 
    +												// calls this method again and again and again
    +bool        g_is_percent_scale = false;         // It indicates if scale unit is percentage
    +int         g_rotation_x = 0;                   // Last value of the rotation around the X axis
    +int         g_rotation_y = 0;                   // Last value of the rotation around the Y axis
    +ModelObjectPtrs*			m_objects;
    +std::shared_ptr m_config;
    +std::shared_ptr m_default_config;
    +wxBoxSizer*					m_option_sizer = nullptr;
    +
    +// option groups for settings
    +std::vector > m_og_settings;
    +
    +int			m_event_object_selection_changed = 0;
    +int			m_event_object_settings_changed = 0;
    +int			m_event_remove_object = 0;
    +int			m_event_update_scene = 0;
    +
    +bool m_parts_changed = false;
    +bool m_part_settings_changed = false;
    +
    +#ifdef __WXOSX__
    +    wxString g_selected_extruder = "";
    +#endif //__WXOSX__
    +
    +// typedef std::map t_category_icon;
    +typedef std::map t_category_icon;
    +inline t_category_icon& get_category_icon() {
    +	static t_category_icon CATEGORY_ICON;
    +	if (CATEGORY_ICON.empty()){
    +		CATEGORY_ICON[L("Layers and Perimeters")]	= wxBitmap(from_u8(Slic3r::var("layers.png")), wxBITMAP_TYPE_PNG);
    +		CATEGORY_ICON[L("Infill")]					= wxBitmap(from_u8(Slic3r::var("infill.png")), wxBITMAP_TYPE_PNG);
    +		CATEGORY_ICON[L("Support material")]		= wxBitmap(from_u8(Slic3r::var("building.png")), wxBITMAP_TYPE_PNG);
    +		CATEGORY_ICON[L("Speed")]					= wxBitmap(from_u8(Slic3r::var("time.png")), wxBITMAP_TYPE_PNG);
    +		CATEGORY_ICON[L("Extruders")]				= wxBitmap(from_u8(Slic3r::var("funnel.png")), wxBITMAP_TYPE_PNG);
    +		CATEGORY_ICON[L("Extrusion Width")]			= wxBitmap(from_u8(Slic3r::var("funnel.png")), wxBITMAP_TYPE_PNG);
    +// 		CATEGORY_ICON[L("Skirt and brim")]			= wxBitmap(from_u8(Slic3r::var("box.png")), wxBITMAP_TYPE_PNG);
    +// 		CATEGORY_ICON[L("Speed > Acceleration")]	= wxBitmap(from_u8(Slic3r::var("time.png")), wxBITMAP_TYPE_PNG);
    +		CATEGORY_ICON[L("Advanced")]				= wxBitmap(from_u8(Slic3r::var("wand.png")), wxBITMAP_TYPE_PNG);
    +	}
    +	return CATEGORY_ICON;
    +}
    +
    +std::vector get_options(const bool is_part)
    +{
    +	PrintRegionConfig reg_config;
    +	auto options = reg_config.keys();
    +	if (!is_part) {
    +		PrintObjectConfig obj_config;
    +		std::vector obj_options = obj_config.keys();
    +		options.insert(options.end(), obj_options.begin(), obj_options.end());
    +	}
    +	return options;
    +}
    +
    +//				  category ->		vector 			 ( option	;  label )
    +typedef std::map< std::string, std::vector< std::pair > > settings_menu_hierarchy;
    +void get_options_menu(settings_menu_hierarchy& settings_menu, bool is_part)
    +{
    +	auto options = get_options(is_part);
    +
    +    auto extruders_cnt = get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA ? 1 :
    +                         get_preset_bundle()->printers.get_edited_preset().config.option("nozzle_diameter")->values.size();
    +
    +	DynamicPrintConfig config;
    +	for (auto& option : options)
    +	{
    +		auto const opt = config.def()->get(option);
    +		auto category = opt->category;
    +        if (category.empty() ||
    +            (category == "Extruders" && extruders_cnt == 1)) continue;
    +
    +		std::pair option_label(option, opt->label);
    +		std::vector< std::pair > new_category;
    +		auto& cat_opt_label = settings_menu.find(category) == settings_menu.end() ? new_category : settings_menu.at(category);
    +		cat_opt_label.push_back(option_label);
    +		if (cat_opt_label.size() == 1)
    +			settings_menu[category] = cat_opt_label;
    +	}
    +}
    +
    +void set_event_object_selection_changed(const int& event){
    +	m_event_object_selection_changed = event;
    +}
    +void set_event_object_settings_changed(const int& event){
    +	m_event_object_settings_changed = event;
    +}
    +void set_event_remove_object(const int& event){
    +	m_event_remove_object = event;
    +}
    +void set_event_update_scene(const int& event){
    +	m_event_update_scene = event;
    +}
    +
    +void set_objects_from_model(Model &model) {
    +	m_objects = &(model.objects);
    +}
    +
    +void init_mesh_icons(){
    +	m_icon_modifiermesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("lambda.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("plugin.png")), wxBITMAP_TYPE_PNG);
    +	m_icon_solidmesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("object.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("package.png")), wxBITMAP_TYPE_PNG);
    +
    +	// init icon for manifold warning
    +	m_icon_manifold_warning = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("exclamation_mark_.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("error.png")), wxBITMAP_TYPE_PNG);
    +
    +	// init bitmap for "Split to sub-objects" context menu
    +    m_bmp_split = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("split.png")), wxBITMAP_TYPE_PNG);
    +
    +	// init bitmap for "Add Settings" context menu
    +	m_bmp_cog = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("cog.png")), wxBITMAP_TYPE_PNG);
    +}
    +
    +bool is_parts_changed(){return m_parts_changed;}
    +bool is_part_settings_changed(){ return m_part_settings_changed; }
    +
    +static wxString dots("…", wxConvUTF8);
    +
    +void set_tooltip_for_item(const wxPoint& pt)
    +{
    +    wxDataViewItem item;
    +    wxDataViewColumn* col;
    +    m_objects_ctrl->HitTest(pt, item, col);
    +    if (!item) return;
    +
    +    if (col->GetTitle() == " ")
    +        m_objects_ctrl->GetMainWindow()->SetToolTip(_(L("Right button click the icon to change the object settings")));
    +    else if (col->GetTitle() == _("Name") &&
    +        m_objects_model->GetIcon(item).GetRefData() == m_icon_manifold_warning.GetRefData()) {
    +        int obj_idx = m_objects_model->GetIdByItem(item);
    +        auto& stats = (*m_objects)[obj_idx]->volumes[0]->mesh.stl.stats;
    +        int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed +
    +            stats.facets_added + stats.facets_reversed + stats.backwards_edges;
    +
    +        wxString tooltip = wxString::Format(_(L("Auto-repaired (%d errors):\n")), errors);
    +
    +        std::map error_msg;
    +        error_msg[L("degenerate facets")] = stats.degenerate_facets;
    +        error_msg[L("edges fixed")] = stats.edges_fixed;
    +        error_msg[L("facets removed")] = stats.facets_removed;
    +        error_msg[L("facets added")] = stats.facets_added;
    +        error_msg[L("facets reversed")] = stats.facets_reversed;
    +        error_msg[L("backwards edges")] = stats.backwards_edges;
    +
    +        for (auto error : error_msg)
    +        {
    +            if (error.second > 0)
    +                tooltip += wxString::Format(_("\t%d %s\n"), error.second, error.first);
    +        }
    +// OR
    +//             tooltip += wxString::Format(_(L("%d degenerate facets, %d edges fixed, %d facets removed, "
    +//                                             "%d facets added, %d facets reversed, %d backwards edges")),
    +//                                             stats.degenerate_facets, stats.edges_fixed, stats.facets_removed,
    +//                                             stats.facets_added, stats.facets_reversed, stats.backwards_edges);
    +
    +        if (is_windows10())
    +            tooltip += _(L("Right button click the icon to fix STL through Netfabb"));
    +
    +        m_objects_ctrl->GetMainWindow()->SetToolTip(tooltip);
    +    }
    +    else
    +        m_objects_ctrl->GetMainWindow()->SetToolTip(""); // hide tooltip
    +}
    +
    +wxPoint get_mouse_position_in_control() {
    +    const wxPoint& pt = wxGetMousePosition();
    +    wxWindow* win = m_objects_ctrl->GetMainWindow();
    +    return wxPoint(pt.x - win->GetScreenPosition().x, 
    +                   pt.y - win->GetScreenPosition().y);
    +}
    +
    +bool is_mouse_position_in_control(wxPoint& pt) {
    +    pt = get_mouse_position_in_control();
    +    const wxSize& cz = m_objects_ctrl->GetSize();
    +    if (pt.x > 0 && pt.x < cz.x &&
    +        pt.y > 0 && pt.y < cz.y)
    +        return true;
    +    return false;
    +}
    +
    +wxDataViewColumn* object_ctrl_create_extruder_column(int extruders_count)
    +{
    +    wxArrayString choices;
    +    choices.Add("default");
    +    for (int i = 1; i <= extruders_count; ++i)
    +        choices.Add(wxString::Format("%d", i));
    +    wxDataViewChoiceRenderer *c =
    +        new wxDataViewChoiceRenderer(choices, wxDATAVIEW_CELL_EDITABLE, wxALIGN_CENTER_HORIZONTAL);
    +    wxDataViewColumn* column = new wxDataViewColumn(_(L("Extruder")), c, 3, 60, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE);
    +    return column;
    +}
    +
    +void create_objects_ctrl(wxWindow* win, wxBoxSizer*& objects_sz)
    +{
    +	m_objects_ctrl = new wxDataViewCtrl(win, wxID_ANY, wxDefaultPosition, wxDefaultSize);
    +	m_objects_ctrl->SetInitialSize(wxSize(-1, 150)); // TODO - Set correct height according to the opened/closed objects
    +
    +	objects_sz = new wxBoxSizer(wxVERTICAL);
    +	objects_sz->Add(m_objects_ctrl, 1, wxGROW | wxLEFT, 20);
    +
    +	m_objects_model = new PrusaObjectDataViewModel;
    +	m_objects_ctrl->AssociateModel(m_objects_model);
    +#if wxUSE_DRAG_AND_DROP && wxUSE_UNICODE
    +	m_objects_ctrl->EnableDragSource(wxDF_UNICODETEXT);
    +	m_objects_ctrl->EnableDropTarget(wxDF_UNICODETEXT);
    +#endif // wxUSE_DRAG_AND_DROP && wxUSE_UNICODE
    +
    +	// column 0(Icon+Text) of the view control:
    +	m_objects_ctrl->AppendIconTextColumn(_(L("Name")), 0, wxDATAVIEW_CELL_INERT, 120,
    +		wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE);
    +
    +	// column 1 of the view control:
    +	m_objects_ctrl->AppendTextColumn(_(L("Copy")), 1, wxDATAVIEW_CELL_INERT, 45,
    +		wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE);
    +
    +	// column 2 of the view control:
    +	m_objects_ctrl->AppendTextColumn(_(L("Scale")), 2, wxDATAVIEW_CELL_INERT, 55,
    +		wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE);
    +
    +	// column 3 of the view control:
    +    m_objects_ctrl->AppendColumn(object_ctrl_create_extruder_column(4));
    +
    +	// column 4 of the view control:
    +	m_objects_ctrl->AppendBitmapColumn(" ", 4, wxDATAVIEW_CELL_INERT, 25,
    +		wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE);
    +}
    +
    +// ****** from GUI.cpp
    +wxBoxSizer* create_objects_list(wxWindow *win)
    +{
    +    wxBoxSizer* objects_sz;
    +    // create control
    +    create_objects_ctrl(win, objects_sz);
    +
    +    // describe control behavior 
    +    m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [](wxEvent& event) {
    +		object_ctrl_selection_changed();
    +#ifndef __WXMSW__
    +        set_tooltip_for_item(get_mouse_position_in_control());
    +#endif //__WXMSW__        
    +	});
    +
    +    m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, [](wxDataViewEvent& event) {
    +        object_ctrl_context_menu();
    +		event.Skip();
    +	});
    +
    +    m_objects_ctrl->Bind(wxEVT_CHAR, [](wxKeyEvent& event) { object_ctrl_key_event(event); }); // doesn't work on OSX
    +
    +#ifdef __WXMSW__
    +    // Extruder value changed
    +	m_objects_ctrl->Bind(wxEVT_CHOICE, [](wxCommandEvent& event) { update_extruder_in_config(event.GetString()); });
    +
    +    m_objects_ctrl->GetMainWindow()->Bind(wxEVT_MOTION, [](wxMouseEvent& event) {
    +         set_tooltip_for_item(event.GetPosition());
    +         event.Skip();
    +    });
    +#else
    +    // equivalent to wxEVT_CHOICE on __WXMSW__
    +    m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, [](wxDataViewEvent& event) { object_ctrl_item_value_change(event); });
    +#endif //__WXMSW__
    +
    +    m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_BEGIN_DRAG,    [](wxDataViewEvent& e) {on_begin_drag(e);});
    +    m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE, [](wxDataViewEvent& e) {on_drop_possible(e); });
    +    m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_DROP,          [](wxDataViewEvent& e) {on_drop(e);});
    +	return objects_sz;
    +}
    +
    +wxBoxSizer* create_edit_object_buttons(wxWindow* win)
    +{
    +	auto sizer = new wxBoxSizer(wxVERTICAL);
    +
    +	auto btn_load_part = new wxButton(win, wxID_ANY, /*Load */"part" + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/);
    +	auto btn_load_modifier = new wxButton(win, wxID_ANY, /*Load */"modifier" + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/);
    +	auto btn_load_lambda_modifier = new wxButton(win, wxID_ANY, /*Load */"generic" + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/);
    +	auto btn_delete = new wxButton(win, wxID_ANY, "Delete"/*" part"*/, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/);
    +	auto btn_split = new wxButton(win, wxID_ANY, "Split"/*" part"*/, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/);
    +	m_btn_move_up = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize/*wxSize(30, -1)*/, wxBU_LEFT);
    +	m_btn_move_down = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize/*wxSize(30, -1)*/, wxBU_LEFT);
    +
    +	//*** button's functions
    +	btn_load_part->Bind(wxEVT_BUTTON, [win](wxEvent&) {
    +		on_btn_load(win);
    +	});
    +
    +	btn_load_modifier->Bind(wxEVT_BUTTON, [win](wxEvent&) {
    +		on_btn_load(win, true);
    +	});
    +
    +	btn_load_lambda_modifier->Bind(wxEVT_BUTTON, [win](wxEvent&) {
    +		on_btn_load(win, true, true);
    +	});
    +
    +	btn_delete		->Bind(wxEVT_BUTTON, [](wxEvent&) { on_btn_del(); });
    +	btn_split		->Bind(wxEVT_BUTTON, [](wxEvent&) { on_btn_split(true); });
    +	m_btn_move_up	->Bind(wxEVT_BUTTON, [](wxEvent&) { on_btn_move_up(); });
    +	m_btn_move_down	->Bind(wxEVT_BUTTON, [](wxEvent&) { on_btn_move_down(); });
    +	//***
    +
    +	m_btn_move_up->SetMinSize(wxSize(20, -1));
    +	m_btn_move_down->SetMinSize(wxSize(20, -1));
    +	btn_load_part->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_add.png")), wxBITMAP_TYPE_PNG));
    +	btn_load_modifier->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_add.png")), wxBITMAP_TYPE_PNG));
    +	btn_load_lambda_modifier->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_add.png")), wxBITMAP_TYPE_PNG));
    +	btn_delete->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_delete.png")), wxBITMAP_TYPE_PNG));
    +	btn_split->SetBitmap(wxBitmap(from_u8(Slic3r::var("shape_ungroup.png")), wxBITMAP_TYPE_PNG));
    +	m_btn_move_up->SetBitmap(wxBitmap(from_u8(Slic3r::var("bullet_arrow_up.png")), wxBITMAP_TYPE_PNG));
    +	m_btn_move_down->SetBitmap(wxBitmap(from_u8(Slic3r::var("bullet_arrow_down.png")), wxBITMAP_TYPE_PNG));
    +
    +	m_sizer_object_buttons = new wxGridSizer(1, 3, 0, 0);
    +	m_sizer_object_buttons->Add(btn_load_part, 0, wxEXPAND);
    +	m_sizer_object_buttons->Add(btn_load_modifier, 0, wxEXPAND);
    +	m_sizer_object_buttons->Add(btn_load_lambda_modifier, 0, wxEXPAND);
    +	m_sizer_object_buttons->Show(false);
    +
    +	m_sizer_part_buttons = new wxGridSizer(1, 3, 0, 0);
    +	m_sizer_part_buttons->Add(btn_delete, 0, wxEXPAND);
    +	m_sizer_part_buttons->Add(btn_split, 0, wxEXPAND);
    +	{
    +		auto up_down_sizer = new wxGridSizer(1, 2, 0, 0);
    +		up_down_sizer->Add(m_btn_move_up, 1, wxEXPAND);
    +		up_down_sizer->Add(m_btn_move_down, 1, wxEXPAND);
    +		m_sizer_part_buttons->Add(up_down_sizer, 0, wxEXPAND);
    +	}
    +	m_sizer_part_buttons->Show(false);
    +
    +	btn_load_part->SetFont(Slic3r::GUI::small_font());
    +	btn_load_modifier->SetFont(Slic3r::GUI::small_font());
    +	btn_load_lambda_modifier->SetFont(Slic3r::GUI::small_font());
    +	btn_delete->SetFont(Slic3r::GUI::small_font());
    +	btn_split->SetFont(Slic3r::GUI::small_font());
    +	m_btn_move_up->SetFont(Slic3r::GUI::small_font());
    +	m_btn_move_down->SetFont(Slic3r::GUI::small_font());
    +
    +	sizer->Add(m_sizer_object_buttons, 0, wxEXPAND | wxLEFT, 20);
    +	sizer->Add(m_sizer_part_buttons, 0, wxEXPAND | wxLEFT, 20);
    +	return sizer;
    +}
    +
    +void update_after_moving()
    +{
    +	auto item = m_objects_ctrl->GetSelection();
    +	if (!item || m_selected_object_id<0)
    +		return;
    +
    +	auto volume_id = m_objects_model->GetVolumeIdByItem(item);
    +	if (volume_id < 0)
    +		return;
    +
    +	Vec3d m = m_move_options;
    +	Vec3d l = m_last_coords;
    +
    +    auto d = Vec3d(m(0) - l(0), m(1) - l(1), m(2) - l(2));
    +	auto volume = (*m_objects)[m_selected_object_id]->volumes[volume_id];
    +    volume->mesh.translate(d(0), d(1), d(2));
    +	m_last_coords = m;
    +
    +	m_parts_changed = true;
    +	parts_changed(m_selected_object_id);
    +}
    +
    +wxSizer* object_movers(wxWindow *win)
    +{
    +// 	DynamicPrintConfig* config = &get_preset_bundle()->/*full_config();//*/printers.get_edited_preset().config; // TODO get config from Model_volume
    +	std::shared_ptr optgroup = std::make_shared(win, "Move"/*, config*/);
    +	optgroup->label_width = 20;
    +	optgroup->m_on_change = [](t_config_option_key opt_key, boost::any value){
    +		int val = boost::any_cast(value);
    +		bool update = false;
    +        if (opt_key == "x" && m_move_options(0) != val){
    +			update = true;
    +            m_move_options(0) = val;
    +		}
    +        else if (opt_key == "y" && m_move_options(1) != val){
    +			update = true;
    +            m_move_options(1) = val;
    +		}
    +        else if (opt_key == "z" && m_move_options(2) != val){
    +			update = true;
    +            m_move_options(2) = val;
    +		}
    +		if (update) update_after_moving();
    +	};
    +
    +	ConfigOptionDef def;
    +	def.label = L("X");
    +	def.type = coInt;
    +	def.gui_type = "slider";
    +	def.default_value = new ConfigOptionInt(0);
    +
    +	Option option = Option(def, "x");
    +	option.opt.full_width = true;
    +	optgroup->append_single_option_line(option);
    +	m_mover_x = dynamic_cast(optgroup->get_field("x")->getWindow());
    +
    +	def.label = L("Y");
    +	option = Option(def, "y");
    +	optgroup->append_single_option_line(option);
    +	m_mover_y = dynamic_cast(optgroup->get_field("y")->getWindow());
    +
    +	def.label = L("Z");
    +	option = Option(def, "z");
    +	optgroup->append_single_option_line(option);
    +	m_mover_z = dynamic_cast(optgroup->get_field("z")->getWindow());
    +
    +	get_optgroups().push_back(optgroup);  // ogObjectMovers
    +
    +	m_sizer_object_movers = optgroup->sizer;
    +	m_sizer_object_movers->Show(false);
    +
    +	m_move_options = Vec3d(0, 0, 0);
    +	m_last_coords = Vec3d(0, 0, 0);
    +
    +	return optgroup->sizer;
    +}
    +
    +wxBoxSizer* content_settings(wxWindow *win)
    +{
    +	DynamicPrintConfig* config = &get_preset_bundle()->/*full_config();//*/printers.get_edited_preset().config; // TODO get config from Model_volume
    +	std::shared_ptr optgroup = std::make_shared(win, "Extruders", config);
    +	optgroup->label_width = label_width();
    +
    +	Option option = optgroup->get_option("extruder");
    +	option.opt.default_value = new ConfigOptionInt(1);
    +	optgroup->append_single_option_line(option);
    +
    +	get_optgroups().push_back(optgroup);  // ogObjectSettings
    +
    +	auto sizer = new wxBoxSizer(wxVERTICAL);
    +	sizer->Add(create_edit_object_buttons(win), 0, wxEXPAND, 0); // *** Edit Object Buttons***
    +
    +	sizer->Add(optgroup->sizer, 1, wxEXPAND | wxLEFT, 20);
    +
    +	auto add_btn = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER);
    +	if (wxMSW) add_btn->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
    +	add_btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("add.png")), wxBITMAP_TYPE_PNG));
    +	sizer->Add(add_btn, 0, wxALIGN_LEFT | wxLEFT, 20);
    +
    +	sizer->Add(object_movers(win), 0, wxEXPAND | wxLEFT, 20);
    +
    +	return sizer;
    +}
    +
    +void add_objects_list(wxWindow* parent, wxBoxSizer* sizer)
    +{
    +	const auto ol_sizer = create_objects_list(parent);
    +	sizer->Add(ol_sizer, 1, wxEXPAND | wxTOP, 20);
    +	set_objects_list_sizer(ol_sizer);
    +}
    +
    +Line add_og_to_object_settings(const std::string& option_name, const std::string& sidetext, int def_value = 0)
    +{
    +	Line line = { _(option_name), "" };
    +
    +	ConfigOptionDef def;
    +	def.type = coInt;
    +	def.default_value = new ConfigOptionInt(def_value);
    +	def.sidetext = sidetext;
    +	def.width = 70;
    +
    +    if (option_name == "Rotation")
    +        def.min = -360;
    +
    +	const std::string lower_name = boost::algorithm::to_lower_copy(option_name);
    +
    +	std::vector axes{ "x", "y", "z" };
    +	for (auto axis : axes) {
    +		def.label = boost::algorithm::to_upper_copy(axis);
    +		Option option = Option(def, lower_name + "_" + axis);
    +		option.opt.full_width = true;
    +		line.append_option(option);
    +	}
    +
    +	if (option_name == "Scale")
    +	{
    +		def.label = L("Units");
    +		def.type = coStrings;
    +		def.gui_type = "select_open";
    +		def.enum_labels.push_back(L("%"));
    +		def.enum_labels.push_back(L("mm"));
    +		def.default_value = new ConfigOptionStrings{ "%" };
    +		def.sidetext = " ";
    +
    +		Option option = Option(def, lower_name + "_unit");
    +		line.append_option(option);
    +	}
    +
    +	return line;
    +}
    +
    +void add_object_settings(wxWindow* parent, wxBoxSizer* sizer)
    +{
    +	auto optgroup = std::make_shared(parent, _(L("Object Settings")));
    +	optgroup->label_width = 100;
    +	optgroup->set_grid_vgap(5);
    +
    +	optgroup->m_on_change = [](t_config_option_key opt_key, boost::any value){
    +		if (opt_key == "scale_unit"){
    +			const wxString& selection = boost::any_cast(value);
    +			std::vector axes{ "x", "y", "z" };
    +			for (auto axis : axes) {
    +				std::string key = "scale_" + axis;
    +				get_optgroup(ogFrequentlyObjectSettings)->set_side_text(key, selection);
    +			}
    +
    +            g_is_percent_scale = selection == _("%");
    +            update_scale_values();
    +		}
    +	};
    +
    +// 	def.label = L("Name");
    +// 	def.type = coString;
    +// 	def.tooltip = L("Object name");
    +// 	def.full_width = true;
    +// 	def.default_value = new ConfigOptionString{ "BlaBla_object.stl" };
    +// 	optgroup->append_single_option_line(Option(def, "object_name"));
    +
    +	ConfigOptionDef def;
    +
    +	def.label = L("Name");
    +// 	def.type = coString;
    +    def.gui_type = "legend";
    +	def.tooltip = L("Object name");
    +	def.full_width = true;
    +	def.default_value = new ConfigOptionString{ " " };
    +	optgroup->append_single_option_line(Option(def, "object_name"));
    +
    +	optgroup->set_flag(ogSIDE_OPTIONS_VERTICAL);
    +	optgroup->sidetext_width = 25;
    +
    +	optgroup->append_line(add_og_to_object_settings(L("Position"), L("mm")));
    +	optgroup->append_line(add_og_to_object_settings(L("Rotation"), "°"));
    +	optgroup->append_line(add_og_to_object_settings(L("Scale"), "%"));
    +
    +	optgroup->set_flag(ogDEFAULT);
    +
    +	def.label = L("Place on bed");
    +	def.type = coBool;
    +	def.tooltip = L("Automatic placing of models on printing bed in Y axis");
    +	def.gui_type = "";
    +	def.sidetext = "";
    +	def.default_value = new ConfigOptionBool{ false };
    +	optgroup->append_single_option_line(Option(def, "place_on_bed"));
    +
    +	m_option_sizer = new wxBoxSizer(wxVERTICAL);
    +	optgroup->sizer->Add(m_option_sizer, 1, wxEXPAND | wxLEFT, 5);
    +
    +	sizer->Add(optgroup->sizer, 0, wxEXPAND | wxLEFT | wxTOP, 20);
    +
    +	optgroup->disable();
    +
    +	get_optgroups().push_back(optgroup);  // ogFrequentlyObjectSettings
    +
    +// 	add_current_settings();
    +}
    +
    +
    +// add Collapsible Pane to sizer
    +wxCollapsiblePane* add_collapsible_pane(wxWindow* parent, wxBoxSizer* sizer_parent, const wxString& name, std::function content_function)
    +{
    +#ifdef __WXMSW__
    +	auto *collpane = new PrusaCollapsiblePaneMSW(parent, wxID_ANY, name);
    +#else
    +	auto *collpane = new PrusaCollapsiblePane/*wxCollapsiblePane*/(parent, wxID_ANY, name);
    +#endif // __WXMSW__
    +	// add the pane with a zero proportion value to the sizer which contains it
    +	sizer_parent->Add(collpane, 0, wxGROW | wxALL, 0);
    +
    +	wxWindow *win = collpane->GetPane();
    +
    +	wxSizer *sizer = content_function(win);
    +
    +	wxSizer *sizer_pane = new wxBoxSizer(wxVERTICAL);
    +	sizer_pane->Add(sizer, 1, wxGROW | wxEXPAND | wxBOTTOM, 2);
    +	win->SetSizer(sizer_pane);
    +	// 	sizer_pane->SetSizeHints(win);
    +	return collpane;
    +}
    +
    +void add_collapsible_panes(wxWindow* parent, wxBoxSizer* sizer)
    +{
    +	// *** Objects List ***	
    +	auto collpane = add_collapsible_pane(parent, sizer, "Objects List:", create_objects_list);
    +	collpane->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, ([collpane](wxCommandEvent& e){
    +		// 		wxWindowUpdateLocker noUpdates(g_right_panel);
    +		if (collpane->IsCollapsed()) {
    +			m_sizer_object_buttons->Show(false);
    +			m_sizer_part_buttons->Show(false);
    +			m_sizer_object_movers->Show(false);
    +			if (!m_objects_ctrl->HasSelection())
    +				m_collpane_settings->Show(false);
    +		}
    +	}));
    +
    +	// *** Object/Part Settings ***
    +	m_collpane_settings = add_collapsible_pane(parent, sizer, "Object Settings", content_settings);
    +}
    +
    +void show_collpane_settings(bool expert_mode)
    +{
    +	m_collpane_settings->Show(expert_mode && !m_objects_model->IsEmpty());
    +}
    +
    +void add_object_to_list(const std::string &name, ModelObject* model_object)
    +{
    +	wxString item_name = name;
    +	int scale = model_object->instances[0]->scaling_factor * 100;
    +	auto item = m_objects_model->Add(item_name, model_object->instances.size(), scale);
    +	m_objects_ctrl->Select(item);
    +
    +	// Add error icon if detected auto-repaire
    +	auto stats = model_object->volumes[0]->mesh.stl.stats;
    +	int errors =	stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + 
    +					stats.facets_added + stats.facets_reversed + stats.backwards_edges;
    +	if (errors > 0)		{
    +		const wxDataViewIconText data(item_name, m_icon_manifold_warning);
    +		wxVariant variant;
    +		variant << data;
    +		m_objects_model->SetValue(variant, item, 0);
    +	}
    +
    +    if (model_object->volumes.size() > 1) {
    +        for (auto id = 0; id < model_object->volumes.size(); id++)
    +            m_objects_model->AddChild(item, 
    +                                      model_object->volumes[id]->name, 
    +                                      m_icon_solidmesh, 
    +                                      model_object->volumes[id]->config.option("extruder")->value,
    +                                      false);
    +        m_objects_ctrl->Expand(item);
    +    }
    +
    +#ifndef __WXOSX__ 
    +	object_ctrl_selection_changed();
    +#endif //__WXMSW__
    +}
    +
    +void delete_object_from_list()
    +{
    +	auto item = m_objects_ctrl->GetSelection();
    +	if (!item || m_objects_model->GetParent(item) != wxDataViewItem(0))
    +		return;
    +// 	m_objects_ctrl->Select(m_objects_model->Delete(item));
    +	m_objects_model->Delete(item);
    +
    +// 	if (m_objects_model->IsEmpty())
    +// 		m_collpane_settings->Show(false);
    +}
    +
    +void delete_all_objects_from_list()
    +{
    +	m_objects_model->DeleteAll();
    +// 	m_collpane_settings->Show(false);
    +}
    +
    +void set_object_count(int idx, int count)
    +{
    +	m_objects_model->SetValue(wxString::Format("%d", count), idx, 1);
    +	m_objects_ctrl->Refresh();
    +}
    +
    +void set_object_scale(int idx, int scale)
    +{
    +	m_objects_model->SetValue(wxString::Format("%d%%", scale), idx, 2);
    +	m_objects_ctrl->Refresh();
    +}
    +
    +void unselect_objects()
    +{
    +    if (!m_objects_ctrl->GetSelection())
    +        return;
    +
    +    g_prevent_list_events = true;
    +    m_objects_ctrl->UnselectAll();
    +    part_selection_changed();
    +    g_prevent_list_events = false;
    +}
    +
    +void select_current_object(int idx)
    +{
    +	g_prevent_list_events = true;
    +	m_objects_ctrl->UnselectAll();
    +	if (idx < 0) {
    +		g_prevent_list_events = false;
    +		return;
    +	}
    +	m_objects_ctrl->Select(m_objects_model->GetItemById(idx));
    +	part_selection_changed();
    +	g_prevent_list_events = false;
    +}
    +
    +void remove()
    +{
    +	auto item = m_objects_ctrl->GetSelection();
    +	if (!item)
    +		return;
    +	
    +	if (m_objects_model->GetParent(item) == wxDataViewItem(0)) {
    +		if (m_event_remove_object > 0) {
    +			wxCommandEvent event(m_event_remove_object);
    +			get_main_frame()->ProcessWindowEvent(event);
    +		}
    +// 		delete_object_from_list();
    +	}
    +	else
    +		on_btn_del();
    +}
    +
    +void object_ctrl_selection_changed()
    +{
    +	if (g_prevent_list_events) return;
    +
    +	part_selection_changed();
    +
    +	if (m_event_object_selection_changed > 0) {
    +		wxCommandEvent event(m_event_object_selection_changed);
    +		event.SetInt(int(m_objects_model->GetParent(m_objects_ctrl->GetSelection()) != wxDataViewItem(0)));
    +		event.SetId(m_selected_object_id);
    +		get_main_frame()->ProcessWindowEvent(event);
    +	}
    +
    +#ifdef __WXOSX__
    +    update_extruder_in_config(g_selected_extruder);
    +#endif //__WXOSX__        
    +}
    +
    +void object_ctrl_context_menu()
    +{
    +    wxDataViewItem item;
    +    wxDataViewColumn* col;
    +    m_objects_ctrl->HitTest(get_mouse_position_in_control(), item, col);
    +    wxString title = col->GetTitle();
    +    if (!item) return;
    +
    +    if (title == " ")
    +        show_context_menu();
    +// ys_FIXME
    +//         else if (title == _("Name") && pt.x >15 &&
    +//                     m_objects_model->GetIcon(item).GetRefData() == m_icon_manifold_warning.GetRefData())
    +//         {
    +//             if (is_windows10())
    +//                 fix_through_netfabb();
    +//         }
    +#ifndef __WXMSW__
    +    m_objects_ctrl->GetMainWindow()->SetToolTip(""); // hide tooltip
    +#endif //__WXMSW__
    +}
    +
    +void object_ctrl_key_event(wxKeyEvent& event)
    +{
    +    if (event.GetKeyCode() == WXK_TAB)
    +        m_objects_ctrl->Navigate(event.ShiftDown() ? wxNavigationKeyEvent::IsBackward : wxNavigationKeyEvent::IsForward);
    +    else if (event.GetKeyCode() == WXK_DELETE
    +#ifdef __WXOSX__
    +        || event.GetKeyCode() == WXK_BACK
    +#endif //__WXOSX__
    +        ){
    +        printf("WXK_BACK\n");
    +        remove();
    +    }
    +    else
    +        event.Skip();
    +}
    +
    +void object_ctrl_item_value_change(wxDataViewEvent& event)
    +{
    +    if (event.GetColumn() == 3)
    +    {
    +        wxVariant variant;
    +        m_objects_model->GetValue(variant, event.GetItem(), 3);
    +#ifdef __WXOSX__
    +        g_selected_extruder = variant.GetString();
    +#else // --> for Linux
    +        update_extruder_in_config(variant.GetString());
    +#endif //__WXOSX__  
    +    }
    +}
    +
    +//update_optgroup
    +void update_settings_list()
    +{
    +#ifdef __WXGTK__
    +    auto parent = get_optgroup(ogFrequentlyObjectSettings)->get_parent();
    +#else
    +    auto parent = get_optgroup(ogFrequentlyObjectSettings)->parent();
    +#endif /* __WXGTK__ */
    +    
    +// There is a bug related to Ubuntu overlay scrollbars, see https://github.com/prusa3d/Slic3r/issues/898 and https://github.com/prusa3d/Slic3r/issues/952.
    +// The issue apparently manifests when Show()ing a window with overlay scrollbars while the UI is frozen. For this reason,
    +// we will Thaw the UI prematurely on Linux. This means destroing the no_updates object prematurely.
    +#ifdef __linux__
    +	std::unique_ptr no_updates(new wxWindowUpdateLocker(parent));
    +#else
    +	wxWindowUpdateLocker noUpdates(parent);
    +#endif
    +
    +	m_option_sizer->Clear(true);
    +
    +	if (m_config) 
    +	{
    +        auto extra_column = [](wxWindow* parent, const Line& line)
    +		{
    +			auto opt_key = (line.get_options())[0].opt_id;  //we assume that we have one option per line
    +
    +			auto btn = new wxBitmapButton(parent, wxID_ANY, wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("erase.png")), wxBITMAP_TYPE_PNG),
    +				wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
    +			btn->Bind(wxEVT_BUTTON, [opt_key](wxEvent &event){
    +				(*m_config)->erase(opt_key);
    +				wxTheApp->CallAfter([]() { update_settings_list(); });
    +			});
    +			return btn;
    +		};
    +
    +		std::map> cat_options;
    +		auto opt_keys = (*m_config)->keys();
    +		if (opt_keys.size() == 1 && opt_keys[0] == "extruder")
    +			return;
    +
    +        auto extruders_cnt = get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA ? 1 :
    +                             get_preset_bundle()->printers.get_edited_preset().config.option("nozzle_diameter")->values.size();
    +
    +		for (auto& opt_key : opt_keys) {
    +			auto category = (*m_config)->def()->get(opt_key)->category;
    +			if (category.empty() ||
    +                (category == "Extruders" && extruders_cnt==1)) continue;
    +
    +			std::vector< std::string > new_category;
    +
    +			auto& cat_opt = cat_options.find(category) == cat_options.end() ? new_category : cat_options.at(category);
    +			cat_opt.push_back(opt_key);
    +			if (cat_opt.size() == 1)
    +				cat_options[category] = cat_opt;
    +		}
    +
    +
    +		m_og_settings.resize(0);
    +		for (auto& cat : cat_options) {
    +			if (cat.second.size() == 1 && cat.second[0] == "extruder")
    +				continue;
    +
    +			auto optgroup = std::make_shared(parent, cat.first, *m_config, false, ogDEFAULT, extra_column);
    +			optgroup->label_width = 100;
    +			optgroup->sidetext_width = 70;
    +
    +			for (auto& opt : cat.second)
    +			{
    +				if (opt == "extruder")
    +					continue;
    +				Option option = optgroup->get_option(opt);
    +				option.opt.width = 70;
    +				optgroup->append_single_option_line(option);
    +			}
    +			optgroup->reload_config();
    +			m_option_sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 0);
    +			m_og_settings.push_back(optgroup);
    +		}
    +	}
    +
    +#ifdef __linux__
    +	no_updates.reset(nullptr);
    +#endif
    +
    +    get_right_panel()->Layout();
    +	get_right_panel()->GetParent()->Layout();
    +}
    +
    +void get_settings_choice(wxMenu *menu, int id, bool is_part)
    +{
    +	auto category_name = menu->GetLabel(id);
    +
    +	wxArrayString names;
    +	wxArrayInt selections;
    +
    +	settings_menu_hierarchy settings_menu;
    +	get_options_menu(settings_menu, is_part);
    +	std::vector< std::pair > *settings_list = nullptr;
    +
    +	auto opt_keys = (*m_config)->keys();
    +
    +	for (auto& cat : settings_menu)
    +	{
    +		if (_(cat.first) == category_name) {
    +			int sel = 0;
    +			for (auto& pair : cat.second) {
    +				names.Add(_(pair.second));
    +				if (find(opt_keys.begin(), opt_keys.end(), pair.first) != opt_keys.end())
    +					selections.Add(sel);
    +				sel++;
    +			}
    +			settings_list = &cat.second;
    +			break;
    +		}
    +	} 
    +
    +	if (!settings_list)
    +		return;
    +
    +	if (wxGetMultipleChoices(selections, _(L("Select showing settings")), category_name, names) ==0 )
    +		return;
    +
    +	std::vector  selected_options;
    +	for (auto sel : selections)
    +		selected_options.push_back((*settings_list)[sel].first);
    +
    +	for (auto& setting:(*settings_list) )
    +	{
    +		auto& opt_key = setting.first;
    +		if (find(opt_keys.begin(), opt_keys.end(), opt_key) != opt_keys.end() &&
    +			find(selected_options.begin(), selected_options.end(), opt_key) == selected_options.end())
    +			(*m_config)->erase(opt_key);
    +		
    +		if(find(opt_keys.begin(), opt_keys.end(), opt_key) == opt_keys.end() &&
    +				find(selected_options.begin(), selected_options.end(), opt_key) != selected_options.end())
    +			(*m_config)->set_key_value(opt_key, m_default_config.get()->option(opt_key)->clone());
    +	}
    +
    +	update_settings_list();
    +}
    +
    +bool cur_item_hase_children()
    +{
    +    wxDataViewItemArray children;
    +    if (m_objects_model->GetChildren(m_objects_ctrl->GetSelection(), children) > 0)
    +        return true;
    +    return false;
    +}
    +
    +wxMenuItem* menu_item_split(wxMenu* menu, int id) {
    +    auto menu_item = new wxMenuItem(menu, id, _(L("Split to parts")));
    +    menu_item->SetBitmap(m_bmp_split);
    +    return menu_item;
    +}
    +
    +wxMenuItem* menu_item_settings(wxMenu* menu, int id) {
    +    auto  menu_item = new wxMenuItem(menu, id, _(L("Add settings")));
    +    menu_item->SetBitmap(m_bmp_cog);
    +
    +    auto sub_menu = create_add_settings_popupmenu(false);
    +    menu_item->SetSubMenu(sub_menu);
    +    return menu_item;
    +}
    +
    +wxMenu *create_add_part_popupmenu()
    +{
    +	wxMenu *menu = new wxMenu;
    +	std::vector menu_items = { L("Add part"), L("Add modifier"), L("Add generic") };
    +
    +	wxWindowID config_id_base = wxWindow::NewControlId(menu_items.size()+2);
    +
    +	int i = 0;
    +	for (auto& item : menu_items) {
    +		auto menu_item = new wxMenuItem(menu, config_id_base + i, _(item));
    +		menu_item->SetBitmap(i == 0 ? m_icon_solidmesh : m_icon_modifiermesh);
    +		menu->Append(menu_item);
    +		i++;
    +    }
    +
    +    menu->AppendSeparator();
    +    auto menu_item = menu_item_split(menu, config_id_base + i);
    +    menu->Append(menu_item);
    +    menu_item->Enable(!cur_item_hase_children());
    +
    +    menu->AppendSeparator();
    +    // Append settings popupmenu
    +    menu->Append(menu_item_settings(menu, config_id_base + i + 1));
    +
    +	wxWindow* win = get_tab_panel()->GetPage(0);
    +
    +	menu->Bind(wxEVT_MENU, [config_id_base, win, menu](wxEvent &event){
    +		switch (event.GetId() - config_id_base) {
    +		case 0:
    +			on_btn_load(win);
    +			break;
    +		case 1:
    +			on_btn_load(win, true);
    +			break;
    +		case 2:
    +			on_btn_load(win, true, true);
    +			break;
    +		case 3:
    +			on_btn_split(false);
    +			break;
    +		default:{
    +			get_settings_choice(menu, event.GetId(), false);
    +			break;}
    +		}
    +	});
    +
    +	return menu;
    +}
    +
    +wxMenu *create_part_settings_popupmenu()
    +{
    +    wxMenu *menu = new wxMenu;
    +    wxWindowID config_id_base = wxWindow::NewControlId(2);
    +
    +    menu->Append(menu_item_split(menu, config_id_base));
    +
    +    menu->AppendSeparator();
    +    // Append settings popupmenu
    +    menu->Append(menu_item_settings(menu, config_id_base + 1));
    +
    +    menu->Bind(wxEVT_MENU, [config_id_base, menu](wxEvent &event){
    +        switch (event.GetId() - config_id_base) {
    +        case 0:
    +            on_btn_split(true);
    +            break;
    +        default:{
    +            get_settings_choice(menu, event.GetId(), true);
    +            break; }
    +        }
    +    });
    +
    +    return menu;
    +}
    +
    +wxMenu *create_add_settings_popupmenu(bool is_part)
    +{
    +	wxMenu *menu = new wxMenu;
    +
    + 	auto categories = get_category_icon();
    +
    +	settings_menu_hierarchy settings_menu;
    +	get_options_menu(settings_menu, is_part);
    +
    +	for (auto cat : settings_menu)
    +	{
    +		auto menu_item = new wxMenuItem(menu, wxID_ANY, _(cat.first));
    +		menu_item->SetBitmap(categories.find(cat.first) == categories.end() ? 
    +								wxNullBitmap : categories.at(cat.first));
    +		menu->Append(menu_item);
    +	}
    +
    +	menu->Bind(wxEVT_MENU, [menu](wxEvent &event) {
    +		get_settings_choice(menu, event.GetId(), true);
    +	});
    +
    +	return menu;
    +}
    +
    +void show_context_menu()
    +{
    +    auto item = m_objects_ctrl->GetSelection();
    +    if (item)
    +    {
    +        if (m_objects_model->GetParent(item) == wxDataViewItem(0))				{
    +            auto menu = create_add_part_popupmenu();
    +            get_tab_panel()->GetPage(0)->PopupMenu(menu);
    +        }
    +        else {
    +            auto menu = create_part_settings_popupmenu();
    +            get_tab_panel()->GetPage(0)->PopupMenu(menu);
    +        }
    +    }
    +}
    +
    +// ******
    +
    +void load_part(	wxWindow* parent, ModelObject* model_object, 
    +				wxArrayString& part_names, const bool is_modifier)
    +{
    +	wxArrayString input_files;
    +	open_model(parent, input_files);
    +	for (int i = 0; i < input_files.size(); ++i) {
    +		std::string input_file = input_files.Item(i).ToStdString();
    +
    +		Model model;
    +		try {
    +			model = Model::read_from_file(input_file);
    +		}
    +		catch (std::exception &e) {
    +			auto msg = _(L("Error! ")) + input_file + " : " + e.what() + ".";
    +			show_error(parent, msg);
    +			exit(1);
    +		}
    +
    +		for ( auto object : model.objects) {
    +			for (auto volume : object->volumes) {
    +				auto new_volume = model_object->add_volume(*volume);
    +				new_volume->modifier = is_modifier;
    +				boost::filesystem::path(input_file).filename().string();
    +				new_volume->name = boost::filesystem::path(input_file).filename().string();
    +
    +				part_names.Add(new_volume->name);
    +
    +				// apply the same translation we applied to the object
    +				new_volume->mesh.translate( model_object->origin_translation(0),
    +											model_object->origin_translation(1), 
    +											model_object->origin_translation(2) );
    +				// set a default extruder value, since user can't add it manually
    +				new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
    +
    +				m_parts_changed = true;
    +			}
    +		}
    +	}
    +}
    +
    +void load_lambda(	wxWindow* parent, ModelObject* model_object,
    +					wxArrayString& part_names, const bool is_modifier)
    +{
    +	auto dlg = new LambdaObjectDialog(parent);
    +	if (dlg->ShowModal() == wxID_CANCEL) {
    +		return;
    +	}
    +
    +	std::string name = "lambda-";
    +	TriangleMesh mesh;
    +
    +	auto params = dlg->ObjectParameters();
    +	switch (params.type)
    +	{
    +	case LambdaTypeBox:{
    +		mesh = make_cube(params.dim[0], params.dim[1], params.dim[2]);
    +		name += "Box";
    +		break;}
    +	case LambdaTypeCylinder:{
    +		mesh = make_cylinder(params.cyl_r, params.cyl_h);
    +		name += "Cylinder";
    +		break;}
    +	case LambdaTypeSphere:{
    +		mesh = make_sphere(params.sph_rho);
    +		name += "Sphere";
    +		break;}
    +	case LambdaTypeSlab:{
    +		const auto& size = model_object->bounding_box().size();
    +        mesh = make_cube(size(0)*1.5, size(1)*1.5, params.slab_h);
    +		// box sets the base coordinate at 0, 0, move to center of plate and move it up to initial_z
    +        mesh.translate(-size(0)*1.5 / 2.0, -size(1)*1.5 / 2.0, params.slab_z);
    +		name += "Slab";
    +		break; }
    +	default:
    +		break;
    +	}
    +	mesh.repair();
    +
    +	auto new_volume = model_object->add_volume(mesh);
    +	new_volume->modifier = is_modifier;
    +	new_volume->name = name;
    +	// set a default extruder value, since user can't add it manually
    +	new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
    +
    +	part_names.Add(name);
    +
    +	m_parts_changed = true;
    +}
    +
    +void on_btn_load(wxWindow* parent, bool is_modifier /*= false*/, bool is_lambda/* = false*/)
    +{
    +	auto item = m_objects_ctrl->GetSelection();
    +	if (!item)
    +		return;
    +	int obj_idx = -1;
    +	if (m_objects_model->GetParent(item) == wxDataViewItem(0))
    +		obj_idx = m_objects_model->GetIdByItem(item);
    +	else
    +		return;
    +
    +	if (obj_idx < 0) return;
    +	wxArrayString part_names;
    +	if (is_lambda)
    +		load_lambda(parent, (*m_objects)[obj_idx], part_names, is_modifier);
    +	else
    +		load_part(parent, (*m_objects)[obj_idx], part_names, is_modifier);
    +
    +	parts_changed(obj_idx);
    +
    +	for (int i = 0; i < part_names.size(); ++i)
    +		m_objects_ctrl->Select(	m_objects_model->AddChild(item, part_names.Item(i), 
    +								is_modifier ? m_icon_modifiermesh : m_icon_solidmesh));
    +// 	part_selection_changed();
    +#ifdef __WXMSW__
    +	object_ctrl_selection_changed();
    +#endif //__WXMSW__
    +}
    +
    +void on_btn_del()
    +{
    +	auto item = m_objects_ctrl->GetSelection();
    +	if (!item) return;
    +
    +	auto volume_id = m_objects_model->GetVolumeIdByItem(item);
    +	if (volume_id < 0)
    +		return;
    +	auto volume = (*m_objects)[m_selected_object_id]->volumes[volume_id];
    +
    +	// if user is deleting the last solid part, throw error
    +	int solid_cnt = 0;
    +	for (auto vol : (*m_objects)[m_selected_object_id]->volumes)
    +		if (!vol->modifier)
    +			++solid_cnt;
    +	if (!volume->modifier && solid_cnt == 1) {
    +		Slic3r::GUI::show_error(nullptr, _(L("You can't delete the last solid part from this object.")));
    +		return;
    +	}
    +
    +	(*m_objects)[m_selected_object_id]->delete_volume(volume_id);
    +	m_parts_changed = true;
    +
    +	parts_changed(m_selected_object_id);
    +
    +	m_objects_ctrl->Select(m_objects_model->Delete(item));
    +	part_selection_changed();
    +// #ifdef __WXMSW__
    +// 	object_ctrl_selection_changed();
    +// #endif //__WXMSW__
    +}
    +
    +void on_btn_split(const bool split_part)
    +{
    +	auto item = m_objects_ctrl->GetSelection();
    +	if (!item || m_selected_object_id<0)
    +		return;
    +	auto volume_id = m_objects_model->GetVolumeIdByItem(item);
    +    ModelVolume* volume;
    +    if (volume_id < 0) {
    +        if (split_part) return;
    +        else
    +            volume = (*m_objects)[m_selected_object_id]->volumes[0]; }
    +    else
    +	    volume = (*m_objects)[m_selected_object_id]->volumes[volume_id];
    + 	DynamicPrintConfig&	config = get_preset_bundle()->printers.get_edited_preset().config;
    +    auto nozzle_dmrs_cnt = config.option("nozzle_diameter")->values.size();
    +    auto split_rez = volume->split(nozzle_dmrs_cnt);
    +    if (split_rez == 1) {
    +        wxMessageBox(_(L("The selected object couldn't be split because it contains only one part.")));
    +        return;
    +    }
    +
    +    auto model_object = (*m_objects)[m_selected_object_id];
    +
    +    if (split_part) {
    +        auto parent = m_objects_model->GetParent(item);
    +        m_objects_model->DeleteChildren(parent);
    +
    +        for (auto id = 0; id < model_object->volumes.size(); id++)
    +            m_objects_model->AddChild(parent, model_object->volumes[id]->name,
    +                                      model_object->volumes[id]->modifier ? m_icon_modifiermesh : m_icon_solidmesh,
    +                                      model_object->volumes[id]->config.option("extruder")->value, 
    +                                      false);
    +
    +        m_objects_ctrl->Expand(parent);
    +    }
    +    else {
    +        for (auto id = 0; id < model_object->volumes.size(); id++)
    +            m_objects_model->AddChild(item, model_object->volumes[id]->name, 
    +                                      m_icon_solidmesh,
    +                                      model_object->volumes[id]->config.option("extruder")->value, 
    +                                      false);
    +        m_objects_ctrl->Expand(item);
    +    }
    +}
    +
    +void on_btn_move_up(){
    +	auto item = m_objects_ctrl->GetSelection();
    +	if (!item)
    +		return;
    +	auto volume_id = m_objects_model->GetVolumeIdByItem(item);
    +	if (volume_id < 0)
    +		return;
    +	auto& volumes = (*m_objects)[m_selected_object_id]->volumes;
    +	if (0 < volume_id && volume_id < volumes.size()) {
    +		std::swap(volumes[volume_id - 1], volumes[volume_id]);
    +		m_parts_changed = true;
    +		m_objects_ctrl->Select(m_objects_model->MoveChildUp(item));
    +		part_selection_changed();
    +// #ifdef __WXMSW__
    +// 		object_ctrl_selection_changed();
    +// #endif //__WXMSW__
    +	}
    +}
    +
    +void on_btn_move_down(){
    +	auto item = m_objects_ctrl->GetSelection();
    +	if (!item)
    +		return;
    +	auto volume_id = m_objects_model->GetVolumeIdByItem(item);
    +	if (volume_id < 0)
    +		return;
    +	auto& volumes = (*m_objects)[m_selected_object_id]->volumes;
    +	if (0 <= volume_id && volume_id+1 < volumes.size()) {
    +		std::swap(volumes[volume_id + 1], volumes[volume_id]);
    +		m_parts_changed = true;
    +		m_objects_ctrl->Select(m_objects_model->MoveChildDown(item));
    +		part_selection_changed();
    +// #ifdef __WXMSW__
    +// 		object_ctrl_selection_changed();
    +// #endif //__WXMSW__
    +	}
    +}
    +
    +void parts_changed(int obj_idx)
    +{ 
    +	if (m_event_object_settings_changed <= 0) return;
    +
    +	wxCommandEvent e(m_event_object_settings_changed);
    +	auto event_str = wxString::Format("%d %d %d", obj_idx,
    +		is_parts_changed() ? 1 : 0,
    +		is_part_settings_changed() ? 1 : 0);
    +	e.SetString(event_str);
    +	get_main_frame()->ProcessWindowEvent(e);
    +}
    +	
    +void update_settings_value()
    +{
    +	auto og = get_optgroup(ogFrequentlyObjectSettings);
    +	if (m_selected_object_id < 0 || m_objects->size() <= m_selected_object_id) {
    +		og->set_value("scale_x", 0);
    +		og->set_value("scale_y", 0);
    +		og->set_value("scale_z", 0);
    +        og->disable();
    +		return;
    +	}
    +    g_is_percent_scale = boost::any_cast(og->get_value("scale_unit")) == _("%");
    +    update_scale_values();
    +    update_rotation_values();
    +    og->enable();
    +}
    +
    +void part_selection_changed()
    +{
    +	auto item = m_objects_ctrl->GetSelection();
    +	int obj_idx = -1;
    +	auto og = get_optgroup(ogFrequentlyObjectSettings);
    +	if (item)
    +	{
    +		bool is_part = false;
    +		if (m_objects_model->GetParent(item) == wxDataViewItem(0)) {
    +			obj_idx = m_objects_model->GetIdByItem(item);
    +			og->set_name(" " + _(L("Object Settings")) + " ");
    +			m_config = std::make_shared(&(*m_objects)[obj_idx]->config);
    +		}
    +		else {
    +			auto parent = m_objects_model->GetParent(item);
    +			// Take ID of the parent object to "inform" perl-side which object have to be selected on the scene
    +			obj_idx = m_objects_model->GetIdByItem(parent);
    +			og->set_name(" " + _(L("Part Settings")) + " ");
    +			is_part = true;
    +			auto volume_id = m_objects_model->GetVolumeIdByItem(item);
    +			m_config = std::make_shared(&(*m_objects)[obj_idx]->volumes[volume_id]->config);
    +		}
    +
    +		auto config = m_config;
    +        og->set_value("object_name", m_objects_model->GetName(item));
    +		m_default_config = std::make_shared(*DynamicPrintConfig::new_from_defaults_keys(get_options(is_part)));
    +	}
    +    else {
    +        wxString empty_str = wxEmptyString;
    +        og->set_value("object_name", empty_str);
    +        m_config = nullptr;
    +    }
    +
    +	update_settings_list();
    +
    +	m_selected_object_id = obj_idx;
    +
    +	update_settings_value();
    +
    +/*	wxWindowUpdateLocker noUpdates(get_right_panel());
    +
    +	m_move_options = Point3(0, 0, 0);
    +	m_last_coords = Point3(0, 0, 0);
    +	// reset move sliders
    +	std::vector opt_keys = {"x", "y", "z"};
    +	auto og = get_optgroup(ogObjectMovers);
    +	for (auto opt_key: opt_keys)
    +		og->set_value(opt_key, int(0));
    +
    +// 	if (!item || m_selected_object_id < 0){
    +	if (m_selected_object_id < 0){
    +		m_sizer_object_buttons->Show(false);
    +		m_sizer_part_buttons->Show(false);
    +		m_sizer_object_movers->Show(false);
    +		m_collpane_settings->Show(false);
    +		return;
    +	}
    +
    +	m_collpane_settings->Show(true);
    +
    +	auto volume_id = m_objects_model->GetVolumeIdByItem(item);
    +	if (volume_id < 0){
    +		m_sizer_object_buttons->Show(true);
    +		m_sizer_part_buttons->Show(false);
    +		m_sizer_object_movers->Show(false);
    +		m_collpane_settings->SetLabelText(_(L("Object Settings")) + ":");
    +
    +// 		elsif($itemData->{type} eq 'object') {
    +// 			# select nothing in 3D preview
    +// 
    +// 			# attach object config to settings panel
    +// 			$self->{optgroup_movers}->disable;
    +// 			$self->{staticbox}->SetLabel('Object Settings');
    +// 			@opt_keys = (map @{$_->get_keys}, Slic3r::Config::PrintObject->new, Slic3r::Config::PrintRegion->new);
    +// 			$config = $self->{model_object}->config;
    +// 		}
    +
    +		return;
    +	}
    +
    +	m_collpane_settings->SetLabelText(_(L("Part Settings")) + ":");
    +	
    +	m_sizer_object_buttons->Show(false);
    +	m_sizer_part_buttons->Show(true);
    +	m_sizer_object_movers->Show(true);
    +
    +	auto bb_size = m_objects[m_selected_object_id]->bounding_box().size();
    +	int scale = 10; //??
    +
    +	m_mover_x->SetMin(-bb_size.x * 4 * scale);
    +	m_mover_x->SetMax(bb_size.x * 4 * scale);
    +
    +	m_mover_y->SetMin(-bb_size.y * 4 * scale);
    +	m_mover_y->SetMax(bb_size.y * 4 * scale);
    +
    +	m_mover_z->SetMin(-bb_size.z * 4 * scale);
    +	m_mover_z->SetMax(bb_size.z * 4 * scale);
    +
    +
    +	
    +//	my ($config, @opt_keys);
    +	m_btn_move_up->Enable(volume_id > 0);
    +	m_btn_move_down->Enable(volume_id + 1 < m_objects[m_selected_object_id]->volumes.size());
    +
    +	// attach volume config to settings panel
    +	auto volume = m_objects[m_selected_object_id]->volumes[volume_id];
    +
    +	if (volume->modifier) 
    +		og->enable();
    +	else 
    +		og->disable();
    +
    +//	auto config = volume->config;
    +
    +	// get default values
    +// 	@opt_keys = @{Slic3r::Config::PrintRegion->new->get_keys};
    +// 	} 
    +/*	
    +	# get default values
    +	my $default_config = Slic3r::Config::new_from_defaults_keys(\@opt_keys);
    +
    +	# append default extruder
    +	push @opt_keys, 'extruder';
    +	$default_config->set('extruder', 0);
    +	$config->set_ifndef('extruder', 0);
    +	$self->{settings_panel}->set_default_config($default_config);
    +	$self->{settings_panel}->set_config($config);
    +	$self->{settings_panel}->set_opt_keys(\@opt_keys);
    +	$self->{settings_panel}->set_fixed_options([qw(extruder)]);
    +	$self->{settings_panel}->enable;
    +	}
    +	 */
    +}
    +
    +void set_extruder_column_hidden(bool hide)
    +{
    +	m_objects_ctrl->GetColumn(3)->SetHidden(hide);
    +}
    +
    +void update_extruder_in_config(const wxString& selection)
    +{
    +    if (!m_config || selection.empty())
    +        return;
    +
    +    int extruder = selection.size() > 1 ? 0 : atoi(selection.c_str());
    +    (*m_config)->set_key_value("extruder", new ConfigOptionInt(extruder));
    +
    +    if (m_event_update_scene > 0) {
    +        wxCommandEvent e(m_event_update_scene);
    +        get_main_frame()->ProcessWindowEvent(e);
    +    }
    +}
    +
    +void update_scale_values()
    +{
    +    update_scale_values((*m_objects)[m_selected_object_id]->instance_bounding_box(0).size(),
    +                        (*m_objects)[m_selected_object_id]->instances[0]->scaling_factor);
    +}
    +
    +void update_scale_values(const Vec3d& size, float scaling_factor)
    +{
    +    auto og = get_optgroup(ogFrequentlyObjectSettings);
    +
    +    if (g_is_percent_scale) {
    +        auto scale = scaling_factor * 100;
    +        og->set_value("scale_x", int(scale));
    +        og->set_value("scale_y", int(scale));
    +        og->set_value("scale_z", int(scale));
    +    }
    +    else {
    +        og->set_value("scale_x", int(size(0) + 0.5));
    +        og->set_value("scale_y", int(size(1) + 0.5));
    +        og->set_value("scale_z", int(size(2) + 0.5));
    +    }
    +}
    +
    +void update_rotation_values()
    +{
    +    auto og = get_optgroup(ogFrequentlyObjectSettings);
    +
    +    og->set_value("rotation_x", 0);
    +    og->set_value("rotation_y", 0);
    +
    +    auto rotation_z = (*m_objects)[m_selected_object_id]->instances[0]->rotation;
    +    auto deg = int(Geometry::rad2deg(rotation_z));
    +//     if (deg > 180) deg -= 360;
    +
    +    og->set_value("rotation_z", deg);
    +}
    +
    +void update_rotation_value(const double angle, const std::string& axis)
    +{
    +    auto og = get_optgroup(ogFrequentlyObjectSettings);
    +    
    +    int deg = int(Geometry::rad2deg(angle));
    +//     if (deg>180) deg -= 360;
    +
    +    og->set_value("rotation_"+axis, deg);
    +}
    +
    +void on_begin_drag(wxDataViewEvent &event)
    +{
    +    wxDataViewItem item(event.GetItem());
    +
    +    // only allow drags for item, not containers
    +    if (m_objects_model->GetParent(item) == wxDataViewItem(0)) {
    +        event.Veto();
    +        return;
    +    }
    +
    +    /* Under MSW or OSX, DnD moves an item to the place of another selected item
    +     * But under GTK, DnD moves an item between another two items.
    +     * And as a result - call EVT_CHANGE_SELECTION to unselect all items.
    +     * To prevent such behavior use g_prevent_list_events
    +    **/
    +    g_prevent_list_events = true;//it's needed for GTK
    +
    +    wxTextDataObject *obj = new wxTextDataObject;
    +    obj->SetText(wxString::Format("%d", m_objects_model->GetVolumeIdByItem(item)));
    +    event.SetDataObject(obj);
    +    event.SetDragFlags(/*wxDrag_AllowMove*/wxDrag_DefaultMove); // allows both copy and move;
    +}
    +
    +void on_drop_possible(wxDataViewEvent &event)
    +{
    +    wxDataViewItem item(event.GetItem());
    +
    +    // only allow drags for item or background, not containers
    +    if (item.IsOk() && m_objects_model->GetParent(item) == wxDataViewItem(0) ||
    +        event.GetDataFormat() != wxDF_UNICODETEXT)
    +        event.Veto();
    +}
    +
    +void on_drop(wxDataViewEvent &event)
    +{
    +    wxDataViewItem item(event.GetItem());
    +
    +    // only allow drops for item, not containers
    +    if (item.IsOk() && m_objects_model->GetParent(item) == wxDataViewItem(0) ||
    +        event.GetDataFormat() != wxDF_UNICODETEXT) {
    +        event.Veto();
    +        return;
    +    }    
    +
    +    wxTextDataObject obj;
    +    obj.SetData(wxDF_UNICODETEXT, event.GetDataSize(), event.GetDataBuffer());
    +
    +    int from_volume_id = std::stoi(obj.GetText().ToStdString());
    +    int to_volume_id = m_objects_model->GetVolumeIdByItem(item);
    +
    +#ifdef __WXGTK__
    +    /* Under GTK, DnD moves an item between another two items.
    +     * And event.GetItem() return item, which is under "insertion line"
    +     * So, if we move item down we should to decrease the to_volume_id value
    +    **/
    +    if (to_volume_id > from_volume_id) to_volume_id--;
    +#endif // __WXGTK__
    +
    +    m_objects_ctrl->Select(m_objects_model->ReorganizeChildren(from_volume_id, to_volume_id,
    +                                                               m_objects_model->GetParent(item)));
    +
    +    auto& volumes = (*m_objects)[m_selected_object_id]->volumes;
    +    auto delta = to_volume_id < from_volume_id ? -1 : 1;
    +    int cnt = 0;
    +    for (int id = from_volume_id; cnt < abs(from_volume_id - to_volume_id); id+=delta, cnt++)
    +        std::swap(volumes[id], volumes[id +delta]);
    +
    +    g_prevent_list_events = false;
    +}
    +
    +void update_objects_list_extruder_column(int extruders_count)
    +{
    +    if (get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA)
    +        extruders_count = 1;
    +
    +    // delete old 3rd column
    +    m_objects_ctrl->DeleteColumn(m_objects_ctrl->GetColumn(3));
    +    // insert new created 3rd column
    +    m_objects_ctrl->InsertColumn(3, object_ctrl_create_extruder_column(extruders_count));
    +    // set show/hide for this column 
    +    set_extruder_column_hidden(extruders_count <= 1);
    +}
    +
    +} //namespace GUI
    +} //namespace Slic3r 
    \ No newline at end of file
    diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp
    new file mode 100644
    index 0000000000..40dfb92797
    --- /dev/null
    +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp
    @@ -0,0 +1,126 @@
    +#ifndef slic3r_GUI_ObjectParts_hpp_
    +#define slic3r_GUI_ObjectParts_hpp_
    +
    +class wxWindow;
    +class wxSizer;
    +class wxBoxSizer;
    +class wxString;
    +class wxArrayString;
    +class wxMenu;
    +class wxDataViewEvent;
    +class wxKeyEvent;
    +
    +namespace Slic3r {
    +class ModelObject;
    +class Model;
    +
    +namespace GUI {
    +
    +enum ogGroup{
    +	ogFrequentlyChangingParameters,
    +	ogFrequentlyObjectSettings,
    +	ogCurrentSettings
    +// 	ogObjectSettings,
    +// 	ogObjectMovers,
    +// 	ogPartSettings
    +};
    +
    +enum LambdaTypeIDs{
    +	LambdaTypeBox,
    +	LambdaTypeCylinder,
    +	LambdaTypeSphere,
    +	LambdaTypeSlab
    +};
    +
    +struct OBJECT_PARAMETERS
    +{
    +	LambdaTypeIDs	type = LambdaTypeBox;
    +	double			dim[3];// = { 1.0, 1.0, 1.0 };
    +	int				cyl_r = 1;
    +	int				cyl_h = 1;
    +	double			sph_rho = 1.0;
    +	double			slab_h = 1.0;
    +	double			slab_z = 0.0;
    +};
    +
    +void add_collapsible_panes(wxWindow* parent, wxBoxSizer* sizer);
    +void add_objects_list(wxWindow* parent, wxBoxSizer* sizer);
    +void add_object_settings(wxWindow* parent, wxBoxSizer* sizer);
    +void show_collpane_settings(bool expert_mode);
    +
    +wxMenu *create_add_settings_popupmenu(bool is_part);
    +wxMenu *create_add_part_popupmenu();
    +wxMenu *create_part_settings_popupmenu();
    +
    +// Add object to the list
    +//void add_object(const std::string &name);
    +void add_object_to_list(const std::string &name, ModelObject* model_object);
    +// Delete object from the list
    +void delete_object_from_list();
    +// Delete all objects from the list
    +void delete_all_objects_from_list();
    +// Set count of object on c++ side
    +void set_object_count(int idx, int count);
    +// Set object scale on c++ side
    +void set_object_scale(int idx, int scale);
    +// Unselect all objects in the list on c++ side
    +void unselect_objects();
    +// Select current object in the list on c++ side
    +void select_current_object(int idx);
    +// Remove objects/sub-object from the list
    +void remove();
    +
    +void object_ctrl_selection_changed();
    +void object_ctrl_context_menu();
    +void object_ctrl_key_event(wxKeyEvent& event);
    +void object_ctrl_item_value_change(wxDataViewEvent& event);
    +void show_context_menu();
    +
    +void init_mesh_icons();
    +void set_event_object_selection_changed(const int& event);
    +void set_event_object_settings_changed(const int& event); 
    +void set_event_remove_object(const int& event);
    +void set_event_update_scene(const int& event);
    +void set_objects_from_model(Model &model);
    +
    +bool is_parts_changed();
    +bool is_part_settings_changed();
    +
    +void load_part(	wxWindow* parent, ModelObject* model_object, 
    +				wxArrayString& part_names, const bool is_modifier); 
    +
    +void load_lambda(wxWindow* parent, ModelObject* model_object, 
    +				wxArrayString& part_names, const bool is_modifier);
    +
    +void on_btn_load(wxWindow* parent, bool is_modifier = false, bool is_lambda = false);
    +void on_btn_del();
    +void on_btn_split(const bool split_part);
    +void on_btn_move_up();
    +void on_btn_move_down();
    +
    +void parts_changed(int obj_idx);
    +void part_selection_changed();
    +
    +void update_settings_value();
    +// show/hide "Extruder" column for Objects List
    +void set_extruder_column_hidden(bool hide);
    +// update extruder in current config
    +void update_extruder_in_config(const wxString& selection);
    +// update scale values after scale unit changing or "gizmos"
    +void update_scale_values();
    +void update_scale_values(const Vec3d& size, float scale);
    +// update rotation values object selection changing
    +void update_rotation_values();
    +// update rotation value after "gizmos"
    +void update_rotation_value(const double angle, const std::string& axis);
    +
    +void on_begin_drag(wxDataViewEvent &event);
    +void on_drop_possible(wxDataViewEvent &event);
    +void on_drop(wxDataViewEvent &event);
    +
    +// update extruder column for objects_ctrl according to extruders count
    +void update_objects_list_extruder_column(int extruders_count);
    +
    +} //namespace GUI
    +} //namespace Slic3r 
    +#endif  //slic3r_GUI_ObjectParts_hpp_
    \ No newline at end of file
    diff --git a/xs/src/slic3r/GUI/LambdaObjectDialog.cpp b/xs/src/slic3r/GUI/LambdaObjectDialog.cpp
    new file mode 100644
    index 0000000000..7543821c0e
    --- /dev/null
    +++ b/xs/src/slic3r/GUI/LambdaObjectDialog.cpp
    @@ -0,0 +1,176 @@
    +#include "LambdaObjectDialog.hpp"
    +
    +#include 
    +#include 
    +#include "OptionsGroup.hpp"
    +
    +namespace Slic3r
    +{
    +namespace GUI
    +{
    +static wxString dots("…", wxConvUTF8);
    +
    +LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent)
    +{
    +	Create(parent, wxID_ANY, _(L("Lambda Object")),
    +		wxDefaultPosition, wxDefaultSize,
    +		wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
    +
    +	// instead of double dim[3] = { 1.0, 1.0, 1.0 };
    +	object_parameters.dim[0] = 1.0;
    +	object_parameters.dim[1] = 1.0;
    +	object_parameters.dim[2] = 1.0;
    +
    +	sizer = new wxBoxSizer(wxVERTICAL);
    +
    +	// modificator options
    +	m_modificator_options_book = new wxChoicebook(	this, wxID_ANY, wxDefaultPosition, 
    +													wxDefaultSize, wxCHB_TOP);
    +	sizer->Add(m_modificator_options_book, 1, wxEXPAND| wxALL, 10);
    +
    +	auto optgroup = init_modificator_options_page(_(L("Box")));
    +		optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
    +			int opt_id =	opt_key == "l" ? 0 :
    +							opt_key == "w" ? 1 : 
    +							opt_key == "h" ? 2 : -1;
    +			if (opt_id < 0) return;
    +			object_parameters.dim[opt_id] = boost::any_cast(value);
    +		};
    +
    +		ConfigOptionDef def;
    +		def.width = 70;
    +		def.type = coFloat;
    +		def.default_value = new ConfigOptionFloat{ 1.0 };
    +		def.label = L("L");
    +		Option option(def, "l");
    +		optgroup->append_single_option_line(option);
    +		
    +		def.label = L("W");
    +		option = Option(def, "w");
    +		optgroup->append_single_option_line(option);
    +		
    +		def.label = L("H");
    +		option = Option(def, "h");
    +		optgroup->append_single_option_line(option);
    +
    +	optgroup = init_modificator_options_page(_(L("Cylinder")));
    +		optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
    +			int val = boost::any_cast(value);
    +			if (opt_key == "cyl_r")
    +				object_parameters.cyl_r = val;
    +			else if (opt_key == "cyl_h")
    +				object_parameters.cyl_h = val;
    +			else return;
    +		};
    +
    +		def.type = coInt;
    +		def.default_value = new ConfigOptionInt{ 1 };
    +		def.label = L("Radius");
    +		option = Option(def, "cyl_r");
    +		optgroup->append_single_option_line(option);
    +
    +		def.label = L("Height");
    +		option = Option(def, "cyl_h");
    +		optgroup->append_single_option_line(option);
    +
    +	optgroup = init_modificator_options_page(_(L("Sphere")));
    +		optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
    +			if (opt_key == "sph_rho")
    +				object_parameters.sph_rho = boost::any_cast(value);
    +			else return;
    +		};
    +
    +		def.type = coFloat;
    +		def.default_value = new ConfigOptionFloat{ 1.0 };
    +		def.label = L("Rho");
    +		option = Option(def, "sph_rho");
    +		optgroup->append_single_option_line(option);
    +
    +	optgroup = init_modificator_options_page(_(L("Slab")));
    +		optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
    +			double val = boost::any_cast(value);
    +			if (opt_key == "slab_z")
    +				object_parameters.slab_z = val;
    +			else if (opt_key == "slab_h")
    +				object_parameters.slab_h = val;
    +			else return;
    +		};
    +
    +		def.label = L("H");
    +		option = Option(def, "slab_h");
    +		optgroup->append_single_option_line(option);
    +
    +		def.label = L("Initial Z");
    +		option = Option(def, "slab_z");
    +		optgroup->append_single_option_line(option);
    +
    +	Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent e)
    +	{
    +		auto page_idx = m_modificator_options_book->GetSelection();
    +		if (page_idx < 0) return;
    +		switch (page_idx)
    +		{
    +		case 0:
    +			object_parameters.type = LambdaTypeBox;
    +			break;
    +		case 1:
    +			object_parameters.type = LambdaTypeCylinder;
    +			break;
    +		case 2:
    +			object_parameters.type = LambdaTypeSphere;
    +			break;
    +		case 3:
    +			object_parameters.type = LambdaTypeSlab;
    +			break;
    +		default:
    +			break;
    +		}
    +	}));
    +
    +
    +	auto button_sizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL);
    +
    +	wxButton* btn_OK = static_cast(FindWindowById(wxID_OK, this));
    +	btn_OK->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
    +		// validate user input
    +		if (!CanClose())return;
    +		EndModal(wxID_OK);
    +		Destroy();
    +	});
    +
    +	wxButton* btn_CANCEL = static_cast(FindWindowById(wxID_CANCEL, this));
    +	btn_CANCEL->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
    +		// validate user input
    +		if (!CanClose())return;
    +		EndModal(wxID_CANCEL);
    +		Destroy();
    +	});
    +
    +	sizer->Add(button_sizer, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
    +
    +	SetSizer(sizer);
    +	sizer->Fit(this);
    +	sizer->SetSizeHints(this);
    +}
    +
    +// Called from the constructor.
    +// Create a panel for a rectangular / circular / custom bed shape.
    +ConfigOptionsGroupShp LambdaObjectDialog::init_modificator_options_page(wxString title){
    +
    +	auto panel = new wxPanel(m_modificator_options_book);
    +
    +	ConfigOptionsGroupShp optgroup;
    +	optgroup = std::make_shared(panel, _(L("Add")) + " " +title + " " +dots);
    +	optgroup->label_width = 100;
    +
    +	m_optgroups.push_back(optgroup);
    +
    +	panel->SetSizerAndFit(optgroup->sizer);
    +	m_modificator_options_book->AddPage(panel, title);
    +
    +	return optgroup;
    +}
    +
    +
    +} //namespace GUI
    +} //namespace Slic3r 
    diff --git a/xs/src/slic3r/GUI/LambdaObjectDialog.hpp b/xs/src/slic3r/GUI/LambdaObjectDialog.hpp
    new file mode 100644
    index 0000000000..a70c124490
    --- /dev/null
    +++ b/xs/src/slic3r/GUI/LambdaObjectDialog.hpp
    @@ -0,0 +1,35 @@
    +#ifndef slic3r_LambdaObjectDialog_hpp_
    +#define slic3r_LambdaObjectDialog_hpp_
    +
    +#include "GUI.hpp"
    +
    +#include 
    +#include 
    +#include 
    +
    +namespace Slic3r
    +{
    +namespace GUI
    +{
    +using ConfigOptionsGroupShp = std::shared_ptr;
    +class LambdaObjectDialog : public wxDialog
    +{
    +	wxChoicebook*	m_modificator_options_book;
    +	std::vector 	m_optgroups;
    +public:
    +	LambdaObjectDialog(wxWindow* parent);
    +	~LambdaObjectDialog(){}
    +
    +	bool CanClose() { return true; }	// ???
    +	OBJECT_PARAMETERS& ObjectParameters(){ return object_parameters; }
    +
    +	ConfigOptionsGroupShp init_modificator_options_page(wxString title);
    +	
    +	// Note whether the window was already closed, so a pending update is not executed.
    +	bool m_already_closed = false;
    +	OBJECT_PARAMETERS object_parameters;
    +	wxBoxSizer* sizer = nullptr;
    +};
    +} //namespace GUI
    +} //namespace Slic3r 
    +#endif  //slic3r_LambdaObjectDialog_hpp_
    diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp
    index d5cc29e19f..453a2f3f74 100644
    --- a/xs/src/slic3r/GUI/OptionsGroup.cpp
    +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp
    @@ -30,6 +30,7 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co
                     opt.gui_type.compare("i_enum_closed") == 0) {
     		m_fields.emplace(id, STDMOVE(Choice::Create(parent(), opt, id)));
         } else if (opt.gui_type.compare("slider") == 0) {
    +		m_fields.emplace(id, STDMOVE(SliderCtrl::Create(parent(), opt, id)));
         } else if (opt.gui_type.compare("i_spin") == 0) { // Spinctrl
         } else if (opt.gui_type.compare("legend") == 0) { // StaticText
     		m_fields.emplace(id, STDMOVE(StaticText::Create(parent(), opt, id)));
    @@ -88,17 +89,23 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co
     		if (!this->m_disabled)
     			this->back_to_sys_value(opt_id);
     	};
    -	if (!m_show_modified_btns) {
    -		field->m_Undo_btn->Hide();
    -		field->m_Undo_to_sys_btn->Hide();
    -	}
    -//	if (nonsys_btn_icon != nullptr)
    -//		field->set_nonsys_btn_icon(*nonsys_btn_icon);
         
     	// assign function objects for callbacks, etc.
         return field;
     }
     
    +void OptionsGroup::add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& field)
    +{
    +	if (!m_show_modified_btns) {
    +		field->m_Undo_btn->Hide();
    +		field->m_Undo_to_sys_btn->Hide();
    +		return;
    +	}
    +
    +	sizer->Add(field->m_Undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL);
    +	sizer->Add(field->m_Undo_btn, 0, wxALIGN_CENTER_VERTICAL);
    +}
    +
     void OptionsGroup::append_line(const Line& line, wxStaticText**	colored_Label/* = nullptr*/) {
     //!    if (line.sizer != nullptr || (line.widget != nullptr && line.full_width > 0)){
     	if ( (line.sizer != nullptr || line.widget != nullptr) && line.full_width){
    @@ -133,8 +140,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText**	colored_Label/*
     		const auto& field = build_field(option);
     
     		auto btn_sizer = new wxBoxSizer(wxHORIZONTAL);
    -		btn_sizer->Add(field->m_Undo_to_sys_btn);
    -		btn_sizer->Add(field->m_Undo_btn);
    +		add_undo_buttuns_to_sizer(btn_sizer, field);
     		tmp_sizer->Add(btn_sizer, 0, wxEXPAND | wxALL, 0);
     		if (is_window_field(field))
     			tmp_sizer->Add(field->getWindow(), 0, wxEXPAND | wxALL, wxOSX ? 0 : 5);
    @@ -149,6 +155,18 @@ void OptionsGroup::append_line(const Line& line, wxStaticText**	colored_Label/*
             m_panel->Layout();
     #endif /* __WXGTK__ */
     
    +	// if we have an extra column, build it
    +	if (extra_column) {
    +		if (extra_column) {
    +			grid_sizer->Add(extra_column(parent(), line), 0, wxALIGN_CENTER_VERTICAL, 0);
    +		}
    +		else {
    +			// if the callback provides no sizer for the extra cell, put a spacer
    +			grid_sizer->AddSpacer(1);
    +		}
    +	}
    +
    +
         // Build a label if we have it
     	wxStaticText* label=nullptr;
         if (label_width != 0) {
    @@ -163,7 +181,8 @@ void OptionsGroup::append_line(const Line& line, wxStaticText**	colored_Label/*
     							wxDefaultPosition, wxSize(label_width, -1), label_style);
             label->SetFont(label_font);
             label->Wrap(label_width); // avoid a Linux/GTK bug
    -		grid_sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) | wxALIGN_CENTER_VERTICAL, 5);
    +		grid_sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) | 
    +						(m_flag == ogSIDE_OPTIONS_VERTICAL ? wxTOP : wxALIGN_CENTER_VERTICAL), 5);
     		if (line.label_tooltip.compare("") != 0)
     			label->SetToolTip(line.label_tooltip);
         }
    @@ -177,28 +196,35 @@ void OptionsGroup::append_line(const Line& line, wxStaticText**	colored_Label/*
     		return;
     	}
     	
    -	// if we have a single option with no sidetext just add it directly to the grid sizer
    -	auto sizer = new wxBoxSizer(wxHORIZONTAL);
    -	grid_sizer->Add(sizer, 0, wxEXPAND | (staticbox ? wxALL : wxBOTTOM|wxTOP|wxLEFT), staticbox ? 0 : 1);
    +	// If we're here, we have more than one option or a single option with sidetext
    +    // so we need a horizontal sizer to arrange these things
    +	auto sizer = new wxBoxSizer(m_flag == ogSIDE_OPTIONS_VERTICAL ? wxVERTICAL : wxHORIZONTAL);
    +	grid_sizer->Add(sizer, 0, wxEXPAND | (staticbox ? wxALL : wxBOTTOM | wxTOP | wxLEFT), staticbox ? 0 : 1);
    +	// If we have a single option with no sidetext just add it directly to the grid sizer
     	if (option_set.size() == 1 && option_set.front().opt.sidetext.size() == 0 &&
     		option_set.front().side_widget == nullptr && line.get_extra_widgets().size() == 0) {
     		const auto& option = option_set.front();
     		const auto& field = build_field(option, label);
     
    -		sizer->Add(field->m_Undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL); 
    -		sizer->Add(field->m_Undo_btn, 0, wxALIGN_CENTER_VERTICAL);
    +		add_undo_buttuns_to_sizer(sizer, field);
     		if (is_window_field(field)) 
     			sizer->Add(field->getWindow(), option.opt.full_width ? 1 : 0, (option.opt.full_width ? wxEXPAND : 0) |
     							wxBOTTOM | wxTOP | wxALIGN_CENTER_VERTICAL, (wxOSX||!staticbox) ? 0 : 2);
     		if (is_sizer_field(field)) 
    -			sizer->Add(field->getSizer(), 0, (option.opt.full_width ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0);
    +			sizer->Add(field->getSizer(), 1, (option.opt.full_width ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0);
     		return;
     	}
     
    -    // if we're here, we have more than one option or a single option with sidetext
    -    // so we need a horizontal sizer to arrange these things
    -	for (auto opt : option_set) {
    +    for (auto opt : option_set) {
     		ConfigOptionDef option = opt.opt;
    +		wxSizer* sizer_tmp;
    +		if (m_flag == ogSIDE_OPTIONS_VERTICAL){
    +			auto sz = new wxFlexGridSizer(1, 3, 2, 2);
    +			sz->RemoveGrowableCol(2);
    +			sizer_tmp = sz;
    +		}
    +    	else
    +    		sizer_tmp = sizer;
     		// add label if any
     		if (option.label != "") {
     			wxString str_label = _(option.label);
    @@ -208,34 +234,38 @@ void OptionsGroup::append_line(const Line& line, wxStaticText**	colored_Label/*
     // 								L_str(option.label);
     			label = new wxStaticText(parent(), wxID_ANY, str_label + ":", wxDefaultPosition, wxDefaultSize);
     			label->SetFont(label_font);
    -			sizer->Add(label, 0, wxALIGN_CENTER_VERTICAL, 0);
    +			sizer_tmp->Add(label, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, 0);
     		}
     
     		// add field
     		const Option& opt_ref = opt;
     		auto& field = build_field(opt_ref, label);
    -		sizer->Add(field->m_Undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL);
    -		sizer->Add(field->m_Undo_btn, 0, wxALIGN_CENTER_VERTICAL, 0);
    +		add_undo_buttuns_to_sizer(sizer_tmp, field);
     		is_sizer_field(field) ? 
    -			sizer->Add(field->getSizer(), 0, wxALIGN_CENTER_VERTICAL, 0) :
    -			sizer->Add(field->getWindow(), 0, wxALIGN_CENTER_VERTICAL, 0);
    +			sizer_tmp->Add(field->getSizer(), 0, wxALIGN_CENTER_VERTICAL, 0) :
    +			sizer_tmp->Add(field->getWindow(), 0, wxALIGN_CENTER_VERTICAL, 0);
     		
     		// add sidetext if any
     		if (option.sidetext != "") {
    -			auto sidetext = new wxStaticText(parent(), wxID_ANY, _(option.sidetext), wxDefaultPosition, wxDefaultSize);
    +			auto sidetext = new wxStaticText(	parent(), wxID_ANY, _(option.sidetext), wxDefaultPosition, 
    +												wxSize(sidetext_width, -1)/*wxDefaultSize*/, wxALIGN_LEFT);
     			sidetext->SetFont(sidetext_font);
    -			sizer->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4);
    +			sizer_tmp->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, m_flag == ogSIDE_OPTIONS_VERTICAL ? 0 : 4);
    +			field->set_side_text_ptr(sidetext);
     		}
     
     		// add side widget if any
     		if (opt.side_widget != nullptr) {
    -			sizer->Add(opt.side_widget(parent())/*!.target()*/, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 1);	//! requires verification
    +			sizer_tmp->Add(opt.side_widget(parent())/*!.target()*/, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 1);	//! requires verification
     		}
     
    -		if (opt.opt_id != option_set.back().opt_id) //! istead of (opt != option_set.back())
    +		if (opt.opt_id != option_set.back().opt_id && m_flag != ogSIDE_OPTIONS_VERTICAL) //! istead of (opt != option_set.back())
     		{
    -			sizer->AddSpacer(6);
    +			sizer_tmp->AddSpacer(6);
     	    }
    +
    +		if (m_flag == ogSIDE_OPTIONS_VERTICAL)
    +			sizer->Add(sizer_tmp, 0, wxALIGN_RIGHT|wxALL, 0);
     	}
     	// add extra sizers if any
     	for (auto extra_widget : line.get_extra_widgets()) {
    @@ -259,7 +289,7 @@ void OptionsGroup::on_change_OG(const t_config_option_key& opt_id, const boost::
     Option ConfigOptionsGroup::get_option(const std::string& opt_key, int opt_index /*= -1*/)
     {
     	if (!m_config->has(opt_key)) {
    -		std::cerr << "No " << opt_key << " in ConfigOptionsGroup config.";
    +		std::cerr << "No " << opt_key << " in ConfigOptionsGroup config.\n";
     	}
     
     	std::string opt_id = opt_index == -1 ? opt_key : opt_key + "#" + std::to_string(opt_index);
    @@ -459,8 +489,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/OptionsGroup.hpp b/xs/src/slic3r/GUI/OptionsGroup.hpp
    index 422e1afd9e..4f263f5e3c 100644
    --- a/xs/src/slic3r/GUI/OptionsGroup.hpp
    +++ b/xs/src/slic3r/GUI/OptionsGroup.hpp
    @@ -26,9 +26,13 @@
     
     namespace Slic3r { namespace GUI {
     
    +enum ogDrawFlag{
    +	ogDEFAULT,
    +	ogSIDE_OPTIONS_VERTICAL
    +};
    +
     /// Widget type describes a function object that returns a wxWindow (our widget) and accepts a wxWidget (parent window).
     using widget_t = std::function;//!std::function;
    -using column_t = std::function;
     
     //auto default_label_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT); //GetSystemColour
     //auto modified_label_clr = *new wxColour(254, 189, 101);
    @@ -71,10 +75,13 @@ private:
         std::vector	m_extra_widgets;//! {std::vector()};
     };
     
    +using column_t = std::function;//std::function;
    +
     using t_optionfield_map = std::map;
     using t_opt_map = std::map< std::string, std::pair >;
     
     class OptionsGroup {
    +	wxStaticBox*	stb;
     public:
         const bool		staticbox {true};
         const wxString	title {wxString("")};
    @@ -88,8 +95,7 @@ public:
     
         wxFont			sidetext_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) };
         wxFont			label_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) };
    -
    -//	std::function	nonsys_btn_icon{ nullptr };
    +	int				sidetext_width{ -1 };
     
         /// Returns a copy of the pointer of the parent wxWindow.
         /// Accessor function is because users are not allowed to change the parent
    @@ -101,6 +107,11 @@ public:
     		return m_parent;
     #endif /* __WXGTK__ */
         }
    +#ifdef __WXGTK__
    +    wxWindow* get_parent() const {
    +        return m_parent;
    +    }
    +#endif /* __WXGTK__ */
     
     	void		append_line(const Line& line, wxStaticText** colored_Label = nullptr);
         Line		create_single_option_line(const Option& option) const;
    @@ -124,23 +135,39 @@ public:
     							return out;
         }
     
    +	bool			set_side_text(const t_config_option_key& opt_key, const wxString& side_text) {
    +							if (m_fields.find(opt_key) == m_fields.end()) return false;
    +							auto st = m_fields.at(opt_key)->m_side_text;
    +							if (!st) return false;
    +							st->SetLabel(side_text);
    +							return true;
    +    }
    +
    +	void			set_name(const wxString& new_name) {
    +							stb->SetLabel(new_name);
    +    }
    +
     	inline void		enable() { for (auto& field : m_fields) field.second->enable(); }
         inline void		disable() { for (auto& field : m_fields) field.second->disable(); }
    +	void			set_flag(ogDrawFlag flag) { m_flag = flag; }
    +	void			set_grid_vgap(int gap) { m_grid_sizer->SetVGap(gap); }
     
     	void set_show_modified_btns_val(bool show) {
     		m_show_modified_btns = show;
         }
     
    -    OptionsGroup(wxWindow* _parent, const wxString& title, bool is_tab_opt=false) : 
    -		m_parent(_parent), title(title), m_show_modified_btns(is_tab_opt), staticbox(title!="") {
    -		auto stb = new wxStaticBox(_parent, wxID_ANY, title);
    +	OptionsGroup(	wxWindow* _parent, const wxString& title, bool is_tab_opt = false, 
    +					ogDrawFlag flag = ogDEFAULT, column_t extra_clmn = nullptr) :
    +					m_parent(_parent), title(title), m_show_modified_btns(is_tab_opt),
    +					staticbox(title!=""), m_flag(flag), extra_column(extra_clmn){
    +		stb = new wxStaticBox(_parent, wxID_ANY, title);
     		stb->SetFont(bold_font());
    -		sizer = (staticbox ? new wxStaticBoxSizer(stb/*new wxStaticBox(_parent, wxID_ANY, title)*/, wxVERTICAL) : new wxBoxSizer(wxVERTICAL));
    +        sizer = (staticbox ? new wxStaticBoxSizer(stb, wxVERTICAL) : new wxBoxSizer(wxVERTICAL));
             auto num_columns = 1U;
             if (label_width != 0) num_columns++;
             if (extra_column != nullptr) num_columns++;
    -        m_grid_sizer = new wxFlexGridSizer(0, num_columns, 0,0);
    -        static_cast(m_grid_sizer)->SetFlexibleDirection(wxHORIZONTAL);
    +        m_grid_sizer = new wxFlexGridSizer(0, num_columns, 1,0);
    +        static_cast(m_grid_sizer)->SetFlexibleDirection(wxBOTH/*wxHORIZONTAL*/);
             static_cast(m_grid_sizer)->AddGrowableCol(label_width != 0);
     #ifdef __WXGTK__
             m_panel = new wxPanel( _parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
    @@ -164,6 +191,8 @@ protected:
     	// "true" if option is created in preset tabs
     	bool					m_show_modified_btns{ false };
     
    +	ogDrawFlag				m_flag{ ogDEFAULT };
    +
     	// This panel is needed for correct showing of the ToolTips for Button, StaticText and CheckBox
     	// Tooltips on GTK doesn't work inside wxStaticBoxSizer unless you insert a panel 
     	// inside it before you insert the other controls.
    @@ -177,6 +206,7 @@ protected:
     	const t_field&		build_field(const t_config_option_key& id, const ConfigOptionDef& opt, wxStaticText* label = nullptr);
     	const t_field&		build_field(const t_config_option_key& id, wxStaticText* label = nullptr);
     	const t_field&		build_field(const Option& opt, wxStaticText* label = nullptr);
    +	void				add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& field);
     
         virtual void		on_kill_focus (){};
     	virtual void		on_change_OG(const t_config_option_key& opt_id, const boost::any& value);
    @@ -186,8 +216,9 @@ protected:
     
     class ConfigOptionsGroup: public OptionsGroup {
     public:
    -	ConfigOptionsGroup(wxWindow* parent, const wxString& title, DynamicPrintConfig* _config = nullptr, bool is_tab_opt = false) :
    -		OptionsGroup(parent, title, is_tab_opt), m_config(_config) {}
    +	ConfigOptionsGroup(	wxWindow* parent, const wxString& title, DynamicPrintConfig* _config = nullptr, 
    +						bool is_tab_opt = false, ogDrawFlag flag = ogDEFAULT, column_t extra_clmn = nullptr) :
    +		OptionsGroup(parent, title, is_tab_opt, flag, extra_clmn), m_config(_config) {}
     
         /// reference to libslic3r config, non-owning pointer (?).
         DynamicPrintConfig*		m_config {nullptr};
    diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp
    index effe9f4fbb..675e06c60f 100644
    --- a/xs/src/slic3r/GUI/Preset.cpp
    +++ b/xs/src/slic3r/GUI/Preset.cpp
    @@ -120,6 +120,11 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
                 VendorProfile::PrinterModel model;
                 model.id = section.first.substr(printer_model_key.size());
                 model.name = section.second.get("name", model.id);
    +            auto technology_field = section.second.get("technology", "FFF");
    +            if (! ConfigOptionEnum::from_string(technology_field, model.technology)) {
    +                BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Invalid printer technology field: `%2%`") % id % technology_field;
    +                model.technology = ptFFF;
    +            }
                 section.second.get("variants", "");
                 const auto variants_field = section.second.get("variants", "");
                 std::vector variants;
    @@ -177,7 +182,7 @@ void Preset::normalize(DynamicPrintConfig &config)
     {
         auto *nozzle_diameter = dynamic_cast(config.option("nozzle_diameter"));
         if (nozzle_diameter != nullptr)
    -        // Loaded the Printer settings. Verify, that all extruder dependent values have enough values.
    +        // Loaded the FFF Printer settings. Verify, that all extruder dependent values have enough values.
             set_num_extruders(config, (unsigned int)nozzle_diameter->values.size());
         if (config.option("filament_diameter") != nullptr) {
             // This config contains single or multiple filament presets.
    @@ -204,12 +209,9 @@ void Preset::normalize(DynamicPrintConfig &config)
         }
     }
     
    -// Load a config file, return a C++ class Slic3r::DynamicPrintConfig with $keys initialized from the config file.
    -// In case of a "default" config item, return the default values.
    -DynamicPrintConfig& Preset::load(const std::vector &keys)
    +DynamicPrintConfig& Preset::load(const std::vector &keys, const StaticPrintConfig &defaults)
     {
         // Set the configuration from the defaults.
    -    Slic3r::FullPrintConfig defaults;
         this->config.apply_only(defaults, keys.empty() ? defaults.keys() : keys);
         if (! this->is_default) {
             // Load the preset file, apply preset values on top of defaults.
    @@ -260,8 +262,9 @@ bool Preset::is_compatible_with_printer(const Preset &active_printer) const
     {
         DynamicPrintConfig config;
         config.set_key_value("printer_preset", new ConfigOptionString(active_printer.name));
    -    config.set_key_value("num_extruders", new ConfigOptionInt(
    -        (int)static_cast(active_printer.config.option("nozzle_diameter"))->values.size()));
    +	const ConfigOption *opt = active_printer.config.option("nozzle_diameter");
    +	if (opt)
    +		config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast(opt)->values.size()));
         return this->is_compatible_with_printer(active_printer, &config);
     }
     
    @@ -329,8 +332,10 @@ const std::vector& Preset::printer_options()
         static std::vector s_opts;
         if (s_opts.empty()) {
             s_opts = {
    +            "printer_technology",
                 "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",
    +            "use_firmware_retraction", "use_volumetric_e", "variable_layer_height",
    +            "host_type", "print_host", "printhost_apikey", "printhost_cafile",
                 "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",
    @@ -360,7 +365,39 @@ const std::vector& Preset::nozzle_options()
         return s_opts;
     }
     
    -PresetCollection::PresetCollection(Preset::Type type, const std::vector &keys) :
    +const std::vector& Preset::sla_printer_options()
    +{    
    +    static std::vector s_opts;
    +    if (s_opts.empty()) {
    +        s_opts = {
    +            "printer_technology",
    +            "bed_shape", "max_print_height",
    +            "display_width", "display_height", "display_pixels_x", "display_pixels_y",
    +            "printer_correction",
    +            "printer_notes",
    +            "inherits"
    +        };
    +    }
    +    return s_opts;
    +}
    +
    +const std::vector& Preset::sla_material_options()
    +{    
    +    static std::vector s_opts;
    +    if (s_opts.empty()) {
    +        s_opts = {
    +            "layer_height", "initial_layer_height",
    +            "exposure_time", "initial_exposure_time",
    +            "material_correction_printing", "material_correction_curing",
    +            "material_notes",
    +            "compatible_printers",
    +            "compatible_printers_condition", "inherits"
    +        };
    +    }
    +    return s_opts;
    +}
    +
    +PresetCollection::PresetCollection(Preset::Type type, const std::vector &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name) :
         m_type(type),
         m_edited_preset(type, "", false),
         m_idx_selected(0),
    @@ -368,8 +405,7 @@ PresetCollection::PresetCollection(Preset::Type type, const std::vectoradd_default_preset(keys, defaults, default_name);
         m_edited_preset.config.apply(m_presets.front().config);
     }
     
    @@ -383,7 +419,7 @@ PresetCollection::~PresetCollection()
     
     void PresetCollection::reset(bool delete_files)
     {
    -    if (m_presets.size() > 1) {
    +    if (m_presets.size() > m_num_default_presets) {
             if (delete_files) {
                 // Erase the preset files.
                 for (Preset &preset : m_presets)
    @@ -391,11 +427,19 @@ void PresetCollection::reset(bool delete_files)
                         boost::nowide::remove(preset.file.c_str());
             }
             // Don't use m_presets.resize() here as it requires a default constructor for Preset.
    -        m_presets.erase(m_presets.begin() + 1, m_presets.end());
    +        m_presets.erase(m_presets.begin() + m_num_default_presets, m_presets.end());
             this->select_preset(0);
         }
     }
     
    +void PresetCollection::add_default_preset(const std::vector &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &preset_name)
    +{
    +    // Insert just the default preset.
    +    m_presets.emplace_back(Preset(this->type(), preset_name, true));
    +    m_presets.back().load(keys, defaults);
    +    ++ m_num_default_presets;
    +}
    +
     // Load all presets found in dir_path.
     // Throws an exception on error.
     void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir)
    @@ -418,14 +462,15 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri
                 try {
                     Preset preset(m_type, name, false);
                     preset.file = dir_entry.path().string();
    -                preset.load(keys);
    +                //FIXME One should initialize with SLAFullPrintConfig for the SLA profiles!
    +                preset.load(keys, static_cast(FullPrintConfig::defaults()));
                     m_presets.emplace_back(preset);
                 } catch (const std::runtime_error &err) {
                     errors_cummulative += err.what();
                     errors_cummulative += "\n";
     			}
             }
    -    std::sort(m_presets.begin() + 1, m_presets.end());
    +    std::sort(m_presets.begin() + m_num_default_presets, m_presets.end());
         this->select_preset(first_visible_idx());
         if (! errors_cummulative.empty())
             throw std::runtime_error(errors_cummulative);
    @@ -643,7 +688,7 @@ Preset* PresetCollection::find_preset(const std::string &name, bool first_visibl
     // Return index of the first visible preset. Certainly at least the '- default -' preset shall be visible.
     size_t PresetCollection::first_visible_idx() const
     {
    -    size_t idx = m_default_suppressed ? 1 : 0;
    +    size_t idx = m_default_suppressed ? m_num_default_presets : 0;
         for (; idx < this->m_presets.size(); ++ idx)
             if (m_presets[idx].is_visible)
                 break;
    @@ -656,7 +701,7 @@ void PresetCollection::set_default_suppressed(bool default_suppressed)
     {
         if (m_default_suppressed != default_suppressed) {
             m_default_suppressed = default_suppressed;
    -        m_presets.front().is_visible = ! default_suppressed || (m_presets.size() > 1 && m_idx_selected > 0);
    +        m_presets.front().is_visible = ! default_suppressed || (m_presets.size() > m_num_default_presets && m_idx_selected > 0);
         }
     }
     
    @@ -664,9 +709,10 @@ size_t PresetCollection::update_compatible_with_printer_internal(const Preset &a
     {
         DynamicPrintConfig config;
         config.set_key_value("printer_preset", new ConfigOptionString(active_printer.name));
    -    config.set_key_value("num_extruders", new ConfigOptionInt(
    -        (int)static_cast(active_printer.config.option("nozzle_diameter"))->values.size()));
    -    for (size_t idx_preset = 1; idx_preset < m_presets.size(); ++ idx_preset) {
    +    const ConfigOption *opt = active_printer.config.option("nozzle_diameter");
    +    if (opt)
    +        config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast(opt)->values.size()));
    +    for (size_t idx_preset = m_num_default_presets; idx_preset < m_presets.size(); ++ idx_preset) {
             bool    selected        = idx_preset == m_idx_selected;
             Preset &preset_selected = m_presets[idx_preset];
             Preset &preset_edited   = selected ? m_edited_preset : preset_selected;
    @@ -707,7 +753,7 @@ void PresetCollection::update_platter_ui(wxBitmapComboBox *ui)
     	wxString selected = "";
     	if (!this->m_presets.front().is_visible)
     		ui->Append("------- " +_(L("System presets")) + " -------", wxNullBitmap);
    -	for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++i) {
    +	for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++i) {
             const Preset &preset = this->m_presets[i];
             if (! preset.is_visible || (! preset.is_compatible && i != m_idx_selected))
                 continue;
    @@ -745,7 +791,7 @@ void PresetCollection::update_platter_ui(wxBitmapComboBox *ui)
     			if (i == m_idx_selected)
     				selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str());
     		}
    -		if (preset.is_default)
    +		if (i + 1 == m_num_default_presets)
     			ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap);
     	}
     	if (!nonsys_presets.empty())
    @@ -775,7 +821,7 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati
     	wxString selected = "";
     	if (!this->m_presets.front().is_visible)
     		ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap);
    -	for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++i) {
    +	for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++i) {
             const Preset &preset = this->m_presets[i];
             if (! preset.is_visible || (! show_incompatible && ! preset.is_compatible && i != m_idx_selected))
                 continue;
    @@ -805,7 +851,7 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati
     			if (i == m_idx_selected)
     				selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str());
     		}
    -		if (preset.is_default)
    +        if (i + 1 == m_num_default_presets)
     			ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap);
         }
     	if (!nonsys_presets.empty())
    @@ -853,11 +899,11 @@ bool PresetCollection::update_dirty_ui(wxBitmapComboBox *ui)
         return was_dirty != is_dirty;
     }
     
    -std::vector PresetCollection::dirty_options(const Preset *edited, const Preset *reference, const bool is_printer_type /*= false*/)
    +std::vector PresetCollection::dirty_options(const Preset *edited, const Preset *reference, const bool deep_compare /*= false*/)
     {
         std::vector changed;
     	if (edited != nullptr && reference != nullptr) {
    -        changed = is_printer_type  ? 
    +        changed = deep_compare ?
     				reference->config.deep_diff(edited->config) :
     				reference->config.diff(edited->config);
             // The "compatible_printers" option key is handled differently from the others:
    @@ -897,7 +943,7 @@ bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, b
             idx = it - m_presets.begin();
         else {
             // Find the first visible preset.
    -        for (size_t i = m_default_suppressed ? 1 : 0; i < m_presets.size(); ++ i)
    +        for (size_t i = m_default_suppressed ? m_num_default_presets : 0; i < m_presets.size(); ++ i)
                 if (m_presets[i].is_visible) {
                     idx = i;
                     break;
    @@ -939,7 +985,7 @@ std::vector PresetCollection::merge_presets(PresetCollection &&othe
             if (preset.is_default || preset.is_external)
                 continue;
             Preset key(m_type, preset.name);
    -        auto it = std::lower_bound(m_presets.begin() + 1, m_presets.end(), key);
    +        auto it = std::lower_bound(m_presets.begin() + m_num_default_presets, m_presets.end(), key);
             if (it == m_presets.end() || it->name != preset.name) {
                 if (preset.vendor != nullptr) {
                     // Re-assign a pointer to the vendor structure in the new PresetBundle.
    diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp
    index 0d00cae485..821d7dc543 100644
    --- a/xs/src/slic3r/GUI/Preset.hpp
    +++ b/xs/src/slic3r/GUI/Preset.hpp
    @@ -52,6 +52,7 @@ public:
             PrinterModel() {}
             std::string                 id;
             std::string                 name;
    +        PrinterTechnology           technology;
             std::vector variants;
             PrinterVariant*       variant(const std::string &name) {
                 for (auto &v : this->variants)
    @@ -83,6 +84,7 @@ public:
             TYPE_INVALID,
             TYPE_PRINT,
             TYPE_FILAMENT,
    +        TYPE_SLA_MATERIAL,
             TYPE_PRINTER,
         };
     
    @@ -123,8 +125,7 @@ public:
         DynamicPrintConfig  config;
     
         // Load this profile for the following keys only.
    -    // Throws std::runtime_error in case the file cannot be read.
    -    DynamicPrintConfig& load(const std::vector &keys);
    +    DynamicPrintConfig& load(const std::vector &keys, const StaticPrintConfig &defaults);
     
         void                save();
     
    @@ -149,6 +150,10 @@ public:
         std::string&        compatible_printers_condition() { return Preset::compatible_printers_condition(this->config); }
         const std::string&  compatible_printers_condition() const { return Preset::compatible_printers_condition(const_cast(this)->config); }
     
    +    static PrinterTechnology& printer_technology(DynamicPrintConfig &cfg) { return cfg.option>("printer_technology", true)->value; }
    +    PrinterTechnology&        printer_technology() { return Preset::printer_technology(this->config); }
    +    const PrinterTechnology&  printer_technology() const { return Preset::printer_technology(const_cast(this)->config); }
    +
         // Mark this preset as compatible if it is compatible with active_printer.
         bool                update_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config);
     
    @@ -167,6 +172,10 @@ public:
         static const std::vector&  printer_options();
         // Nozzle options of the printer options.
         static const std::vector&  nozzle_options();
    +
    +    static const std::vector&  sla_printer_options();
    +    static const std::vector&  sla_material_options();
    +
     	static void update_suffix_modified();
     
     protected:
    @@ -184,15 +193,15 @@ class PresetCollection
     {
     public:
         // Initialize the PresetCollection with the "- default -" preset.
    -    PresetCollection(Preset::Type type, const std::vector &keys);
    +    PresetCollection(Preset::Type type, const std::vector &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name = "- default -");
         ~PresetCollection();
     
         typedef std::deque::iterator Iterator;
         typedef std::deque::const_iterator ConstIterator;
    -    Iterator begin() { return m_presets.begin() + 1; }
    -    ConstIterator begin() const { return m_presets.begin() + 1; }
    -    Iterator end() { return m_presets.end(); }
    -    ConstIterator end() const { return m_presets.end(); }
    +    Iterator        begin() { return m_presets.begin() + m_num_default_presets; }
    +    ConstIterator   begin() const { return m_presets.begin() + m_num_default_presets; }
    +    Iterator        end() { return m_presets.end(); }
    +    ConstIterator   end() const { return m_presets.end(); }
     
         void            reset(bool delete_files);
     
    @@ -200,6 +209,9 @@ public:
         std::string     name() const;
         const std::deque& operator()() const { return m_presets; }
     
    +    // Add default preset at the start of the collection, increment the m_default_preset counter.
    +    void            add_default_preset(const std::vector &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &preset_name);
    +
         // Load ini files of the particular type from the provided directory path.
         void            load_presets(const std::string &dir_path, const std::string &subdir);
     
    @@ -247,6 +259,8 @@ public:
         Preset&         get_selected_preset()       { return m_presets[m_idx_selected]; }
         const Preset&   get_selected_preset() const { return m_presets[m_idx_selected]; }
         int             get_selected_idx()    const { return m_idx_selected; }
    +    // Returns the name of the selected preset, or an empty string if no preset is selected.
    +    std::string     get_selected_preset_name() const { return (m_idx_selected == -1) ? std::string() : this->get_selected_preset().name; }
         // For the current edited preset, return the parent preset if there is one.
         // If there is no parent preset, nullptr is returned.
         // The parent preset may be a system preset or a user preset, which will be
    @@ -283,7 +297,7 @@ public:
         template
         size_t          first_compatible_idx(PreferedCondition prefered_condition) const
         {
    -        size_t i = m_default_suppressed ? 1 : 0;
    +        size_t i = m_default_suppressed ? m_num_default_presets : 0;
             size_t n = this->m_presets.size();
             size_t i_compatible = n;
             for (; i < n; ++ i)
    @@ -309,7 +323,8 @@ public:
         const Preset&   first_compatible() const    { return this->preset(this->first_compatible_idx()); }
     
         // Return number of presets including the "- default -" preset.
    -    size_t          size() const                { return this->m_presets.size(); }
    +    size_t          size() const                { return m_presets.size(); }
    +    bool            has_defaults_only() const   { return m_presets.size() <= m_num_default_presets; }
     
         // For Print / Filament presets, disable those, which are not compatible with the printer.
         template
    @@ -327,11 +342,11 @@ public:
         // Compare the content of get_selected_preset() with get_edited_preset() configs, return true if they differ.
         bool                        current_is_dirty() const { return ! this->current_dirty_options().empty(); }
         // Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ.
    -    std::vector    current_dirty_options(const bool is_printer_type = false) const
    -        { return dirty_options(&this->get_edited_preset(), &this->get_selected_preset(), is_printer_type); }
    +    std::vector    current_dirty_options(const bool deep_compare = false) const
    +        { return dirty_options(&this->get_edited_preset(), &this->get_selected_preset(), deep_compare); }
         // Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ.
    -    std::vector    current_different_from_parent_options(const bool is_printer_type = false) const
    -        { return dirty_options(&this->get_edited_preset(), this->get_selected_preset_parent(), is_printer_type); }
    +    std::vector    current_different_from_parent_options(const bool deep_compare = false) const
    +        { return dirty_options(&this->get_edited_preset(), this->get_selected_preset_parent(), deep_compare); }
     
         // Update the choice UI from the list of presets.
         // If show_incompatible, all presets are shown, otherwise only the compatible presets are shown.
    @@ -374,8 +389,16 @@ private:
         std::deque::iterator find_preset_internal(const std::string &name)
         {
             Preset key(m_type, name);
    -        auto it = std::lower_bound(m_presets.begin() + 1, m_presets.end(), key);
    -        return ((it == m_presets.end() || it->name != name) && m_presets.front().name == name) ? m_presets.begin() : it;
    +        auto it = std::lower_bound(m_presets.begin() + m_num_default_presets, m_presets.end(), key);
    +        if (it == m_presets.end() || it->name != name) {
    +            // Preset has not been not found in the sorted list of non-default presets. Try the defaults.
    +            for (size_t i = 0; i < m_num_default_presets; ++ i)
    +                if (m_presets[i].name == name) {
    +                    it = m_presets.begin() + i;
    +                    break;
    +                }
    +        }
    +        return it;
         }
         std::deque::const_iterator find_preset_internal(const std::string &name) const
             { return const_cast(this)->find_preset_internal(name); }
    @@ -395,7 +418,8 @@ private:
         // Selected preset.
         int                     m_idx_selected;
         // Is the "- default -" preset suppressed?
    -    bool                    m_default_suppressed = true;
    +    bool                    m_default_suppressed  = true;
    +    size_t                  m_num_default_presets = 0;
         // Compatible & incompatible marks, to be placed at the wxBitmapComboBox items of a Platter.
         // These bitmaps are not owned by PresetCollection, but by a PresetBundle.
         const wxBitmap         *m_bitmap_compatible   = nullptr;
    diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp
    index 94baa0e0ef..cd3924dd0a 100644
    --- a/xs/src/slic3r/GUI/PresetBundle.cpp
    +++ b/xs/src/slic3r/GUI/PresetBundle.cpp
    @@ -40,9 +40,10 @@ static std::vector s_project_options {
     };
     
     PresetBundle::PresetBundle() :
    -    prints(Preset::TYPE_PRINT, Preset::print_options()), 
    -    filaments(Preset::TYPE_FILAMENT, Preset::filament_options()), 
    -    printers(Preset::TYPE_PRINTER, Preset::printer_options()),
    +    prints(Preset::TYPE_PRINT, Preset::print_options(), static_cast(FullPrintConfig::defaults())), 
    +    filaments(Preset::TYPE_FILAMENT, Preset::filament_options(), static_cast(FullPrintConfig::defaults())), 
    +    sla_materials(Preset::TYPE_SLA_MATERIAL, Preset::sla_material_options(), static_cast(SLAFullPrintConfig::defaults())), 
    +    printers(Preset::TYPE_PRINTER, Preset::printer_options(), static_cast(FullPrintConfig::defaults()), "- default FFF -"),
         m_bitmapCompatible(new wxBitmap),
         m_bitmapIncompatible(new wxBitmap),
         m_bitmapLock(new wxBitmap),
    @@ -69,24 +70,35 @@ PresetBundle::PresetBundle() :
         this->filaments.default_preset().compatible_printers_condition();
         this->filaments.default_preset().inherits();
     
    -    this->printers.default_preset().config.optptr("printer_settings_id", true);
    -    this->printers.default_preset().config.optptr("printer_vendor", true);
    -    this->printers.default_preset().config.optptr("printer_model", true);
    -    this->printers.default_preset().config.optptr("printer_variant", true);
    -    this->printers.default_preset().config.optptr("default_print_profile", true);
    -    this->printers.default_preset().config.option("default_filament_profile", true)->values = { "" };
    -	this->printers.default_preset().inherits();
    +    this->sla_materials.default_preset().config.optptr("sla_material_settings_id", true);
    +    this->sla_materials.default_preset().compatible_printers_condition();
    +    this->sla_materials.default_preset().inherits();
    +
    +    this->printers.add_default_preset(Preset::sla_printer_options(), static_cast(SLAFullPrintConfig::defaults()), "- default SLA -");
    +    this->printers.preset(1).printer_technology() = ptSLA;
    +    for (size_t i = 0; i < 2; ++ i) {
    +        Preset &preset = this->printers.preset(i);
    +        preset.config.optptr("printer_settings_id", true);
    +        preset.config.optptr("printer_vendor", true);
    +        preset.config.optptr("printer_model", true);
    +        preset.config.optptr("printer_variant", true);
    +        preset.config.optptr("default_print_profile", true);
    +        preset.config.option("default_filament_profile", true)->values = { "" };
    +        preset.inherits();
    +    }
     
     	// Load the default preset bitmaps.
    -    this->prints   .load_bitmap_default("cog.png");
    -    this->filaments.load_bitmap_default("spool.png");
    -    this->printers .load_bitmap_default("printer_empty.png");
    +    this->prints       .load_bitmap_default("cog.png");
    +    this->filaments    .load_bitmap_default("spool.png");
    +    this->sla_materials.load_bitmap_default("package_green.png");
    +    this->printers     .load_bitmap_default("printer_empty.png");
         this->load_compatible_bitmaps();
     
         // Re-activate the default presets, so their "edited" preset copies will be updated with the additional configuration values above.
    -    this->prints   .select_preset(0);
    -    this->filaments.select_preset(0);
    -    this->printers .select_preset(0);
    +    this->prints       .select_preset(0);
    +    this->filaments    .select_preset(0);
    +    this->sla_materials.select_preset(0);
    +    this->printers     .select_preset(0);
     
         this->project_config.apply_only(FullPrintConfig::defaults(), s_project_options);
     }
    @@ -113,13 +125,15 @@ void PresetBundle::reset(bool delete_files)
     {
         // Clear the existing presets, delete their respective files.
         this->vendors.clear();
    -    this->prints   .reset(delete_files);
    -    this->filaments.reset(delete_files);
    -    this->printers .reset(delete_files);
    +    this->prints       .reset(delete_files);
    +    this->filaments    .reset(delete_files);
    +    this->sla_materials.reset(delete_files);
    +    this->printers     .reset(delete_files);
         this->filament_presets.clear();
    -    this->filament_presets.emplace_back(this->filaments.get_selected_preset().name);
    +    this->filament_presets.emplace_back(this->filaments.get_selected_preset_name());
         this->obsolete_presets.prints.clear();
         this->obsolete_presets.filaments.clear();
    +    this->obsolete_presets.sla_materials.clear();
         this->obsolete_presets.printers.clear();
     }
     
    @@ -135,11 +149,13 @@ void PresetBundle::setup_directories()
             data_dir / "presets", 
             data_dir / "presets" / "print", 
             data_dir / "presets" / "filament", 
    +        data_dir / "presets" / "sla_material", 
             data_dir / "presets" / "printer" 
     #else
             // Store the print/filament/printer presets at the same location as the upstream Slic3r.
             data_dir / "print", 
             data_dir / "filament", 
    +        data_dir / "sla_material", 
             data_dir / "printer" 
     #endif
         };
    @@ -175,6 +191,11 @@ void PresetBundle::load_presets(const AppConfig &config)
         } catch (const std::runtime_error &err) {
             errors_cummulative += err.what();
         }
    +    try {
    +        this->sla_materials.load_presets(dir_user_presets, "sla_material");
    +    } catch (const std::runtime_error &err) {
    +        errors_cummulative += err.what();
    +    }
         try {
             this->printers.load_presets(dir_user_presets, "printer");
         } catch (const std::runtime_error &err) {
    @@ -238,13 +259,16 @@ std::string PresetBundle::load_system_presets()
     std::vector PresetBundle::merge_presets(PresetBundle &&other)
     {
         this->vendors.insert(other.vendors.begin(), other.vendors.end());
    -    std::vector duplicate_prints    = this->prints   .merge_presets(std::move(other.prints),    this->vendors);
    -    std::vector duplicate_filaments = this->filaments.merge_presets(std::move(other.filaments), this->vendors);
    -    std::vector duplicate_printers  = this->printers .merge_presets(std::move(other.printers),  this->vendors);
    -	append(this->obsolete_presets.prints,    std::move(other.obsolete_presets.prints));
    -	append(this->obsolete_presets.filaments, std::move(other.obsolete_presets.filaments));
    -	append(this->obsolete_presets.printers,  std::move(other.obsolete_presets.printers));
    +    std::vector duplicate_prints        = this->prints       .merge_presets(std::move(other.prints),        this->vendors);
    +    std::vector duplicate_filaments     = this->filaments    .merge_presets(std::move(other.filaments),     this->vendors);
    +    std::vector duplicate_sla_materials = this->sla_materials.merge_presets(std::move(other.sla_materials), this->vendors);
    +    std::vector duplicate_printers      = this->printers     .merge_presets(std::move(other.printers),      this->vendors);
    +	append(this->obsolete_presets.prints,        std::move(other.obsolete_presets.prints));
    +	append(this->obsolete_presets.filaments,     std::move(other.obsolete_presets.filaments));
    +    append(this->obsolete_presets.sla_materials, std::move(other.obsolete_presets.sla_materials));
    +	append(this->obsolete_presets.printers,      std::move(other.obsolete_presets.printers));
     	append(duplicate_prints, std::move(duplicate_filaments));
    +    append(duplicate_prints, std::move(duplicate_sla_materials));
         append(duplicate_prints, std::move(duplicate_printers));
         return duplicate_prints;
     }
    @@ -275,9 +299,10 @@ void PresetBundle::load_selections(const AppConfig &config)
     	this->load_installed_printers(config);
     
         // Parse the initial print / filament / printer profile names.
    -    std::string initial_print_profile_name    = remove_ini_suffix(config.get("presets", "print"));
    -    std::string initial_filament_profile_name = remove_ini_suffix(config.get("presets", "filament"));
    -	std::string initial_printer_profile_name  = remove_ini_suffix(config.get("presets", "printer"));
    +    std::string initial_print_profile_name        = remove_ini_suffix(config.get("presets", "print"));
    +    std::string initial_filament_profile_name     = remove_ini_suffix(config.get("presets", "filament"));
    +    std::string initial_sla_material_profile_name = remove_ini_suffix(config.get("presets", "sla_material"));
    +	std::string initial_printer_profile_name      = remove_ini_suffix(config.get("presets", "printer"));
     
     	// Activate print / filament / printer profiles from the config.
     	// If the printer profile enumerated by the config are not visible, select an alternate preset.
    @@ -285,21 +310,24 @@ void PresetBundle::load_selections(const AppConfig &config)
         // will be selected by the following call of this->update_compatible_with_printer(true).
         prints.select_preset_by_name_strict(initial_print_profile_name);
         filaments.select_preset_by_name_strict(initial_filament_profile_name);
    +    sla_materials.select_preset_by_name_strict(initial_sla_material_profile_name);
         printers.select_preset_by_name(initial_printer_profile_name, true);
     
    -    // Load the names of the other filament profiles selected for a multi-material printer.
    -    auto   *nozzle_diameter = dynamic_cast(printers.get_selected_preset().config.option("nozzle_diameter"));
    -    size_t  num_extruders = nozzle_diameter->values.size();
    -    this->filament_presets = { initial_filament_profile_name };
    -    for (unsigned int i = 1; i < (unsigned int)num_extruders; ++ i) {
    -        char name[64];
    -        sprintf(name, "filament_%d", i);
    -        if (! config.has("presets", name))
    -            break;
    -        this->filament_presets.emplace_back(remove_ini_suffix(config.get("presets", name)));
    +    if (printers.get_selected_preset().printer_technology() == ptFFF){
    +        // Load the names of the other filament profiles selected for a multi-material printer.
    +        auto   *nozzle_diameter = dynamic_cast(printers.get_selected_preset().config.option("nozzle_diameter"));
    +        size_t  num_extruders = nozzle_diameter->values.size();
    +        this->filament_presets = { initial_filament_profile_name };
    +        for (unsigned int i = 1; i < (unsigned int)num_extruders; ++i) {
    +            char name[64];
    +            sprintf(name, "filament_%d", i);
    +            if (!config.has("presets", name))
    +                break;
    +            this->filament_presets.emplace_back(remove_ini_suffix(config.get("presets", name)));
    +        }
    +        // Do not define the missing filaments, so that the update_compatible_with_printer() will use the preferred filaments.
    +        this->filament_presets.resize(num_extruders, "");
         }
    -    // Do not define the missing filaments, so that the update_compatible_with_printer() will use the preferred filaments.
    -    this->filament_presets.resize(num_extruders, "");
     
         // Update visibility of presets based on their compatibility with the active printer.
         // Always try to select a compatible print and filament preset to the current printer preset,
    @@ -313,25 +341,33 @@ void PresetBundle::load_selections(const AppConfig &config)
     void PresetBundle::export_selections(AppConfig &config)
     {
         assert(filament_presets.size() >= 1);
    -    assert(filament_presets.size() > 1 || filaments.get_selected_preset().name == filament_presets.front());
    +    assert(filament_presets.size() > 1 || filaments.get_selected_preset_name() == filament_presets.front());
         config.clear_section("presets");
    -    config.set("presets", "print",    prints.get_selected_preset().name);
    -    config.set("presets", "filament", filament_presets.front());
    +    config.set("presets", "print",        prints.get_selected_preset_name());
    +    config.set("presets", "filament",     filament_presets.front());
     	for (int i = 1; i < filament_presets.size(); ++i) {
             char name[64];
             sprintf(name, "filament_%d", i);
             config.set("presets", name, filament_presets[i]);
         }
    -    config.set("presets", "printer",  printers.get_selected_preset().name);
    +    config.set("presets", "sla_material", sla_materials.get_selected_preset_name());
    +    config.set("presets", "printer",  printers.get_selected_preset_name());
     }
     
     void PresetBundle::export_selections(PlaceholderParser &pp)
     {
         assert(filament_presets.size() >= 1);
    -    assert(filament_presets.size() > 1 || filaments.get_selected_preset().name == filament_presets.front());
    -    pp.set("print_preset",    prints.get_selected_preset().name);
    -    pp.set("filament_preset", filament_presets);
    -    pp.set("printer_preset",  printers.get_selected_preset().name);
    +    assert(filament_presets.size() > 1 || filaments.get_selected_preset_name() == filament_presets.front());
    +    switch (printers.get_edited_preset().printer_technology()) {
    +    case ptFFF:
    +        pp.set("print_preset",        prints.get_selected_preset().name);
    +        pp.set("filament_preset",     filament_presets);
    +        break;
    +    case ptSLA:
    +        pp.set("sla_material_preset", sla_materials.get_selected_preset().name);
    +        break;
    +    }
    +    pp.set("printer_preset",      printers.get_selected_preset().name);
     }
     
     bool PresetBundle::load_compatible_bitmaps()
    @@ -349,32 +385,43 @@ bool PresetBundle::load_compatible_bitmaps()
         bool loaded_lock_open = m_bitmapLockOpen->LoadFile(
             wxString::FromUTF8(Slic3r::var(path_bitmap_lock_open).c_str()), wxBITMAP_TYPE_PNG);
         if (loaded_compatible) {
    -        prints   .set_bitmap_compatible(m_bitmapCompatible);
    -        filaments.set_bitmap_compatible(m_bitmapCompatible);
    +        prints       .set_bitmap_compatible(m_bitmapCompatible);
    +        filaments    .set_bitmap_compatible(m_bitmapCompatible);
    +        sla_materials.set_bitmap_compatible(m_bitmapCompatible);
     //        printers .set_bitmap_compatible(m_bitmapCompatible);
         }
         if (loaded_incompatible) {
    -        prints   .set_bitmap_incompatible(m_bitmapIncompatible);
    -        filaments.set_bitmap_incompatible(m_bitmapIncompatible);
    -//        printers .set_bitmap_incompatible(m_bitmapIncompatible);        
    +        prints       .set_bitmap_incompatible(m_bitmapIncompatible);
    +        filaments    .set_bitmap_incompatible(m_bitmapIncompatible);
    +        sla_materials.set_bitmap_incompatible(m_bitmapIncompatible);
    +//        printers .set_bitmap_incompatible(m_bitmapIncompatible);
         }
         if (loaded_lock) {
    -        prints   .set_bitmap_lock(m_bitmapLock);
    -        filaments.set_bitmap_lock(m_bitmapLock);
    -        printers .set_bitmap_lock(m_bitmapLock);
    +        prints       .set_bitmap_lock(m_bitmapLock);
    +        filaments    .set_bitmap_lock(m_bitmapLock);
    +        sla_materials.set_bitmap_lock(m_bitmapLock);
    +        printers     .set_bitmap_lock(m_bitmapLock);
         }
         if (loaded_lock_open) {
    -        prints   .set_bitmap_lock_open(m_bitmapLock);
    -        filaments.set_bitmap_lock_open(m_bitmapLock);
    -        printers .set_bitmap_lock_open(m_bitmapLock);
    +        prints       .set_bitmap_lock_open(m_bitmapLock);
    +        filaments    .set_bitmap_lock_open(m_bitmapLock);
    +        sla_materials.set_bitmap_lock_open(m_bitmapLock);
    +        printers     .set_bitmap_lock_open(m_bitmapLock);
         }
         return loaded_compatible && loaded_incompatible && loaded_lock && loaded_lock_open;
     }
     
     DynamicPrintConfig PresetBundle::full_config() const
    +{
    +    return (this->printers.get_edited_preset().printer_technology() == ptFFF) ?
    +        this->full_fff_config() :
    +        this->full_sla_config();
    +}
    +
    +DynamicPrintConfig PresetBundle::full_fff_config() const
     {    
         DynamicPrintConfig out;
    -    out.apply(FullPrintConfig());
    +    out.apply(FullPrintConfig::defaults());
         out.apply(this->prints.get_edited_preset().config);
         // Add the default filament preset to have the "filament_preset_id" defined.
     	out.apply(this->filaments.default_preset().config);
    @@ -466,6 +513,48 @@ DynamicPrintConfig PresetBundle::full_config() const
         return out;
     }
     
    +DynamicPrintConfig PresetBundle::full_sla_config() const
    +{    
    +    DynamicPrintConfig out;
    +    out.apply(SLAFullPrintConfig::defaults());
    +    out.apply(this->sla_materials.get_edited_preset().config);
    +    out.apply(this->printers.get_edited_preset().config);
    +    // There are no project configuration values as of now, the project_config is reserved for FFF printers.
    +//    out.apply(this->project_config);
    +
    +    // Collect the "compatible_printers_condition" and "inherits" values over all presets (sla_materials, printers) into a single vector.
    +    std::vector compatible_printers_condition;
    +    std::vector inherits;
    +    compatible_printers_condition.emplace_back(this->/*prints*/sla_materials.get_edited_preset().compatible_printers_condition());
    +    inherits                     .emplace_back(this->/*prints*/sla_materials.get_edited_preset().inherits());
    +    inherits                     .emplace_back(this->printers.get_edited_preset().inherits());
    +
    +    // These two value types clash between the print and filament profiles. They should be renamed.
    +    out.erase("compatible_printers");
    +    out.erase("compatible_printers_condition");
    +    out.erase("inherits");
    +    
    +    out.option("sla_material_settings_id", true)->value  = this->sla_materials.get_selected_preset().name;
    +    out.option("printer_settings_id",      true)->value  = this->printers.get_selected_preset().name;
    +
    +    // Serialize the collected "compatible_printers_condition" and "inherits" fields.
    +    // There will be 1 + num_exturders fields for "inherits" and 2 + num_extruders for "compatible_printers_condition" stored.
    +    // The vector will not be stored if all fields are empty strings.
    +    auto add_if_some_non_empty = [&out](std::vector &&values, const std::string &key) {
    +        bool nonempty = false;
    +        for (const std::string &v : values)
    +            if (! v.empty()) {
    +                nonempty = true;
    +                break;
    +            }
    +        if (nonempty)
    +            out.set_key_value(key, new ConfigOptionStrings(std::move(values)));
    +    };
    +    add_if_some_non_empty(std::move(compatible_printers_condition), "compatible_printers_condition_cummulative");
    +    add_if_some_non_empty(std::move(inherits),                      "inherits_cummulative");
    +    return out;
    +}
    +
     // Load an external config file containing the print, filament and printer presets.
     // Instead of a config file, a G-code may be loaded containing the full set of parameters.
     // In the future the configuration will likely be read from an AMF file as well.
    @@ -530,6 +619,8 @@ void PresetBundle::load_config_string(const char* str, const char* source_filena
     // Load a config file from a boost property_tree. This is a private method called from load_config_file.
     void PresetBundle::load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config)
     {
    +    PrinterTechnology printer_technology = Preset::printer_technology(config);
    +
         // The "compatible_printers" field should not have been exported into a config.ini or a G-code anyway, 
         // but some of the alpha versions of Slic3r did.
         {
    @@ -541,8 +632,10 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
             }
         }
     
    -    size_t num_extruders = std::min(config.option("nozzle_diameter"  )->values.size(), 
    -                                    config.option("filament_diameter")->values.size());
    +    size_t num_extruders = (printer_technology == ptFFF) ?
    +        std::min(config.option("nozzle_diameter"  )->values.size(), 
    +                 config.option("filament_diameter")->values.size()) :
    +        0;
         // Make a copy of the "compatible_printers_condition_cummulative" and "inherits_cummulative" vectors, which 
         // accumulate values over all presets (print, filaments, printers).
         // These values will be distributed into their particular presets when loading.
    @@ -553,7 +646,8 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
         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());
    +    if (printer_technology == ptFFF)
    +        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.
    @@ -562,81 +656,83 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
         // 2) If the loading succeeded, split and load the config into print / filament / printer settings.
         // First load the print and printer presets.
         for (size_t i_group = 0; i_group < 2; ++ i_group) {
    -        PresetCollection &presets = (i_group == 0) ? this->prints : this->printers;
    +        PresetCollection &presets = (i_group == 0) ? ((printer_technology == ptFFF) ? this->prints : this->sla_materials) : this->printers;
             // Split the "compatible_printers_condition" and "inherits" values one by one from a single vector to the print & printer profiles.
             size_t idx = (i_group == 0) ? 0 : num_extruders + 1;
             inherits                      = inherits_values[idx];
             compatible_printers_condition = compatible_printers_condition_values[idx];
     		if (is_external)
                 presets.load_external_preset(name_or_path, name,
    -                config.opt_string((i_group == 0) ? "print_settings_id" : "printer_settings_id", true), 
    +                config.opt_string((i_group == 0) ? ((printer_technology == ptFFF) ? "print_settings_id" : "sla_material_id") :  "printer_settings_id", true),
                     config);
             else
                 presets.load_preset(presets.path_from_name(name), name, config).save();
         }
     
    -    // 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());
    +    if (Preset::printer_technology(config) == ptFFF) {
    +        // 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());
     
    -    if (num_extruders <= 1) {
    -        // Split the "compatible_printers_condition" and "inherits" from the cummulative vectors to separate filament presets.
    -        inherits                      = inherits_values[1];
    -        compatible_printers_condition = compatible_printers_condition_values[1];
    -        if (is_external)
    -            this->filaments.load_external_preset(name_or_path, name, old_filament_profile_names->values.front(), config);
    -        else
    -            this->filaments.load_preset(this->filaments.path_from_name(name), name, config).save();
    -        this->filament_presets.clear();
    -        this->filament_presets.emplace_back(name);
    -    } else {
    -        // Split the filament presets, load each of them separately.
    -        std::vector configs(num_extruders, this->filaments.default_preset().config);
    -        // loop through options and scatter them into configs.
    -        for (const t_config_option_key &key : this->filaments.default_preset().config.keys()) {
    -            const ConfigOption *other_opt = config.option(key);
    -            if (other_opt == nullptr)
    -                continue;
    -            if (other_opt->is_scalar()) {
    -                for (size_t i = 0; i < configs.size(); ++ i)
    -                    configs[i].option(key, false)->set(other_opt);
    -            } else if (key != "compatible_printers") {
    -                for (size_t i = 0; i < configs.size(); ++ i)
    -                    static_cast(configs[i].option(key, false))->set_at(other_opt, 0, i);
    -            }
    -        }
    -        // Load the configs into this->filaments and make them active.
    -        this->filament_presets.clear();
    -        for (size_t i = 0; i < configs.size(); ++ i) {
    -            DynamicPrintConfig &cfg = configs[i];
    +        if (num_extruders <= 1) {
                 // Split the "compatible_printers_condition" and "inherits" from the cummulative vectors to separate filament presets.
    -            cfg.opt_string("compatible_printers_condition", true) = compatible_printers_condition_values[i + 1];
    -            cfg.opt_string("inherits", true)                      = inherits_values[i + 1];
    -            // Load all filament presets, but only select the first one in the preset dialog.
    -            Preset *loaded = nullptr;
    +            inherits                      = inherits_values[1];
    +            compatible_printers_condition = compatible_printers_condition_values[1];
                 if (is_external)
    -                loaded = &this->filaments.load_external_preset(name_or_path, name,
    -                    (i < old_filament_profile_names->values.size()) ? old_filament_profile_names->values[i] : "",
    -                    std::move(cfg), i == 0);
    -            else {
    -                // Used by the config wizard when creating a custom setup.
    -                // Therefore this block should only be called for a single extruder.
    -                char suffix[64];
    -                if (i == 0)
    -                    suffix[0] = 0;
    -                else
    -                    sprintf(suffix, "%d", i);
    -                std::string new_name = name + suffix;
    -                loaded = &this->filaments.load_preset(this->filaments.path_from_name(new_name),
    -                    new_name, std::move(cfg), i == 0);
    -                loaded->save();
    +                this->filaments.load_external_preset(name_or_path, name, old_filament_profile_names->values.front(), config);
    +            else
    +                this->filaments.load_preset(this->filaments.path_from_name(name), name, config).save();
    +            this->filament_presets.clear();
    +            this->filament_presets.emplace_back(name);
    +        } else {
    +            // Split the filament presets, load each of them separately.
    +            std::vector configs(num_extruders, this->filaments.default_preset().config);
    +            // loop through options and scatter them into configs.
    +            for (const t_config_option_key &key : this->filaments.default_preset().config.keys()) {
    +                const ConfigOption *other_opt = config.option(key);
    +                if (other_opt == nullptr)
    +                    continue;
    +                if (other_opt->is_scalar()) {
    +                    for (size_t i = 0; i < configs.size(); ++ i)
    +                        configs[i].option(key, false)->set(other_opt);
    +                } else if (key != "compatible_printers") {
    +                    for (size_t i = 0; i < configs.size(); ++ i)
    +                        static_cast(configs[i].option(key, false))->set_at(other_opt, 0, i);
    +                }
    +            }
    +            // Load the configs into this->filaments and make them active.
    +            this->filament_presets.clear();
    +            for (size_t i = 0; i < configs.size(); ++ i) {
    +                DynamicPrintConfig &cfg = configs[i];
    +                // Split the "compatible_printers_condition" and "inherits" from the cummulative vectors to separate filament presets.
    +                cfg.opt_string("compatible_printers_condition", true) = compatible_printers_condition_values[i + 1];
    +                cfg.opt_string("inherits", true)                      = inherits_values[i + 1];
    +                // Load all filament presets, but only select the first one in the preset dialog.
    +                Preset *loaded = nullptr;
    +                if (is_external)
    +                    loaded = &this->filaments.load_external_preset(name_or_path, name,
    +                        (i < old_filament_profile_names->values.size()) ? old_filament_profile_names->values[i] : "",
    +                        std::move(cfg), i == 0);
    +                else {
    +                    // Used by the config wizard when creating a custom setup.
    +                    // Therefore this block should only be called for a single extruder.
    +                    char suffix[64];
    +                    if (i == 0)
    +                        suffix[0] = 0;
    +                    else
    +                        sprintf(suffix, "%d", i);
    +                    std::string new_name = name + suffix;
    +                    loaded = &this->filaments.load_preset(this->filaments.path_from_name(new_name),
    +                        new_name, std::move(cfg), i == 0);
    +                    loaded->save();
    +                }
    +                this->filament_presets.emplace_back(loaded->name);
                 }
    -            this->filament_presets.emplace_back(loaded->name);
             }
    -    }
     
    -    // 4) Load the project config values (the per extruder wipe matrix etc).
    -    this->project_config.apply_only(config, s_project_options);
    +        // 4) Load the project config values (the per extruder wipe matrix etc).
    +        this->project_config.apply_only(config, s_project_options);
    +    }
     
         this->update_compatible_with_printer(false);
     }
    @@ -691,9 +787,10 @@ void PresetBundle::load_config_file_config_bundle(const std::string &path, const
             collection_dst.load_preset(path, preset_name_dst, std::move(preset_src->config), activate).is_external = true;
             return preset_name_dst;
         };
    -    load_one(this->prints,    tmp_bundle.prints,    tmp_bundle.prints   .get_selected_preset().name, true);
    -    load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filaments.get_selected_preset().name, true);
    -    load_one(this->printers,  tmp_bundle.printers,  tmp_bundle.printers .get_selected_preset().name, true);
    +    load_one(this->prints,        tmp_bundle.prints,        tmp_bundle.prints       .get_selected_preset().name, true);
    +    load_one(this->filaments,     tmp_bundle.filaments,     tmp_bundle.filaments    .get_selected_preset().name, true);
    +    load_one(this->sla_materials, tmp_bundle.sla_materials, tmp_bundle.sla_materials.get_selected_preset().name, true);
    +    load_one(this->printers,      tmp_bundle.printers,      tmp_bundle.printers     .get_selected_preset().name, true);
         this->update_multi_material_filament_presets();
         for (size_t i = 1; i < std::min(tmp_bundle.filament_presets.size(), this->filament_presets.size()); ++ i)
             this->filament_presets[i] = load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filament_presets[i], false);
    @@ -817,6 +914,7 @@ static void flatten_configbundle_hierarchy(boost::property_tree::ptree &tree)
     {
         flatten_configbundle_hierarchy(tree, "print");
         flatten_configbundle_hierarchy(tree, "filament");
    +    flatten_configbundle_hierarchy(tree, "sla_material");
         flatten_configbundle_hierarchy(tree, "printer");
     }
     
    @@ -853,9 +951,11 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
         // Parse the obsolete preset names, to be deleted when upgrading from the old configuration structure.
         std::vector loaded_prints;
         std::vector loaded_filaments;
    +    std::vector loaded_sla_materials;
         std::vector loaded_printers;
         std::string              active_print;
         std::vector active_filaments;
    +    std::string              active_sla_material;
         std::string              active_printer;
         size_t                   presets_loaded = 0;
         for (const auto §ion : tree) {
    @@ -870,6 +970,10 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
                 presets = &this->filaments;
                 loaded  = &loaded_filaments;
                 preset_name = section.first.substr(9);
    +        } else if (boost::starts_with(section.first, "sla_material:")) {
    +            presets = &this->sla_materials;
    +            loaded  = &loaded_sla_materials;
    +            preset_name = section.first.substr(9);
             } else if (boost::starts_with(section.first, "printer:")) {
                 presets = &this->printers;
                 loaded  = &loaded_printers;
    @@ -886,6 +990,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
                                 active_filaments.resize(idx + 1, std::string());
                             active_filaments[idx] = kvp.second.data();
                         }
    +                } else if (kvp.first == "sla_material") {
    +                    active_sla_material = kvp.second.data();
                     } else if (kvp.first == "printer") {
                         active_printer = kvp.second.data();
                     }
    @@ -899,6 +1005,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
                         dst = &this->obsolete_presets.prints;
                     else if (kvp.first == "filament")
                         dst = &this->obsolete_presets.filaments;
    +                else if (kvp.first == "sla_material")
    +                    dst = &this->obsolete_presets.sla_materials;
                     else if (kvp.first == "printer")
                         dst = &this->obsolete_presets.printers;
                     if (dst)
    @@ -999,6 +1107,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
         if ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) {
             if (! active_print.empty()) 
                 prints.select_preset_by_name(active_print, true);
    +        if (! active_sla_material.empty()) 
    +            sla_materials.select_preset_by_name(active_sla_material, true);
             if (! active_printer.empty())
                 printers.select_preset_by_name(active_printer, true);
             // Activate the first filament preset.
    @@ -1015,6 +1125,9 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
     
     void PresetBundle::update_multi_material_filament_presets()
     {
    +    if (printers.get_edited_preset().printer_technology() != ptFFF)
    +        return;
    +
         // Verify and select the filament presets.
         auto   *nozzle_diameter = static_cast(printers.get_edited_preset().config.option("nozzle_diameter"));
         size_t  num_extruders   = nozzle_diameter->values.size();
    @@ -1055,36 +1168,51 @@ void PresetBundle::update_multi_material_filament_presets()
     void PresetBundle::update_compatible_with_printer(bool select_other_if_incompatible)
     {
         const Preset                   &printer_preset             = this->printers.get_edited_preset();
    -    const std::string              &prefered_print_profile     = printer_preset.config.opt_string("default_print_profile");
    -    const std::vector &prefered_filament_profiles = printer_preset.config.option("default_filament_profile")->values;
    -    prefered_print_profile.empty() ?
    -        this->prints.update_compatible_with_printer(printer_preset, select_other_if_incompatible) :
    -        this->prints.update_compatible_with_printer(printer_preset, select_other_if_incompatible,
    -            [&prefered_print_profile](const std::string& profile_name){ return profile_name == prefered_print_profile; });
    -    prefered_filament_profiles.empty() ?
    -        this->filaments.update_compatible_with_printer(printer_preset, select_other_if_incompatible) :
    -        this->filaments.update_compatible_with_printer(printer_preset, select_other_if_incompatible,
    -            [&prefered_filament_profiles](const std::string& profile_name)
    -                { return std::find(prefered_filament_profiles.begin(), prefered_filament_profiles.end(), profile_name) != prefered_filament_profiles.end(); });
    -    if (select_other_if_incompatible) {
    -        // Verify validity of the current filament presets.
    -        this->filament_presets.front() = this->filaments.get_edited_preset().name;
    -        for (size_t idx = 1; idx < this->filament_presets.size(); ++ idx) {
    -            std::string &filament_name = this->filament_presets[idx];
    -            Preset      *preset        = this->filaments.find_preset(filament_name, false);
    -            if (preset == nullptr || ! preset->is_compatible) {
    -                // Pick a compatible profile. If there are prefered_filament_profiles, use them.
    -                if (prefered_filament_profiles.empty())
    -                    filament_name = this->filaments.first_compatible().name;
    -                else {
    -                    const std::string &preferred = (idx < prefered_filament_profiles.size()) ? 
    -                        prefered_filament_profiles[idx] : prefered_filament_profiles.front();
    -                    filament_name = this->filaments.first_compatible(
    -                        [&preferred](const std::string& profile_name){ return profile_name == preferred; }).name;
    +
    +    switch (printers.get_edited_preset().printer_technology()) {
    +    case ptFFF:
    +    {
    +        const std::string              &prefered_print_profile     = printer_preset.config.opt_string("default_print_profile");
    +        const std::vector &prefered_filament_profiles = printer_preset.config.option("default_filament_profile")->values;
    +        prefered_print_profile.empty() ?
    +            this->prints.update_compatible_with_printer(printer_preset, select_other_if_incompatible) :
    +            this->prints.update_compatible_with_printer(printer_preset, select_other_if_incompatible,
    +                [&prefered_print_profile](const std::string& profile_name){ return profile_name == prefered_print_profile; });
    +        prefered_filament_profiles.empty() ?
    +            this->filaments.update_compatible_with_printer(printer_preset, select_other_if_incompatible) :
    +            this->filaments.update_compatible_with_printer(printer_preset, select_other_if_incompatible,
    +                [&prefered_filament_profiles](const std::string& profile_name)
    +                    { return std::find(prefered_filament_profiles.begin(), prefered_filament_profiles.end(), profile_name) != prefered_filament_profiles.end(); });
    +        if (select_other_if_incompatible) {
    +            // Verify validity of the current filament presets.
    +            this->filament_presets.front() = this->filaments.get_edited_preset().name;
    +            for (size_t idx = 1; idx < this->filament_presets.size(); ++ idx) {
    +                std::string &filament_name = this->filament_presets[idx];
    +                Preset      *preset        = this->filaments.find_preset(filament_name, false);
    +                if (preset == nullptr || ! preset->is_compatible) {
    +                    // Pick a compatible profile. If there are prefered_filament_profiles, use them.
    +                    if (prefered_filament_profiles.empty())
    +                        filament_name = this->filaments.first_compatible().name;
    +                    else {
    +                        const std::string &preferred = (idx < prefered_filament_profiles.size()) ? 
    +                            prefered_filament_profiles[idx] : prefered_filament_profiles.front();
    +                        filament_name = this->filaments.first_compatible(
    +                            [&preferred](const std::string& profile_name){ return profile_name == preferred; }).name;
    +                    }
                     }
                 }
             }
         }
    +    case ptSLA:
    +    {
    +        const std::string              &prefered_print_profile     = printer_preset.config.opt_string("default_print_profile");
    +        const std::vector &prefered_filament_profiles = printer_preset.config.option("default_filament_profile")->values;
    +        prefered_print_profile.empty() ?
    +            this->sla_materials.update_compatible_with_printer(printer_preset, select_other_if_incompatible) :
    +			this->sla_materials.update_compatible_with_printer(printer_preset, select_other_if_incompatible,
    +                [&prefered_print_profile](const std::string& profile_name){ return profile_name == prefered_print_profile; });        
    +    }
    +    }
     }
     
     void PresetBundle::export_configbundle(const std::string &path) //, const DynamicPrintConfig &settings
    @@ -1111,6 +1239,7 @@ void PresetBundle::export_configbundle(const std::string &path) //, const Dynami
         // Export the names of the active presets.
         c << std::endl << "[presets]" << std::endl;
         c << "print = " << this->prints.get_selected_preset().name << std::endl;
    +    c << "sla_material = " << this->sla_materials.get_selected_preset().name << std::endl;
         c << "printer = " << this->printers.get_selected_preset().name << std::endl;
         for (size_t i = 0; i < this->filament_presets.size(); ++ i) {
             char suffix[64];
    @@ -1170,7 +1299,7 @@ bool PresetBundle::parse_color(const std::string &scolor, unsigned char *rgb_out
     
     void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitmapComboBox *ui)
     {
    -    if (ui == nullptr)
    +    if (ui == nullptr || this->printers.get_edited_preset().printer_technology() == ptSLA)
             return;
     
         unsigned char rgb[3];
    @@ -1265,6 +1394,7 @@ void PresetBundle::set_default_suppressed(bool default_suppressed)
     {
         prints.set_default_suppressed(default_suppressed);
         filaments.set_default_suppressed(default_suppressed);
    +    sla_materials.set_default_suppressed(default_suppressed);
         printers.set_default_suppressed(default_suppressed);
     }
     
    diff --git a/xs/src/slic3r/GUI/PresetBundle.hpp b/xs/src/slic3r/GUI/PresetBundle.hpp
    index a5c5682f94..68ec534dae 100644
    --- a/xs/src/slic3r/GUI/PresetBundle.hpp
    +++ b/xs/src/slic3r/GUI/PresetBundle.hpp
    @@ -40,6 +40,7 @@ public:
     
         PresetCollection            prints;
         PresetCollection            filaments;
    +    PresetCollection            sla_materials;
         PresetCollection            printers;
         // Filament preset names for a multi-extruder or multi-material print.
         // extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size()
    @@ -57,12 +58,13 @@ public:
         struct ObsoletePresets {
             std::vector prints;
             std::vector filaments;
    +        std::vector sla_materials;
             std::vector printers;
         };
         ObsoletePresets             obsolete_presets;
     
         bool                        has_defauls_only() const 
    -        { return prints.size() <= 1 && filaments.size() <= 1 && printers.size() <= 1; }
    +        { return prints.has_defaults_only() && filaments.has_defaults_only() && printers.has_defaults_only(); }
     
         DynamicPrintConfig          full_config() const;
     
    @@ -146,6 +148,9 @@ private:
         void                        load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree);
         bool                        load_compatible_bitmaps();
     
    +    DynamicPrintConfig          full_fff_config() const;
    +    DynamicPrintConfig          full_sla_config() const;
    +
         // Indicator, that the preset is compatible with the selected printer.
         wxBitmap                            *m_bitmapCompatible;
         // Indicator, that the preset is NOT compatible with the selected printer.
    diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp
    index 7c4322c5a0..111b2b933d 100644
    --- a/xs/src/slic3r/GUI/Tab.cpp
    +++ b/xs/src/slic3r/GUI/Tab.cpp
    @@ -5,7 +5,7 @@
     #include "../../libslic3r/Utils.hpp"
     
     #include "slic3r/Utils/Http.hpp"
    -#include "slic3r/Utils/OctoPrint.hpp"
    +#include "slic3r/Utils/PrintHost.hpp"
     #include "slic3r/Utils/Serial.hpp"
     #include "BonjourDialog.hpp"
     #include "WipeTowerDialog.hpp"
    @@ -214,7 +214,7 @@ void Tab::load_initial_data()
     	m_tt_non_system = m_presets->get_selected_preset_parent() ? &m_tt_value_unlock : &m_ttg_white_bullet_ns;
     }
     
    -PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages/* = false*/)
    +Slic3r::GUI::PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages /*= false*/)
     {
     	// Index of icon in an icon list $self->{icons}.
     	auto icon_idx = 0;
    @@ -238,7 +238,8 @@ PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bo
     	page->SetScrollbars(1, 1, 1, 1);
     	page->Hide();
     	m_hsizer->Add(page.get(), 1, wxEXPAND | wxLEFT, 5);
    -	if (!is_extruder_pages) 
    +
    +    if (!is_extruder_pages) 
     		m_pages.push_back(page);
     
     	page->set_config(m_config);
    @@ -313,10 +314,10 @@ void Tab::update_changed_ui()
     	if (m_postpone_update_ui) 
     		return;
     
    -	const bool is_printer_type = (name() == "printer");
    -	auto dirty_options = m_presets->current_dirty_options(is_printer_type);
    -	auto nonsys_options = m_presets->current_different_from_parent_options(is_printer_type);
    -	if (is_printer_type){
    +	const bool deep_compare = (m_name == "printer" || m_name == "sla_material");
    +	auto dirty_options = m_presets->current_dirty_options(deep_compare);
    +	auto nonsys_options = m_presets->current_different_from_parent_options(deep_compare);
    +    if (name() == "printer"){
     		TabPrinter* tab = static_cast(this);
     		if (tab->m_initial_extruders_count != tab->m_extruders_count)
     			dirty_options.emplace_back("extruders_count");
    @@ -397,7 +398,7 @@ void Tab::init_options_list()
     }
     
     template
    -void add_correct_opts_to_options_list(const std::string &opt_key, std::map& map, TabPrinter *tab, const int& value)
    +void add_correct_opts_to_options_list(const std::string &opt_key, std::map& map, Tab *tab, const int& value)
     {
     	T *opt_cur = static_cast(tab->m_config->option(opt_key));
     	for (int i = 0; i < opt_cur->values.size(); i++)
    @@ -429,6 +430,30 @@ void TabPrinter::init_options_list()
     	m_options_list.emplace("extruders_count", m_opt_status_value);
     }
     
    +void TabSLAMaterial::init_options_list()
    +{
    +    if (!m_options_list.empty())
    +        m_options_list.clear();
    +
    +    for (const auto opt_key : m_config->keys())
    +    {
    +        if (opt_key == "compatible_printers"){
    +            m_options_list.emplace(opt_key, m_opt_status_value);
    +            continue;
    +        }
    +        switch (m_config->option(opt_key)->type())
    +        {
    +        case coInts:	add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value);	break;
    +        case coBools:	add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value);	break;
    +        case coFloats:	add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value);	break;
    +        case coStrings:	add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value);	break;
    +        case coPercents:add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value);	break;
    +        case coPoints:	add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value);	break;
    +        default:		m_options_list.emplace(opt_key, m_opt_status_value);		break;
    +        }
    +    }
    +}
    +
     void Tab::get_sys_and_mod_flags(const std::string& opt_key, bool& sys_page, bool& modified_page)
     {
     	auto opt = m_options_list.find(opt_key);
    @@ -649,12 +674,21 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
     			int val = boost::any_cast(value);
     			event.SetInt(val);
     		}
    +
    +        if (opt_key == "printer_technology")
    +        {
    +            int val = boost::any_cast(value);
    +            event.SetInt(val);
    +            g_wxMainFrame->ProcessWindowEvent(event);
    +            return;
    +        }
    +        
     		g_wxMainFrame->ProcessWindowEvent(event);
     	}
     	if (opt_key == "fill_density")
     	{
    -		boost::any val = get_optgroup()->get_config_value(*m_config, opt_key);
    -		get_optgroup()->set_value(opt_key, val);
    +		boost::any val = get_optgroup(ogFrequentlyChangingParameters)->get_config_value(*m_config, opt_key);
    +		get_optgroup(ogFrequentlyChangingParameters)->set_value(opt_key, val);
     	}
     	if (opt_key == "support_material" || opt_key == "support_material_buildplate_only")
     	{
    @@ -663,12 +697,12 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
     								m_config->opt_bool("support_material_buildplate_only") ?
     									_("Support on build plate only") :
     									_("Everywhere");
    -		get_optgroup()->set_value("support", new_selection);
    +		get_optgroup(ogFrequentlyChangingParameters)->set_value("support", new_selection);
     	}
     	if (opt_key == "brim_width")
     	{
     		bool val = m_config->opt_float("brim_width") > 0.0 ? true : false;
    -		get_optgroup()->set_value("brim", val);
    +		get_optgroup(ogFrequentlyChangingParameters)->set_value("brim", val);
     	}
     
         if (opt_key == "wipe_tower" || opt_key == "single_extruder_multi_material" || opt_key == "extruders_count" )
    @@ -677,17 +711,16 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
     	update();
     }
     
    -
     // Show/hide the 'purging volumes' button
     void Tab::update_wiping_button_visibility() {
    +    if (get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA)
    +        return; // ys_FIXME
         bool wipe_tower_enabled = dynamic_cast(  (m_preset_bundle->prints.get_edited_preset().config  ).option("wipe_tower"))->value;
         bool multiple_extruders = dynamic_cast((m_preset_bundle->printers.get_edited_preset().config).option("nozzle_diameter"))->values.size() > 1;
         bool single_extruder_mm = dynamic_cast(  (m_preset_bundle->printers.get_edited_preset().config).option("single_extruder_multi_material"))->value;
     
    -    if (wipe_tower_enabled && multiple_extruders && single_extruder_mm)
    -        get_wiping_dialog_button()->Show();
    -    else get_wiping_dialog_button()->Hide();
    -
    +	get_wiping_dialog_button()->Show(wipe_tower_enabled && multiple_extruders && single_extruder_mm);
    +	
         (get_wiping_dialog_button()->GetParent())->Layout();
     }
     
    @@ -754,18 +787,18 @@ void Tab::update_preset_description_line()
     
     void Tab::update_frequently_changed_parameters()
     {
    -	boost::any value = get_optgroup()->get_config_value(*m_config, "fill_density");
    -	get_optgroup()->set_value("fill_density", value);
    +	boost::any value = get_optgroup(ogFrequentlyChangingParameters)->get_config_value(*m_config, "fill_density");
    +	get_optgroup(ogFrequentlyChangingParameters)->set_value("fill_density", value);
     
     	wxString new_selection = !m_config->opt_bool("support_material") ?
     							_("None") :
     							m_config->opt_bool("support_material_buildplate_only") ?
     								_("Support on build plate only") :
     								_("Everywhere");
    -	get_optgroup()->set_value("support", new_selection);
    +	get_optgroup(ogFrequentlyChangingParameters)->set_value("support", new_selection);
     
     	bool val = m_config->opt_float("brim_width") > 0.0 ? true : false;
    -	get_optgroup()->set_value("brim", val);
    +	get_optgroup(ogFrequentlyChangingParameters)->set_value("brim", val);
     
     	update_wiping_button_visibility();
     }
    @@ -1007,6 +1040,9 @@ void TabPrint::reload_config(){
     
     void TabPrint::update()
     {
    +    if (get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA)
    +        return; // ys_FIXME
    +
     	Freeze();
     
     	double fill_density = m_config->option("fill_density")->value;
    @@ -1366,6 +1402,9 @@ void TabFilament::reload_config(){
     
     void TabFilament::update()
     {
    +    if (get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA)
    +        return; // ys_FIXME
    +
     	Freeze();
     	wxString text = from_u8(PresetHints::cooling_description(m_presets->get_edited_preset()));
     	m_cooling_description_line->SetText(text);
    @@ -1411,6 +1450,17 @@ void TabPrinter::build()
     	m_presets = &m_preset_bundle->printers;
     	load_initial_data();
     
    +    m_printer_technology = m_presets->get_selected_preset().printer_technology();
    +
    +    m_presets->get_selected_preset().printer_technology() == ptSLA ? build_sla() : build_fff();
    +
    +//     on_value_change("printer_technology", m_printer_technology); // to update show/hide preset ComboBoxes
    +}
    +
    +void TabPrinter::build_fff()
    +{
    +    if (!m_pages.empty())
    +        m_pages.resize(0);
     	// to avoid redundant memory allocation / deallocation during extruders count changing
     	m_pages.reserve(30);
     
    @@ -1426,7 +1476,7 @@ void TabPrinter::build()
     		Line line{ _(L("Bed shape")), "" };
     		line.widget = [this](wxWindow* parent){
     			auto btn = new wxButton(parent, wxID_ANY, _(L(" Set "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT);
    -			//			btn->SetFont(Slic3r::GUI::small_font);
    +			btn->SetFont(Slic3r::GUI::small_font());
     			btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("printer_empty.png")), wxBITMAP_TYPE_PNG));
     
     			auto sizer = new wxBoxSizer(wxHORIZONTAL);
    @@ -1494,7 +1544,7 @@ void TabPrinter::build()
     			auto serial_test = [this](wxWindow* parent){
     				auto btn = m_serial_test_btn = new wxButton(parent, wxID_ANY,
     					_(L("Test")), wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT);
    -//				btn->SetFont($Slic3r::GUI::small_font);
    +				btn->SetFont(Slic3r::GUI::small_font());
     				btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("wrench.png")), wxBITMAP_TYPE_PNG));
     				auto sizer = new wxBoxSizer(wxHORIZONTAL);
     				sizer->Add(btn);
    @@ -1521,10 +1571,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 +1584,50 @@ 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."))
    -					);
    +				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);
    +					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));
     				}
     			});
     
     			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 +1637,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 +1657,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);
     
     		}
    @@ -1692,6 +1747,76 @@ void TabPrinter::build()
     		update_serial_ports();
     }
     
    +void TabPrinter::build_sla()
    +{
    +    if (!m_pages.empty())
    +        m_pages.resize(0);
    +    auto page = add_options_page(_(L("General")), "printer_empty.png");
    +    auto optgroup = page->new_optgroup(_(L("Size and coordinates")));
    +
    +    Line line{ _(L("Bed shape")), "" };
    +    line.widget = [this](wxWindow* parent){
    +        auto btn = new wxButton(parent, wxID_ANY, _(L(" Set ")) + dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT);
    +        //			btn->SetFont(Slic3r::GUI::small_font);
    +        btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("printer_empty.png")), wxBITMAP_TYPE_PNG));
    +
    +        auto sizer = new wxBoxSizer(wxHORIZONTAL);
    +        sizer->Add(btn);
    +
    +        btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e)
    +        {
    +            auto dlg = new BedShapeDialog(this);
    +            dlg->build_dialog(m_config->option("bed_shape"));
    +            if (dlg->ShowModal() == wxID_OK){
    +                load_key_value("bed_shape", dlg->GetValue());
    +                update_changed_ui();
    +            }
    +        }));
    +
    +        return sizer;
    +    };
    +    optgroup->append_line(line, &m_colored_Label);
    +    optgroup->append_single_option_line("max_print_height");
    +
    +    optgroup = page->new_optgroup(_(L("Display")));
    +    optgroup->append_single_option_line("display_width");
    +    optgroup->append_single_option_line("display_height");
    +
    +    auto option = optgroup->get_option("display_pixels_x");
    +    line = { _(option.opt.full_label), "" };
    +    line.append_option(option);
    +    line.append_option(optgroup->get_option("display_pixels_y"));
    +    optgroup->append_line(line);
    +
    +    optgroup = page->new_optgroup(_(L("Corrections")));
    +    line = Line{ m_config->def()->get("printer_correction")->full_label, "" };
    +    std::vector axes{ "X", "Y", "Z" };
    +    int id = 0;
    +    for (auto& axis : axes) {
    +        auto opt = optgroup->get_option("printer_correction", id);
    +        opt.opt.label = axis;
    +        line.append_option(opt);
    +        ++id;
    +    }
    +    optgroup->append_line(line);
    +
    +    page = add_options_page(_(L("Notes")), "note.png");
    +    optgroup = page->new_optgroup(_(L("Notes")), 0);
    +    option = optgroup->get_option("printer_notes");
    +    option.opt.full_width = true;
    +    option.opt.height = 250;
    +    optgroup->append_single_option_line(option);
    +
    +    page = add_options_page(_(L("Dependencies")), "wrench.png");
    +    optgroup = page->new_optgroup(_(L("Profile dependencies")));
    +    line = Line{ "", "" };
    +    line.full_width = 1;
    +    line.widget = [this](wxWindow* parent) {
    +        return description_line_widget(parent, &m_parent_preset_description_line);
    +    };
    +    optgroup->append_line(line);
    +}
    +
     void TabPrinter::update_serial_ports(){
     	Field *field = get_field("serial_port");
     	Choice *choice = static_cast(field);
    @@ -1705,6 +1830,7 @@ void TabPrinter::extruders_count_changed(size_t extruders_count){
     	build_extruder_pages();
     	reload_config();
     	on_value_change("extruders_count", extruders_count);
    +    update_objects_list_extruder_column(extruders_count);
     }
     
     void TabPrinter::append_option_line(ConfigOptionsGroupShp optgroup, const std::string opt_key)
    @@ -1883,7 +2009,38 @@ void TabPrinter::on_preset_loaded()
     	extruders_count_changed(extruders_count);
     }
     
    -void TabPrinter::update(){
    +void TabPrinter::update_pages()
    +{
    +    // update m_pages ONLY if printer technology is changed
    +    if (m_presets->get_edited_preset().printer_technology() == m_printer_technology)
    +        return;
    +
    +    // hide all old pages
    +    for (auto& el : m_pages)
    +        el.get()->Hide();
    +
    +    // set m_pages to m_pages_(technology before changing)
    +    m_printer_technology == ptFFF ? m_pages.swap(m_pages_fff) : m_pages.swap(m_pages_sla);
    +
    +    // build Tab according to the technology, if it's not exist jet OR
    +    // set m_pages_(technology after changing) to m_pages
    +    if (m_presets->get_edited_preset().printer_technology() == ptFFF)
    +        m_pages_fff.empty() ? build_fff() : m_pages.swap(m_pages_fff);
    +    else 
    +        m_pages_sla.empty() ? build_sla() : m_pages.swap(m_pages_sla);
    +
    +    rebuild_page_tree(true);
    +
    +    on_value_change("printer_technology", m_presets->get_edited_preset().printer_technology()); // to update show/hide preset ComboBoxes
    +}
    +
    +void TabPrinter::update()
    +{
    +    m_presets->get_edited_preset().printer_technology() == ptFFF ? update_fff() : update_sla();
    +}
    +
    +void TabPrinter::update_fff()
    +{
     	Freeze();
     
     	bool en;
    @@ -1897,8 +2054,12 @@ void TabPrinter::update(){
     			m_serial_test_btn->Disable();
     	}
     
    -	m_octoprint_host_test_btn->Enable(!m_config->opt_string("octoprint_host").empty());
    -	
    +	{
    +		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);
    @@ -1982,20 +2143,25 @@ void TabPrinter::update(){
     	Thaw();
     }
     
    +void TabPrinter::update_sla(){ ; }
    +
     // Initialize the UI from the current preset
     void Tab::load_current_preset()
     {
     	auto preset = m_presets->get_edited_preset();
     
     	(preset.is_default || preset.is_system) ? m_btn_delete_preset->Disable() : m_btn_delete_preset->Enable(true);
    -	update();
    -	// For the printer profile, generate the extruder pages.
    -	on_preset_loaded();
    -	// Reload preset pages with the new configuration values.
    -	reload_config();
    +
    +    update();
    +    // For the printer profile, generate the extruder pages.
    +    if (preset.printer_technology() == ptFFF)
    +        on_preset_loaded();
    +    // Reload preset pages with the new configuration values.
    +    reload_config();
    +
     	m_bmp_non_system = m_presets->get_selected_preset_parent() ? &m_bmp_value_unlock : &m_bmp_white_bullet;
     	m_ttg_non_system = m_presets->get_selected_preset_parent() ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns;
    -	m_tt_non_system = m_presets->get_selected_preset_parent() ? &m_tt_value_unlock : &m_ttg_white_bullet_ns;
    +	m_tt_non_system = m_presets->get_selected_preset_parent()  ? &m_tt_value_unlock  : &m_ttg_white_bullet_ns;
     
     	m_undo_to_sys_btn->Enable(!preset.is_default);
     
    @@ -2008,6 +2174,27 @@ void Tab::load_current_preset()
     		if (!checked_tab(this))
     			return;
     		update_tab_ui();
    +
    +        // update show/hide tabs
    +        if (m_name == "printer"){
    +            PrinterTechnology& printer_technology = m_presets->get_edited_preset().printer_technology();
    +            if (printer_technology != static_cast(this)->m_printer_technology)
    +            {
    +                for (auto& tab : get_preset_tabs()){
    +                    if (tab.technology != printer_technology)
    +                    {
    +                        int page_id = get_tab_panel()->FindPage(tab.panel);
    +                        get_tab_panel()->GetPage(page_id)->Show(false);
    +                        get_tab_panel()->RemovePage(page_id);
    +                    }
    +                    else
    +                        get_tab_panel()->InsertPage(get_tab_panel()->FindPage(this), tab.panel, tab.panel->title());
    +                }
    +
    +                static_cast(this)->m_printer_technology = printer_technology;
    +            }
    +        }
    +
     		on_presets_changed();
     
     		if (name() == "print")
    @@ -2025,7 +2212,7 @@ void Tab::load_current_preset()
     }
     
     //Regerenerate content of the page tree.
    -void Tab::rebuild_page_tree()
    +void Tab::rebuild_page_tree(bool tree_sel_change_event /*= false*/)
     {
     	Freeze();
     	// get label of the currently selected item
    @@ -2040,9 +2227,9 @@ void Tab::rebuild_page_tree()
     		m_treectrl->SetItemTextColour(itemId, p->get_item_colour());
     		if (p->title() == selected) {
     			if (!(p->title() == _(L("Machine limits")) || p->title() == _(L("Single extruder MM setup")))) // These Pages have to be updated inside OnTreeSelChange
    -				m_disable_tree_sel_changed_event = 1;
    +				m_disable_tree_sel_changed_event = !tree_sel_change_event;
     			m_treectrl->SelectItem(itemId);
    -			m_disable_tree_sel_changed_event = 0;
    +			m_disable_tree_sel_changed_event = false;
     			have_selection = 1;
     		}
     	}
    @@ -2057,48 +2244,53 @@ void Tab::rebuild_page_tree()
     // Called by the UI combo box when the user switches profiles.
     // Select a preset by a name.If !defined(name), then the default preset is selected.
     // If the current profile is modified, user is asked to save the changes.
    -void Tab::select_preset(const std::string& preset_name /*= ""*/)
    +void Tab::select_preset(std::string preset_name /*= ""*/)
     {
    -	std::string name = preset_name;
    -	auto force = false;
    -	auto presets = m_presets;
     	// If no name is provided, select the "-- default --" preset.
    -	if (name.empty())
    -		name= presets->default_preset().name;
    -	auto current_dirty = presets->current_is_dirty();
    -	auto canceled = false;
    -	auto printer_tab = presets->name().compare("printer")==0;
    +	if (preset_name.empty())
    +		preset_name = m_presets->default_preset().name;
    +	auto current_dirty = m_presets->current_is_dirty();
    +	auto printer_tab   = m_presets->name() == "printer";
    +	auto canceled      = false;
     	m_reload_dependent_tabs = {};
    -	if (!force && current_dirty && !may_discard_current_dirty_preset()) {
    +	if (current_dirty && !may_discard_current_dirty_preset()) {
     		canceled = true;
    -	} else if(printer_tab) {
    +	} else if (printer_tab) {
     		// Before switching the printer to a new one, verify, whether the currently active print and filament
     		// are compatible with the new printer.
     		// If they are not compatible and the current print or filament are dirty, let user decide
     		// whether to discard the changes or keep the current printer selection.
    -		auto new_printer_preset = presets->find_preset(name, true);
    -		auto print_presets = &m_preset_bundle->prints;
    -		bool print_preset_dirty = print_presets->current_is_dirty();
    -		bool print_preset_compatible = print_presets->get_edited_preset().is_compatible_with_printer(*new_printer_preset);
    -		canceled = !force && print_preset_dirty && !print_preset_compatible &&
    -			!may_discard_current_dirty_preset(print_presets, name);
    -		auto filament_presets = &m_preset_bundle->filaments;
    -		bool filament_preset_dirty = filament_presets->current_is_dirty();
    -		bool filament_preset_compatible = filament_presets->get_edited_preset().is_compatible_with_printer(*new_printer_preset);
    -		if (!canceled && !force) {
    -			canceled = filament_preset_dirty && !filament_preset_compatible &&
    -				!may_discard_current_dirty_preset(filament_presets, name);
    +		//
    +		// With the introduction of the SLA printer types, we need to support switching between
    +		// the FFF and SLA printers.
    +		const Preset 		&new_printer_preset     = *m_presets->find_preset(preset_name, true);
    +		PrinterTechnology    old_printer_technology = m_presets->get_edited_preset().printer_technology();
    +		PrinterTechnology    new_printer_technology = new_printer_preset.printer_technology();
    +		struct PresetUpdate {
    +			std::string          name;
    +			PresetCollection 	*presets;
    +			PrinterTechnology    technology;
    +			bool    	         old_preset_dirty;
    +			bool         	     new_preset_compatible;
    +		};
    +		std::vector updates = {
    +			{ "print",			&m_preset_bundle->prints,			ptFFF },
    +			{ "filament",		&m_preset_bundle->filaments,		ptFFF },
    + 			{ "sla_material",	&m_preset_bundle->sla_materials,	ptSLA }
    +		};
    +		for (PresetUpdate &pu : updates) {
    +			pu.old_preset_dirty      = (old_printer_technology == pu.technology) && pu.presets->current_is_dirty();
    +			pu.new_preset_compatible = (new_printer_technology == pu.technology) && pu.presets->get_edited_preset().is_compatible_with_printer(new_printer_preset);
    +			if (! canceled)
    +				canceled = pu.old_preset_dirty && ! pu.new_preset_compatible && ! may_discard_current_dirty_preset(pu.presets, preset_name);
     		}
    -		if (!canceled) {
    -			if (!print_preset_compatible) {
    +		if (! canceled) {
    +			for (PresetUpdate &pu : updates) {
     				// The preset will be switched to a different, compatible preset, or the '-- default --'.
    -				m_reload_dependent_tabs.push_back("print");
    -				if (print_preset_dirty) print_presets->discard_current_changes();
    -			}
    -			if (!filament_preset_compatible) {
    -				// The preset will be switched to a different, compatible preset, or the '-- default --'.
    -				m_reload_dependent_tabs.push_back("filament");
    -				if (filament_preset_dirty) filament_presets->discard_current_changes();
    +                if (pu.technology == new_printer_technology)
    +				    m_reload_dependent_tabs.emplace_back(pu.name);
    +				if (pu.old_preset_dirty)
    +					pu.presets->discard_current_changes();
     			}
     		}
     	}
    @@ -2107,19 +2299,20 @@ void Tab::select_preset(const std::string& preset_name /*= ""*/)
     		// Trigger the on_presets_changed event so that we also restore the previous value in the plater selector,
     		// if this action was initiated from the platter.
     		on_presets_changed();
    -	}
    -	else {
    -		if (current_dirty) presets->discard_current_changes() ;
    -		presets->select_preset_by_name(name, force);
    +	} else {
    +		if (current_dirty)
    +			m_presets->discard_current_changes() ;
    +		m_presets->select_preset_by_name(preset_name, false);
     		// Mark the print & filament enabled if they are compatible with the currently selected preset.
     		// The following method should not discard changes of current print or filament presets on change of a printer profile,
     		// if they are compatible with the current printer.
     		if (current_dirty || printer_tab)
     			m_preset_bundle->update_compatible_with_printer(true);
     		// Initialize the UI from the current preset.
    +        if (printer_tab)
    +            static_cast(this)->update_pages();
     		load_current_preset();
     	}
    -
     }
     
     // If the current preset is dirty, the user is asked whether the changes may be discarded.
    @@ -2761,5 +2954,71 @@ void SavePresetWindow::accept()
     	}
     }
     
    +void TabSLAMaterial::build()
    +{
    +    m_presets = &m_preset_bundle->sla_materials;
    +    load_initial_data();
    +
    +    auto page = add_options_page(_(L("Material")), "package_green.png");
    +
    +    auto optgroup = page->new_optgroup(_(L("Layers")));
    +    optgroup->append_single_option_line("layer_height");
    +    optgroup->append_single_option_line("initial_layer_height");
    +
    +    optgroup = page->new_optgroup(_(L("Exposure")));
    +    optgroup->append_single_option_line("exposure_time");
    +    optgroup->append_single_option_line("initial_exposure_time");
    +
    +    optgroup = page->new_optgroup(_(L("Corrections")));
    +    optgroup->label_width = 190;
    +    std::vector corrections = { "material_correction_printing", "material_correction_curing" };
    +    std::vector axes{ "X", "Y", "Z" };
    +    for (auto& opt_key : corrections){
    +        auto line = Line{ m_config->def()->get(opt_key)->full_label, "" };
    +        int id = 0;
    +        for (auto& axis : axes) {
    +            auto opt = optgroup->get_option(opt_key, id);
    +            opt.opt.label = axis;
    +            opt.opt.width = 60;
    +            line.append_option(opt);
    +            ++id;
    +        }
    +        optgroup->append_line(line);
    +    }
    +
    +    page = add_options_page(_(L("Notes")), "note.png");
    +    optgroup = page->new_optgroup(_(L("Notes")), 0);
    +    optgroup->label_width = 0;
    +    Option option = optgroup->get_option("material_notes");
    +    option.opt.full_width = true;
    +    option.opt.height = 250;
    +    optgroup->append_single_option_line(option);
    +
    +    page = add_options_page(_(L("Dependencies")), "wrench.png");
    +    optgroup = page->new_optgroup(_(L("Profile dependencies")));
    +    auto line = Line { _(L("Compatible printers")), "" };
    +    line.widget = [this](wxWindow* parent){
    +        return compatible_printers_widget(parent, &m_compatible_printers_checkbox, &m_compatible_printers_btn);
    +    };
    +    optgroup->append_line(line, &m_colored_Label);
    +
    +    option = optgroup->get_option("compatible_printers_condition");
    +    option.opt.full_width = true;
    +    optgroup->append_single_option_line(option);
    +
    +    line = Line{ "", "" };
    +    line.full_width = 1;
    +    line.widget = [this](wxWindow* parent) {
    +        return description_line_widget(parent, &m_parent_preset_description_line);
    +    };
    +    optgroup->append_line(line);
    +}
    +
    +void TabSLAMaterial::update()
    +{
    +    if (get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptFFF)
    +        return; // ys_FIXME
    +}
    +
     } // GUI
     } // Slic3r
    diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp
    index 8b4eae7de5..a1214465ea 100644
    --- a/xs/src/slic3r/GUI/Tab.hpp
    +++ b/xs/src/slic3r/GUI/Tab.hpp
    @@ -218,8 +218,8 @@ public:
     	
     	void		create_preset_tab(PresetBundle *preset_bundle);
     	void		load_current_preset();
    -	void		rebuild_page_tree();
    -	void		select_preset(const std::string& preset_name = "");
    +	void        rebuild_page_tree(bool tree_sel_change_event = false);
    +	void		select_preset(std::string preset_name = "");
     	bool		may_discard_current_dirty_preset(PresetCollection* presets = nullptr, const std::string& new_printer_name = "");
     	wxSizer*	compatible_printers_widget(wxWindow* parent, wxCheckBox** checkbox, wxButton** btn);
     
    @@ -267,11 +267,11 @@ public:
     
     	void			on_value_change(const std::string& opt_key, const boost::any& value);
     
    +    void            update_wiping_button_visibility();
     protected:
     	void			on_presets_changed();
     	void			update_preset_description_line();
     	void			update_frequently_changed_parameters();
    -    void            update_wiping_button_visibility();
     	void			update_tab_presets(wxComboCtrl* ui, bool show_incompatible);
     	void			fill_icon_descriptions();
     	void			set_tooltips_text();
    @@ -319,21 +319,32 @@ class TabPrinter : public Tab
     	bool		m_use_silent_mode = false;
     	void		append_option_line(ConfigOptionsGroupShp optgroup, const std::string opt_key);
     	bool		m_rebuild_kinematics_page = false;
    +
    +    std::vector			m_pages_fff;
    +    std::vector			m_pages_sla;
     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;
     	size_t		m_initial_extruders_count;
     	size_t		m_sys_extruders_count;
     
    +    PrinterTechnology               m_printer_technology = ptFFF;
    +
     	TabPrinter() {}
     	TabPrinter(wxNotebook* parent, bool no_controller) : Tab(parent, _(L("Printer Settings")), "printer", no_controller) {}
     	~TabPrinter(){}
     
     	void		build() override;
    -	void		update() override;
    +    void		build_fff();
    +    void		build_sla();
    +    void		update() override;
    +    void		update_fff();
    +    void		update_sla();
    +    void        update_pages(); // update m_pages according to printer technology
     	void		update_serial_ports();
     	void		extruders_count_changed(size_t extruders_count);
     	PageShp		build_kinematics_page();
    @@ -342,6 +353,19 @@ public:
     	void		init_options_list() override;
     };
     
    +class TabSLAMaterial : public Tab
    +{
    +public:
    +    TabSLAMaterial() {}
    +    TabSLAMaterial(wxNotebook* parent, bool no_controller) :
    +		Tab(parent, _(L("SLA Material Settings")), "sla_material", no_controller) {}
    +    ~TabSLAMaterial(){}
    +
    +	void		build() override;
    +	void		update() override;
    +    void		init_options_list() override;
    +};
    +
     class SavePresetWindow :public wxDialog
     {
     public:
    diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp
    index 5949efb377..7fcad9e656 100644
    --- a/xs/src/slic3r/GUI/wxExtensions.cpp
    +++ b/xs/src/slic3r/GUI/wxExtensions.cpp
    @@ -1,5 +1,13 @@
     #include "wxExtensions.hpp"
     
    +#include "GUI.hpp"
    +#include "../../libslic3r/Utils.hpp"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +
     const unsigned int wxCheckListBoxComboPopup::DefaultWidth = 200;
     const unsigned int wxCheckListBoxComboPopup::DefaultHeight = 200;
     const unsigned int wxCheckListBoxComboPopup::DefaultItemHeight = 18;
    @@ -182,3 +190,1216 @@ void wxDataViewTreeCtrlComboPopup::OnDataViewTreeCtrlSelection(wxCommandEvent& e
     	auto selected = GetItemText(GetSelection());
     	cmb->SetText(selected);
     }
    +
    +// ----------------------------------------------------------------------------
    +// ***  PrusaCollapsiblePane  ***    
    +// ----------------------------------------------------------------------------
    +void PrusaCollapsiblePane::OnStateChange(const wxSize& sz)
    +{
    +#ifdef __WXOSX__
    +	wxCollapsiblePane::OnStateChange(sz);
    +#else
    +	SetSize(sz);
    +
    +	if (this->HasFlag(wxCP_NO_TLW_RESIZE))
    +	{
    +		// the user asked to explicitly handle the resizing itself...
    +		return;
    +	}
    +
    +	auto top = GetParent(); //right_panel
    +	if (!top)
    +		return;
    +
    +	wxSizer *sizer = top->GetSizer();
    +	if (!sizer)
    +		return;
    +
    +	const wxSize newBestSize = sizer->ComputeFittingClientSize(top);
    +	top->SetMinClientSize(newBestSize);
    +
    +	wxWindowUpdateLocker noUpdates_p(top->GetParent());
    +	// we shouldn't attempt to resize a maximized window, whatever happens
    +	// 	if (!top->IsMaximized())
    +	// 		top->SetClientSize(newBestSize);
    +	top->GetParent()->Layout();
    +	top->Refresh();
    +#endif //__WXOSX__
    +}
    +
    +// ----------------------------------------------------------------------------
    +// ***  PrusaCollapsiblePaneMSW  ***    used only #ifdef __WXMSW__
    +// ----------------------------------------------------------------------------
    +#ifdef __WXMSW__
    +bool PrusaCollapsiblePaneMSW::Create(wxWindow *parent, wxWindowID id, const wxString& label, 
    +	const wxPoint& pos, const wxSize& size, long style, const wxValidator& val, const wxString& name)
    +{
    +	if (!wxControl::Create(parent, id, pos, size, style, val, name))
    +		return false;
    +	m_pStaticLine = NULL;
    +	m_strLabel = label;
    +
    +	// sizer containing the expand button and possibly a static line
    +	m_sz = new wxBoxSizer(wxHORIZONTAL);
    +
    +	m_bmp_close.LoadFile(Slic3r::GUI::from_u8(Slic3r::var("disclosure_triangle_close.png")), wxBITMAP_TYPE_PNG);
    +	m_bmp_open.LoadFile(Slic3r::GUI::from_u8(Slic3r::var("disclosure_triangle_open.png")), wxBITMAP_TYPE_PNG);
    +
    +	m_pDisclosureTriangleButton = new wxButton(this, wxID_ANY, m_strLabel, wxPoint(0, 0),
    +		wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER);
    +	UpdateBtnBmp();
    +	m_pDisclosureTriangleButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event)
    +	{
    +		if (event.GetEventObject() != m_pDisclosureTriangleButton)
    +		{
    +			event.Skip();
    +			return;
    +		}
    +
    +		Collapse(!IsCollapsed());
    +
    +		// this change was generated by the user - send the event
    +		wxCollapsiblePaneEvent ev(this, GetId(), IsCollapsed());
    +		GetEventHandler()->ProcessEvent(ev);
    +	});
    +
    +	m_sz->Add(m_pDisclosureTriangleButton, 0, wxLEFT | wxTOP | wxBOTTOM, GetBorder());
    +
    +	// do not set sz as our sizers since we handle the pane window without using sizers
    +	m_pPane = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
    +		wxTAB_TRAVERSAL | wxNO_BORDER, wxT("wxCollapsiblePanePane"));
    +
    +	wxColour& clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
    +	m_pDisclosureTriangleButton->SetBackgroundColour(clr);
    +	this->SetBackgroundColour(clr);
    +	m_pPane->SetBackgroundColour(clr);
    +
    +	// start as collapsed:
    +	m_pPane->Hide();
    +
    +	return true;
    +}
    +
    +void PrusaCollapsiblePaneMSW::UpdateBtnBmp()
    +{
    +	if (IsCollapsed())
    +		m_pDisclosureTriangleButton->SetBitmap(m_bmp_close);
    +	else{
    +		m_pDisclosureTriangleButton->SetBitmap(m_bmp_open);
    +		// To updating button bitmap it's needed to lost focus on this button, so
    +		// we set focus to mainframe 
    +		//GetParent()->GetParent()->GetParent()->SetFocus();
    +		//or to pane
    +		GetPane()->SetFocus();
    +	}
    +	Layout();
    +}
    +
    +void PrusaCollapsiblePaneMSW::SetLabel(const wxString &label)
    +{
    +	m_strLabel = label;
    +	m_pDisclosureTriangleButton->SetLabel(m_strLabel);
    +	Layout();
    +}
    +
    +bool PrusaCollapsiblePaneMSW::Layout()
    +{
    +	if (!m_pDisclosureTriangleButton || !m_pPane || !m_sz)
    +		return false;     // we need to complete the creation first!
    +
    +	wxSize oursz(GetSize());
    +
    +	// move & resize the button and the static line
    +	m_sz->SetDimension(0, 0, oursz.GetWidth(), m_sz->GetMinSize().GetHeight());
    +	m_sz->Layout();
    +
    +	if (IsExpanded())
    +	{
    +		// move & resize the container window
    +		int yoffset = m_sz->GetSize().GetHeight() + GetBorder();
    +		m_pPane->SetSize(0, yoffset,
    +			oursz.x, oursz.y - yoffset);
    +
    +		// this is very important to make the pane window layout show correctly
    +		m_pPane->Layout();
    +	}
    +
    +	return true;
    +}
    +
    +void PrusaCollapsiblePaneMSW::Collapse(bool collapse)
    +{
    +	// optimization
    +	if (IsCollapsed() == collapse)
    +		return;
    +
    +	InvalidateBestSize();
    +
    +	// update our state
    +	m_pPane->Show(!collapse);
    +
    +	// update button bitmap
    +	UpdateBtnBmp();
    +
    +	OnStateChange(GetBestSize());
    +}
    +#endif //__WXMSW__
    +
    +// *****************************************************************************
    +// ----------------------------------------------------------------------------
    +// PrusaObjectDataViewModelNode
    +// ----------------------------------------------------------------------------
    +
    +void PrusaObjectDataViewModelNode::set_object_action_icon() {
    +	m_action_icon = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("add_object.png")), wxBITMAP_TYPE_PNG);
    +}
    +void  PrusaObjectDataViewModelNode::set_part_action_icon() {
    +	m_action_icon = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("cog.png")), wxBITMAP_TYPE_PNG);
    +}
    +
    +// *****************************************************************************
    +// ----------------------------------------------------------------------------
    +// PrusaObjectDataViewModel
    +// ----------------------------------------------------------------------------
    +
    +wxDataViewItem PrusaObjectDataViewModel::Add(wxString &name)
    +{
    +	auto root = new PrusaObjectDataViewModelNode(name);
    +	m_objects.push_back(root);
    +	// notify control
    +	wxDataViewItem child((void*)root);
    +	wxDataViewItem parent((void*)NULL);
    +	ItemAdded(parent, child);
    +	return child;
    +}
    +
    +wxDataViewItem PrusaObjectDataViewModel::Add(wxString &name, int instances_count, int scale)
    +{
    +	auto root = new PrusaObjectDataViewModelNode(name, instances_count, scale);
    +	m_objects.push_back(root);
    +	// notify control
    +	wxDataViewItem child((void*)root);
    +	wxDataViewItem parent((void*)NULL);
    +	ItemAdded(parent, child);
    +	return child;
    +}
    +
    +wxDataViewItem PrusaObjectDataViewModel::AddChild(	const wxDataViewItem &parent_item,
    +													const wxString &name,
    +													const wxIcon& icon,
    +                                                    const int extruder/* = 0*/,
    +                                                    const bool create_frst_child/* = true*/)
    +{
    +	PrusaObjectDataViewModelNode *root = (PrusaObjectDataViewModelNode*)parent_item.GetID();
    +	if (!root) return wxDataViewItem(0);
    +
    +    wxString extruder_str = extruder == 0 ? "default" : wxString::Format("%d", extruder);
    +
    +    if (root->GetChildren().Count() == 0 && create_frst_child)
    +	{
    +		auto icon_solid_mesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("object.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("package.png")), wxBITMAP_TYPE_PNG);
    +		auto node = new PrusaObjectDataViewModelNode(root, root->m_name, icon_solid_mesh, extruder_str, 0);
    +		root->Append(node);
    +		// notify control
    +		wxDataViewItem child((void*)node);
    +		ItemAdded(parent_item, child);
    +	}
    +
    +	auto volume_id = root->GetChildCount();
    +	auto node = new PrusaObjectDataViewModelNode(root, name, icon, extruder_str, volume_id);
    +	root->Append(node);
    +	// notify control
    +	wxDataViewItem child((void*)node);
    +	ItemAdded(parent_item, child);
    +	return child;
    +}
    +
    +wxDataViewItem PrusaObjectDataViewModel::Delete(const wxDataViewItem &item)
    +{
    +	auto ret_item = wxDataViewItem(0);
    +	PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID();
    +	if (!node)      // happens if item.IsOk()==false
    +		return ret_item;
    +
    +	auto node_parent = node->GetParent();
    +	wxDataViewItem parent(node_parent);
    +
    +	// first remove the node from the parent's array of children;
    +	// NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_
    +	//       thus removing the node from it doesn't result in freeing it
    +	if (node_parent){
    +		auto id = node_parent->GetChildren().Index(node);
    +		auto v_id = node->GetVolumeId();
    +		node_parent->GetChildren().Remove(node);
    +		if (id > 0){ 
    +			if(id == node_parent->GetChildCount()) id--;
    +			ret_item = wxDataViewItem(node_parent->GetChildren().Item(id));
    +		}
    +
    +		//update volume_id value for remaining child-nodes
    +		auto children = node_parent->GetChildren();
    +		for (size_t i = 0; i < node_parent->GetChildCount(); i++)
    +		{
    +			auto volume_id = children[i]->GetVolumeId();
    +			if (volume_id > v_id)
    +				children[i]->SetVolumeId(volume_id-1);
    +		}
    +	}
    +	else
    +	{
    +		auto it = find(m_objects.begin(), m_objects.end(), node);
    +		auto id = it - m_objects.begin();
    +		if (it != m_objects.end())
    +			m_objects.erase(it);
    +		if (id > 0){ 
    +			if(id == m_objects.size()) id--;
    +			ret_item = wxDataViewItem(m_objects[id]);
    +		}
    +	}
    +	// free the node
    +	delete node;
    +
    +	// set m_containet to FALSE if parent has no child
    +	if (node_parent && node_parent->GetChildCount() == 0){
    +#ifndef __WXGTK__
    +		node_parent->m_container = false;
    +#endif //__WXGTK__
    +		ret_item = parent;
    +	}
    +
    +	// notify control
    +	ItemDeleted(parent, item);
    +	return ret_item;
    +}
    +
    +void PrusaObjectDataViewModel::DeleteAll()
    +{
    +	while (!m_objects.empty())
    +	{
    +		auto object = m_objects.back();
    +// 		object->RemoveAllChildren();
    +		Delete(wxDataViewItem(object));	
    +	}
    +}
    +
    +void PrusaObjectDataViewModel::DeleteChildren(wxDataViewItem& parent)
    +{
    +    PrusaObjectDataViewModelNode *root = (PrusaObjectDataViewModelNode*)parent.GetID();
    +    if (!root)      // happens if item.IsOk()==false
    +        return;
    +
    +    // first remove the node from the parent's array of children;
    +    // NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_
    +    //       thus removing the node from it doesn't result in freeing it
    +    auto& children = root->GetChildren();
    +    for (int id = root->GetChildCount() - 1; id >= 0; --id)
    +    {
    +        auto node = children[id];
    +        auto item = wxDataViewItem(node);
    +        children.RemoveAt(id);
    +
    +        // free the node
    +        delete node;
    +
    +        // notify control
    +        ItemDeleted(parent, item);
    +    }
    +
    +    // set m_containet to FALSE if parent has no child
    +#ifndef __WXGTK__
    +        root->m_container = false;
    +#endif //__WXGTK__
    +}
    +
    +wxDataViewItem PrusaObjectDataViewModel::GetItemById(int obj_idx)
    +{
    +	if (obj_idx >= m_objects.size())
    +	{
    +		printf("Error! Out of objects range.\n");
    +		return wxDataViewItem(0);
    +	}
    +	return wxDataViewItem(m_objects[obj_idx]);
    +}
    +
    +
    +int PrusaObjectDataViewModel::GetIdByItem(wxDataViewItem& item)
    +{
    +	wxASSERT(item.IsOk());
    +
    +	PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID();
    +	auto it = find(m_objects.begin(), m_objects.end(), node);
    +	if (it == m_objects.end())
    +		return -1;
    +
    +	return it - m_objects.begin();
    +}
    +
    +int PrusaObjectDataViewModel::GetVolumeIdByItem(wxDataViewItem& item)
    +{
    +	wxASSERT(item.IsOk());
    +
    +	PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID();
    +	if (!node)      // happens if item.IsOk()==false
    +		return -1;
    +	return node->GetVolumeId();
    +}
    +
    +wxString PrusaObjectDataViewModel::GetName(const wxDataViewItem &item) const
    +{
    +	PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID();
    +	if (!node)      // happens if item.IsOk()==false
    +		return wxEmptyString;
    +
    +	return node->m_name;
    +}
    +
    +wxString PrusaObjectDataViewModel::GetCopy(const wxDataViewItem &item) const
    +{
    +	PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID();
    +	if (!node)      // happens if item.IsOk()==false
    +		return wxEmptyString;
    +
    +	return node->m_copy;
    +}
    +
    +wxString PrusaObjectDataViewModel::GetScale(const wxDataViewItem &item) const
    +{
    +	PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID();
    +	if (!node)      // happens if item.IsOk()==false
    +		return wxEmptyString;
    +
    +	return node->m_scale;
    +}
    +
    +wxIcon& PrusaObjectDataViewModel::GetIcon(const wxDataViewItem &item) const
    +{
    +    PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID();
    +    return node->m_icon;
    +}
    +
    +void PrusaObjectDataViewModel::GetValue(wxVariant &variant, const wxDataViewItem &item, unsigned int col) const
    +{
    +	wxASSERT(item.IsOk());
    +
    +	PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID();
    +	switch (col)
    +	{
    +	case 0:{
    +		const wxDataViewIconText data(node->m_name, node->m_icon);
    +		variant << data;
    +		break;}
    +	case 1:
    +		variant = node->m_copy;
    +		break;
    +	case 2:
    +		variant = node->m_scale;
    +		break;
    +	case 3:
    +		variant = node->m_extruder;
    +		break;
    +	case 4:
    +		variant << node->m_action_icon;
    +		break;
    +	default:
    +		;
    +	}
    +}
    +
    +bool PrusaObjectDataViewModel::SetValue(const wxVariant &variant, const wxDataViewItem &item, unsigned int col)
    +{
    +	wxASSERT(item.IsOk());
    +
    +	PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID();
    +	return node->SetValue(variant, col);
    +}
    +
    +bool PrusaObjectDataViewModel::SetValue(const wxVariant &variant, const int item_idx, unsigned int col)
    +{
    +	if (item_idx < 0 || item_idx >= m_objects.size())
    +		return false;
    +
    +	return m_objects[item_idx]->SetValue(variant, col);
    +}
    +
    +wxDataViewItem PrusaObjectDataViewModel::MoveChildUp(const wxDataViewItem &item)
    +{
    +	auto ret_item = wxDataViewItem(0);
    +	wxASSERT(item.IsOk());
    +	PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID();
    +	if (!node)      // happens if item.IsOk()==false
    +		return ret_item;
    +
    +	auto node_parent = node->GetParent();
    +	if (!node_parent) // If isn't part, but object
    +		return ret_item;
    +
    +	auto volume_id = node->GetVolumeId();
    +	if (0 < volume_id && volume_id < node_parent->GetChildCount()){
    +		node_parent->SwapChildrens(volume_id - 1, volume_id);
    +		ret_item = wxDataViewItem(node_parent->GetNthChild(volume_id - 1));
    +		ItemChanged(item);
    +		ItemChanged(ret_item);
    +	}
    +	else
    +		ret_item = wxDataViewItem(node_parent->GetNthChild(0));
    +	return ret_item;
    +}
    +
    +wxDataViewItem PrusaObjectDataViewModel::MoveChildDown(const wxDataViewItem &item)
    +{
    +	auto ret_item = wxDataViewItem(0);
    +	wxASSERT(item.IsOk());
    +	PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID();
    +	if (!node)      // happens if item.IsOk()==false
    +		return ret_item;
    +
    +	auto node_parent = node->GetParent();
    +	if (!node_parent) // If isn't part, but object
    +		return ret_item;
    +
    +	auto volume_id = node->GetVolumeId();
    +	if (0 <= volume_id && volume_id+1 < node_parent->GetChildCount()){
    +		node_parent->SwapChildrens(volume_id + 1, volume_id);
    +		ret_item = wxDataViewItem(node_parent->GetNthChild(volume_id + 1));
    +		ItemChanged(item);
    +		ItemChanged(ret_item);
    +	}
    +	else
    +		ret_item = wxDataViewItem(node_parent->GetNthChild(node_parent->GetChildCount()-1));
    +	return ret_item;
    +}
    +
    +wxDataViewItem PrusaObjectDataViewModel::ReorganizeChildren(int current_volume_id, int new_volume_id, const wxDataViewItem &parent)
    +{
    +    auto ret_item = wxDataViewItem(0);
    +    if (current_volume_id == new_volume_id)
    +        return ret_item;
    +    wxASSERT(parent.IsOk());
    +    PrusaObjectDataViewModelNode *node_parent = (PrusaObjectDataViewModelNode*)parent.GetID();
    +    if (!node_parent)      // happens if item.IsOk()==false
    +        return ret_item;
    +
    +    PrusaObjectDataViewModelNode *deleted_node = node_parent->GetNthChild(current_volume_id);
    +    node_parent->GetChildren().Remove(deleted_node);
    +    ItemDeleted(parent, wxDataViewItem(deleted_node));
    +    node_parent->Insert(deleted_node, new_volume_id);
    +    ItemAdded(parent, wxDataViewItem(deleted_node));
    +
    +    //update volume_id value for child-nodes
    +    auto children = node_parent->GetChildren();
    +    int id_frst = current_volume_id < new_volume_id ? current_volume_id : new_volume_id;
    +    int id_last = current_volume_id > new_volume_id ? current_volume_id : new_volume_id;
    +    for (int id = id_frst; id <= id_last; ++id)
    +        children[id]->SetVolumeId(id);
    +
    +    return wxDataViewItem(node_parent->GetNthChild(new_volume_id));
    +}
    +
    +// bool MyObjectTreeModel::IsEnabled(const wxDataViewItem &item, unsigned int col) const
    +// {
    +// 
    +// }
    +
    +wxDataViewItem PrusaObjectDataViewModel::GetParent(const wxDataViewItem &item) const
    +{
    +	// the invisible root node has no parent
    +	if (!item.IsOk())
    +		return wxDataViewItem(0);
    +
    +	PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID();
    +
    +	// objects nodes has no parent too
    +	if (find(m_objects.begin(), m_objects.end(),node) != m_objects.end())
    +		return wxDataViewItem(0);
    +
    +	return wxDataViewItem((void*)node->GetParent());
    +}
    +
    +bool PrusaObjectDataViewModel::IsContainer(const wxDataViewItem &item) const
    +{
    +	// the invisible root node can have children
    +	if (!item.IsOk())
    +		return true;
    +
    +	PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID();
    +	return node->IsContainer();
    +}
    +
    +unsigned int PrusaObjectDataViewModel::GetChildren(const wxDataViewItem &parent, wxDataViewItemArray &array) const
    +{
    +	PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)parent.GetID();
    +	if (!node)
    +	{
    +		for (auto object : m_objects)
    +			array.Add(wxDataViewItem((void*)object));
    +		return m_objects.size();
    +	}
    +
    +	if (node->GetChildCount() == 0)
    +	{
    +		return 0;
    +	}
    +
    +	unsigned int count = node->GetChildren().GetCount();
    +	for (unsigned int pos = 0; pos < count; pos++)
    +	{
    +		PrusaObjectDataViewModelNode *child = node->GetChildren().Item(pos);
    +		array.Add(wxDataViewItem((void*)child));
    +	}
    +
    +	return count;
    +}
    +
    +// ************************************** EXPERIMENTS ***************************************
    +PrusaDoubleSlider::PrusaDoubleSlider(   wxWindow *parent,
    +                                        wxWindowID id,
    +                                        int lowerValue, 
    +                                        int higherValue, 
    +                                        int minValue, 
    +                                        int maxValue,
    +                                        const wxPoint& pos,
    +                                        const wxSize& size,
    +                                        long style,
    +                                        const wxValidator& val,
    +                                        const wxString& name) : 
    +    wxControl(parent, id, pos, size, wxWANTS_CHARS | wxBORDER_NONE),
    +    m_lower_value(lowerValue), m_higher_value (higherValue), 
    +    m_min_value(minValue), m_max_value(maxValue),
    +    m_style(style == wxSL_HORIZONTAL || style == wxSL_VERTICAL ? style: wxSL_HORIZONTAL)
    +{
    +#ifndef __WXOSX__ // SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX
    +    SetDoubleBuffered(true);
    +#endif //__WXOSX__
    +
    +    m_bmp_thumb_higher = wxBitmap(style == wxSL_HORIZONTAL ? Slic3r::GUI::from_u8(Slic3r::var("right_half_circle.png")) :
    +                                                             Slic3r::GUI::from_u8(Slic3r::var("up_half_circle.png")), wxBITMAP_TYPE_PNG);
    +    m_bmp_thumb_lower  = wxBitmap(style == wxSL_HORIZONTAL ? Slic3r::GUI::from_u8(Slic3r::var("left_half_circle.png")) :
    +                                                             Slic3r::GUI::from_u8(Slic3r::var("down_half_circle.png")), wxBITMAP_TYPE_PNG);
    +    m_thumb_size = m_bmp_thumb_lower.GetSize();
    +
    +    m_bmp_add_tick_on  = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_add_on.png")), wxBITMAP_TYPE_PNG);
    +    m_bmp_add_tick_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_add_off.png")), wxBITMAP_TYPE_PNG);
    +    m_bmp_del_tick_on  = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_delete_on.png")), wxBITMAP_TYPE_PNG);
    +    m_bmp_del_tick_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_delete_off.png")), wxBITMAP_TYPE_PNG);
    +    m_tick_icon_dim = m_bmp_add_tick_on.GetSize().x;
    +
    +    m_bmp_one_layer_lock_on    = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_lock_on.png")), wxBITMAP_TYPE_PNG);
    +    m_bmp_one_layer_lock_off   = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_lock_off.png")), wxBITMAP_TYPE_PNG);
    +    m_bmp_one_layer_unlock_on  = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_unlock_on.png")), wxBITMAP_TYPE_PNG);
    +    m_bmp_one_layer_unlock_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_unlock_off.png")), wxBITMAP_TYPE_PNG);
    +    m_lock_icon_dim = m_bmp_one_layer_lock_on.GetSize().x;
    +
    +    m_selection = ssUndef;
    +
    +    // slider events
    +    Bind(wxEVT_PAINT,       &PrusaDoubleSlider::OnPaint,    this);
    +    Bind(wxEVT_LEFT_DOWN,   &PrusaDoubleSlider::OnLeftDown, this);
    +    Bind(wxEVT_MOTION,      &PrusaDoubleSlider::OnMotion,   this);
    +    Bind(wxEVT_LEFT_UP,     &PrusaDoubleSlider::OnLeftUp,   this);
    +    Bind(wxEVT_MOUSEWHEEL,  &PrusaDoubleSlider::OnWheel,    this);
    +    Bind(wxEVT_ENTER_WINDOW,&PrusaDoubleSlider::OnEnterWin, this);
    +    Bind(wxEVT_LEAVE_WINDOW,&PrusaDoubleSlider::OnLeaveWin, this);
    +    Bind(wxEVT_KEY_DOWN,    &PrusaDoubleSlider::OnKeyDown,  this);
    +    Bind(wxEVT_KEY_UP,      &PrusaDoubleSlider::OnKeyUp,    this);
    +    Bind(wxEVT_RIGHT_DOWN,  &PrusaDoubleSlider::OnRightDown,this);
    +    Bind(wxEVT_RIGHT_UP,    &PrusaDoubleSlider::OnRightUp,  this);
    +
    +    // control's view variables
    +    SLIDER_MARGIN     = 4 + (style == wxSL_HORIZONTAL ? m_bmp_thumb_higher.GetWidth() : m_bmp_thumb_higher.GetHeight());
    +
    +    DARK_ORANGE_PEN   = wxPen(wxColour(253, 84, 2));
    +    ORANGE_PEN        = wxPen(wxColour(253, 126, 66));
    +    LIGHT_ORANGE_PEN  = wxPen(wxColour(254, 177, 139));
    +
    +    DARK_GREY_PEN     = wxPen(wxColour(128, 128, 128));
    +    GREY_PEN          = wxPen(wxColour(164, 164, 164));
    +    LIGHT_GREY_PEN    = wxPen(wxColour(204, 204, 204));
    +
    +    line_pens = { &DARK_GREY_PEN, &GREY_PEN, &LIGHT_GREY_PEN };
    +    segm_pens = { &DARK_ORANGE_PEN, &ORANGE_PEN, &LIGHT_ORANGE_PEN };
    +}
    +
    +int PrusaDoubleSlider::GetActiveValue() const
    +{
    +    return m_selection == ssLower ?
    +    m_lower_value : m_selection == ssHigher ?
    +                m_higher_value : -1;
    +}
    +
    +wxSize PrusaDoubleSlider::DoGetBestSize() const
    +{
    +    const wxSize size = wxControl::DoGetBestSize();
    +    if (size.x > 1 && size.y > 1)
    +        return size;
    +    const int new_size = is_horizontal() ? 80 : 120;
    +    return wxSize(new_size, new_size);
    +}
    +
    +void PrusaDoubleSlider::SetLowerValue(const int lower_val)
    +{
    +    m_lower_value = lower_val;
    +    Refresh();
    +    Update();
    +}
    +
    +void PrusaDoubleSlider::SetHigherValue(const int higher_val)
    +{
    +    m_higher_value = higher_val;
    +    Refresh();
    +    Update();
    +}
    +
    +void PrusaDoubleSlider::SetMaxValue(const int max_value)
    +{
    +    m_max_value = max_value;
    +    Refresh();
    +    Update();
    +}
    +
    +void PrusaDoubleSlider::draw_scroll_line(wxDC& dc, const int lower_pos, const int higher_pos)
    +{
    +    int width;
    +    int height;
    +    get_size(&width, &height);
    +
    +    wxCoord line_beg_x = is_horizontal() ? SLIDER_MARGIN : width*0.5 - 1;
    +    wxCoord line_beg_y = is_horizontal() ? height*0.5 - 1 : SLIDER_MARGIN;
    +    wxCoord line_end_x = is_horizontal() ? width - SLIDER_MARGIN + 1 : width*0.5 - 1;
    +    wxCoord line_end_y = is_horizontal() ? height*0.5 - 1 : height - SLIDER_MARGIN + 1;
    +
    +    wxCoord segm_beg_x = is_horizontal() ? lower_pos : width*0.5 - 1;
    +    wxCoord segm_beg_y = is_horizontal() ? height*0.5 - 1 : lower_pos-1;
    +    wxCoord segm_end_x = is_horizontal() ? higher_pos : width*0.5 - 1;
    +    wxCoord segm_end_y = is_horizontal() ? height*0.5 - 1 : higher_pos-1;
    +
    +    for (int id = 0; id < line_pens.size(); id++)
    +    {
    +        dc.SetPen(*line_pens[id]);
    +        dc.DrawLine(line_beg_x, line_beg_y, line_end_x, line_end_y);
    +        dc.SetPen(*segm_pens[id]);
    +        dc.DrawLine(segm_beg_x, segm_beg_y, segm_end_x, segm_end_y);
    +        if (is_horizontal())
    +            line_beg_y = line_end_y = segm_beg_y = segm_end_y += 1;
    +        else
    +            line_beg_x = line_end_x = segm_beg_x = segm_end_x += 1;
    +    }
    +}
    +
    +double PrusaDoubleSlider::get_scroll_step()
    +{
    +    const wxSize sz = get_size();
    +    const int& slider_len = m_style == wxSL_HORIZONTAL ? sz.x : sz.y;
    +    return double(slider_len - SLIDER_MARGIN * 2) / (m_max_value - m_min_value);
    +}
    +
    +// get position on the slider line from entered value
    +wxCoord PrusaDoubleSlider::get_position_from_value(const int value)
    +{
    +    const double step = get_scroll_step();
    +    const int val = is_horizontal() ? value : m_max_value - value;
    +    return wxCoord(SLIDER_MARGIN + int(val*step + 0.5));
    +}
    +
    +wxSize PrusaDoubleSlider::get_size()
    +{
    +    int w, h;
    +    get_size(&w, &h);
    +    return wxSize(w, h);
    +}
    +
    +void PrusaDoubleSlider::get_size(int *w, int *h)
    +{
    +    GetSize(w, h);
    +    is_horizontal() ? *w -= m_lock_icon_dim : *h -= m_lock_icon_dim;
    +}
    +
    +void PrusaDoubleSlider::get_lower_and_higher_position(int& lower_pos, int& higher_pos)
    +{
    +    const double step = get_scroll_step();
    +    if (is_horizontal()) {
    +        lower_pos = SLIDER_MARGIN + int(m_lower_value*step + 0.5);
    +        higher_pos = SLIDER_MARGIN + int(m_higher_value*step + 0.5);
    +    }
    +    else {
    +        lower_pos = SLIDER_MARGIN + int((m_max_value - m_lower_value)*step + 0.5);
    +        higher_pos = SLIDER_MARGIN + int((m_max_value - m_higher_value)*step + 0.5);
    +    }
    +}
    +
    +void PrusaDoubleSlider::draw_focus_rect()
    +{
    +    if (!m_is_focused) 
    +        return;
    +    const wxSize sz = GetSize();
    +    wxPaintDC dc(this);
    +    const wxPen pen = wxPen(wxColour(128, 128, 10), 1, wxPENSTYLE_DOT);
    +    dc.SetPen(pen);
    +    dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT));
    +    dc.DrawRectangle(1, 1, sz.x - 2, sz.y - 2);
    +}
    +
    +void PrusaDoubleSlider::render()
    +{
    +    SetBackgroundColour(GetParent()->GetBackgroundColour());
    +    draw_focus_rect();
    +
    +    wxPaintDC dc(this);
    +    wxFont font = dc.GetFont();
    +    const wxFont smaller_font = font.Smaller();
    +    dc.SetFont(smaller_font);
    +
    +    const wxCoord lower_pos = get_position_from_value(m_lower_value);
    +    const wxCoord higher_pos = get_position_from_value(m_higher_value);
    +
    +    // draw line
    +    draw_scroll_line(dc, lower_pos, higher_pos);
    +
    +//     //lower slider:
    +//     draw_thumb(dc, lower_pos, ssLower);
    +//     //higher slider:
    +//     draw_thumb(dc, higher_pos, ssHigher);
    +
    +    // draw both sliders
    +    draw_thumbs(dc, lower_pos, higher_pos);
    +
    +    //draw color print ticks
    +    draw_ticks(dc);
    +
    +    //draw color print ticks
    +    draw_one_layer_icon(dc);
    +}
    +
    +void PrusaDoubleSlider::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end)
    +{
    +    const int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
    +    wxBitmap* icon = m_is_action_icon_focesed ? &m_bmp_add_tick_off : &m_bmp_add_tick_on;
    +    if (m_ticks.find(tick) != m_ticks.end())
    +        icon = m_is_action_icon_focesed ? &m_bmp_del_tick_off : &m_bmp_del_tick_on;
    +
    +    wxCoord x_draw, y_draw;
    +    is_horizontal() ? x_draw = pt_beg.x - 0.5*m_tick_icon_dim : y_draw = pt_beg.y - 0.5*m_tick_icon_dim;
    +    if (m_selection == ssLower)
    +        is_horizontal() ? y_draw = pt_end.y + 3 : x_draw = pt_beg.x - m_tick_icon_dim-2;
    +    else
    +        is_horizontal() ? y_draw = pt_beg.y - m_tick_icon_dim-2 : x_draw = pt_end.x + 3;
    +
    +    dc.DrawBitmap(*icon, x_draw, y_draw);
    +
    +    //update rect of the tick action icon
    +    m_rect_tick_action = wxRect(x_draw, y_draw, m_tick_icon_dim, m_tick_icon_dim);
    +}
    +
    +void PrusaDoubleSlider::draw_info_line_with_icon(wxDC& dc, const wxPoint& pos, const SelectedSlider selection)
    +{
    +    if (m_selection == selection) {
    +        //draw info line
    +        dc.SetPen(DARK_ORANGE_PEN);
    +        const wxPoint pt_beg = is_horizontal() ? wxPoint(pos.x, pos.y - m_thumb_size.y) : wxPoint(pos.x - m_thumb_size.x, pos.y - 1);
    +        const wxPoint pt_end = is_horizontal() ? wxPoint(pos.x, pos.y + m_thumb_size.y) : wxPoint(pos.x + m_thumb_size.x, pos.y - 1);
    +        dc.DrawLine(pt_beg, pt_end);
    +
    +        //draw action icon
    +        draw_action_icon(dc, pt_beg, pt_end);
    +    }
    +}
    +
    +wxString PrusaDoubleSlider::get_label(const SelectedSlider& selection) const
    +{
    +    const int value = selection == ssLower ? m_lower_value : m_higher_value;
    +
    +    if (m_label_koef == 1.0 && m_values.empty())
    +        return wxString::Format("%d", value);
    +
    +    const wxString str = m_values.empty() ? 
    +                         wxNumberFormatter::ToString(m_label_koef*value, 2, wxNumberFormatter::Style_None) :
    +                         wxNumberFormatter::ToString(m_values[value], 2, wxNumberFormatter::Style_None);
    +    return wxString::Format("%s\n(%d)", str, value);
    +}
    +
    +void PrusaDoubleSlider::draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const
    +{
    +    if (m_is_one_layer && selection != m_selection || !selection) 
    +        return;
    +    wxCoord text_width, text_height;
    +    const wxString label = get_label(selection);
    +    dc.GetMultiLineTextExtent(label, &text_width, &text_height);
    +    wxPoint text_pos;
    +    if (selection ==ssLower)
    +        text_pos = is_horizontal() ? wxPoint(pos.x + 1, pos.y + m_thumb_size.x) :
    +                           wxPoint(pos.x + m_thumb_size.x+1, pos.y - 0.5*text_height - 1);
    +    else
    +        text_pos = is_horizontal() ? wxPoint(pos.x - text_width - 1, pos.y - m_thumb_size.x - text_height) :
    +                    wxPoint(pos.x - text_width - 1 - m_thumb_size.x, pos.y - 0.5*text_height + 1);
    +    dc.DrawText(label, text_pos);
    +}
    +
    +void PrusaDoubleSlider::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection)
    +{
    +    wxCoord x_draw, y_draw;
    +    if (selection == ssLower) {
    +        if (is_horizontal()) {
    +            x_draw = pos.x - m_thumb_size.x;
    +            y_draw = pos.y - int(0.5*m_thumb_size.y);
    +        }
    +        else {
    +            x_draw = pos.x - int(0.5*m_thumb_size.x);
    +            y_draw = pos.y;
    +        }
    +    }
    +    else{
    +        if (is_horizontal()) {
    +            x_draw = pos.x;
    +            y_draw = pos.y - int(0.5*m_thumb_size.y);
    +        }
    +        else {
    +            x_draw = pos.x - int(0.5*m_thumb_size.x);
    +            y_draw = pos.y - m_thumb_size.y;
    +        }
    +    }
    +    dc.DrawBitmap(selection == ssLower ? m_bmp_thumb_lower : m_bmp_thumb_higher, x_draw, y_draw);
    +
    +    // Update thumb rect
    +    update_thumb_rect(x_draw, y_draw, selection);
    +}
    +
    +void PrusaDoubleSlider::draw_thumb(wxDC& dc, const wxCoord& pos_coord, const SelectedSlider& selection)
    +{
    +    //calculate thumb position on slider line
    +    int width, height;
    +    get_size(&width, &height);
    +    const wxPoint pos = is_horizontal() ? wxPoint(pos_coord, height*0.5) : wxPoint(0.5*width, pos_coord);
    +
    +    // Draw thumb
    +    draw_thumb_item(dc, pos, selection);
    +
    +    // Draw info_line
    +    draw_info_line_with_icon(dc, pos, selection);
    +
    +    // Draw thumb text
    +    draw_thumb_text(dc, pos, selection);
    +}
    +
    +void PrusaDoubleSlider::draw_thumbs(wxDC& dc, const wxCoord& lower_pos, const wxCoord& higher_pos)
    +{
    +    //calculate thumb position on slider line
    +    int width, height;
    +    get_size(&width, &height);
    +    const wxPoint pos_l = is_horizontal() ? wxPoint(lower_pos, height*0.5) : wxPoint(0.5*width, lower_pos);
    +    const wxPoint pos_h = is_horizontal() ? wxPoint(higher_pos, height*0.5) : wxPoint(0.5*width, higher_pos);
    +
    +    // Draw lower thumb
    +    draw_thumb_item(dc, pos_l, ssLower);
    +    // Draw lower info_line
    +    draw_info_line_with_icon(dc, pos_l, ssLower);
    +
    +    // Draw higher thumb
    +    draw_thumb_item(dc, pos_h, ssHigher);
    +    // Draw higher info_line
    +    draw_info_line_with_icon(dc, pos_h, ssHigher);
    +    // Draw higher thumb text
    +    draw_thumb_text(dc, pos_h, ssHigher);
    +
    +    // Draw lower thumb text
    +    draw_thumb_text(dc, pos_l, ssLower);
    +}
    +
    +void PrusaDoubleSlider::draw_ticks(wxDC& dc)
    +{
    +    dc.SetPen(DARK_GREY_PEN);
    +    int height, width;
    +    get_size(&width, &height);
    +    const wxCoord mid = is_horizontal() ? 0.5*height : 0.5*width;
    +    for (auto tick : m_ticks)
    +    {
    +        const wxCoord pos = get_position_from_value(tick);
    +
    +        is_horizontal() ?   dc.DrawLine(pos, mid-14, pos, mid-9) :
    +                            dc.DrawLine(mid - 14, pos - 1, mid - 9, pos - 1);
    +        is_horizontal() ?   dc.DrawLine(pos, mid+14, pos, mid+9) :
    +                            dc.DrawLine(mid + 14, pos - 1, mid + 9, pos - 1);
    +    }
    +}
    +
    +void PrusaDoubleSlider::draw_one_layer_icon(wxDC& dc)
    +{
    +    wxBitmap* icon = m_is_one_layer ?
    +                     m_is_one_layer_icon_focesed ? &m_bmp_one_layer_lock_off : &m_bmp_one_layer_lock_on :
    +                     m_is_one_layer_icon_focesed ? &m_bmp_one_layer_unlock_off : &m_bmp_one_layer_unlock_on;
    +
    +    int width, height;
    +    get_size(&width, &height);
    +
    +    wxCoord x_draw, y_draw;
    +    is_horizontal() ? x_draw = width-2 : x_draw = 0.5*width - 0.5*m_lock_icon_dim;
    +    is_horizontal() ? y_draw = 0.5*height - 0.5*m_lock_icon_dim : y_draw = height-2;
    +
    +    dc.DrawBitmap(*icon, x_draw, y_draw);
    +
    +    //update rect of the lock/unlock icon
    +    m_rect_one_layer_icon = wxRect(x_draw, y_draw, m_lock_icon_dim, m_lock_icon_dim);
    +}
    +
    +void PrusaDoubleSlider::update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection)
    +{
    +    const wxRect& rect = wxRect(begin_x, begin_y, m_thumb_size.x, m_thumb_size.y);
    +    if (selection == ssLower)
    +        m_rect_lower_thumb = rect;
    +    else
    +        m_rect_higher_thumb = rect;
    +}
    +
    +int PrusaDoubleSlider::get_value_from_position(const wxCoord x, const wxCoord y)
    +{
    +    const int height = get_size().y;
    +    const double step = get_scroll_step();
    +    
    +    if (is_horizontal()) 
    +        return int(double(x - SLIDER_MARGIN) / step + 0.5);
    +    else 
    +        return int(m_min_value + double(height - SLIDER_MARGIN - y) / step + 0.5);
    +}
    +
    +void PrusaDoubleSlider::detect_selected_slider(const wxPoint& pt, const bool is_mouse_wheel /*= false*/)
    +{
    +    if (is_mouse_wheel)
    +    {
    +        if (is_horizontal()) {
    +            m_selection = pt.x <= m_rect_lower_thumb.GetRight() ? ssLower :
    +                          pt.x >= m_rect_higher_thumb.GetLeft() ? ssHigher : ssUndef;
    +        }
    +        else {
    +            m_selection = pt.y >= m_rect_lower_thumb.GetTop() ? ssLower :
    +                          pt.y <= m_rect_higher_thumb.GetBottom() ? ssHigher : ssUndef;            
    +        }
    +        return;
    +    }
    +
    +    m_selection = is_point_in_rect(pt, m_rect_lower_thumb) ? ssLower :
    +                  is_point_in_rect(pt, m_rect_higher_thumb) ? ssHigher : ssUndef;
    +}
    +
    +bool PrusaDoubleSlider::is_point_in_rect(const wxPoint& pt, const wxRect& rect)
    +{
    +    if (rect.GetLeft() <= pt.x && pt.x <= rect.GetRight() && 
    +        rect.GetTop()  <= pt.y && pt.y <= rect.GetBottom())
    +        return true;
    +    return false;
    +}
    +
    +void PrusaDoubleSlider::OnLeftDown(wxMouseEvent& event)
    +{
    +    this->CaptureMouse();
    +    wxClientDC dc(this);
    +    wxPoint pos = event.GetLogicalPosition(dc);
    +    if (is_point_in_rect(pos, m_rect_tick_action)) {
    +        action_tick(taOnIcon);
    +        return;
    +    }
    +
    +    m_is_left_down = true;
    +    if (is_point_in_rect(pos, m_rect_one_layer_icon)){
    +        m_is_one_layer = !m_is_one_layer;
    +        m_selection == ssLower ? correct_lower_value() : correct_higher_value();
    +        if (!m_selection) m_selection = ssHigher;
    +    }
    +    else
    +        detect_selected_slider(pos);
    +
    +    Refresh();
    +    Update();
    +    event.Skip();
    +}
    +
    +void PrusaDoubleSlider::correct_lower_value()
    +{
    +    if (m_lower_value < m_min_value)
    +        m_lower_value = m_min_value;
    +    else if (m_lower_value > m_max_value)
    +        m_lower_value = m_max_value;
    +    
    +    if (m_lower_value >= m_higher_value && m_lower_value <= m_max_value || m_is_one_layer)
    +        m_higher_value = m_lower_value;
    +}
    +
    +void PrusaDoubleSlider::correct_higher_value()
    +{
    +    if (m_higher_value > m_max_value)
    +        m_higher_value = m_max_value;
    +    else if (m_higher_value < m_min_value)
    +        m_higher_value = m_min_value;
    +    
    +    if (m_higher_value <= m_lower_value && m_higher_value >= m_min_value || m_is_one_layer)
    +        m_lower_value = m_higher_value;
    +}
    +
    +void PrusaDoubleSlider::OnMotion(wxMouseEvent& event)
    +{
    +    const wxClientDC dc(this);
    +    const wxPoint pos = event.GetLogicalPosition(dc);
    +    m_is_one_layer_icon_focesed = is_point_in_rect(pos, m_rect_one_layer_icon);
    +    if (!m_is_left_down && !m_is_one_layer){
    +        m_is_action_icon_focesed = is_point_in_rect(pos, m_rect_tick_action);
    +    }
    +    else if (m_is_left_down || m_is_right_down){
    +        if (m_selection == ssLower) {
    +            m_lower_value = get_value_from_position(pos.x, pos.y);
    +            correct_lower_value();
    +        }
    +        else if (m_selection == ssHigher) {
    +            m_higher_value = get_value_from_position(pos.x, pos.y);
    +            correct_higher_value();
    +        }
    +    }
    +    Refresh();
    +    Update();
    +    event.Skip();
    +}
    +
    +void PrusaDoubleSlider::OnLeftUp(wxMouseEvent& event)
    +{
    +    this->ReleaseMouse();
    +    m_is_left_down = false;
    +    Refresh();
    +    Update();
    +    event.Skip();
    +
    +    wxCommandEvent e(wxEVT_SCROLL_CHANGED);
    +    e.SetEventObject(this);
    +    ProcessWindowEvent(e);
    +}
    +
    +void PrusaDoubleSlider::enter_window(wxMouseEvent& event, const bool enter)
    +{
    +    m_is_focused = enter;
    +    Refresh();
    +    Update();
    +    event.Skip();
    +}
    +
    +// "condition" have to be true for:
    +//    -  value increase (if wxSL_VERTICAL)
    +//    -  value decrease (if wxSL_HORIZONTAL) 
    +void PrusaDoubleSlider::move_current_thumb(const bool condition)
    +{
    +    m_is_one_layer = wxGetKeyState(WXK_CONTROL);
    +    int delta = condition ? -1 : 1;
    +    if (is_horizontal())
    +        delta *= -1;
    +
    +    if (m_selection == ssLower) {
    +        m_lower_value -= delta;
    +        correct_lower_value();
    +    }
    +    else if (m_selection == ssHigher) {
    +        m_higher_value -= delta;
    +        correct_higher_value();
    +    }
    +    Refresh();
    +    Update();
    +
    +    wxCommandEvent e(wxEVT_SCROLL_CHANGED);
    +    e.SetEventObject(this);
    +    ProcessWindowEvent(e);
    +}
    +
    +void PrusaDoubleSlider::action_tick(const TicksAction action)
    +{
    +    if (m_selection == ssUndef)
    +        return;
    +
    +    const int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
    +
    +    if (action == taOnIcon && !m_ticks.insert(tick).second)
    +        m_ticks.erase(tick);
    +    else {
    +        const auto it = m_ticks.find(tick);
    +        if (it == m_ticks.end() && action == taAdd)
    +            m_ticks.insert(tick);
    +        else if (it != m_ticks.end() && action == taDel)
    +            m_ticks.erase(tick);
    +        else
    +            return;
    +    }
    +
    +    Refresh();
    +    Update();
    +}
    +
    +void PrusaDoubleSlider::OnWheel(wxMouseEvent& event)
    +{
    +    wxClientDC dc(this);
    +    wxPoint pos = event.GetLogicalPosition(dc);
    +    detect_selected_slider(pos, true);
    +
    +    if (m_selection == ssUndef)
    +        return;
    +
    +    move_current_thumb(event.GetWheelRotation() > 0);
    +}
    +
    +void PrusaDoubleSlider::OnKeyDown(wxKeyEvent &event)
    +{
    +    const int key = event.GetKeyCode();
    +    if (key == '+' || key == WXK_NUMPAD_ADD)
    +        action_tick(taAdd);
    +    else if (key == '-' || key == 390 || key == WXK_DELETE || key == WXK_BACK)
    +        action_tick(taDel);
    +    else if (is_horizontal())
    +    {
    +        if (key == WXK_LEFT || key == WXK_RIGHT)
    +            move_current_thumb(key == WXK_LEFT); 
    +        else if (key == WXK_UP || key == WXK_DOWN){
    +            m_selection = key == WXK_UP ? ssHigher : ssLower;
    +            Refresh();
    +        }
    +    }
    +    else {
    +        if (key == WXK_LEFT || key == WXK_RIGHT) {
    +            m_selection = key == WXK_LEFT ? ssHigher : ssLower;
    +            Refresh();
    +        }
    +        else if (key == WXK_UP || key == WXK_DOWN)
    +            move_current_thumb(key == WXK_UP);
    +    }
    +}
    +
    +void PrusaDoubleSlider::OnKeyUp(wxKeyEvent &event)
    +{
    +    if (event.GetKeyCode() == WXK_CONTROL)
    +        m_is_one_layer = false;
    +    Refresh();
    +    Update();
    +    event.Skip();
    +}
    +
    +void PrusaDoubleSlider::OnRightDown(wxMouseEvent& event)
    +{
    +    this->CaptureMouse();
    +    const wxClientDC dc(this);
    +    detect_selected_slider(event.GetLogicalPosition(dc));
    +    if (!m_selection)
    +        return;
    +
    +    if (m_selection == ssLower)
    +        m_higher_value = m_lower_value;
    +    else
    +        m_lower_value = m_higher_value;
    +
    +    m_is_right_down = m_is_one_layer = true;
    +
    +    Refresh();
    +    Update();
    +    event.Skip();
    +}
    +
    +void PrusaDoubleSlider::OnRightUp(wxMouseEvent& event)
    +{
    +    this->ReleaseMouse();
    +    m_is_right_down = m_is_one_layer = false;
    +
    +    Refresh();
    +    Update();
    +    event.Skip();
    +}
    +
    +// *****************************************************************************
    diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp
    index 3667c7905b..064ce10389 100644
    --- a/xs/src/slic3r/GUI/wxExtensions.hpp
    +++ b/xs/src/slic3r/GUI/wxExtensions.hpp
    @@ -4,6 +4,14 @@
     #include 
     #include 
     #include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#include 
    +#include 
     
     class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup
     {
    @@ -65,4 +73,568 @@ public:
     	void				SetItemsCnt(int cnt) { m_cnt_open_items = cnt; }
     };
     
    +
    +
    +// ***  PrusaCollapsiblePane  *** 
    +// ----------------------------------------------------------------------------
    +class PrusaCollapsiblePane : public wxCollapsiblePane
    +{
    +public:
    +	PrusaCollapsiblePane() {}
    +	PrusaCollapsiblePane(wxWindow *parent,
    +		wxWindowID winid,
    +		const wxString& label,
    +		const wxPoint& pos = wxDefaultPosition,
    +		const wxSize& size = wxDefaultSize,
    +		long style = wxCP_DEFAULT_STYLE,
    +		const wxValidator& val = wxDefaultValidator,
    +		const wxString& name = wxCollapsiblePaneNameStr)
    +	{
    +		Create(parent, winid, label, pos, size, style, val, name);
    +	}
    +	~PrusaCollapsiblePane() {}
    +
    +	void OnStateChange(const wxSize& sz); //override/hide of OnStateChange from wxCollapsiblePane
    +	virtual bool Show(bool show = true) override {
    +		wxCollapsiblePane::Show(show);
    +		OnStateChange(GetBestSize());
    +		return true;
    +	}
    +};
    +
    +
    +// ***  PrusaCollapsiblePaneMSW  ***  used only #ifdef __WXMSW__
    +// ----------------------------------------------------------------------------
    +#ifdef __WXMSW__
    +class PrusaCollapsiblePaneMSW : public PrusaCollapsiblePane//wxCollapsiblePane
    +{
    +	wxButton*	m_pDisclosureTriangleButton = nullptr;
    +	wxBitmap	m_bmp_close;
    +	wxBitmap	m_bmp_open;
    +public:
    +	PrusaCollapsiblePaneMSW() {}
    +	PrusaCollapsiblePaneMSW(	wxWindow *parent,
    +							wxWindowID winid,
    +							const wxString& label,
    +							const wxPoint& pos = wxDefaultPosition,
    +							const wxSize& size = wxDefaultSize,
    +							long style = wxCP_DEFAULT_STYLE,
    +							const wxValidator& val = wxDefaultValidator,
    +							const wxString& name = wxCollapsiblePaneNameStr)
    +	{
    +		Create(parent, winid, label, pos, size, style, val, name);
    +	}
    +
    +	~PrusaCollapsiblePaneMSW() {}
    +
    +	bool Create(wxWindow *parent,
    +				wxWindowID id,
    +				const wxString& label,
    +				const wxPoint& pos,
    +				const wxSize& size,
    +				long style,
    +				const wxValidator& val,
    +				const wxString& name);
    +
    +	void UpdateBtnBmp();
    +	void SetLabel(const wxString &label) override;
    +	bool Layout() override;
    +	void Collapse(bool collapse) override;
    +};
    +#endif //__WXMSW__
    +
    +// *****************************************************************************
    +// ----------------------------------------------------------------------------
    +// PrusaObjectDataViewModelNode: a node inside PrusaObjectDataViewModel
    +// ----------------------------------------------------------------------------
    +
    +class PrusaObjectDataViewModelNode;
    +WX_DEFINE_ARRAY_PTR(PrusaObjectDataViewModelNode*, MyObjectTreeModelNodePtrArray);
    +
    +class PrusaObjectDataViewModelNode
    +{
    +	PrusaObjectDataViewModelNode*	m_parent;
    +	MyObjectTreeModelNodePtrArray   m_children;
    +    wxIcon                          m_empty_icon; 
    +public:
    +	PrusaObjectDataViewModelNode(const wxString &name, int instances_count=1, int scale=100) {
    +		m_parent	= NULL;
    +		m_name		= name;
    +		m_copy		= wxString::Format("%d", instances_count);
    +		m_scale		= wxString::Format("%d%%", scale);
    +		m_type		= "object";
    +		m_volume_id	= -1;
    +#ifdef __WXGTK__
    +        // it's necessary on GTK because of control have to know if this item will be container
    +        // in another case you couldn't to add subitem for this item
    +        // it will be produce "segmentation fault"
    +        m_container = true;
    +#endif  //__WXGTK__
    +		set_object_action_icon();
    +	}
    +
    +	PrusaObjectDataViewModelNode(	PrusaObjectDataViewModelNode* parent,
    +									const wxString& sub_obj_name, 
    +									const wxIcon& icon, 
    +                                    const wxString& extruder, 
    +									const int volume_id=-1) {
    +		m_parent	= parent;
    +		m_name		= sub_obj_name;
    +		m_copy		= wxEmptyString;
    +		m_scale		= wxEmptyString;
    +		m_icon		= icon;
    +		m_type		= "volume";
    +		m_volume_id = volume_id;
    +        m_extruder  = extruder;
    +		set_part_action_icon();
    +	}
    +
    +	~PrusaObjectDataViewModelNode()
    +	{
    +		// free all our children nodes
    +		size_t count = m_children.GetCount();
    +		for (size_t i = 0; i < count; i++)
    +		{
    +			PrusaObjectDataViewModelNode *child = m_children[i];
    +			delete child;
    +		}
    +	}
    +	
    +	wxString				m_name;
    +	wxIcon&					m_icon = m_empty_icon;
    +	wxString				m_copy;
    +	wxString				m_scale;
    +	std::string				m_type;
    +	int						m_volume_id;
    +	bool					m_container = false;
    +	wxString				m_extruder = "default";
    +	wxBitmap				m_action_icon;
    +
    +	bool IsContainer() const
    +	{
    +		return m_container;
    +	}
    +
    +	PrusaObjectDataViewModelNode* GetParent()
    +	{
    +		return m_parent;
    +	}
    +	MyObjectTreeModelNodePtrArray& GetChildren()
    +	{
    +		return m_children;
    +	}
    +	PrusaObjectDataViewModelNode* GetNthChild(unsigned int n)
    +	{
    +		return m_children.Item(n);
    +	}
    +	void Insert(PrusaObjectDataViewModelNode* child, unsigned int n)
    +	{
    +		m_children.Insert(child, n);
    +	}
    +	void Append(PrusaObjectDataViewModelNode* child)
    +	{
    +		if (!m_container)
    +			m_container = true;
    +		m_children.Add(child);
    +	}
    +	void RemoveAllChildren()
    +	{
    +		if (GetChildCount() == 0)
    +			return;
    +		for (size_t id = GetChildCount() - 1; id >= 0; --id)
    +		{
    +			if (m_children.Item(id)->GetChildCount() > 0)
    +				m_children[id]->RemoveAllChildren();
    +			auto node = m_children[id];
    +			m_children.RemoveAt(id);
    +			delete node;
    +		}
    +	}
    +
    +	size_t GetChildCount() const
    +	{
    +		return m_children.GetCount();
    +	}
    +
    +	bool SetValue(const wxVariant &variant, unsigned int col)
    +	{
    +		switch (col)
    +		{
    +		case 0:{
    +			wxDataViewIconText data;
    +			data << variant;
    +			m_icon = data.GetIcon();
    +			m_name = data.GetText();
    +			return true;}
    +		case 1:
    +			m_copy = variant.GetString();
    +			return true;
    +		case 2:
    +			m_scale = variant.GetString();
    +			return true;
    +		case 3:
    +			m_extruder = variant.GetString();
    +			return true;
    +		case 4:
    +			m_action_icon << variant;
    +			return true;
    +		default:
    +			printf("MyObjectTreeModel::SetValue: wrong column");
    +		}
    +		return false;
    +	}
    +	void SetIcon(const wxIcon &icon)
    +	{
    +		m_icon = icon;
    +	}
    +	
    +	void SetType(const std::string& type){
    +		m_type = type;
    +	}	
    +	const std::string& GetType(){
    +		return m_type;
    +	}
    +
    +	void SetVolumeId(const int& volume_id){
    +		m_volume_id = volume_id;
    +	}
    +	const int& GetVolumeId(){
    +		return m_volume_id;
    +	}
    +
    +	// use this function only for childrens
    +	void AssignAllVal(PrusaObjectDataViewModelNode& from_node)
    +	{
    +		// ! Don't overwrite other values because of equality of this values for all children --
    +		m_name = from_node.m_name;
    +		m_icon = from_node.m_icon;
    +		m_volume_id = from_node.m_volume_id;
    +		m_extruder = from_node.m_extruder;
    +	}
    +
    +	bool SwapChildrens(int frst_id, int scnd_id) {
    +		if (GetChildCount() < 2 || 
    +			frst_id < 0 || frst_id >= GetChildCount() || 
    +			scnd_id < 0 || scnd_id >= GetChildCount())
    +			return false;
    +
    +		PrusaObjectDataViewModelNode new_scnd = *GetNthChild(frst_id);
    +		PrusaObjectDataViewModelNode new_frst = *GetNthChild(scnd_id);
    +
    +		new_scnd.m_volume_id = m_children.Item(scnd_id)->m_volume_id;
    +		new_frst.m_volume_id = m_children.Item(frst_id)->m_volume_id;
    +
    +		m_children.Item(frst_id)->AssignAllVal(new_frst);
    +		m_children.Item(scnd_id)->AssignAllVal(new_scnd);
    +		return true;
    +	}
    +
    +	// Set action icons for node
    +	void set_object_action_icon();
    +	void set_part_action_icon();
    +};
    +
    +// ----------------------------------------------------------------------------
    +// PrusaObjectDataViewModel
    +// ----------------------------------------------------------------------------
    +
    +class PrusaObjectDataViewModel :public wxDataViewModel
    +{
    +	std::vector m_objects;
    +public:
    +	PrusaObjectDataViewModel(){}
    +	~PrusaObjectDataViewModel()
    +	{
    +		for (auto object : m_objects)
    +			delete object;		
    +	}
    +
    +	wxDataViewItem Add(wxString &name);
    +	wxDataViewItem Add(wxString &name, int instances_count, int scale);
    +	wxDataViewItem AddChild(const wxDataViewItem &parent_item, 
    +							const wxString &name, 
    +                            const wxIcon& icon,
    +                            const int = 0,
    +                            const bool create_frst_child = true);
    +	wxDataViewItem Delete(const wxDataViewItem &item);
    +	void DeleteAll();
    +    void DeleteChildren(wxDataViewItem& parent);
    +	wxDataViewItem GetItemById(int obj_idx);
    +	int GetIdByItem(wxDataViewItem& item);
    +	int GetVolumeIdByItem(wxDataViewItem& item);
    +	bool IsEmpty() { return m_objects.empty(); }
    +
    +	// helper method for wxLog
    +
    +	wxString GetName(const wxDataViewItem &item) const;
    +	wxString GetCopy(const wxDataViewItem &item) const;
    +	wxString GetScale(const wxDataViewItem &item) const;
    +	wxIcon&  GetIcon(const wxDataViewItem &item) const;
    +
    +	// helper methods to change the model
    +
    +	virtual unsigned int GetColumnCount() const override { return 3;}
    +	virtual wxString GetColumnType(unsigned int col) const override{ return wxT("string"); }
    +
    +	virtual void GetValue(wxVariant &variant,
    +		const wxDataViewItem &item, unsigned int col) const override;
    +	virtual bool SetValue(const wxVariant &variant,
    +		const wxDataViewItem &item, unsigned int col) override;
    +	bool SetValue(const wxVariant &variant, const int item_idx, unsigned int col);
    +
    +	wxDataViewItem MoveChildUp(const wxDataViewItem &item);
    +	wxDataViewItem MoveChildDown(const wxDataViewItem &item);
    +    // For parent move child from cur_volume_id place to new_volume_id 
    +    // Remaining items will moved up/down accordingly
    +    wxDataViewItem ReorganizeChildren(int cur_volume_id, 
    +                                      int new_volume_id,
    +                                      const wxDataViewItem &parent);
    +
    +// 	virtual bool IsEnabled(const wxDataViewItem &item,
    +// 		unsigned int col) const override;
    +
    +	virtual wxDataViewItem GetParent(const wxDataViewItem &item) const override;
    +	virtual bool IsContainer(const wxDataViewItem &item) const override;
    +	virtual unsigned int GetChildren(const wxDataViewItem &parent,
    +		wxDataViewItemArray &array) const override;
    +
    +	// Is the container just a header or an item with all columns
    +	// In our case it is an item with all columns 
    +	virtual bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override {	return true; }
    +};
    +
    +
    +
    +// ----------------------------------------------------------------------------
    +// MyCustomRenderer
    +// ----------------------------------------------------------------------------
    +
    +class MyCustomRenderer : public wxDataViewCustomRenderer
    +{
    +public:
    +	// This renderer can be either activatable or editable, for demonstration
    +	// purposes. In real programs, you should select whether the user should be
    +	// able to activate or edit the cell and it doesn't make sense to switch
    +	// between the two -- but this is just an example, so it doesn't stop us.
    +	explicit MyCustomRenderer(wxDataViewCellMode mode)
    +		: wxDataViewCustomRenderer("string", mode, wxALIGN_CENTER)
    +	{ }
    +
    +	virtual bool Render(wxRect rect, wxDC *dc, int state) override/*wxOVERRIDE*/
    +	{
    +		dc->SetBrush(*wxLIGHT_GREY_BRUSH);
    +		dc->SetPen(*wxTRANSPARENT_PEN);
    +
    +		rect.Deflate(2);
    +		dc->DrawRoundedRectangle(rect, 5);
    +
    +		RenderText(m_value,
    +			0, // no offset
    +			wxRect(dc->GetTextExtent(m_value)).CentreIn(rect),
    +			dc,
    +			state);
    +		return true;
    +	}
    +
    +		virtual bool ActivateCell(const wxRect& WXUNUSED(cell),
    +		wxDataViewModel *WXUNUSED(model),
    +		const wxDataViewItem &WXUNUSED(item),
    +		unsigned int WXUNUSED(col),
    +		const wxMouseEvent *mouseEvent) override/*wxOVERRIDE*/
    +	{
    +		wxString position;
    +		if (mouseEvent)
    +			position = wxString::Format("via mouse at %d, %d", mouseEvent->m_x, mouseEvent->m_y);
    +		else
    +			position = "from keyboard";
    +//		wxLogMessage("MyCustomRenderer ActivateCell() %s", position);
    +		return false;
    +	}
    +
    +		virtual wxSize GetSize() const override/*wxOVERRIDE*/
    +	{
    +		return wxSize(60, 20);
    +	}
    +
    +		virtual bool SetValue(const wxVariant &value) override/*wxOVERRIDE*/
    +	{
    +		m_value = value.GetString();
    +		return true;
    +	}
    +
    +		virtual bool GetValue(wxVariant &WXUNUSED(value)) const override/*wxOVERRIDE*/{ return true; }
    +
    +		virtual bool HasEditorCtrl() const override/*wxOVERRIDE*/{ return true; }
    +
    +		virtual wxWindow*
    +		CreateEditorCtrl(wxWindow* parent,
    +		wxRect labelRect,
    +		const wxVariant& value) override/*wxOVERRIDE*/
    +	{
    +		wxTextCtrl* text = new wxTextCtrl(parent, wxID_ANY, value,
    +		labelRect.GetPosition(),
    +		labelRect.GetSize(),
    +		wxTE_PROCESS_ENTER);
    +		text->SetInsertionPointEnd();
    +
    +		return text;
    +	}
    +
    +		virtual bool
    +			GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) override/*wxOVERRIDE*/
    +	{
    +		wxTextCtrl* text = wxDynamicCast(ctrl, wxTextCtrl);
    +		if (!text)
    +			return false;
    +
    +		value = text->GetValue();
    +
    +		return true;
    +	}
    +
    +private:
    +	wxString m_value;
    +};
    +// ******************************* EXPERIMENTS **********************************************
    +enum SelectedSlider {
    +    ssUndef,
    +    ssLower,
    +    ssHigher
    +};
    +enum TicksAction{
    +    taOnIcon,
    +    taAdd,
    +    taDel
    +};
    +class PrusaDoubleSlider : public wxControl
    +{
    +public:
    +    PrusaDoubleSlider(
    +        wxWindow *parent,
    +        wxWindowID id,
    +        int lowerValue, 
    +        int higherValue, 
    +        int minValue, 
    +        int maxValue,
    +        const wxPoint& pos = wxDefaultPosition,
    +        const wxSize& size = wxDefaultSize,
    +        long style = wxSL_HORIZONTAL,
    +        const wxValidator& val = wxDefaultValidator,
    +        const wxString& name = wxEmptyString);
    +
    +    int GetLowerValue() const {
    +        return m_lower_value;
    +    }
    +    int GetHigherValue() const {
    +        return m_higher_value;
    +    }
    +    int GetActiveValue() const;
    +    wxSize DoGetBestSize() const override;
    +    void SetLowerValue(const int lower_val);
    +    void SetHigherValue(const int higher_val);
    +    void SetMaxValue(const int max_value);
    +    void SetKoefForLabels(const double koef) {
    +        m_label_koef = koef;
    +    }
    +    void SetSliderValues(const std::vector& values) {
    +        m_values = values;
    +    }
    +
    +    void OnPaint(wxPaintEvent& ){ render();}
    +    void OnLeftDown(wxMouseEvent& event);
    +    void OnMotion(wxMouseEvent& event);
    +    void OnLeftUp(wxMouseEvent& event);
    +    void OnEnterWin(wxMouseEvent& event){ enter_window(event, true); }
    +    void OnLeaveWin(wxMouseEvent& event){ enter_window(event, false); }
    +    void OnWheel(wxMouseEvent& event);
    +    void OnKeyDown(wxKeyEvent &event);
    +    void OnKeyUp(wxKeyEvent &event);
    +    void OnRightDown(wxMouseEvent& event);
    +    void OnRightUp(wxMouseEvent& event);
    +
    +protected:
    + 
    +    void    render();
    +    void    draw_focus_rect();
    +    void    draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end);
    +    void    draw_scroll_line(wxDC& dc, const int lower_pos, const int higher_pos);
    +    void    draw_thumb(wxDC& dc, const wxCoord& pos_coord, const SelectedSlider& selection);
    +    void    draw_thumbs(wxDC& dc, const wxCoord& lower_pos, const wxCoord& higher_pos);
    +    void    draw_ticks(wxDC& dc);
    +    void    draw_one_layer_icon(wxDC& dc);
    +    void    draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection);
    +    void    draw_info_line_with_icon(wxDC& dc, const wxPoint& pos, SelectedSlider selection);
    +    void    draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const;
    +
    +    void    update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection);
    +    void    detect_selected_slider(const wxPoint& pt, const bool is_mouse_wheel = false);
    +    void    correct_lower_value();
    +    void    correct_higher_value();
    +    void    move_current_thumb(const bool condition);
    +    void    action_tick(const TicksAction action);
    +    void    enter_window(wxMouseEvent& event, const bool enter);
    +
    +    bool    is_point_in_rect(const wxPoint& pt, const wxRect& rect);
    +    bool    is_horizontal() const { return m_style == wxSL_HORIZONTAL; }
    +
    +    double      get_scroll_step();
    +    wxString    get_label(const SelectedSlider& selection) const;
    +    void        get_lower_and_higher_position(int& lower_pos, int& higher_pos);
    +    int         get_value_from_position(const wxCoord x, const wxCoord y);
    +    wxCoord     get_position_from_value(const int value);
    +    wxSize      get_size();
    +    void        get_size(int *w, int *h);
    +
    +private:
    +    int         m_min_value;
    +    int         m_max_value;
    +    int         m_lower_value;
    +    int         m_higher_value;
    +    wxBitmap    m_bmp_thumb_higher;
    +    wxBitmap    m_bmp_thumb_lower;
    +    wxBitmap    m_bmp_add_tick_on;
    +    wxBitmap    m_bmp_add_tick_off;
    +    wxBitmap    m_bmp_del_tick_on;
    +    wxBitmap    m_bmp_del_tick_off;
    +    wxBitmap    m_bmp_one_layer_lock_on;
    +    wxBitmap    m_bmp_one_layer_lock_off;
    +    wxBitmap    m_bmp_one_layer_unlock_on;
    +    wxBitmap    m_bmp_one_layer_unlock_off;
    +    SelectedSlider  m_selection;
    +    bool        m_is_left_down = false;
    +    bool        m_is_right_down = false;
    +    bool        m_is_one_layer = false;
    +    bool        m_is_focused = false;
    +    bool        m_is_action_icon_focesed = false;
    +    bool        m_is_one_layer_icon_focesed = false;
    +
    +    wxRect      m_rect_lower_thumb;
    +    wxRect      m_rect_higher_thumb;
    +    wxRect      m_rect_tick_action;
    +    wxRect      m_rect_one_layer_icon;
    +    wxSize      m_thumb_size;
    +    int         m_tick_icon_dim;
    +    int         m_lock_icon_dim = 16;
    +    long        m_style;
    +    float       m_label_koef = 1.0;
    +
    +// control's view variables
    +    wxCoord SLIDER_MARGIN; // margin around slider
    +
    +    wxPen   DARK_ORANGE_PEN;
    +    wxPen   ORANGE_PEN;
    +    wxPen   LIGHT_ORANGE_PEN;
    +
    +    wxPen   DARK_GREY_PEN;
    +    wxPen   GREY_PEN;
    +    wxPen   LIGHT_GREY_PEN;
    +
    +    std::vector line_pens;
    +    std::vector segm_pens;
    +    std::set       m_ticks;
    +    std::vector m_values;
    +};
    +// ******************************************************************************************
    +
    +
     #endif // slic3r_GUI_wxExtensions_hpp_
    diff --git a/xs/src/slic3r/Utils/Duet.cpp b/xs/src/slic3r/Utils/Duet.cpp
    new file mode 100644
    index 0000000000..865d2b4187
    --- /dev/null
    +++ b/xs/src/slic3r/Utils/Duet.cpp
    @@ -0,0 +1,279 @@
    +#include "Duet.hpp"
    +#include "PrintHostSendDialog.hpp"
    +
    +#include 
    +#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"))
    +{}
    +
    +Duet::~Duet() {}
    +
    +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.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);
    +			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) {
    +					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::has_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
    +{
    +	enum { BUFFER_SIZE = 32 };
    +
    +	auto t = std::time(nullptr);
    +	auto tm = *std::localtime(&t);
    +
    +	char buffer[BUFFER_SIZE];
    +	std::strftime(buffer, BUFFER_SIZE, "time=%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)
    +{
    +	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..bc210d7a45
    --- /dev/null
    +++ b/xs/src/slic3r/Utils/Duet.hpp
    @@ -0,0 +1,47 @@
    +#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);
    +	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 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;
    +	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..a92e399a08 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 set_post_body(const fs::path &path);
     
     	std::string curl_error(CURLcode curlcode);
     	std::string body_size_error();
    @@ -187,6 +190,13 @@ void Http::priv::form_add_file(const char *name, const fs::path &path, const cha
     	}
     }
     
    +void Http::priv::set_post_body(const fs::path &path)
    +{
    +	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)
     {
     	return (boost::format("%1% (%2%)")
    @@ -229,6 +239,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 +353,12 @@ Http& Http::form_add_file(const std::string &name, const fs::path &path, const s
     	return *this;
     }
     
    +Http& Http::set_post_body(const fs::path &path)
    +{
    +	if (p) { p->set_post_body(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..f1302b0ed9 100644
    --- a/xs/src/slic3r/Utils/Http.hpp
    +++ b/xs/src/slic3r/Utils/Http.hpp
    @@ -73,6 +73,11 @@ 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);
     
    +	// 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);
     	// 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..db86d76974 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,49 +13,14 @@ 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"))
     {}
     
    +OctoPrint::~OctoPrint() {}
    +
     bool OctoPrint::test(wxString &msg) const
     {
     	// Since the request is performed synchronously here,
    @@ -91,6 +46,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 +64,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 +127,16 @@ bool OctoPrint::send_gcode(const std::string &filename) const
     	return res;
     }
     
    +bool OctoPrint::has_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..f6c4d58c87 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,19 @@ namespace Slic3r {
     class DynamicPrintConfig;
     class Http;
     
    -class OctoPrint
    +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 has_auto_discovery() const;
    +	bool can_test() const;
     private:
     	std::string host;
     	std::string apikey;
    diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp
    index 6e23ab4219..2e423dc5ee 100644
    --- a/xs/src/slic3r/Utils/PresetUpdater.cpp
    +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp
    @@ -447,6 +447,7 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
     
     			for (const auto &name : bundle.obsolete_presets.prints)    { obsolete_remover("print", name); }
     			for (const auto &name : bundle.obsolete_presets.filaments) { obsolete_remover("filament", name); }
    +			for (const auto &name : bundle.obsolete_presets.filaments) { obsolete_remover("sla_material", name); }
     			for (const auto &name : bundle.obsolete_presets.printers)  { obsolete_remover("printer", name); }
     		}
     	}
    diff --git a/xs/src/slic3r/Utils/PrintHost.cpp b/xs/src/slic3r/Utils/PrintHost.cpp
    new file mode 100644
    index 0000000000..dd72bae40f
    --- /dev/null
    +++ b/xs/src/slic3r/Utils/PrintHost.cpp
    @@ -0,0 +1,23 @@
    +#include "OctoPrint.hpp"
    +#include "Duet.hpp"
    +
    +#include "libslic3r/PrintConfig.hpp"
    +
    +namespace Slic3r {
    +
    +
    +PrintHost::~PrintHost() {}
    +
    +PrintHost* PrintHost::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 nullptr;
    +}
    +
    +
    +}
    diff --git a/xs/src/slic3r/Utils/PrintHost.hpp b/xs/src/slic3r/Utils/PrintHost.hpp
    new file mode 100644
    index 0000000000..bc828ea469
    --- /dev/null
    +++ b/xs/src/slic3r/Utils/PrintHost.hpp
    @@ -0,0 +1,35 @@
    +#ifndef slic3r_PrintHost_hpp_
    +#define slic3r_PrintHost_hpp_
    +
    +#include 
    +#include 
    +#include 
    +
    +
    +namespace Slic3r {
    +
    +
    +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 has_auto_discovery() const = 0;
    +	virtual bool can_test() const = 0;
    +
    +	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..c5d441f876
    --- /dev/null
    +++ b/xs/src/slic3r/Utils/PrintHostSendDialog.cpp
    @@ -0,0 +1,52 @@
    +#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());
    +
    +	box_print->Enable(can_start_print);
    +
    +	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..dc4a8d6f7c
    --- /dev/null
    +++ b/xs/src/slic3r/Utils/PrintHostSendDialog.hpp
    @@ -0,0 +1,38 @@
    +#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/src/xsinit.h b/xs/src/xsinit.h
    index e32717532b..c9e3636027 100644
    --- a/xs/src/xsinit.h
    +++ b/xs/src/xsinit.h
    @@ -197,9 +197,9 @@ void from_SV_check(SV* poly_sv, Polyline* THIS);
     SV* to_SV_pureperl(const Point* THIS);
     void from_SV(SV* point_sv, Point* point);
     void from_SV_check(SV* point_sv, Point* point);
    -SV* to_SV_pureperl(const Pointf* point);
    -bool from_SV(SV* point_sv, Pointf* point);
    -bool from_SV_check(SV* point_sv, Pointf* point);
    +SV* to_SV_pureperl(const Vec2d* point);
    +bool from_SV(SV* point_sv, Vec2d* point);
    +bool from_SV_check(SV* point_sv, Vec2d* point);
     void from_SV_check(SV* surface_sv, Surface* THIS);
     SV* to_SV(TriangleMesh* THIS);
     
    diff --git a/xs/xsp/BoundingBox.xsp b/xs/xsp/BoundingBox.xsp
    index d65f8a5237..a34cad0bc8 100644
    --- a/xs/xsp/BoundingBox.xsp
    +++ b/xs/xsp/BoundingBox.xsp
    @@ -56,15 +56,15 @@ new_from_points(CLASS, points)
         Clone clone()
             %code{% RETVAL = THIS; %};
         void merge(BoundingBoxf* bb) %code{% THIS->merge(*bb); %};
    -    void merge_point(Pointf* point) %code{% THIS->merge(*point); %};
    +    void merge_point(Vec2d* point) %code{% THIS->merge(*point); %};
         void scale(double factor);
         void translate(double x, double y);
    -    Clone size();
    -    Clone center();
    +    Clone size();
    +    Clone center();
         double radius();
         bool empty() %code{% RETVAL = empty(*THIS); %};
    -    Clone min_point() %code{% RETVAL = THIS->min; %};
    -    Clone max_point() %code{% RETVAL = THIS->max; %};
    +    Clone min_point() %code{% RETVAL = THIS->min; %};
    +    Clone max_point() %code{% RETVAL = THIS->max; %};
         double x_min() %code{% RETVAL = THIS->min(0); %};
         double x_max() %code{% RETVAL = THIS->max(0); %};
         double y_min() %code{% RETVAL = THIS->min(1); %};
    @@ -96,17 +96,17 @@ new_from_points(CLASS, points)
         Clone clone()
             %code{% RETVAL = THIS; %};
         void merge(BoundingBoxf3* bb) %code{% THIS->merge(*bb); %};
    -    void merge_point(Pointf3* point) %code{% THIS->merge(*point); %};
    +    void merge_point(Vec3d* point) %code{% THIS->merge(*point); %};
         void scale(double factor);
         void translate(double x, double y, double z);
         void offset(double delta);
    -    bool contains_point(Pointf3* point) %code{% RETVAL = THIS->contains(*point); %};
    -    Clone size();
    -    Clone center();
    +    bool contains_point(Vec3d* point) %code{% RETVAL = THIS->contains(*point); %};
    +    Clone size();
    +    Clone center();
         double radius();
         bool empty() %code{% RETVAL = empty(*THIS); %};
    -    Clone min_point() %code{% RETVAL = THIS->min; %};
    -    Clone max_point() %code{% RETVAL = THIS->max; %};
    +    Clone min_point() %code{% RETVAL = THIS->min; %};
    +    Clone max_point() %code{% RETVAL = THIS->max; %};
         double x_min() %code{% RETVAL = THIS->min(0); %};
         double x_max() %code{% RETVAL = THIS->max(0); %};
         double y_min() %code{% RETVAL = THIS->min(1); %};
    diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp
    index c1856ccf7b..9e04edd4c6 100644
    --- a/xs/xsp/GCode.xsp
    +++ b/xs/xsp/GCode.xsp
    @@ -35,9 +35,9 @@
                 }
             %};
     
    -    Ref origin()
    +    Ref origin()
             %code{% RETVAL = &(THIS->origin()); %};
    -    void set_origin(Pointf* pointf)
    +    void set_origin(Vec2d* pointf)
             %code{% THIS->set_origin(*pointf); %};
         Ref last_pos()
             %code{% RETVAL = &(THIS->last_pos()); %};
    diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp
    index c6eead1ad5..b9183af1de 100644
    --- a/xs/xsp/GUI.xsp
    +++ b/xs/xsp/GUI.xsp
    @@ -87,6 +87,80 @@ void add_frequently_changed_parameters(SV *ui_parent, SV *ui_sizer, SV *ui_p_siz
                                                                (wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"),
                                                                (wxFlexGridSizer*)wxPli_sv_2_object(aTHX_ ui_p_sizer, "Wx::FlexGridSizer")); %};
     
    +void add_expert_mode_part(  SV *ui_parent, SV *ui_sizer, 
    +                            Model *model,
    +                            int event_object_selection_changed,
    +                            int event_object_settings_changed,
    +                            int event_remove_object, 
    +                            int event_update_scene)
    +    %code%{ Slic3r::GUI::add_expert_mode_part(  (wxWindow*)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"),
    +                                                (wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"), 
    +                                                *model,  
    +                                                event_object_selection_changed,
    +                                                event_object_settings_changed,
    +                                                event_remove_object, 
    +                                                event_update_scene); %};
    +
    +void set_objects_from_perl( SV *ui_parent, 
    +                            SV *frequently_changed_parameters_sizer,
    +                            SV *expert_mode_part_sizer,
    +                            SV *scrolled_window_sizer,
    +                            SV *btn_export_gcode,
    +                            SV *btn_export_stl,
    +                            SV *btn_reslice,
    +                            SV *btn_print,
    +                            SV *btn_send_gcode,
    +                            SV *manifold_warning_icon)
    +    %code%{ Slic3r::GUI::set_objects_from_perl(
    +                            (wxWindow *)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"),
    +                            (wxBoxSizer *)wxPli_sv_2_object(aTHX_ frequently_changed_parameters_sizer, "Wx::BoxSizer"),
    +                            (wxBoxSizer *)wxPli_sv_2_object(aTHX_ expert_mode_part_sizer, "Wx::BoxSizer"),
    +                            (wxBoxSizer *)wxPli_sv_2_object(aTHX_ scrolled_window_sizer, "Wx::BoxSizer"),
    +                            (wxButton *)wxPli_sv_2_object(aTHX_ btn_export_gcode, "Wx::Button"),
    +                            (wxButton *)wxPli_sv_2_object(aTHX_ btn_export_stl, "Wx::Button"),
    +                            (wxButton *)wxPli_sv_2_object(aTHX_ btn_reslice, "Wx::Button"),
    +                            (wxButton *)wxPli_sv_2_object(aTHX_ btn_print, "Wx::Button"),
    +                            (wxButton *)wxPli_sv_2_object(aTHX_ btn_send_gcode, "Wx::Button"),
    +                            (wxStaticBitmap *)wxPli_sv_2_object(aTHX_ manifold_warning_icon, "Wx::StaticBitmap")); %};
    +
    +void set_show_print_info(bool show)
    +    %code%{ Slic3r::GUI::set_show_print_info(show); %};
    +
    +void set_show_manifold_warning_icon(bool show)
    +    %code%{ Slic3r::GUI::set_show_manifold_warning_icon(show); %};
    +
    +void update_mode()
    +    %code%{ Slic3r::GUI::update_mode(); %};
    +
    +void add_object_to_list(const char *name, SV *object_model)
    +    %code%{ Slic3r::GUI::add_object_to_list(
    +                    name, 
    +                    (ModelObject *)wxPli_sv_2_object(aTHX_ object_model, "Slic3r::Model::Object") ); %};
    +
    +void delete_object_from_list()
    +    %code%{ Slic3r::GUI::delete_object_from_list(); %};
    +
    +void delete_all_objects_from_list()
    +    %code%{ Slic3r::GUI::delete_all_objects_from_list(); %};
    +
    +void set_object_count(int idx, int count)
    +    %code%{ Slic3r::GUI::set_object_count(idx, count); %};
    +
    +void set_object_scale(int idx, int scale)
    +    %code%{ Slic3r::GUI::set_object_scale(idx, scale); %};
    +
    +void unselect_objects()
    +    %code%{ Slic3r::GUI::unselect_objects(); %};
    +
    +void select_current_object(int idx)
    +    %code%{ Slic3r::GUI::select_current_object(idx); %};
    +
    +void remove_obj()
    +    %code%{ Slic3r::GUI::remove(); %};
    +
    +void update_rotation_value(double angle, const char *axis)
    +    %code%{ Slic3r::GUI::update_rotation_value(angle, axis); %};
    +
     std::string fold_utf8_to_ascii(const char *src)
         %code%{ RETVAL = Slic3r::fold_utf8_to_ascii(src); %};
     
    diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp
    index 6349a7c766..c15fdc196f 100644
    --- a/xs/xsp/GUI_3DScene.xsp
    +++ b/xs/xsp/GUI_3DScene.xsp
    @@ -55,13 +55,12 @@
         int                 object_idx() const;
         int                 volume_idx() const;
         int                 instance_idx() const;
    -    Clone      origin() const
    +    Clone        origin() const
             %code%{ RETVAL = THIS->get_origin(); %};
         void                translate(double x, double y, double z)
    -        %code%{ THIS->set_origin(THIS->get_origin() + Pointf3(x, y, z)); %};
    +        %code%{ THIS->set_origin(THIS->get_origin() + Vec3d(x, y, z)); %};
         Clone bounding_box() const
             %code%{ RETVAL = THIS->bounding_box; %};
    -    Clone transformed_bounding_box() const;
     
         bool                empty() const;
         bool                indexed() const;
    @@ -419,6 +418,13 @@ enable_shader(canvas, enable)
         CODE:
             _3DScene::enable_shader((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable);
     
    +void
    +enable_toolbar(canvas, enable)
    +        SV   *canvas;
    +        bool enable;
    +    CODE:
    +        _3DScene::enable_toolbar((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable);
    +
     void
     enable_force_zoom_to_bed(canvas, enable)
             SV   *canvas;
    @@ -440,6 +446,23 @@ allow_multisample(canvas, allow)
         CODE:
             _3DScene::allow_multisample((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), allow);
     
    +void
    +enable_toolbar_item(canvas, item, enable)
    +        SV          *canvas;
    +        std::string item;
    +        bool        enable;
    +    CODE:
    +        _3DScene::enable_toolbar_item((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), item, enable);
    +
    +bool
    +is_toolbar_item_pressed(canvas, item)
    +        SV          *canvas;
    +        std::string item;
    +    CODE:
    +        RETVAL = _3DScene::is_toolbar_item_pressed((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), item);
    +    OUTPUT:
    +        RETVAL
    +
     void
     zoom_to_bed(canvas)
             SV *canvas;
    @@ -626,7 +649,77 @@ register_on_update_geometry_info_callback(canvas, callback)
             SV *callback;
         CODE:
             _3DScene::register_on_update_geometry_info_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
    -             
    +
    +void
    +register_action_add_callback(canvas, callback)
    +        SV *canvas;
    +        SV *callback;
    +    CODE:
    +        _3DScene::register_action_add_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
    +
    +void
    +register_action_delete_callback(canvas, callback)
    +        SV *canvas;
    +        SV *callback;
    +    CODE:
    +        _3DScene::register_action_delete_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
    +
    +void
    +register_action_deleteall_callback(canvas, callback)
    +        SV *canvas;
    +        SV *callback;
    +    CODE:
    +        _3DScene::register_action_deleteall_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
    +
    +void
    +register_action_arrange_callback(canvas, callback)
    +        SV *canvas;
    +        SV *callback;
    +    CODE:
    +        _3DScene::register_action_arrange_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
    +
    +void
    +register_action_more_callback(canvas, callback)
    +        SV *canvas;
    +        SV *callback;
    +    CODE:
    +        _3DScene::register_action_more_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
    +
    +void
    +register_action_fewer_callback(canvas, callback)
    +        SV *canvas;
    +        SV *callback;
    +    CODE:
    +        _3DScene::register_action_fewer_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
    +
    +void
    +register_action_split_callback(canvas, callback)
    +        SV *canvas;
    +        SV *callback;
    +    CODE:
    +        _3DScene::register_action_split_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
    +
    +void
    +register_action_cut_callback(canvas, callback)
    +        SV *canvas;
    +        SV *callback;
    +    CODE:
    +        _3DScene::register_action_cut_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
    +
    +void
    +register_action_settings_callback(canvas, callback)
    +        SV *canvas;
    +        SV *callback;
    +    CODE:
    +        _3DScene::register_action_settings_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
    +
    +void
    +register_action_layersediting_callback(canvas, callback)
    +        SV *canvas;
    +        SV *callback;
    +    CODE:
    +        _3DScene::register_action_layersediting_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
    +
     void
     reset_legend_texture()
         CODE:
    diff --git a/xs/xsp/GUI_Preset.xsp b/xs/xsp/GUI_Preset.xsp
    index 2c63db10c4..99d23a1421 100644
    --- a/xs/xsp/GUI_Preset.xsp
    +++ b/xs/xsp/GUI_Preset.xsp
    @@ -32,7 +32,6 @@
     %name{Slic3r::GUI::PresetCollection} class PresetCollection {
     
         Ref             preset(size_t idx) %code%{ RETVAL = &THIS->preset(idx); %};
    -    Ref             default_preset() %code%{ RETVAL = &THIS->default_preset(); %};
         size_t                  size() const;
         size_t                  num_visible() const;
         std::string             name() const;
    @@ -133,6 +132,7 @@ PresetCollection::arrayref()
     
         Ref       print()    %code%{ RETVAL = &THIS->prints;   %};
         Ref       filament() %code%{ RETVAL = &THIS->filaments; %};
    +    Ref       sla_material() %code%{ RETVAL = &THIS->sla_materials; %};
         Ref       printer()  %code%{ RETVAL = &THIS->printers;  %};
         Ref     project_config() %code%{ RETVAL = &THIS->project_config; %};
     
    diff --git a/xs/xsp/Geometry.xsp b/xs/xsp/Geometry.xsp
    index b23bbeffa9..b7e92ba695 100644
    --- a/xs/xsp/Geometry.xsp
    +++ b/xs/xsp/Geometry.xsp
    @@ -8,7 +8,7 @@
     
     %package{Slic3r::Geometry};
     
    -Pointfs arrange(size_t total_parts, Pointf* part, coordf_t dist, BoundingBoxf* bb = NULL)
    +Pointfs arrange(size_t total_parts, Vec2d* part, coordf_t dist, BoundingBoxf* bb = NULL)
         %code{% 
             Pointfs points;
             if (! Slic3r::Geometry::arrange(total_parts, *part, dist, bb, points))
    diff --git a/xs/xsp/Line.xsp b/xs/xsp/Line.xsp
    index f7437de01c..777dc41fa7 100644
    --- a/xs/xsp/Line.xsp
    +++ b/xs/xsp/Line.xsp
    @@ -78,15 +78,15 @@ Line::coincides_with(line_sv)
     
     
     %name{Slic3r::Linef3} class Linef3 {
    -    Linef3(Pointf3* a, Pointf3* b)
    +    Linef3(Vec3d* a, Vec3d* b)
             %code{% RETVAL = new Linef3(*a, *b); %};
         ~Linef3();
         Clone clone()
             %code{% RETVAL = THIS; %};
    -    Ref a()
    +    Ref a()
             %code{% RETVAL = &THIS->a; %};
    -    Ref b()
    +    Ref b()
             %code{% RETVAL = &THIS->b; %};
    -    Clone intersect_plane(double z);
    +    Clone intersect_plane(double z);
         void scale(double factor);
     };
    diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp
    index 25c26c3804..ac265a3b36 100644
    --- a/xs/xsp/Model.xsp
    +++ b/xs/xsp/Model.xsp
    @@ -81,7 +81,7 @@
     
         bool add_default_instances();
         Clone bounding_box();
    -    void center_instances_around_point(Pointf* point)
    +    void center_instances_around_point(Vec2d* point)
             %code%{ THIS->center_instances_around_point(*point); %};
         void translate(double x, double y, double z);
         Clone mesh();
    @@ -289,9 +289,9 @@ ModelMaterial::attributes()
         void set_layer_height_profile(std::vector profile)
             %code%{ THIS->layer_height_profile = profile; THIS->layer_height_profile_valid = true; %};
     
    -    Ref origin_translation()
    +    Ref origin_translation()
             %code%{ RETVAL = &THIS->origin_translation; %};
    -    void set_origin_translation(Pointf3* point)
    +    void set_origin_translation(Vec3d* point)
             %code%{ THIS->origin_translation = *point; %};
         
         bool needed_repair() const;
    @@ -299,7 +299,7 @@ ModelMaterial::attributes()
         int facets_count();
         void center_around_origin();
         void translate(double x, double y, double z);
    -    void scale_xyz(Pointf3* versor)
    +    void scale_xyz(Vec3d* versor)
             %code{% THIS->scale(*versor); %};
         void rotate(float angle, Axis axis);
         void mirror(Axis axis);
    @@ -357,14 +357,14 @@ ModelMaterial::attributes()
             %code%{ RETVAL = THIS->rotation; %};
         double scaling_factor()
             %code%{ RETVAL = THIS->scaling_factor; %};
    -    Ref offset()
    +    Ref offset()
             %code%{ RETVAL = &THIS->offset; %};
     
         void set_rotation(double val)
             %code%{ THIS->rotation = val; THIS->get_object()->invalidate_bounding_box(); %};
         void set_scaling_factor(double val)
             %code%{ THIS->scaling_factor = val; THIS->get_object()->invalidate_bounding_box(); %};
    -    void set_offset(Pointf *offset)
    +    void set_offset(Vec2d *offset)
             %code%{ THIS->offset = *offset; %};
         
         void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const;
    diff --git a/xs/xsp/Point.xsp b/xs/xsp/Point.xsp
    index 2a98f9c396..beefc62494 100644
    --- a/xs/xsp/Point.xsp
    +++ b/xs/xsp/Point.xsp
    @@ -77,24 +77,10 @@ Point::coincides_with(point_sv)
     
     };
     
    -%name{Slic3r::Point3} class Point3 {
    -    Point3(int _x = 0, int _y = 0, int _z = 0);
    -    ~Point3();
    -    Clone clone()
    -        %code{% RETVAL = THIS; %};
    -    int x()
    -        %code{% RETVAL = (*THIS)(0); %};
    -    int y()
    -        %code{% RETVAL = (*THIS)(1); %};
    -    int z()
    -        %code{% RETVAL = (*THIS)(2); %};
    -    std::string serialize() %code{% char buf[2048]; sprintf(buf, "%ld,%ld,%ld", (*THIS)(0), (*THIS)(1), (*THIS)(2)); RETVAL = buf; %};
    -};
    -
    -%name{Slic3r::Pointf} class Pointf {
    -    Pointf(double _x = 0, double _y = 0);
    -    ~Pointf();
    -    Clone clone()
    +%name{Slic3r::Pointf} class Vec2d {
    +    Vec2d(double _x = 0, double _y = 0);
    +    ~Vec2d();
    +    Clone clone()
             %code{% RETVAL = THIS; %};
         SV* arrayref()
             %code{% RETVAL = to_SV_pureperl(THIS); %};
    @@ -109,22 +95,22 @@ Point::coincides_with(point_sv)
         void set_y(double val)
             %code{% (*THIS)(1) = val; %};
         void translate(double x, double y)
    -        %code{% *THIS += Pointf(x, y); %};
    +        %code{% *THIS += Vec2d(x, y); %};
         void scale(double factor)
             %code{% *THIS *= factor; %};
    -    void rotate(double angle, Pointf* center)
    -        %code{% THIS->rotate(angle, *center); %};
    -    Pointf* negative()
    -        %code{% RETVAL = new Pointf(- *THIS); %};
    -    Pointf* vector_to(Pointf* point)
    -        %code{% RETVAL = new Pointf(*point - *THIS); %};
    +    void rotate(double angle, Vec2d* center)
    +        %code{% *THIS = Eigen::Translation2d(*center) * Eigen::Rotation2Dd(angle) * Eigen::Translation2d(- *center) * Eigen::Vector2d((*THIS)(0), (*THIS)(1)); %};
    +    Vec2d* negative()
    +        %code{% RETVAL = new Vec2d(- *THIS); %};
    +    Vec2d* vector_to(Vec2d* point)
    +        %code{% RETVAL = new Vec2d(*point - *THIS); %};
         std::string serialize() %code{% char buf[2048]; sprintf(buf, "%lf,%lf", (*THIS)(0), (*THIS)(1)); RETVAL = buf; %};
     };
     
    -%name{Slic3r::Pointf3} class Pointf3 {
    -    Pointf3(double _x = 0, double _y = 0, double _z = 0);
    -    ~Pointf3();
    -    Clone clone()
    +%name{Slic3r::Pointf3} class Vec3d {
    +    Vec3d(double _x = 0, double _y = 0, double _z = 0);
    +    ~Vec3d();
    +    Clone clone()
             %code{% RETVAL = THIS; %};
         double x()
             %code{% RETVAL = (*THIS)(0); %};
    @@ -139,14 +125,14 @@ Point::coincides_with(point_sv)
         void set_z(double val)
             %code{% (*THIS)(2) = val; %};
         void translate(double x, double y, double z)
    -        %code{% *THIS += Pointf3(x, y, z); %};
    +        %code{% *THIS += Vec3d(x, y, z); %};
         void scale(double factor)
             %code{% *THIS *= factor; %};
    -    double distance_to(Pointf3* point)
    +    double distance_to(Vec3d* point)
             %code{% RETVAL = (*point - *THIS).norm(); %};
    -    Pointf3* negative()
    -        %code{% RETVAL = new Pointf3(- *THIS); %};
    -    Pointf3* vector_to(Pointf3* point)
    -        %code{% RETVAL = new Pointf3(*point - *THIS); %};
    +    Vec3d* negative()
    +        %code{% RETVAL = new Vec3d(- *THIS); %};
    +    Vec3d* vector_to(Vec3d* point)
    +        %code{% RETVAL = new Vec3d(*point - *THIS); %};
         std::string serialize() %code{% char buf[2048]; sprintf(buf, "%lf,%lf,%lf", (*THIS)(0), (*THIS)(1), (*THIS)(2)); RETVAL = buf; %};
     };
    diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp
    index 361e8a2bcb..96004d1a50 100644
    --- a/xs/xsp/Print.xsp
    +++ b/xs/xsp/Print.xsp
    @@ -63,8 +63,6 @@ _constant()
             %code%{ RETVAL = THIS->layer_height_ranges; %};
         std::vector layer_height_profile()
             %code%{ RETVAL = THIS->layer_height_profile; %};
    -    Ref size()
    -        %code%{ RETVAL = &THIS->size; %};
         Clone bounding_box();
         
         Points _shifted_copies()
    @@ -72,7 +70,7 @@ _constant()
         void set_shifted_copies(Points value)
             %code%{ THIS->_shifted_copies = value; %};
     
    -    bool add_copy(Pointf* point)
    +    bool add_copy(Vec2d* point)
             %code%{ RETVAL = THIS->add_copy(*point); %};
         bool delete_last_copy();
         bool delete_all_copies();
    diff --git a/xs/xsp/TriangleMesh.xsp b/xs/xsp/TriangleMesh.xsp
    index 1691066a50..2bc20da3a0 100644
    --- a/xs/xsp/TriangleMesh.xsp
    +++ b/xs/xsp/TriangleMesh.xsp
    @@ -16,7 +16,7 @@
         void repair();
         void WriteOBJFile(char* output_file);
         void scale(float factor);
    -    void scale_xyz(Pointf3* versor)
    +    void scale_xyz(Vec3d* versor)
             %code{% THIS->scale(*versor); %};
         void translate(float x, float y, float z);
         void rotate_x(float angle);
    @@ -33,7 +33,7 @@
         ExPolygons horizontal_projection();
         Clone convex_hull();
         Clone bounding_box();
    -    Clone center()
    +    Clone center()
             %code{% RETVAL = THIS->bounding_box().center(); %};
         int facets_count();
         void reset_repair_stats();
    @@ -60,14 +60,14 @@ TriangleMesh::ReadFromPerl(vertices, facets)
             for (int i = 0; i < stl.stats.number_of_facets; i++) {
                 AV* facet_av = (AV*)SvRV(*av_fetch(facets_av, i, 0));
                 stl_facet facet;
    -            facet.normal.x = 0;
    -            facet.normal.y = 0;
    -            facet.normal.z = 0;
    +            facet.normal(0) = 0;
    +            facet.normal(1) = 0;
    +            facet.normal(2) = 0;
                 for (unsigned int v = 0; v <= 2; v++) {
                     AV* vertex_av = (AV*)SvRV(*av_fetch(vertices_av, SvIV(*av_fetch(facet_av, v, 0)), 0));
    -                facet.vertex[v].x = SvNV(*av_fetch(vertex_av, 0, 0));
    -                facet.vertex[v].y = SvNV(*av_fetch(vertex_av, 1, 0));
    -                facet.vertex[v].z = SvNV(*av_fetch(vertex_av, 2, 0));
    +                facet.vertex[v](0) = SvNV(*av_fetch(vertex_av, 0, 0));
    +                facet.vertex[v](1) = SvNV(*av_fetch(vertex_av, 1, 0));
    +                facet.vertex[v](2) = SvNV(*av_fetch(vertex_av, 2, 0));
                 }
                 facet.extra[0] = 0;
                 facet.extra[1] = 0;
    @@ -110,9 +110,9 @@ TriangleMesh::vertices()
                 AV* vertex = newAV();
                 av_store(vertices, i, newRV_noinc((SV*)vertex));
                 av_extend(vertex, 2);
    -            av_store(vertex, 0, newSVnv(THIS->stl.v_shared[i].x));
    -            av_store(vertex, 1, newSVnv(THIS->stl.v_shared[i].y));
    -            av_store(vertex, 2, newSVnv(THIS->stl.v_shared[i].z));
    +            av_store(vertex, 0, newSVnv(THIS->stl.v_shared[i](0)));
    +            av_store(vertex, 1, newSVnv(THIS->stl.v_shared[i](1)));
    +            av_store(vertex, 2, newSVnv(THIS->stl.v_shared[i](2)));
             }
             
             RETVAL = newRV_noinc((SV*)vertices);
    @@ -155,9 +155,9 @@ TriangleMesh::normals()
                 AV* facet = newAV();
                 av_store(normals, i, newRV_noinc((SV*)facet));
                 av_extend(facet, 2);
    -            av_store(facet, 0, newSVnv(THIS->stl.facet_start[i].normal.x));
    -            av_store(facet, 1, newSVnv(THIS->stl.facet_start[i].normal.y));
    -            av_store(facet, 2, newSVnv(THIS->stl.facet_start[i].normal.z));
    +            av_store(facet, 0, newSVnv(THIS->stl.facet_start[i].normal(0)));
    +            av_store(facet, 1, newSVnv(THIS->stl.facet_start[i].normal(1)));
    +            av_store(facet, 2, newSVnv(THIS->stl.facet_start[i].normal(2)));
             }
             
             RETVAL = newRV_noinc((SV*)normals);
    @@ -169,9 +169,9 @@ TriangleMesh::size()
         CODE:
             AV* size = newAV();
             av_extend(size, 2);
    -        av_store(size, 0, newSVnv(THIS->stl.stats.size.x));
    -        av_store(size, 1, newSVnv(THIS->stl.stats.size.y));
    -        av_store(size, 2, newSVnv(THIS->stl.stats.size.z));
    +        av_store(size, 0, newSVnv(THIS->stl.stats.size(0)));
    +        av_store(size, 1, newSVnv(THIS->stl.stats.size(1)));
    +        av_store(size, 2, newSVnv(THIS->stl.stats.size(2)));
             RETVAL = newRV_noinc((SV*)size);
         OUTPUT:
             RETVAL
    @@ -216,12 +216,12 @@ TriangleMesh::cut(z, upper, lower)
     std::vector
     TriangleMesh::bb3()
         CODE:
    -        RETVAL.push_back(THIS->stl.stats.min.x);
    -        RETVAL.push_back(THIS->stl.stats.min.y);
    -        RETVAL.push_back(THIS->stl.stats.max.x);
    -        RETVAL.push_back(THIS->stl.stats.max.y);
    -        RETVAL.push_back(THIS->stl.stats.min.z);
    -        RETVAL.push_back(THIS->stl.stats.max.z);
    +        RETVAL.push_back(THIS->stl.stats.min(0));
    +        RETVAL.push_back(THIS->stl.stats.min(1));
    +        RETVAL.push_back(THIS->stl.stats.max(0));
    +        RETVAL.push_back(THIS->stl.stats.max(1));
    +        RETVAL.push_back(THIS->stl.stats.min(2));
    +        RETVAL.push_back(THIS->stl.stats.max(2));
         OUTPUT:
             RETVAL
     
    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..59c09c4317
    --- /dev/null
    +++ b/xs/xsp/Utils_PrintHost.xsp
    @@ -0,0 +1,12 @@
    +%module{Slic3r::XS};
    +
    +%{
    +#include 
    +#include "slic3r/Utils/PrintHost.hpp"
    +%}
    +
    +%name{Slic3r::PrintHost} class PrintHost {
    +	bool send_gcode(std::string filename) const;
    +
    +	static PrintHost* get_print_host(DynamicPrintConfig *config);
    +};
    diff --git a/xs/xsp/my.map b/xs/xsp/my.map
    index 4a14f483fc..d857340863 100644
    --- a/xs/xsp/my.map
    +++ b/xs/xsp/my.map
    @@ -66,13 +66,13 @@ Point3*                    O_OBJECT_SLIC3R
     Ref                O_OBJECT_SLIC3R_T
     Clone              O_OBJECT_SLIC3R_T
     
    -Pointf*                    O_OBJECT_SLIC3R
    -Ref                O_OBJECT_SLIC3R_T
    -Clone              O_OBJECT_SLIC3R_T
    +Vec2d*                     O_OBJECT_SLIC3R
    +Ref                 O_OBJECT_SLIC3R_T
    +Clone               O_OBJECT_SLIC3R_T
     
    -Pointf3*                   O_OBJECT_SLIC3R
    -Ref               O_OBJECT_SLIC3R_T
    -Clone             O_OBJECT_SLIC3R_T
    +Vec3d*                     O_OBJECT_SLIC3R
    +Ref                 O_OBJECT_SLIC3R_T
    +Clone               O_OBJECT_SLIC3R_T
     
     Line*                      O_OBJECT_SLIC3R
     Ref                  O_OBJECT_SLIC3R_T
    @@ -239,9 +239,7 @@ 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
    +PrintHost*                  O_OBJECT_SLIC3R
     
     Axis                  T_UV
     ExtrusionLoopRole     T_UV
    diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt
    index b576b1373e..b3d73b9b33 100644
    --- a/xs/xsp/typemap.xspt
    +++ b/xs/xsp/typemap.xspt
    @@ -22,12 +22,12 @@
     %typemap{Point3*};
     %typemap{Ref}{simple};
     %typemap{Clone}{simple};
    -%typemap{Pointf*};
    -%typemap{Ref}{simple};
    -%typemap{Clone}{simple};
    -%typemap{Pointf3*};
    -%typemap{Ref}{simple};
    -%typemap{Clone}{simple};
    +%typemap{Vec2d*};
    +%typemap{Ref}{simple};
    +%typemap{Clone}{simple};
    +%typemap{Vec3d*};
    +%typemap{Ref}{simple};
    +%typemap{Clone}{simple};
     %typemap{BoundingBox*};
     %typemap{Ref}{simple};
     %typemap{Clone}{simple};
    @@ -270,3 +270,4 @@
     };
     %typemap{AppController*};
     %typemap{PrintController*};
    +%typemap{PrintHost*};