From efd68a5f922ca427bc17e841ce8df3c2387481b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Fri, 9 Jun 2023 19:15:16 +0200 Subject: [PATCH] Update offset meshing plugin - fixed not working for polygon soups - fixed broken formula for both giving Mesh_3 assertions and inverted meshes - fixed applicable() allowing wrong combinations - fixed inflate() needlessly normalizing normals - fixed crashing for non triangulated inputs - fixed some memory leaks - fixed dialog name - removed useless sqrts - avoid recomputing bbox 4 times - cleaned UI - etc. --- .../Polyhedron/Plugins/Mesh_3/CMakeLists.txt | 13 + .../Offset_meshing_dialog.ui} | 146 ++-- .../Plugins/Mesh_3/Offset_meshing_plugin.cpp | 765 ++++++++++++++++++ .../Plugins/Surface_mesh/CMakeLists.txt | 12 - .../Surface_mesh/Offset_meshing_plugin.cpp | 691 ---------------- 5 files changed, 857 insertions(+), 770 deletions(-) rename Polyhedron/demo/Polyhedron/Plugins/{Surface_mesh/Remeshing_dialog.ui => Mesh_3/Offset_meshing_dialog.ui} (81%) create mode 100644 Polyhedron/demo/Polyhedron/Plugins/Mesh_3/Offset_meshing_plugin.cpp delete mode 100644 Polyhedron/demo/Polyhedron/Plugins/Surface_mesh/Offset_meshing_plugin.cpp diff --git a/Polyhedron/demo/Polyhedron/Plugins/Mesh_3/CMakeLists.txt b/Polyhedron/demo/Polyhedron/Plugins/Mesh_3/CMakeLists.txt index eb4e9c5b57d..2cc3f58854f 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Mesh_3/CMakeLists.txt +++ b/Polyhedron/demo/Polyhedron/Plugins/Mesh_3/CMakeLists.txt @@ -116,3 +116,16 @@ if(TBB_FOUND) target_link_libraries(c3t3_io_plugin PUBLIC CGAL::TBB_support) target_link_libraries(c3t3_rib_exporter_plugin PUBLIC CGAL::TBB_support) endif() + +qt5_wrap_ui(offsetMeshingUI_FILES Offset_meshing_dialog.ui) +polyhedron_demo_plugin(offset_meshing_plugin Offset_meshing_plugin + ${offsetMeshingUI_FILES}) +target_link_libraries(offset_meshing_plugin PUBLIC scene_surface_mesh_item + scene_polygon_soup_item + scene_polylines_item) +if(TARGET CGAL::Eigen3_support) + target_link_libraries(offset_meshing_plugin PUBLIC CGAL::Eigen3_support) +endif() +if(TARGET CGAL::TBB_support) + target_link_libraries(offset_meshing_plugin PUBLIC CGAL::TBB_support) +endif() diff --git a/Polyhedron/demo/Polyhedron/Plugins/Surface_mesh/Remeshing_dialog.ui b/Polyhedron/demo/Polyhedron/Plugins/Mesh_3/Offset_meshing_dialog.ui similarity index 81% rename from Polyhedron/demo/Polyhedron/Plugins/Surface_mesh/Remeshing_dialog.ui rename to Polyhedron/demo/Polyhedron/Plugins/Mesh_3/Offset_meshing_dialog.ui index 37b9073000f..33df99c77bb 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Surface_mesh/Remeshing_dialog.ui +++ b/Polyhedron/demo/Polyhedron/Plugins/Mesh_3/Offset_meshing_dialog.ui @@ -1,64 +1,70 @@ - Remeshing_dialog - + Offset_meshing_dialog + 0 0 - 376 - 216 + 413 + 294 Meshing criteria - + 25.0 - - - - 0.00 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - + + - Approximation &error: + Edge size Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - 0.00 - - - - - - - &Topological criterion: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - tags - - - + + + true + + + 0.00 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + @@ -77,43 +83,47 @@ - - - - Qt::Vertical + + + + Topological criterion - - - 20 - 40 - + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + tags - - - true - + 0.00 - - + + - &Edge size: + Approximation error + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0.00 + + + + + + + Size Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -121,22 +131,24 @@ - + - &Size: + Angle Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - &Angle: + + + + + 15 + - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + Meshing Criteria @@ -154,7 +166,7 @@ buttonBox accepted() - Remeshing_dialog + Offset_meshing_dialog accept() @@ -170,7 +182,7 @@ buttonBox rejected() - Remeshing_dialog + Offset_meshing_dialog reject() diff --git a/Polyhedron/demo/Polyhedron/Plugins/Mesh_3/Offset_meshing_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Mesh_3/Offset_meshing_plugin.cpp new file mode 100644 index 00000000000..8b0e0bab57b --- /dev/null +++ b/Polyhedron/demo/Polyhedron/Plugins/Mesh_3/Offset_meshing_plugin.cpp @@ -0,0 +1,765 @@ +#include "config.h" + +#ifdef CGAL_POLYHEDRON_DEMO_USE_SURFACE_MESHER + +#include +#include "ui_Offset_meshing_dialog.h" + +#include "C3t3_type.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include "Scene_surface_mesh_item.h" +#include "Scene_polygon_soup_item.h" +#include "Scene_polylines_item.h" +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace CGAL::Three; + +namespace CGAL { + +template +class Offset_function +{ + using Primitive = AABB_face_graph_triangle_primitive; + using Traits = AABB_traits; + using Tree = AABB_tree; + using Side_of = Side_of_triangle_mesh; + + using FT = typename GeomTraits::FT; + using Point_3 = typename GeomTraits::Point_3; + +public: + Offset_function(const TriangleMesh& tm, + double offset_distance) + : m_tree_ptr(std::make_shared(std::begin(faces(tm)), std::end(faces(tm)), tm)), + m_side_of_ptr(std::make_shared(*m_tree_ptr)), + m_is_inset(offset_distance < 0), + m_sq_offset_distance(CGAL::square(offset_distance)), + m_is_closed(is_closed(tm)) + { + CGAL_assertion(!m_tree_ptr->empty()); + } + + // we only need negative inside, and positive outside, so we can compare square roots + double operator()(const Point_3& p) const + { + const Bounded_side side = m_is_closed ? m_side_of_ptr->operator()(p) : ON_UNBOUNDED_SIDE; + + if(m_is_inset) // also means that the mesh is closed + { + // - ON_UNBOUNDED_SIDE is outside the offset since we are insetting + // - ON_BOUNDARY is outside the offset since we are insetting + if(side != ON_BOUNDED_SIDE) + return 1; + + // inside the offset if the distance to the input mesh is greater than the offset distance + const FT sq_distance = m_tree_ptr->squared_distance(p); + return (sq_distance > m_sq_offset_distance) ? -1 : 1; + } + else // outset + { + // - ON_BOUNDED_SIDE can only happen if it's a closed mesh, and in that case, being inside + // the mesh is being inside the offset + // - ON_BOUNDARY is in the offset whether the mesh is open or closed + if(side != ON_UNBOUNDED_SIDE) + return - 1; + + // inside the offset if the distance to the input mesh is smaller than the offset distance + const FT sq_distance = m_tree_ptr->squared_distance(p); + return (sq_distance < m_sq_offset_distance) ? -1 : 1; + } + } + +private: + std::shared_ptr m_tree_ptr; + std::shared_ptr m_side_of_ptr; + const bool m_is_inset; + const double m_sq_offset_distance; + const bool m_is_closed; +}; + +template +class Polygon_soup_offset_function +{ + using Polygon_iterator = typename Polygons::const_iterator; + + class Polygon_soup_point_property_map + { + const Points* points_vector_ptr; + + public: + using key_type = Polygon_iterator; + using value_type = EPICK::Point_3; + using reference = const value_type&; + using category = boost::readable_property_map_tag; + + Polygon_soup_point_property_map() = default; + Polygon_soup_point_property_map(const Points* ptr) : points_vector_ptr(ptr) { } + + friend reference get(Polygon_soup_point_property_map map, + key_type polygon_it) + { + return (*map.points_vector_ptr)[*polygon_it->begin()]; + } + }; + + class Polygon_soup_triangle_property_map + { + const Points* points_vector_ptr; + + public: + using key_type = Polygon_iterator; + using value_type = EPICK::Triangle_3; + using reference = value_type; + using category = boost::readable_property_map_tag; + + Polygon_soup_triangle_property_map() = default; + Polygon_soup_triangle_property_map(const Points* ptr) : points_vector_ptr(ptr) { } + + friend value_type get(Polygon_soup_triangle_property_map map, + key_type polygon_it) + { + auto it = polygon_it->begin(); + CGAL_assertion(it != polygon_it->end()); + const auto id0 = *it++; + CGAL_assertion(it != polygon_it->end()); + const auto id1 = *it++; + CGAL_assertion(it != polygon_it->end()); + const auto id2 = *it++; + CGAL_assertion(it == polygon_it->end()); + + return value_type((*map.points_vector_ptr)[id0], + (*map.points_vector_ptr)[id1], + (*map.points_vector_ptr)[id2]); + } + }; + + struct AABB_polygon_soup_triangle_primitive + : public CGAL::AABB_primitive + { + using Base = CGAL::AABB_primitive; + + using Id = Polygon_iterator; + + template + AABB_polygon_soup_triangle_primitive(Id id, + ObjectPmap&& opmap, + PointPmap&& ppmap) + : Base(id, std::forward(opmap), std::forward(ppmap)) + { + } + + template + AABB_polygon_soup_triangle_primitive(Iterator it, + ObjectPmap&& opmap, + PointPmap&& ppmap) + : Base(*it, std::forward(opmap), std::forward(ppmap)) + { + } + }; // struct template Polygon_soup_primitive + + using AABB_traits = CGAL::AABB_traits; + using AABB_tree = CGAL::AABB_tree; + + std::shared_ptr m_tree_ptr; + double m_sq_offset_distance; + +public: + Polygon_soup_offset_function(const Points& points, + const Polygons& polygons, + const double offset_distance) + : m_tree_ptr(std::make_shared(std::begin(polygons), + std::end(polygons), + Polygon_soup_triangle_property_map(&points), + Polygon_soup_point_property_map(&points))), + m_sq_offset_distance(square(offset_distance)) + { + CGAL_assertion(!m_tree_ptr->empty()); + } + + // we only need negative inside, and positive outside, so we can compare square roots + double operator()(const EPICK::Point_3& p) const + { + // it's a soup so it's open by definition ==> treat inset and outset identically + const double sq_distance = m_tree_ptr->squared_distance(p); + return sq_distance - m_sq_offset_distance; + } + +}; // class Polygon_soup_offset_function + +} // namespace CGAL + +CGAL::Offset_function +offset_function(Scene_surface_mesh_item* item, double offset_value) +{ + return { *(item->face_graph()), offset_value }; +} + +CGAL::Polygon_soup_offset_function +offset_function(Scene_polygon_soup_item* item, double offset_value) +{ + return { item->points(), item->polygons(), offset_value }; +} + +class MeshGuard +{ + SMesh* mesh; + bool done; + +public: + MeshGuard(SMesh* mesh) : mesh(mesh), done(false) { } + void setDone() { done = true; } + ~MeshGuard() + { + if(!done) + delete mesh; + } +}; + +// declare the CGAL function +template +SMesh* cgal_off_meshing(QWidget*, + SourceItem* source_item, + Scene_polylines_item* polylines_item, + const double offset_value, + const double angle, + const double sizing, + const double approx, + const double edge_size, + int tag) +{ + using GT = EPICK; + using Sphere_3 = GT::Sphere_3; + + using Mesh_domain_base = CGAL::Labeled_mesh_domain_3; + using Mesh_domain = CGAL::Mesh_domain_with_polyline_features_3; + using Tr = C3t3::Triangulation; + using Mesh_criteria = CGAL::Mesh_criteria_3; + + const CGAL::Bbox_3 bbox = source_item->bbox(); + + const GT::Point_3 center((bbox.xmax() + bbox.xmin()) / 2, + (bbox.ymax() + bbox.ymin()) / 2, + (bbox.zmax() + bbox.zmin()) / 2); + const double rad = 0.6 * std::sqrt(CGAL::square(bbox.xmax() - bbox.xmin()) + + CGAL::square(bbox.ymax() - bbox.ymin()) + + CGAL::square(bbox.zmax() - bbox.zmin())) + + offset_value; + const double sqrad = CGAL::square(rad); + + CGAL::Timer timer; + timer.start(); + + namespace p = CGAL::parameters; + + Mesh_domain domain = + Mesh_domain::create_implicit_mesh_domain + (p::function = offset_function(source_item, offset_value), + p::bounding_object = Sphere_3(center, sqrad), + p::relative_error_bound = 1e-7, + p::construct_surface_patch_index = [](int i, int j) { return (i * 1000 + j); }); + + const CGAL::Mesh_facet_topology topology = CGAL::FACET_VERTICES_ON_SAME_SURFACE_PATCH; + auto manifold_option = p::non_manifold(); + if(tag == 1) + manifold_option = p::manifold_with_boundary(); + if(tag == 2) + manifold_option = p::manifold(); + + Mesh_criteria criteria(p::facet_angle = angle, + p::facet_size = sizing, + p::facet_distance = approx, + p::facet_topology = topology, + p::edge_size = edge_size); + + if(polylines_item != nullptr) + { + typedef std::vector Surface_patch_ids; + std::vector surface_patch_ids; + + domain.add_features_and_incidences(polylines_item->polylines.begin(), + polylines_item->polylines.end(), + CGAL::Identity_property_map(), + CGAL::Constant_property_map(surface_patch_ids)); + } + + C3t3 c3t3 = CGAL::make_mesh_3(domain, criteria, + p::no_perturb(), + p::no_exude(), + manifold_option); + + timer.stop(); + std::cerr << "done (" << timer.time() << " ms, " << c3t3.triangulation().number_of_vertices() << " vertices)" << std::endl; + + if(c3t3.number_of_facets_in_complex() > 0) + { + SMesh* pRemesh = new SMesh(); + + // if the thread is interrupted before the mesh is returned, delete it. + MeshGuard guard(pRemesh); + CGAL::facets_in_complex_3_to_triangle_mesh(c3t3, *pRemesh); + guard.setDone(); + + CGAL_postcondition(CGAL::Polygon_mesh_processing::is_outward_oriented(*pRemesh)); + + return pRemesh; + } + else + { + return nullptr; + } +} + +struct Mesher_thread + : public QThread +{ + Q_OBJECT + +private: + Scene_surface_mesh_item* sm_item; + Scene_polygon_soup_item* soup_item; + Scene_polylines_item* polylines_item; + + const double offset_value; + const double angle; + const double sizing; + const double approx; + const double edge_size; + int tag_index; + +public: + Mesher_thread(Scene_surface_mesh_item* sm_item, + Scene_polygon_soup_item* soup_item, + Scene_polylines_item* polylines_item, + const double offset_value, + const double angle, + const double sizing, + const double approx, + const double edge_size, + int tag) + : sm_item(sm_item), soup_item(soup_item), polylines_item(polylines_item), + offset_value(offset_value), + angle(angle), sizing(sizing), approx(approx), edge_size(edge_size), tag_index(tag) + { + } + + void run() override + { + SMesh* offset_mesh = nullptr; + + if(soup_item) + { + offset_mesh = cgal_off_meshing(Three::mainWindow(), + soup_item, polylines_item, + offset_value, + angle, sizing, approx, edge_size, tag_index); + } + else + { + offset_mesh = cgal_off_meshing(Three::mainWindow(), + sm_item, polylines_item, + offset_value, + angle, sizing, approx, edge_size, tag_index); + } + + Three::getMutex()->lock(); + Three::getWaitCondition()->wakeAll(); + Three::getMutex()->unlock(); + + Q_EMIT resultReady(offset_mesh); + } + +Q_SIGNALS: + void resultReady(SMesh *offset_mesh); +}; + +class Polyhedron_demo_offset_meshing_plugin + : public QObject, + protected Polyhedron_demo_plugin_interface +{ + Q_OBJECT + Q_INTERFACES(CGAL::Three::Polyhedron_demo_plugin_interface) + Q_PLUGIN_METADATA(IID "com.geometryfactory.PolyhedronDemo.PluginInterface/1.0") + +private: + QAction* actionOffsetMeshing; + QAction* actionInflateMesh; + + Scene_interface *scene; + QMainWindow *mw; + +public: + void init(QMainWindow* mainWindow, + Scene_interface* scene_interface, + Messages_interface*) + { + this->scene = scene_interface; + this->mw = mainWindow; + + actionOffsetMeshing = new QAction(tr("Offset Meshing"), mw); + actionOffsetMeshing->setProperty("subMenuName", "3D Surface Mesh Generation"); + connect(actionOffsetMeshing, SIGNAL(triggered()), + this, SLOT(offset_meshing())); + + actionInflateMesh = new QAction(tr("Inflate Mesh"), mw); + actionInflateMesh->setProperty("subMenuName", "Operations on Polyhedra"); + connect(actionInflateMesh, SIGNAL(triggered()), + this, SLOT(inflate_mesh())); + } + + bool applicable(QAction* action) const + { + if(action == actionOffsetMeshing) + { + if(scene->selectionIndices().size() == 1) + { + const int index = scene->mainSelectionIndex(); + return (qobject_cast(scene->item(index)) || + qobject_cast(scene->item(index))); + } + + // Can provide a polyline item for feature protection + if(scene->selectionIndices().size() != 2) + return false; + + // One needs to be a surface mesh or polygon soup item, and the other a polyline item + const int index1 = scene->selectionIndices().at(0); + const int index2 = scene->selectionIndices().at(1); + Scene_item* item1 = scene->item(index1); + Scene_item* item2 = scene->item(index2); + + if((qobject_cast(item1) || + qobject_cast(item1)) && + qobject_cast(item2)) + return true; + + if((qobject_cast(item2) || + qobject_cast(item2)) && + qobject_cast(item1)) + return true; + } + else if(action == actionInflateMesh) + { + if(scene->selectionIndices().size() == 1) + { + const int index = scene->mainSelectionIndex(); + return qobject_cast(scene->item(index)); + } + } + + return false; + } + + QList actions() const + { + return QList() << actionOffsetMeshing + << actionInflateMesh; + } + +public Q_SLOTS: + void offset_meshing(); + void inflate_mesh(); +}; // class Polyhedron_demo_offset_meshing_plugin + +void +Polyhedron_demo_offset_meshing_plugin:: +offset_meshing() +{ + Scene_item* item = nullptr; + Scene_surface_mesh_item* sm_item = nullptr; + Scene_polygon_soup_item* soup_item = nullptr; + Scene_polylines_item* polylines_item = nullptr; + + bool mesh_or_soup_item_found = false; + Q_FOREACH(Scene_interface::Item_id index, scene->selectionIndices()) + { + if(!mesh_or_soup_item_found) + { + sm_item = qobject_cast(scene->item(index)); + if(sm_item == nullptr) + { + soup_item = qobject_cast(scene->item(index)); + if(soup_item != nullptr) + { + item = scene->item(index); + mesh_or_soup_item_found = true; + continue; + } + } + else + { + item = scene->item(index); + mesh_or_soup_item_found = true; + continue; + } + } + + polylines_item = qobject_cast(scene->item(index)); + } + + QApplication::setOverrideCursor(Qt::WaitCursor); + + if(!mesh_or_soup_item_found) + return; + + if(sm_item) + { + if(!is_triangle_mesh(*(sm_item->face_graph()))) + { + QMessageBox::critical(mw, + tr("Offset Meshing"), + tr("The selected mesh is not a triangle mesh.")); + return; + } + } + else + { + for(const auto& p : soup_item->polygons()) + { + if(p.size() != 3) + { + QMessageBox::critical(mw, + tr("Offset Meshing"), + tr("The selected polygon soup is not a triangle soup.")); + return; + } + } + } + + double diag; + if(sm_item) + diag = sm_item->bboxDiagonal(); + else + diag = soup_item->bboxDiagonal(); + + QApplication::restoreOverrideCursor(); + + bool ok = true; + double offset_value = QInputDialog::getDouble(mw, + QString("Choose Offset Value"), + QString("Offset Value (use a negative number to compute the inset of a closed mesh)"), + 0.1 * diag, + - (std::numeric_limits::max)(), + (std::numeric_limits::max)(), 10, &ok); + if(!ok) + return; + + if(offset_value < 0 && (!sm_item || !is_closed(*(sm_item->face_graph())))) + { + QMessageBox::critical(mw, + tr("Offset Meshing"), + tr("Insetting is only possible for closed polygon meshes.")); + return; + } + + QDialog dialog(mw); + Ui::Offset_meshing_dialog ui; + ui.setupUi(&dialog); + ui.angle->setRange(1.0, 30.0); + + connect(ui.buttonBox, SIGNAL(accepted()), + &dialog, SLOT(accept())); + connect(ui.buttonBox, SIGNAL(rejected()), + &dialog, SLOT(reject())); + + ui.sizing->setRange(diag * 10e-6, diag); + ui.sizing->setValue(diag * 0.05); // default value + ui.approx->setRange(diag * 10e-7, diag); + ui.approx->setValue(diag * 0.005); + + if(polylines_item != nullptr) + { + ui.edge_sizing->setRange(diag * 10e-6, diag); + ui.edge_sizing->setValue(diag * 0.05); // default value + } + else + { + ui.edge_sizing->setEnabled(false); + } + + int i = dialog.exec(); + if(i == QDialog::Rejected) + return; + + const double angle = ui.angle->value(); + const double approx = ui.approx->value(); + const double sizing = ui.sizing->value(); + const double edge_size = (polylines_item != nullptr) ? ui.edge_sizing->value() : 0; + const int tag_index = ui.tags->currentIndex(); + + if(tag_index < 0) + return; + + QApplication::setOverrideCursor(Qt::BusyCursor); + + std::cerr << "mesh with:" + << "\n angle= " << angle + << "\n sizing= " << sizing + << "\n approx= " << approx + << "\n tag= " << tag_index + << std::boolalpha + << std::endl; + + Mesher_thread* worker = nullptr; + if(soup_item) + { + worker = new Mesher_thread(nullptr, soup_item, polylines_item, + offset_value, + angle, sizing, approx, edge_size, tag_index); + } + else + { + worker = new Mesher_thread(sm_item, nullptr, polylines_item, + offset_value, + angle, sizing, approx, edge_size, tag_index); + } + + connect(worker, &QThread::finished, + worker, &QObject::deleteLater); + + connect(worker, &Mesher_thread::resultReady, + this, [item, angle, sizing, approx, offset_value/* , index */](SMesh* offset_mesh) + { + if(!offset_mesh) + { + QApplication::restoreOverrideCursor(); + + Three::getMutex()->lock(); + Three::isLocked() = false; + Three::getMutex()->unlock(); + + return; + } + + Scene_surface_mesh_item* offset_item = new Scene_surface_mesh_item(offset_mesh); + offset_item->setName(tr("%1 offset %5 (%2 %3 %4)").arg(item->name()) + .arg(angle) + .arg(sizing) + .arg(approx) + .arg(offset_value)); + offset_item->setColor(Qt::magenta); + offset_item->setRenderingMode(Wireframe); + Three::scene()->addItem(offset_item); + + QApplication::restoreOverrideCursor(); + + Three::getMutex()->lock(); + Three::isLocked() = false; + Three::getMutex()->unlock(); + }); + + QMessageBox* message_box = new QMessageBox(QMessageBox::NoIcon, + "Meshing", + "Offset meshing in progress...", + QMessageBox::Cancel, + mw); + message_box->setDefaultButton(QMessageBox::Cancel); + QAbstractButton* cancelButton = message_box->button(QMessageBox::Cancel); + cancelButton->setText(tr("Stop")); + + connect(cancelButton, &QAbstractButton::clicked, + this, [worker](){ worker->terminate(); }); + connect(worker, &Mesher_thread::finished, + message_box, &QMessageBox::close); + + Three::getMutex()->lock(); + Three::isLocked() = true; + Three::getMutex()->unlock(); + + message_box->open(); + worker->start(); +} + +void +Polyhedron_demo_offset_meshing_plugin:: +inflate_mesh() +{ + const Scene_interface::Item_id index = scene->mainSelectionIndex(); + Scene_item* item = scene->item(index); + if(item == nullptr) + return; + + Scene_surface_mesh_item* sm_item = qobject_cast(item); + if(sm_item == nullptr) + return; + + SMesh* sMesh = sm_item->face_graph(); + if(sMesh == nullptr) + return; + + const double diag = sm_item->bboxDiagonal(); + + bool ok = true; + const double offset_value = QInputDialog::getDouble(mw, + QString("Choose Inflate Distance"), + QString("Inflate Distance (use a negative number to deflate)"), + 0.1 * diag, + -(std::numeric_limits::max)(), + (std::numeric_limits::max)(), + 10, + &ok); + if(!ok) + return; + + auto vpm = get(CGAL::vertex_point, *sMesh); + auto vnm = sMesh->property_map("v:normal").first; + + QApplication::setOverrideCursor(Qt::WaitCursor); + + for(const auto& v : vertices(*sMesh)) + { + const EPICK::Vector_3& n = get(vnm, v); + put(vpm, v, get(vpm, v) + offset_value * n); + } + + sm_item->invalidateOpenGLBuffers(); + sm_item->itemChanged(); + sm_item->itemVisibilityChanged(); + + QApplication::restoreOverrideCursor(); +} + +#include "Offset_meshing_plugin.moc" + +#endif // CGAL_POLYHEDRON_DEMO_USE_SURFACE_MESHER diff --git a/Polyhedron/demo/Polyhedron/Plugins/Surface_mesh/CMakeLists.txt b/Polyhedron/demo/Polyhedron/Plugins/Surface_mesh/CMakeLists.txt index 508716a21e1..845dedbc31f 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Surface_mesh/CMakeLists.txt +++ b/Polyhedron/demo/Polyhedron/Plugins/Surface_mesh/CMakeLists.txt @@ -41,18 +41,6 @@ if(NOT CGAL_DISABLE_GMP) polyhedron_demo_plugin(mesh_simplification_plugin Mesh_simplification_plugin ${mesh_simplificationUI_FILES}) target_link_libraries(mesh_simplification_plugin PUBLIC scene_surface_mesh_item scene_selection_item) - qt5_wrap_ui(remeshingUI_FILES Remeshing_dialog.ui) - polyhedron_demo_plugin(offset_meshing_plugin Offset_meshing_plugin - ${remeshingUI_FILES}) - target_link_libraries(offset_meshing_plugin PUBLIC scene_surface_mesh_item - scene_polygon_soup_item - scene_polylines_item) - if(TARGET CGAL::Eigen3_support) - target_link_libraries(offset_meshing_plugin PUBLIC CGAL::Eigen3_support) - endif() - if(TARGET CGAL::TBB_support) - target_link_libraries(offset_meshing_plugin PUBLIC CGAL::TBB_support) - endif() qt5_wrap_ui(shortestPathUI_FILES Shortest_path_widget.ui) polyhedron_demo_plugin(shortest_path_plugin Shortest_path_plugin diff --git a/Polyhedron/demo/Polyhedron/Plugins/Surface_mesh/Offset_meshing_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Surface_mesh/Offset_meshing_plugin.cpp deleted file mode 100644 index 860cc2d66e9..00000000000 --- a/Polyhedron/demo/Polyhedron/Plugins/Surface_mesh/Offset_meshing_plugin.cpp +++ /dev/null @@ -1,691 +0,0 @@ -#include "config.h" -#ifdef CGAL_POLYHEDRON_DEMO_USE_SURFACE_MESHER -#include -#include "ui_Remeshing_dialog.h" - -#include -#include -#include -#include -#include -#include -#include -#include "Scene_surface_mesh_item.h" -#include "Scene_polygon_soup_item.h" -#include "Scene_polylines_item.h" -#include -#include -#include -#include - -#include "C3t3_type.h" - -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include // std::shared_ptr - -namespace CGAL{ - -template -class Offset_function -{ - typedef AABB_face_graph_triangle_primitive Primitive; - typedef AABB_traits Traits; - typedef AABB_tree Tree; - typedef Side_of_triangle_mesh Side_of; - -public: - - Offset_function(TriangleMesh& tm, double offset_distance) - : m_tree_ptr(new Tree(boost::begin(faces(tm)), - boost::end(faces(tm)), - tm) ) - , m_side_of_ptr( new Side_of(*m_tree_ptr) ) - , m_offset_distance(offset_distance) - , m_is_closed( is_closed(tm) ) - { - CGAL_assertion(!m_tree_ptr->empty()); - } - - double operator()(const typename GeomTraits::Point_3& p) const - { - using CGAL::sqrt; - - Bounded_side side = m_is_closed?m_side_of_ptr->operator()(p):ON_UNBOUNDED_SIDE; - if (side==ON_BOUNDARY) return m_offset_distance; - - typename GeomTraits::Point_3 closest_point = m_tree_ptr->closest_point(p); - double distance = sqrt(squared_distance(p, closest_point)); - - return (side == ON_UNBOUNDED_SIDE ? -distance : distance) + m_offset_distance; - } - -private: - std::shared_ptr m_tree_ptr; - std::shared_ptr m_side_of_ptr; - double m_offset_distance; - bool m_is_closed; - -}; - -class Polygon_soup_offset_function { - typedef Scene_polygon_soup_item::Points Points; - typedef Scene_polygon_soup_item::Polygons Polygons; - - typedef Polygons::const_iterator Polygon_iterator; - - - class Polygon_soup_point_property_map { - const Points* points_vector_ptr; - public: - typedef Polygon_iterator key_type; - typedef EPICK::Point_3 value_type; - typedef const value_type& reference; - typedef boost::readable_property_map_tag category; - - Polygon_soup_point_property_map() = default; - Polygon_soup_point_property_map(const Points* ptr) - : points_vector_ptr(ptr) - {} - - friend reference get(Polygon_soup_point_property_map map, - key_type polygon_it) - { - return (*map.points_vector_ptr)[*polygon_it->begin()]; - } - }; - - - class Polygon_soup_triangle_property_map { - const Points* points_vector_ptr; - public: - typedef Polygon_iterator key_type; - typedef EPICK::Triangle_3 value_type; - typedef value_type reference; - typedef boost::readable_property_map_tag category; - - Polygon_soup_triangle_property_map() = default; - Polygon_soup_triangle_property_map(const Points* ptr) - : points_vector_ptr(ptr) - {} - - friend value_type get(Polygon_soup_triangle_property_map map, - key_type polygon_it) - { - auto it = polygon_it->begin(); - CGAL_assertion(it != polygon_it->end()); - const auto id0 = *it++; - CGAL_assertion(it != polygon_it->end()); - const auto id1 = *it++; - CGAL_assertion(it != polygon_it->end()); - const auto id2 = *it++; - CGAL_assertion(it == polygon_it->end()); - - return value_type( (*map.points_vector_ptr)[id0], - (*map.points_vector_ptr)[id1], - (*map.points_vector_ptr)[id2] ); - } - }; - - struct AABB_primitive : - public CGAL::AABB_primitive - { - typedef CGAL::AABB_primitive Base; - - typedef Polygon_iterator Id; - - template - AABB_primitive(Id id, ObjectPmap&& opmap, PointPmap&& ppmap) - : Base(id, std::forward(opmap), std::forward(ppmap)) - {} - - template - AABB_primitive(Iterator it, ObjectPmap&& opmap, PointPmap&& ppmap) - : Base(*it, std::forward(opmap), std::forward(ppmap)) - {} - }; // end struct template AABB_primitive - - - typedef CGAL::AABB_traits AABB_traits; - typedef CGAL::AABB_tree AABB_tree; - - std::shared_ptr m_tree_ptr; - double m_offset_distance; - - typedef Polygon_soup_triangle_property_map ObjectPmap; - typedef Polygon_soup_point_property_map PointPmap; -public: - Polygon_soup_offset_function(const Scene_polygon_soup_item* soup, - const double offset_distance) - : m_tree_ptr - (std::make_shared(begin(soup->polygons()), - end(soup->polygons()), - ObjectPmap(&soup->points()), - PointPmap(&soup->points())) - ) - , m_offset_distance(offset_distance) - { - CGAL_assertion(! m_tree_ptr->empty() ); - } - - double operator()(const EPICK::Point_3& p) const - { - using CGAL::sqrt; - - EPICK::Point_3 closest_point = m_tree_ptr->closest_point(p); - double distance = sqrt(squared_distance(p, closest_point)); - - return m_offset_distance - distance; - } - -}; // end class Polygon_soup_offset_function - -} //end of CGAL namespace - -Scene_surface_mesh_item* make_item(SMesh* sm) -{ - return new Scene_surface_mesh_item(sm); -} - -CGAL::Offset_function -offset_function(SMesh* surface_mesh_ptr, double offset_value) { - return { *surface_mesh_ptr, offset_value }; -} - -CGAL::Polygon_soup_offset_function -offset_function(Scene_polygon_soup_item* item, double offset_value) { - return { item, offset_value }; -} - -template -struct Result_type { - typedef T type; -}; - -template <> -struct Result_type { - typedef SMesh type; -}; - -template -CGAL::Bbox_3 bbox(Mesh* mesh_ptr) { - return CGAL::Polygon_mesh_processing::bbox(*mesh_ptr); -} - -CGAL::Bbox_3 bbox(Scene_polygon_soup_item* item) { - return item->bbox(); -} -class MeshGuard{ - SMesh* mesh; - bool done; -public: - MeshGuard(SMesh* mesh):mesh(mesh), done(false){} - void setDone(){done = true;} - ~MeshGuard(){ - if(!done) - delete mesh; - } -}; -// declare the CGAL function -template -SMesh* cgal_off_meshing(QWidget*, - Mesh* tm_ptr, - Scene_polylines_item* polylines_item, - const double offset_value, - const double angle, - const double sizing, - const double approx, - const double edge_size, - int tag) -{ - typedef EPICK GT; - typedef CGAL::Labeled_mesh_domain_3 Mesh_domain_base; - typedef CGAL::Mesh_domain_with_polyline_features_3 Mesh_domain; - typedef C3t3::Triangulation Tr; - typedef CGAL::Mesh_criteria_3 Mesh_criteria; - typedef GT::Sphere_3 Sphere_3; - - CGAL::Bbox_3 bbox = ::bbox(tm_ptr); - - GT::Point_3 center((bbox.xmax()+bbox.xmin())/2, - (bbox.ymax()+bbox.ymin())/2, - (bbox.zmax()+bbox.zmin())/2); - double sqrad = 0.6 * std::sqrt( CGAL::square(bbox.xmax()-bbox.xmin())+ - CGAL::square(bbox.ymax()-bbox.ymin())+ - CGAL::square(bbox.zmax()-bbox.zmin()) ) - + offset_value; - sqrad=CGAL::square(sqrad); - - CGAL::Timer timer; - timer.start(); - - namespace p = CGAL::parameters; - - Mesh_domain domain = - Mesh_domain::create_implicit_mesh_domain - (p::function = offset_function(tm_ptr, offset_value), - p::bounding_object = Sphere_3(center, sqrad), - p::relative_error_bound = 1e-7, - p::construct_surface_patch_index = [](int i, int j) { return (i * 1000 + j); }); - - const CGAL::Mesh_facet_topology topology = CGAL::FACET_VERTICES_ON_SAME_SURFACE_PATCH; - auto manifold_option = p::non_manifold(); - if(tag == 1) manifold_option = p::manifold_with_boundary(); - if(tag == 2) manifold_option = p::manifold(); - Mesh_criteria criteria(p::facet_angle = angle, - p::facet_size = sizing, - p::facet_distance = approx, - p::facet_topology = topology, - p::edge_size = edge_size); - - if (polylines_item!=nullptr) - { - typedef std::vector Surface_patch_ids; - std::vector surface_patch_ids; - - domain.add_features_and_incidences(polylines_item->polylines.begin(), - polylines_item->polylines.end(), - CGAL::Identity_property_map(), - CGAL::Constant_property_map( - surface_patch_ids)); - } - - C3t3 c3t3 = CGAL::make_mesh_3(domain, criteria, - p::no_perturb(), - p::no_exude(), - manifold_option); - - const Tr& tr = c3t3.triangulation(); - - timer.stop(); - std::cerr << "done (" << timer.time() << " ms, " << tr.number_of_vertices() << " vertices)" << std::endl; - - if(tr.number_of_vertices() > 0) - { - typedef typename Result_type::type Result_mesh; - // add remesh as new polyhedron - Result_mesh *pRemesh = new Result_mesh; - //if the thread is interrupted before the mesh is returned, delete it. - MeshGuard guard(pRemesh); - CGAL::facets_in_complex_3_to_triangle_mesh(c3t3, *pRemesh); - guard.setDone(); - if(CGAL::is_closed(*pRemesh) - && ! CGAL::Polygon_mesh_processing::is_outward_oriented(*pRemesh)) - { - CGAL::Polygon_mesh_processing::reverse_face_orientations(*pRemesh); - } - - return pRemesh; - } - else - return nullptr; -} - -struct Mesher_thread:public QThread{ - Q_OBJECT - -private: - SMesh* sMesh; - Scene_polygon_soup_item* soup_item; - Scene_polylines_item* polylines_item; - const double offset_value; - const double angle; - const double sizing; - const double approx; - const double edge_size; - int tag_index; -public: - Mesher_thread( SMesh* tm_ptr, - Scene_polygon_soup_item* soup_item, - Scene_polylines_item* polylines_item, - const double offset_value, - const double angle, - const double sizing, - const double approx, - const double edge_size, - int tag) - :sMesh(tm_ptr), soup_item(soup_item), polylines_item(polylines_item), - offset_value(offset_value), angle(angle), - sizing(sizing), approx(approx), edge_size(edge_size), tag_index(tag){ - } - void run() override { - SMesh* new_mesh= nullptr; - if(soup_item) - new_mesh = cgal_off_meshing(CGAL::Three::Three::mainWindow(), - soup_item, - polylines_item, - offset_value, - angle, - sizing, - approx, - edge_size, - tag_index); - else - new_mesh = cgal_off_meshing(CGAL::Three::Three::mainWindow(), - sMesh, - polylines_item, - offset_value, - angle, - sizing, - approx, - edge_size, - tag_index); - CGAL::Three::Three::getMutex()->lock(); - CGAL::Three::Three::getWaitCondition()->wakeAll(); - CGAL::Three::Three::getMutex()->unlock(); - Q_EMIT resultReady(new_mesh); - } -Q_SIGNALS: - void resultReady(SMesh *new_mesh); -}; - -using namespace CGAL::Three; -class Polyhedron_demo_offset_meshing_plugin : - public QObject, - protected Polyhedron_demo_plugin_interface -{ - Q_OBJECT - Q_INTERFACES(CGAL::Three::Polyhedron_demo_plugin_interface) - Q_PLUGIN_METADATA(IID "com.geometryfactory.PolyhedronDemo.PluginInterface/1.0") - -public: - void init(QMainWindow* mainWindow, CGAL::Three::Scene_interface* scene_interface, Messages_interface*) { - this->scene = scene_interface; - this->mw = mainWindow; - actionOffsetMeshing = new QAction(tr("Offset Meshing"), mw); - actionOffsetMeshing->setProperty("subMenuName", "3D Surface Mesh Generation"); - if(actionOffsetMeshing) { - connect(actionOffsetMeshing, SIGNAL(triggered()), - this, SLOT(offset_meshing())); - } - - actionInflateMesh= new QAction(tr("Inflate Mesh"), mw); - actionInflateMesh->setProperty("subMenuName", "Operations on Polyhedra"); - if(actionInflateMesh) { - connect(actionInflateMesh, SIGNAL(triggered()), - this, SLOT(inflate_mesh())); - } - } - - bool applicable(QAction*) const { - if ( scene->selectionIndices().size() != 1 && - scene->selectionIndices().size() != 2 ) - { - return false; - } - - Q_FOREACH(CGAL::Three::Scene_interface::Item_id index, scene->selectionIndices()) - { - if ( qobject_cast(scene->item(index)) || - qobject_cast(scene->item(index)) ) - return true; - } - return false; - } - - QList actions() const { - return QList() << actionOffsetMeshing - << actionInflateMesh; - } -public Q_SLOTS: - void offset_meshing(); - void inflate_mesh(); - -private: - QAction* actionOffsetMeshing; - QAction* actionInflateMesh; - Scene_interface *scene; - QMainWindow *mw; -}; // end class Polyhedron_demo_offset_meshing_plugin - -void Polyhedron_demo_offset_meshing_plugin::inflate_mesh() -{ - const CGAL::Three::Scene_interface::Item_id index = scene->mainSelectionIndex(); - Scene_item* item = scene->item(index); - if(item == nullptr){ - return; - } - - Scene_surface_mesh_item* sm_item = - qobject_cast(item); - - if(sm_item == nullptr){ - return; - } - - SMesh* sMesh = sm_item->face_graph(); - - if(sMesh == nullptr){ - return; - } - - double diag = sm_item->diagonalBbox(); - double offset_value = QInputDialog::getDouble(mw, - QString("Choose Inflate Distance"), - QString("Inflate Distance (use negative number for deflate)"), - 0.1*diag, - -(std::numeric_limits::max)(), - (std::numeric_limits::max)(), 10); - - auto vpm = get(CGAL::vertex_point,*sMesh); - auto vnm = - sMesh->property_map("v:normal").first; - - for(const auto& v : vertices(*sMesh)) - { - Point_3 p = get(vpm, v); - EPICK::Vector_3 n = get(vnm, v); - n/=(CGAL::sqrt(n.squared_length())); - put(vpm, v, p + offset_value*n); - } - sm_item->invalidateOpenGLBuffers(); -} - -void Polyhedron_demo_offset_meshing_plugin::offset_meshing() -{ - Scene_surface_mesh_item* sm_item = nullptr; - Scene_polygon_soup_item* soup_item = nullptr; - Scene_polylines_item* polylines_item = nullptr; - Scene_item* item = nullptr; - - bool mesh_or_soup_item_found = false; - - Q_FOREACH(CGAL::Three::Scene_interface::Item_id index, scene->selectionIndices()) - { - if (!mesh_or_soup_item_found) - { - sm_item = qobject_cast(scene->item(index)); - if (sm_item == nullptr) - { - soup_item = qobject_cast(item); - if (soup_item != nullptr) - { - item=scene->item(index); - mesh_or_soup_item_found = true; - continue; - } - } - else - { - item=scene->item(index); - mesh_or_soup_item_found = true; - continue; - } - } - polylines_item = qobject_cast(scene->item(index)); - } - - SMesh* sMesh = nullptr; - double diag = 0; - Scene_item::Bbox box; - if(sm_item) - { - sMesh = sm_item->face_graph(); - if(!sMesh) - return; - box = bbox(sMesh); - } - else if(soup_item != nullptr) - { - box = bbox(soup_item); - } - else if(soup_item == nullptr) - return; - double X=(box.max)(0)-(box.min)(0), - Y = (box.max)(1)-(box.min)(1), - Z = (box.max)(2)-(box.min)(2); - diag = CGAL::sqrt(X*X+Y*Y+Z*Z); - double offset_value = QInputDialog::getDouble(mw, - QString("Choose Offset Value"), - QString("Offset Value (use negative number for inset)"), - 0.1*diag, - -(std::numeric_limits::max)(), - (std::numeric_limits::max)(), 10); - - QDialog dialog(mw); - Ui::Remeshing_dialog ui; - ui.setupUi(&dialog); - ui.angle->setRange(1.0, 30.0); - connect(ui.buttonBox, SIGNAL(accepted()), - &dialog, SLOT(accept())); - connect(ui.buttonBox, SIGNAL(rejected()), - &dialog, SLOT(reject())); - - ui.sizing->setRange(diag * 10e-6, // min - diag); // max - ui.sizing->setValue(diag * 0.05); // default value - - ui.approx->setRange(diag * 10e-7, // min - diag); // max - ui.approx->setValue(diag * 0.005); - - if (polylines_item!=nullptr) - { - ui.edge_sizing->setRange(diag * 10e-6, // min - diag); // max - ui.edge_sizing->setValue(diag * 0.05); // default value - } - else - ui.edge_sizing->setEnabled(false); - - int i = dialog.exec(); - if(i == QDialog::Rejected) - return; - - const double angle = ui.angle->value(); - const double approx = ui.approx->value(); - const double sizing = ui.sizing->value(); - const double edge_size=polylines_item!=nullptr?ui.edge_sizing->value():0; - const int tag_index = ui.tags->currentIndex(); - - if(tag_index < 0) return; - - QApplication::setOverrideCursor(Qt::WaitCursor); - - std::cerr << "mesh with:" - << "\n angle=" << angle - << "\n sizing=" << sizing - << "\n approx=" << approx - << "\n tag=" << tag_index - << std::boolalpha - << std::endl; - Mesher_thread* worker = nullptr; - if(soup_item) - worker = new Mesher_thread(nullptr, - soup_item, - polylines_item, - offset_value, - angle, - sizing, - approx, - edge_size, - tag_index); - else - worker = new Mesher_thread(sMesh, - nullptr, - polylines_item, - offset_value, - angle, - sizing, - approx, - edge_size, - tag_index); - connect(worker, &QThread::finished, worker, &QObject::deleteLater); - connect(worker, &Mesher_thread::resultReady, this, - [item, angle, sizing, approx, offset_value/* , index */] - (SMesh *new_mesh){ - QApplication::restoreOverrideCursor(); - if(!new_mesh){ - CGAL::Three::Three::getMutex()->lock(); - CGAL::Three::Three::isLocked() = false; - CGAL::Three::Three::getMutex()->unlock(); - return; - } - Scene_surface_mesh_item* new_item = new Scene_surface_mesh_item(new_mesh); - new_item->setName(tr("%1 offset %5 (%2 %3 %4)") - .arg(item->name()) - .arg(angle) - .arg(sizing) - .arg(approx) - .arg(offset_value)); - new_item->setColor(Qt::magenta); - new_item->setWireframeMode(); - CGAL::Three::Three::scene()->addItem(new_item); -// CGAL::Three::Three::scene()->itemChanged(index); - QApplication::restoreOverrideCursor(); - CGAL::Three::Three::getMutex()->lock(); - CGAL::Three::Three::isLocked() = false; - CGAL::Three::Three::getMutex()->unlock(); - }); - QMessageBox* message_box = new QMessageBox(QMessageBox::NoIcon, - "Meshing", - "Offset meshing in progress...", - QMessageBox::Cancel, - mw); - message_box->setDefaultButton(QMessageBox::Cancel); - QAbstractButton* cancelButton = message_box->button(QMessageBox::Cancel); - cancelButton->setText(tr("Stop")); - - connect(cancelButton, &QAbstractButton::clicked, - this, [worker](){ - worker->terminate(); - QApplication::restoreOverrideCursor();//waitcursor - QApplication::restoreOverrideCursor();//busycursor - }); - connect(worker, &Mesher_thread::finished, - message_box, &QMessageBox::close); - message_box->open(); - - QApplication::setOverrideCursor(Qt::BusyCursor); - CGAL::Three::Three::getMutex()->lock(); - CGAL::Three::Three::isLocked() = true; - CGAL::Three::Three::getMutex()->unlock(); - worker->start(); -} - -#include "Offset_meshing_plugin.moc" - -#endif // CGAL_POLYHEDRON_DEMO_USE_SURFACE_MESHER