diff --git a/Alpha_wrap_3/include/CGAL/Alpha_wrap_3/internal/Alpha_wrap_3.h b/Alpha_wrap_3/include/CGAL/Alpha_wrap_3/internal/Alpha_wrap_3.h index ba170ba02a9..e88e00fafe0 100644 --- a/Alpha_wrap_3/include/CGAL/Alpha_wrap_3/internal/Alpha_wrap_3.h +++ b/Alpha_wrap_3/include/CGAL/Alpha_wrap_3/internal/Alpha_wrap_3.h @@ -99,6 +99,32 @@ public: }; }; +struct Wrapping_default_visitor +{ + Wrapping_default_visitor() { } + + template + void on_alpha_wrapping_begin(const AlphaWrapper&) { } + + template + void on_flood_fill_begin(const AlphaWrapper&) { } + + template + void before_facet_treatment(const AlphaWrapper&, const Gate&) { } + + template + void before_Steiner_point_insertion(const Wrapper&, const Point&) { } + + template + void after_Steiner_point_insertion(const Wrapper&, VertexHandle) { } + + template + void on_flood_fill_end(const AlphaWrapper&) { } + + template + void on_alpha_wrapping_end(const AlphaWrapper&) { }; +}; + template class Alpha_wrap_3 { @@ -172,6 +198,9 @@ public: public: const Geom_traits& geom_traits() const { return m_dt.geom_traits(); } + Dt& triangulation() { return m_dt; } + const Dt& triangulation() const { return m_dt; } + const Alpha_PQ& queue() const { return m_queue; } double default_alpha() const { @@ -216,6 +245,15 @@ public: OVPM ovpm = choose_parameter(get_parameter(np, internal_np::vertex_point), get_property_map(vertex_point, output_mesh)); + typedef typename internal_np::Lookup_named_param_def < + internal_np::visitor_t, + NamedParameters, + Wrapping_default_visitor // default + >::reference Visitor; + + Wrapping_default_visitor default_visitor; + Visitor visitor = choose_parameter(get_parameter_reference(np, internal_np::visitor), default_visitor); + std::vector no_seeds; using Seeds = typename internal_np::Lookup_named_param_def< internal_np::seed_points_t, NamedParameters, std::vector >::reference; @@ -228,6 +266,8 @@ public: t.start(); #endif + visitor.on_alpha_wrapping_begin(*this); + if(!initialize(alpha, offset, seeds)) return; @@ -237,7 +277,7 @@ public: CGAL::parameters::vertex_point_map(ovpm).stream_precision(17)); #endif - alpha_flood_fill(); + alpha_flood_fill(visitor); #ifdef CGAL_AW3_TIMER t.stop(); @@ -315,6 +355,8 @@ public: dump_triangulation_faces("final_dt3.off", false /*only_boundary_faces*/); #endif #endif + + visitor.on_alpha_wrapping_end(*this); } // Convenience overloads @@ -607,11 +649,12 @@ private: return true; } +public: // Manifoldness is tolerated while debugging and extracting at intermediate states // Not the preferred way because it uses 3*nv storage template void extract_possibly_non_manifold_surface(OutputMesh& output_mesh, - OVPM ovpm) + OVPM ovpm) const { namespace PMP = Polygon_mesh_processing; @@ -696,7 +739,7 @@ private: template void extract_manifold_surface(OutputMesh& output_mesh, - OVPM ovpm) + OVPM ovpm) const { namespace PMP = Polygon_mesh_processing; @@ -748,7 +791,12 @@ private: if(faces.empty()) return; - CGAL_assertion(PMP::is_polygon_soup_a_polygon_mesh(faces)); + if(!PMP::is_polygon_soup_a_polygon_mesh(faces)) + { + CGAL_warning_msg(false, "Could NOT extract mesh..."); + return; + } + PMP::polygon_soup_to_polygon_mesh(points, faces, output_mesh, CGAL::parameters::default_values(), CGAL::parameters::vertex_point_map(ovpm)); @@ -763,7 +811,7 @@ private: template void extract_surface(OutputMesh& output_mesh, OVPM ovpm, - const bool tolerate_non_manifoldness = false) + const bool tolerate_non_manifoldness = false) const { if(tolerate_non_manifoldness) extract_possibly_non_manifold_surface(output_mesh, ovpm); @@ -990,12 +1038,15 @@ private: return initialize_with_cavities(seeds); } - void alpha_flood_fill() + template + void alpha_flood_fill(Visitor& visitor) { #ifdef CGAL_AW3_DEBUG std::cout << "> Flood fill..." << std::endl; #endif + visitor.on_flood_fill_begin(*this); + // Explore all finite cells that are reachable from one of the initial outside cells. while(!m_queue.empty()) { @@ -1022,6 +1073,8 @@ private: std::cout << "Priority: " << gate.priority() << std::endl; #endif + visitor.before_facet_treatment(*this, gate); + m_queue.pop(); #ifdef CGAL_AW3_DEBUG_DUMP_EVERY_STEP @@ -1082,10 +1135,14 @@ private: m_queue.erase(Gate(mf)); } + visitor.before_Steiner_point_insertion(*this, steiner_point); + // Actual insertion of the Steiner point Vertex_handle vh = m_dt.insert(steiner_point, lt, conflict_cell, li, lj); vh->info() = DEFAULT; + visitor.after_Steiner_point_insertion(*this, vh); + std::vector new_cells; new_cells.reserve(32); m_dt.incident_cells(vh, std::back_inserter(new_cells)); @@ -1133,6 +1190,8 @@ private: } } // while(!queue.empty()) + visitor.on_flood_fill_end(*this); + // Check that no useful facet has been ignored CGAL_postcondition_code(for(auto fit=m_dt.finite_facets_begin(), fend=m_dt.finite_facets_end(); fit!=fend; ++fit) {) CGAL_postcondition_code( if(fit->first->info().is_outside == fit->first->neighbor(fit->second)->info().is_outside) continue;) diff --git a/GraphicsView/include/CGAL/Qt/qglviewer.h b/GraphicsView/include/CGAL/Qt/qglviewer.h index da0dbace517..e353c8de66e 100644 --- a/GraphicsView/include/CGAL/Qt/qglviewer.h +++ b/GraphicsView/include/CGAL/Qt/qglviewer.h @@ -563,6 +563,21 @@ public: */ void saveSnapshot(); + /*! + * Takes a snapshot without any dialog + */ + void saveSnapshot(const QString& fileName, + const qreal finalWidth, + const qreal finalHeight, + const bool expand = false, + const double oversampling = 1., + qglviewer::SnapShotBackground background_color = qglviewer::CURRENT_BACKGROUND); + + void saveSnapshot(const QString& fileName) + { + return saveSnapshot(fileName, this->width(), this->height()); + } + public: Q_SIGNALS: /*! Signal emitted by the default init() method. diff --git a/GraphicsView/include/CGAL/Qt/qglviewer_impl.h b/GraphicsView/include/CGAL/Qt/qglviewer_impl.h index 07eb161909e..1e10b72d0b0 100644 --- a/GraphicsView/include/CGAL/Qt/qglviewer_impl.h +++ b/GraphicsView/include/CGAL/Qt/qglviewer_impl.h @@ -3761,8 +3761,29 @@ void CGAL::QGLViewer::saveSnapshot() } } +CGAL_INLINE_FUNCTION +void CGAL::QGLViewer::saveSnapshot(const QString& fileName, + const qreal finalWidth, const qreal finalHeight, + const bool expand, + const double oversampling, + qglviewer::SnapShotBackground background_color) +{ + if(fileName.isEmpty()) + return; + + QSize finalSize(finalWidth, finalHeight); + + QImage* image = takeSnapshot(qglviewer::SnapShotBackground(background_color), + finalSize, oversampling, expand); + if(image) + { + image->save(fileName); + delete image; + } } +} // namespace CGAL + CGAL_INLINE_FUNCTION bool CGAL::QGLViewer::isSharing() const { diff --git a/Polyhedron/demo/Polyhedron/Plugins/Alpha_wrap_3/Alpha_wrap_3_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Alpha_wrap_3/Alpha_wrap_3_plugin.cpp index 49f943f5437..8a190360c42 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Alpha_wrap_3/Alpha_wrap_3_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Alpha_wrap_3/Alpha_wrap_3_plugin.cpp @@ -12,13 +12,15 @@ #include #include -#include #include -#include #include -#include #include +#include +#include #include +#include +#include +#include #include #include @@ -29,6 +31,183 @@ #include "ui_alpha_wrap_3_dialog.h" +struct Iterative_AW3_visualization_visitor +{ +private: + bool m_do_snapshot; + Scene_polygon_soup_item* m_iterative_wrap_item = nullptr; + int sid = 0; + +public: + template + Iterative_AW3_visualization_visitor(Scene* scene, + const bool visualize_iterations, + const bool do_snapshot) + : m_do_snapshot(do_snapshot) + { + if(!visualize_iterations) + return; + + m_iterative_wrap_item = new Scene_polygon_soup_item(); + m_iterative_wrap_item->setName(QString("Iterative wrap")); + scene->addItem(m_iterative_wrap_item); + } + +public: + template + void on_alpha_wrapping_begin(const AlphaWrapper&) { } + + template + void on_flood_fill_begin(const AlphaWrapper&) { } + + template + void before_facet_treatment(const AlphaWrapper&, + const Facet&) { } + + template + void before_Steiner_point_insertion(const AlphaWrapper& wrapper, + const Point& /* p */) + { + if(m_iterative_wrap_item == nullptr) + return; + + // If the next top of the queue has vertices on the bbox, don't draw (as to avoid producing + // spikes in the visualization) +// const auto& gate = wrapper.queue().top(); +// if(wrapper.triangulation().number_of_vertices() > 500 && gate.is_artificial_facet()) +// return; + + // Skip some... + if(wrapper.triangulation().number_of_vertices() % 50 != 0) + return; + + // Extract the wrap as a triangle soup + + using Dt = typename std::decay::type; + using Vertex_handle = typename Dt::Vertex_handle; + using Facet = typename Dt::Facet; + using Cell_handle = typename Dt::Cell_handle; + + std::vector points; + std::vector > faces; + + std::unordered_map vertex_to_id; + std::size_t nv = 0; + + // This is used to compute colors depending on what is old and what is new. + // It is not currently used (a uniform gray color is used), but leaving it as it might be useful. + std::size_t min_time_stamp = -1, max_time_stamp = 0; + for(auto cit=wrapper.triangulation().finite_cells_begin(), cend=wrapper.triangulation().finite_cells_end(); cit!=cend; ++cit) + { + if(cit->time_stamp() > max_time_stamp) + max_time_stamp = cit->time_stamp(); + if(cit->time_stamp() < min_time_stamp) + min_time_stamp = cit->time_stamp(); + } + + std::vector vcolors; + std::vector fcolors; + + for(auto fit=wrapper.triangulation().finite_facets_begin(), fend=wrapper.triangulation().finite_facets_end(); fit!=fend; ++fit) + { + Facet f = *fit; + if(!f.first->info().is_outside) + f = wrapper.triangulation().mirror_facet(f); + + const Cell_handle c = f.first; + const int s = f.second; + const Cell_handle nh = c->neighbor(s); + if(c->info().is_outside == nh->info().is_outside) + continue; + + std::array ids; + for(int pos=0; pos<3; ++pos) + { + Vertex_handle vh = c->vertex(Dt::vertex_triple_index(s, pos)); + auto insertion_res = vertex_to_id.emplace(vh, nv); + if(insertion_res.second) // successful insertion, never-seen-before vertex + { + points.push_back(wrapper.triangulation().point(vh)); + vcolors.push_back(CGAL::IO::Color(0, 0, 0)); + ++nv; + } + + ids[pos] = insertion_res.first->second; + } + + faces.emplace_back(std::vector{ids[0], ids[1], ids[2]}); + double color_val = double(c->time_stamp() - min_time_stamp) / double(max_time_stamp - min_time_stamp); + color_val = int(256. * color_val); + + // fcolors.push_back(CGAL::IO::Color(color_val, 10, 150)); // young is red, old is blue + // fcolors.push_back(CGAL::IO::Color(256 - color_val, 256 - color_val, 256 - color_val)); // young is light, old is dark + fcolors.push_back(CGAL::IO::Color(100, 100, 100)); // uniform darkish gray + } + + // Update the wrap item's visualization + m_iterative_wrap_item->load(points, faces, fcolors, vcolors); + m_iterative_wrap_item->setName(QString("Iterative wrap #%1").arg(sid)); + m_iterative_wrap_item->setAlpha(255 / 2); + + m_iterative_wrap_item->invalidateOpenGLBuffers(); + m_iterative_wrap_item->redraw(); + m_iterative_wrap_item->itemChanged(); + + // Refresh the view + QApplication::processEvents(); + + if(m_do_snapshot) + { + std::stringstream oss; + oss << "Wrap_iteration-" << sid << ".png" << std::ends; + QString filename = QString::fromStdString(oss.str().c_str()); + + CGAL::Three::Viewer_interface* viewer = CGAL::Three::Three::activeViewer(); + viewer->saveSnapshot(filename, 1920, 1080, true /*expand*/, 2.0 /*oversampling*/); + } + + ++sid; + } + + template + void after_Steiner_point_insertion(const AlphaWrapper&, + const VertexHandle) { } + + template + void on_flood_fill_end(const AlphaWrapper&) { } + + template + void on_alpha_wrapping_end(const AlphaWrapper&) + { + if(m_iterative_wrap_item == nullptr) + return; + + m_iterative_wrap_item->setName(QString("Iterative wrap #%1").arg(sid)); + + m_iterative_wrap_item->setAlpha(255); + m_iterative_wrap_item->invalidateOpenGLBuffers(); + m_iterative_wrap_item->redraw(); + m_iterative_wrap_item->itemChanged(); + + QApplication::processEvents(); + + if(m_do_snapshot) + { + std::stringstream oss; + oss << "Wrap_iteration-" << sid << ".png" << std::ends; + QString filename = QString::fromStdString(oss.str().c_str()); + + CGAL::Three::Viewer_interface* viewer = CGAL::Three::Three::activeViewer(); + viewer->saveSnapshot(filename); + } + + m_iterative_wrap_item->setVisible(false); + + // Refresh the view + QApplication::processEvents(); + } +}; + class Polyhedron_demo_alpha_wrap_3_plugin : public QObject, public CGAL::Three::Polyhedron_demo_plugin_helper @@ -90,6 +269,9 @@ private: connect(ui.wrapEdges, SIGNAL(clicked(bool)), this, SLOT(toggle_wrap_faces())); connect(ui.wrapFaces, SIGNAL(clicked(bool)), this, SLOT(toggle_wrap_edges())); + connect(ui.visualizeIterations, SIGNAL(clicked(bool)), + this, SLOT(update_iteration_snapshot_checkbox())); + connect(ui.buttonBox, SIGNAL(accepted()), dialog, SLOT(accept())); connect(ui.buttonBox, SIGNAL(rejected()), dialog, SLOT(reject())); @@ -109,6 +291,11 @@ public Q_SLOTS: ui.wrapEdges->setChecked(true); } + void update_iteration_snapshot_checkbox() + { + ui.snapshotIterations->setCheckable(ui.visualizeIterations->isChecked()); + } + void on_actionAlpha_wrap_3_triggered() { using Triangles = std::vector; @@ -132,6 +319,8 @@ public Q_SLOTS: const bool enforce_manifoldness = ui.runManifoldness->isChecked(); double alpha = ui.alphaValue->value(); double offset = ui.offsetValue->value(); + const bool visualize_iterations = ui.visualizeIterations->isChecked(); + const bool do_snapshot_iterations = ui.snapshotIterations->isChecked(); if(alpha <= 0. || offset <= 0.) return; @@ -170,6 +359,8 @@ public Q_SLOTS: get(vpm, source(h, *pMesh))); } + sm_item->setRenderingMode(Flat); + continue; } @@ -192,6 +383,8 @@ public Q_SLOTS: soup_item->points()[p[2]]); } + soup_item->setRenderingMode(Flat); + continue; } @@ -287,6 +480,11 @@ public Q_SLOTS: } } + std::cout << triangles.size() << " triangles" << std::endl; + std::cout << segments.size() << " edges" << std::endl; + std::cout << points.size() << " points" << std::endl; + std::cout << "do wrap edges/faces: " << wrap_segments << " " << wrap_triangles << std::endl; + if(wrap_triangles) oracle.add_triangle_soup(triangles); if(wrap_segments) @@ -314,13 +512,18 @@ public Q_SLOTS: CGAL::Alpha_wraps_3::internal::Alpha_wrap_3 aw3(oracle); + Iterative_AW3_visualization_visitor visitor(scene, + visualize_iterations, + do_snapshot_iterations); + SMesh wrap; aw3(alpha, offset, wrap, - CGAL::parameters::do_enforce_manifoldness(enforce_manifoldness)); + CGAL::parameters::do_enforce_manifoldness(enforce_manifoldness) + .visitor(visitor)); Scene_surface_mesh_item* wrap_item = new Scene_surface_mesh_item(wrap); - wrap_item->setName(tr("Wrap alpha %2 offset %3").arg(alpha).arg(offset)); - wrap_item->setColor(Qt::cyan); + wrap_item->setName(tr("Wrap with alpha %2 offset %3").arg(alpha).arg(offset)); + wrap_item->setColor(Qt::gray); scene->addItem(wrap_item); QApplication::restoreOverrideCursor(); diff --git a/Polyhedron/demo/Polyhedron/Plugins/Alpha_wrap_3/alpha_wrap_3_dialog.ui b/Polyhedron/demo/Polyhedron/Plugins/Alpha_wrap_3/alpha_wrap_3_dialog.ui index 6877e6a99e5..bbf25c4b345 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Alpha_wrap_3/alpha_wrap_3_dialog.ui +++ b/Polyhedron/demo/Polyhedron/Plugins/Alpha_wrap_3/alpha_wrap_3_dialog.ui @@ -10,31 +10,21 @@ 0 0 646 - 432 + 673 3D Alpha Wrapping - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - 3D Alpha Wrapping - - + + @@ -62,71 +52,21 @@ - + true - Enforce 2-manifoldness + <html><head/><body><p>Enforce 2-manifold output</p></body></html> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - true - - - - - - - - - - true - - - - - - - - - - true - - - - - - - Offset value: - - - - - + + Qt::Vertical @@ -148,27 +88,59 @@ - - + + - Qt::Horizontal + Qt::Vertical - + + + 20 + 40 + + + - - + + + + Qt::Vertical + + + + 20 + 40 + + + + + + - <html><head/><body><p>Wrap edges<br/><span style=" font-size:9pt;">If deactivated, only extremities of the edges are taken into account</span></p></body></html> + Visualize iterations - <html><head/><body><p>Use relative-to-bbox offset<br/><span style=" font-size:9pt;">As ratio of the length of the bbox's diagonal<br/>(value </span><span style=" font-size:9pt; font-style:italic;">x </span><span style=" font-size:9pt;">means </span><span style=" font-size:9pt; font-style:italic;">offset := bbox_diag_l / x)</span></p></body></html> + <html><head/><body><p>Use relative-to-bbox offset<br/><span style=" font-size:9pt;">As ratio of the length of the bbox's diagonal<br/>(i.e., value </span><span style=" font-size:9pt; font-style:italic;">x </span><span style=" font-size:9pt;">means </span><span style=" font-size:9pt; font-style:italic;">offset := bbox_diag_l / x)</span></p></body></html> + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -188,8 +160,29 @@ - - + + + + Qt::Horizontal + + + + + + + <html><head/><body><p>Wrap faces<br/><span style=" font-size:9pt;">If deactivated, only edges of the faces are taken into account</span></p></body></html> + + + + + + + <html><head/><body><p>Wrap edges<br/><span style=" font-size:9pt;">If deactivated, only extremities of the edges are taken into account</span></p></body></html> + + + + + @@ -198,17 +191,47 @@ - - - - <html><head/><body><p>Wrap faces<br/><span style=" font-size:9pt;">If deactivated, only edges of the faces are taken into account</span></p></body></html> + + + + Qt::Vertical - + + + 20 + 40 + + + - <html><head/><body><p>Use relative-to-bbox alpha<br/><span style=" font-size:9pt;">As ratio of the length of the bbox's diagonal<br/>(value </span><span style=" font-size:9pt; font-style:italic;">x </span><span style=" font-size:9pt;">means alpha</span><span style=" font-size:9pt; font-style:italic;"> := bbox_diag_l / x)</span></p></body></html> + <html><head/><body><p>Use relative-to-bbox alpha<br/><span style=" font-size:9pt;">As ratio of the length of the bbox's diagonal<br/>(i.e., value </span><span style=" font-size:9pt; font-style:italic;">x </span><span style=" font-size:9pt;">means alpha</span><span style=" font-size:9pt; font-style:italic;"> := bbox_diag_l / x)</span></p></body></html> + + + + + + + Qt::Vertical + + + + + + + + + + + + + + + + + true @@ -225,16 +248,90 @@ - + + + + + + + true + + + + + + + Qt::Horizontal + + + + + + + Offset value: + + + + Qt::Horizontal + + + + + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + <html><head/><body><p>Snapshot iterations<br/><span style=" font-size:9pt;">For each iteration, save a snapshot of the viewer <br/>to a file named </span><span style=" font-size:9pt; font-style:italic;">Wrap-iteration_i.png</span></p></body></html> + + + + + + + + + + false + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + +