diff --git a/Data/data/polylines_2/Archimedean_spiral.svg b/Data/data/polylines_2/Archimedean_spiral.svg
new file mode 100644
index 00000000000..c2481821d07
--- /dev/null
+++ b/Data/data/polylines_2/Archimedean_spiral.svg
@@ -0,0 +1,40 @@
+
+
diff --git a/Data/data/polylines_2/nano.svg b/Data/data/polylines_2/nano.svg
new file mode 100644
index 00000000000..15635a16f74
--- /dev/null
+++ b/Data/data/polylines_2/nano.svg
@@ -0,0 +1,27 @@
+
+
+
+
diff --git a/Documentation/doc/Documentation/packages.txt b/Documentation/doc/Documentation/packages.txt
index 9b1fa73107e..e8a1738d1ee 100644
--- a/Documentation/doc/Documentation/packages.txt
+++ b/Documentation/doc/Documentation/packages.txt
@@ -118,6 +118,7 @@
\package_listing{Surface_mesh_shortest_path}
\package_listing{Surface_mesh_skeletonization}
\package_listing{Surface_mesh_approximation}
+\package_listing{Vector_graphics_on_surfaces}
\package_listing{Ridges_3}
\package_listing{Jet_fitting_3}
\package_listing{Point_set_3}
diff --git a/Installation/cmake/modules/CGAL_NanoSVG_support.cmake b/Installation/cmake/modules/CGAL_NanoSVG_support.cmake
new file mode 100644
index 00000000000..b1db19f3152
--- /dev/null
+++ b/Installation/cmake/modules/CGAL_NanoSVG_support.cmake
@@ -0,0 +1,6 @@
+if(NanoSVG_FOUND AND NOT TARGET CGAL::NanoSVG_support)
+ add_library(CGAL::NanoSVG_support INTERFACE IMPORTED)
+ set_target_properties(CGAL::NanoSVG_support PROPERTIES
+ INTERFACE_COMPILE_DEFINITIONS "CGAL_NANOSVG_ENABLED")
+ target_link_libraries(CGAL::NanoSVG_support INTERFACE NanoSVG::nanosvg)
+endif()
diff --git a/Installation/include/CGAL/license/Vector_graphics_on_surfaces.h b/Installation/include/CGAL/license/Vector_graphics_on_surfaces.h
new file mode 100644
index 00000000000..32ca4b440c5
--- /dev/null
+++ b/Installation/include/CGAL/license/Vector_graphics_on_surfaces.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2016 GeometryFactory SARL (France).
+// All rights reserved.
+//
+// This file is part of CGAL (www.cgal.org)
+//
+// $URL$
+// $Id$
+// SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial
+//
+// Author(s) : Andreas Fabri
+//
+// Warning: this file is generated, see include/CGAL/license/README.md
+
+#ifndef CGAL_LICENSE_VECTOR_GRAPHICS_ON_SURFACES_H
+#define CGAL_LICENSE_VECTOR_GRAPHICS_ON_SURFACES_H
+
+#include
+#include
+
+#ifdef CGAL_VECTOR_GRAPHICS_ON_SURFACES_COMMERCIAL_LICENSE
+
+# if CGAL_VECTOR_GRAPHICS_ON_SURFACES_COMMERCIAL_LICENSE < CGAL_RELEASE_DATE
+
+# if defined(CGAL_LICENSE_WARNING)
+
+ CGAL_pragma_warning("Your commercial license for CGAL does not cover "
+ "this release of the Vector_graphics_on_surfaces package.")
+# endif
+
+# ifdef CGAL_LICENSE_ERROR
+# error "Your commercial license for CGAL does not cover this release \
+ of the Vector_graphics_on_surfaces package. \
+ You get this error, as you defined CGAL_LICENSE_ERROR."
+# endif // CGAL_LICENSE_ERROR
+
+# endif // CGAL_VECTOR_GRAPHICS_ON_SURFACES_COMMERCIAL_LICENSE < CGAL_RELEASE_DATE
+
+#else // no CGAL_VECTOR_GRAPHICS_ON_SURFACES_COMMERCIAL_LICENSE
+
+# if defined(CGAL_LICENSE_WARNING)
+ CGAL_pragma_warning("\nThe macro CGAL_VECTOR_GRAPHICS_ON_SURFACES_COMMERCIAL_LICENSE is not defined."
+ "\nYou use the CGAL Vector_graphics_on_surfaces package under "
+ "the terms of the GPLv3+.")
+# endif // CGAL_LICENSE_WARNING
+
+# ifdef CGAL_LICENSE_ERROR
+# error "The macro CGAL_VECTOR_GRAPHICS_ON_SURFACES_COMMERCIAL_LICENSE is not defined.\
+ You use the CGAL Vector_graphics_on_surfaces package under the terms of \
+ the GPLv3+. You get this error, as you defined CGAL_LICENSE_ERROR."
+# endif // CGAL_LICENSE_ERROR
+
+#endif // no CGAL_VECTOR_GRAPHICS_ON_SURFACES_COMMERCIAL_LICENSE
+
+#endif // CGAL_LICENSE_VECTOR_GRAPHICS_ON_SURFACES_H
diff --git a/Installation/include/CGAL/license/gpl_package_list.txt b/Installation/include/CGAL/license/gpl_package_list.txt
index 75ff9d5c9ed..6d7bd6ea978 100644
--- a/Installation/include/CGAL/license/gpl_package_list.txt
+++ b/Installation/include/CGAL/license/gpl_package_list.txt
@@ -106,5 +106,6 @@ Triangulation_2 2D Triangulation
Triangulation_3 3D Triangulations
Triangulation dD Triangulations
Triangulation_on_sphere_2 2D Triangulation on Sphere
+Vector_graphics_on_surfaces
Visibility_2 2D Visibility Computation
Voronoi_diagram_2 2D Voronoi Diagram Adaptor
diff --git a/Lab/demo/Lab/CMakeLists.txt b/Lab/demo/Lab/CMakeLists.txt
index eb2a5dc56b6..d94c08e37bb 100644
--- a/Lab/demo/Lab/CMakeLists.txt
+++ b/Lab/demo/Lab/CMakeLists.txt
@@ -274,6 +274,11 @@ if(CGAL_Qt6_FOUND AND Qt6_FOUND)
add_item(scene_edit_box_item Plugins/PCA/Scene_edit_box_item.cpp)
+ if (CGAL_Eigen3_support)
+ add_item(locally_shortest_path_item Plugins/Bsurf/Locally_shortest_path_item.cpp)
+ target_link_libraries(locally_shortest_path_item PUBLIC scene_surface_mesh_item scene_polylines_item CGAL::Eigen3_support)
+ endif()
+
add_item(scene_image_item Scene_image_item.cpp)
add_item(scene_surface_mesh_item Scene_surface_mesh_item.cpp)
diff --git a/Lab/demo/Lab/Plugins/Bsurf/CMakeLists.txt b/Lab/demo/Lab/Plugins/Bsurf/CMakeLists.txt
new file mode 100644
index 00000000000..9c3c4f1dbab
--- /dev/null
+++ b/Lab/demo/Lab/Plugins/Bsurf/CMakeLists.txt
@@ -0,0 +1,9 @@
+include(CGALlab_macros)
+
+if(TARGET CGAL::Eigen3_support)
+ cgal_lab_plugin(locally_shortest_path_plugin Locally_shortest_path_plugin)
+ target_link_libraries(locally_shortest_path_plugin PUBLIC locally_shortest_path_item
+ scene_surface_mesh_item
+ scene_polylines_item
+ CGAL::Eigen3_support)
+endif()
diff --git a/Lab/demo/Lab/Plugins/Bsurf/Locally_shortest_path_item.cpp b/Lab/demo/Lab/Plugins/Bsurf/Locally_shortest_path_item.cpp
new file mode 100644
index 00000000000..224d4aaf838
--- /dev/null
+++ b/Lab/demo/Lab/Plugins/Bsurf/Locally_shortest_path_item.cpp
@@ -0,0 +1,869 @@
+#include "Locally_shortest_path_item.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+
+using namespace CGAL::Three;
+
+namespace VGoS = CGAL::Vector_graphics_on_surfaces;
+namespace PMP = CGAL::Polygon_mesh_processing;
+
+typedef Viewer_interface Vi;
+typedef Triangle_container Tc;
+typedef Edge_container Ec;
+typedef Locally_shortest_path_item_priv Priv;
+
+
+typedef Scene_surface_mesh_item::Face_graph Mesh;
+typedef CGAL::AABB_face_graph_triangle_primitive Primitive;
+typedef CGAL::AABB_traits_3 Traits;
+typedef CGAL::AABB_tree Tree;
+
+// TODO: update
+struct Locally_shortest_path_item::vertex{
+ int id;
+ double x;
+ double y;
+ double z;
+
+ CGAL::Point_3 position()const
+ {
+ return CGAL::Point_3(x,y,z);
+ }
+ double operator[](int i)
+ {
+ switch(i)
+ {
+ case 0:
+ return x;
+ case 1:
+ return y;
+ case 2:
+ return z;
+ default:
+ return 0;
+ }
+ }
+
+ template
+ void set(const P& p, int id_)
+ {
+ x=p.x();
+ y=p.y();
+ z=p.z();
+ id=id_;
+ }
+};
+
+struct Locally_shortest_path_item_priv{
+ typedef CGAL::Simple_cartesian Kernel;
+ enum Face_containers{
+ Faces = 0,
+ S_Faces,
+ Spheres,
+ S_Spheres,
+ P_Spheres,
+ P_Faces,
+ Nbf
+ };
+
+ enum Line_containers{
+ Edges = 0,
+ S_Edges,
+ P_Edges,
+ Nbe
+ };
+
+ enum HL_Primitive{
+ VERTEX=0,
+ EDGE,
+ FACE,
+ NO_TYPE
+ };
+
+ Locally_shortest_path_item_priv(const CGAL::Three::Scene_interface* scene_interface,
+ const Scene_surface_mesh_item* sm_item,
+ Scene_polylines_item* polyline_item,
+ std::size_t nb_pts,
+ Locally_shortest_path_item* ebi)
+ {
+ const CGAL::qglviewer::Vec offset = static_cast(CGAL::QGLViewer::QGLViewerPool().first())->offset();
+ ready_to_hl = true;
+ scene = scene_interface;
+ mesh_item = sm_item;
+ spath_item=polyline_item;
+ spath_item->polylines.resize(1);
+ const Mesh& mesh = *mesh_item->face_graph();
+ aabb_tree = Tree(faces(mesh).first,
+ faces(mesh).second,
+ mesh);
+ item = ebi;
+ selection_on = false;
+ Scene_item::Bbox bb = scene->bbox();
+ double x=(bb.xmin()+bb.xmax())/2;
+ double y=(bb.ymin()+bb.ymax())/2;
+ double z=(bb.zmin()+bb.zmax())/2;
+ center_ = CGAL::qglviewer::Vec(x,y,z);
+ relative_center_ = CGAL::qglviewer::Vec(0,0,0);
+ remodel_frame = new Scene_item::ManipulatedFrame();
+ remodel_frame->setTranslationSensitivity(1.0);
+ frame = new Scene_item::ManipulatedFrame();
+ frame->setPosition(center_+offset);
+ frame->setSpinningSensitivity(100.0); //forbid spinning
+ constraint.setRotationConstraintType(CGAL::qglviewer::AxisPlaneConstraint::AXIS);
+ constraint.setTranslationConstraintType(CGAL::qglviewer::AxisPlaneConstraint::FREE);
+ constraint.setRotationConstraintDirection(CGAL::qglviewer::Vec(.0,.0,.1));
+ frame->setConstraint(&constraint);
+ //create the sphere model
+ vertex_spheres.resize(0);
+ normal_spheres.resize(0);
+ create_flat_sphere(1.0f, vertex_spheres, normal_spheres,10);
+
+
+ // TODO: change the default
+ if (nb_pts==2)
+ {
+ //shortest_path
+ vertices.resize(2);
+ vertices[0].set( mesh.point(*mesh.vertices().begin()), 0 );
+ vertices[1].set( mesh.point(*std::next(mesh.vertices().begin())), 1 );
+ locations.resize(2);
+ }
+ else if (nb_pts==4)
+ {
+ // bezier
+ vertices.resize(4);
+ vertices[0].set( mesh.point(*mesh.vertices().begin()), 0 );
+ vertices[1].set( mesh.point(*std::next(mesh.vertices().begin())), 1 );
+ vertices[2].set( mesh.point(*std::next(mesh.vertices().begin(),2)), 2 );
+ vertices[3].set( mesh.point(*std::next(mesh.vertices().begin(),3)), 3 );
+ locations.resize(4);
+ }
+
+
+ vertex_faces.resize(0);
+ normal_faces.resize(0);
+
+ reset_selection();
+ last_picked_id = -1;
+ last_picked_type = -1;
+ QPixmap pix(":/cgal/cursors/resources/rotate_around_cursor.png");
+ rotate_cursor = QCursor(pix);
+
+#ifndef CGAL_BSURF_USE_DIJKSTRA_SP
+ VGoS::init_geodesic_dual_solver(geodesic_solver, mesh);
+#endif
+ }
+
+ ~Locally_shortest_path_item_priv(){
+ delete frame;
+ delete remodel_frame;
+ }
+
+ mutable std::vector vertex_edges;
+ mutable std::vector color_edges;
+ mutable std::vector vertex_spheres;
+ mutable std::vector normal_spheres;
+ mutable std::vector center_spheres;
+ mutable std::vector color_spheres;
+ mutable std::vector vertex_faces;
+ mutable std::vector normal_faces;
+ mutable std::vector color_faces;
+ mutable std::vector hl_vertex;
+ mutable std::vector hl_normal;
+
+ bool ready_to_hl;
+
+ CGAL::qglviewer::ManipulatedFrame* frame;
+ CGAL::qglviewer::ManipulatedFrame* remodel_frame;
+ CGAL::qglviewer::Vec rf_last_pos;
+ CGAL::qglviewer::LocalConstraint constraint;
+ CGAL::qglviewer::Vec center_;
+ CGAL::qglviewer::Vec relative_center_;
+
+ mutable std::vector vertices;
+ mutable std::vector> locations;
+ std::vector selected_vertices;
+
+ void reset_selection();
+ bool selection_on;
+ void picking(int& type, int& id, Viewer_interface *viewer);
+
+ void initializeBuffers(Viewer_interface *viewer)const;
+
+ void computeElements() const;
+ void draw_picking(Viewer_interface*);
+ void update_points(const QVector3D &dir);
+
+ const Scene_interface* scene;
+ const Scene_surface_mesh_item* mesh_item;
+ Scene_polylines_item* spath_item;
+ Locally_shortest_path_item* item;
+ Tree aabb_tree;
+ QPoint picked_pixel;
+ HL_Primitive hl_type;
+ int last_picked_id;
+ int last_picked_type;
+ QCursor rotate_cursor;
+ bool path_invalidated=true;
+#ifndef CGAL_BSURF_USE_DIJKSTRA_SP
+ VGoS::Dual_geodesic_solver geodesic_solver;
+#endif
+};
+
+Locally_shortest_path_item::Locally_shortest_path_item(const CGAL::Three::Scene_interface* scene_interface,
+ const Scene_surface_mesh_item *sm_item,
+ Scene_polylines_item* polyline_item,
+ std::size_t nb_pts)
+{
+ d = new Locally_shortest_path_item_priv(scene_interface, sm_item, polyline_item, nb_pts, this);
+
+ are_buffers_filled = false;
+
+ for(CGAL::QGLViewer* v : CGAL::QGLViewer::QGLViewerPool())
+ {
+ v->setMouseTracking(true);
+ }
+ connect(Three::mainWindow(), SIGNAL(newViewerCreated(QObject*)),
+ this, SLOT(connectNewViewer(QObject*)));
+
+ setTriangleContainer(Priv::P_Faces , new Tc(Vi::PROGRAM_NO_SELECTION, false));
+ setTriangleContainer(Priv::Faces , new Tc(Vi::PROGRAM_WITH_LIGHT, false));
+ setTriangleContainer(Priv::S_Faces , new Tc(Vi::PROGRAM_WITH_LIGHT, false));
+ setTriangleContainer(Priv::Spheres , new Tc(Vi::PROGRAM_SPHERES, false));
+ setTriangleContainer(Priv::S_Spheres, new Tc(Vi::PROGRAM_SPHERES, false));
+ setTriangleContainer(Priv::P_Spheres, new Tc(Vi::PROGRAM_DARK_SPHERES, false));
+
+
+ for(int i=Priv::Nbe-1; i>=0; --i)
+ {
+ setEdgeContainer(i,
+ new Ec(Three::mainViewer()->isOpenGL_4_3()
+ ? Vi::PROGRAM_SOLID_WIREFRAME
+ : Vi::PROGRAM_NO_SELECTION,
+ false));
+ }
+ contextMenu();
+}
+QString Locally_shortest_path_item::toolTip() const
+{
+ std::stringstream ss;
+ ss << "Face locations:
";
+ ss << std::setprecision(17);
+ for (auto fl : d->locations)
+ {
+ ss << " - " << fl.first << " (" << fl.second[0] << "," << fl.second[1] << "," << fl.second[2] << ")
";
+ }
+ return QString::fromStdString(ss.str());
+}
+
+
+QMenu* Locally_shortest_path_item::contextMenu()
+{
+ // disable "Alpha slider" in menu
+ QMenu* resMenu = Scene_item::contextMenu();
+ bool prop = property("menu_changed").toBool();
+ if(!prop)
+ {
+ setProperty("menu_changed", true);
+ }
+ return resMenu;
+}
+
+void Locally_shortest_path_item::drawSpheres(Viewer_interface *viewer, const QMatrix4x4 f_matrix ) const
+{
+ GLdouble d_mat[16];
+ QMatrix4x4 mv_mat;
+ viewer->camera()->getModelViewMatrix(d_mat);
+ for (int i=0; i<16; ++i)
+ mv_mat.data()[i] = GLfloat(d_mat[i]);
+ mv_mat = mv_mat*f_matrix;
+ // TODO: must depend on the mesh (and zoom?)
+ double radius = 0.01 ;
+
+ Tc* tc = getTriangleContainer(Priv::Spheres);
+ tc->setFrameMatrix(f_matrix);
+ tc->setMvMatrix(mv_mat);
+ tc->setClipping(false);
+ tc->getVao(viewer)->bind();
+ tc->getVao(viewer)->program->setAttributeValue("radius",radius);
+ tc->getVao(viewer)->release();
+ tc->setColor(QColor(Qt::red));
+ tc->draw(viewer, true);
+}
+
+void Locally_shortest_path_item::drawPath() const
+{
+ if (!d->path_invalidated) return;
+ typedef PMP::Edge_location Edge_location;
+ typedef PMP::Face_location Face_location;
+
+ const Mesh& mesh = *d->mesh_item->face_graph();
+
+ if (d->vertices.size()==2)
+ {
+ std::vector edge_locations;
+ CGAL::Epick::Point_3 src_pt(d->vertices[0].x,d->vertices[0].y,d->vertices[0].z),
+ tgt_pt(d->vertices[1].x,d->vertices[1].y,d->vertices[1].z);
+
+ //TODO store that in the vector vertices
+ d->locations[0] = PMP::locate_with_AABB_tree(src_pt, d->aabb_tree, mesh);
+ d->locations[1] = PMP::locate_with_AABB_tree(tgt_pt, d->aabb_tree, mesh);
+
+ VGoS::locally_shortest_path(d->locations[0], d->locations[1], mesh, edge_locations, d->geodesic_solver);
+ d->spath_item->polylines.back().clear();
+ d->spath_item->polylines.back().push_back(src_pt);
+ for (auto el : edge_locations)
+ d->spath_item->polylines.back().push_back(PMP::construct_point(el, mesh));
+ d->spath_item->polylines.back().push_back(tgt_pt);
+ d->spath_item->setRenderingMode(Wireframe);
+ d->spath_item->invalidateOpenGLBuffers();
+ }
+ else if (d->vertices.size()==4)
+ {
+ std::vector edge_locations;
+ CGAL::Epick::Point_3 c1_pt(d->vertices[0].x,d->vertices[0].y,d->vertices[0].z),
+ c2_pt(d->vertices[1].x,d->vertices[1].y,d->vertices[1].z),
+ c3_pt(d->vertices[2].x,d->vertices[2].y,d->vertices[2].z),
+ c4_pt(d->vertices[3].x,d->vertices[3].y,d->vertices[3].z);
+
+ //TODO store that in the vector vertices
+ d->locations[0] = PMP::locate_with_AABB_tree(c1_pt, d->aabb_tree, mesh);
+ d->locations[1] = PMP::locate_with_AABB_tree(c2_pt, d->aabb_tree, mesh);
+ d->locations[2] = PMP::locate_with_AABB_tree(c3_pt, d->aabb_tree, mesh);
+ d->locations[3] = PMP::locate_with_AABB_tree(c4_pt, d->aabb_tree, mesh);
+
+ VGoS::Bezier_segment control_points=CGAL::make_array(d->locations[0],
+ d->locations[1],
+ d->locations[2],
+ d->locations[3]);
+
+ std::vector face_locations =
+ VGoS::recursive_de_Casteljau(mesh, control_points, 8, d->geodesic_solver);
+
+ // TODO: we should connect points with geodesics and not segments
+ d->spath_item->polylines.back().clear();
+ for (auto fl : face_locations)
+ d->spath_item->polylines.back().push_back(PMP::construct_point(fl, mesh));
+ d->spath_item->setRenderingMode(Wireframe);
+ d->spath_item->invalidateOpenGLBuffers();
+ }
+ d->path_invalidated=false;
+}
+
+void Locally_shortest_path_item::draw(Viewer_interface *viewer) const
+{
+ if(!isInit(viewer))
+ initGL(viewer);
+ if ( getBuffersFilled() &&
+ ! getBuffersInit(viewer))
+ {
+ initializeBuffers(viewer);
+ setBuffersInit(viewer, true);
+ }
+ if(!getBuffersFilled())
+ {
+ computeElements();
+ initializeBuffers(viewer);
+ }
+ QMatrix4x4 f_matrix;
+ for (int i=0; i<16; ++i){
+ f_matrix.data()[i] = (float)d->frame->matrix()[i];
+ }
+
+ drawSpheres(viewer, f_matrix);
+ drawHl(viewer);
+
+ drawPath();
+}
+
+void Locally_shortest_path_item::compute_bbox() const
+{
+ double xmin=d->vertices[0].x, xmax=xmin;
+ double ymin=d->vertices[0].y, ymax=ymin;
+ double zmin=d->vertices[0].z, zmax=zmin;
+ for(std::size_t i=0; ivertices.size(); ++i)
+ {
+ xmin=(std::min)(xmin, d->vertices[i].x);
+ ymin=(std::min)(ymin, d->vertices[i].y);
+ zmin=(std::min)(zmin, d->vertices[i].z);
+ xmax=(std::max)(xmax, d->vertices[i].x);
+ ymax=(std::max)(ymax, d->vertices[i].y);
+ zmax=(std::max)(zmax, d->vertices[i].z);
+ }
+
+ setBbox(Scene_item::Bbox(xmin, ymin, zmin,xmax, ymax, zmax));
+}
+
+void Locally_shortest_path_item_priv::computeElements() const
+{
+ vertex_edges.clear();
+ vertex_faces.clear();
+ normal_faces.clear();
+ center_spheres.clear();
+ color_edges.clear();
+ color_faces.clear();
+ color_spheres.clear();
+
+ for (std::size_t i=0; isetMouseTracking(false);
+
+ delete d;
+}
+
+// Indicate if rendering mode is supported
+bool Locally_shortest_path_item::supportsRenderingMode(RenderingMode m) const {
+ return m==FlatPlusEdges;
+}
+
+Scene_item::ManipulatedFrame*
+Locally_shortest_path_item::manipulatedFrame()
+{
+ return d->frame;
+}
+
+void Locally_shortest_path_item::highlight(Viewer_interface *viewer)
+{
+ d->ready_to_hl = true;
+ viewer->makeCurrent();
+ int type = -1, id = -1;
+ //pick
+ if(!d->selection_on)
+ {
+ d->picking(type, id, viewer);
+ d->last_picked_id = id;
+ d->last_picked_type = type;
+ }
+ //highlight
+ d->hl_normal.clear();
+ d->hl_vertex.clear();
+ if(type !=-1)
+ {
+ switch(d->last_picked_type)
+ {
+ case 0:
+ {
+ //compute
+ d->hl_vertex.push_back(d->vertices[d->last_picked_id].x);
+ d->hl_vertex.push_back(d->vertices[d->last_picked_id].y);
+ d->hl_vertex.push_back(d->vertices[d->last_picked_id].z);
+ //fill buffers
+ Tc* tc = getTriangleContainer(Priv::S_Spheres);
+ tc->reset_vbos(ALL);
+ tc->allocate(
+ Tc::Flat_vertices,
+ d->vertex_spheres.data(),
+ static_cast(d->vertex_spheres.size()*sizeof(float)));
+
+ tc->allocate(
+ Tc::Flat_normals,
+ d->normal_spheres.data(),
+ static_cast(d->normal_spheres.size()*sizeof(float)));
+
+ tc->allocate(
+ Tc::Facet_centers,
+ d->hl_vertex.data(),
+ static_cast(d->hl_vertex.size()*sizeof(float)));
+
+ tc->setFlatDataSize(d->vertex_spheres.size());
+ tc->setCenterSize(d->hl_vertex.size());
+ //draw
+ d->hl_type = Locally_shortest_path_item_priv::VERTEX;
+ for(CGAL::QGLViewer* v : CGAL::QGLViewer::QGLViewerPool())
+ {
+ CGAL::Three::Viewer_interface* viewer =
+ static_cast(v);
+ tc->initializeBuffers(viewer);
+ }
+ break;
+ }
+ default:
+ d->hl_type = Locally_shortest_path_item_priv::NO_TYPE;
+ break;
+ }
+ }
+ else
+ clearHL();
+ redraw();
+
+ d->ready_to_hl = false;
+}
+
+void Locally_shortest_path_item::clearHL()
+{
+ Viewer_interface* viewer = dynamic_cast(*CGAL::QGLViewer::QGLViewerPool().begin());
+ viewer->makeCurrent();
+ d->hl_normal.clear();
+ d->hl_vertex.clear();
+
+ Tc* tc = getTriangleContainer(Priv::S_Spheres);
+ tc->reset_vbos(ALL);
+ tc->allocate(Tc::Flat_vertices, d->vertex_spheres.data(),
+ static_cast(d->vertex_spheres.size()*sizeof(float)));
+ tc->allocate(Tc::Flat_normals,
+ d->normal_spheres.data(),
+ static_cast(d->normal_spheres.size()*sizeof(float)));
+
+ tc->allocate(Tc::Facet_centers, nullptr, 0);
+
+ tc->initializeBuffers(viewer);
+ tc->setFlatDataSize(0);
+ tc->setCenterSize(0);
+ //draw
+ Ec* ec = getEdgeContainer(Priv::S_Edges);
+ ec->reset_vbos(ALL);
+ ec->allocate(Ec::Vertices, nullptr, 0);
+ ec->initializeBuffers(viewer);
+ ec->setFlatDataSize(0);
+
+ tc = getTriangleContainer(Priv::S_Faces);
+ tc->reset_vbos(ALL);
+ tc->allocate(Tc::Flat_vertices, nullptr, 0);
+ tc->allocate(Tc::Flat_normals, nullptr, 0);
+ tc->initializeBuffers(viewer);
+ tc->setFlatDataSize(0);
+ d->hl_type = Locally_shortest_path_item_priv::NO_TYPE;
+
+ itemChanged();
+
+}
+void Locally_shortest_path_item_priv::reset_selection()
+{
+ selection_on = false;
+ CGAL::QGLViewer* viewer = *CGAL::QGLViewer::QGLViewerPool().begin();
+ viewer->setManipulatedFrame(frame);
+ viewer->setMouseBinding(Qt::ShiftModifier, Qt::LeftButton, CGAL::qglviewer::SELECT);
+ constraint.setTranslationConstraintType(CGAL::qglviewer::AxisPlaneConstraint::FREE);
+ selected_vertices.clear();
+}
+
+//intercept events for picking
+bool Locally_shortest_path_item::eventFilter(QObject *obj, QEvent *event)
+{
+ if(!visible())
+ return false;
+ Viewer_interface* viewer = qobject_cast(obj);
+ if(!viewer)
+ return false;
+ if(event->type() == QEvent::MouseButtonPress)
+ {
+ QMouseEvent* e = static_cast(event);
+ if(e->modifiers() == Qt::NoModifier)
+ {
+ //pick
+ int type, picked;
+ d->picked_pixel = e->pos();
+ d->picking(type, picked, viewer);
+ viewer->makeCurrent();
+ if(type == 0)
+ {
+ bool found = false;
+ QApplication::setOverrideCursor(Qt::DragMoveCursor);
+ CGAL::qglviewer::Vec pos = viewer->camera()->pointUnderPixel(d->picked_pixel, found);
+ if(found)
+ {
+ d->rf_last_pos = pos;
+ d->remodel_frame->setPosition(pos);
+ }
+
+ d->selection_on = true;
+ d->selected_vertices.push_back(&d->vertices[picked]);
+ d->constraint.setTranslationConstraintType(CGAL::qglviewer::AxisPlaneConstraint::FREE);
+ d->remodel_frame->setConstraint(&d->constraint);
+
+ viewer->setManipulatedFrame(d->remodel_frame);
+ viewer->setMouseBinding(
+ Qt::NoModifier,
+ Qt::LeftButton,
+ CGAL::qglviewer::FRAME,
+ CGAL::qglviewer::TRANSLATE);
+ }
+ else
+ {
+ d->reset_selection();
+ }
+ }
+ return false;
+ }
+ else if(event->type() == QEvent::MouseMove)
+ {
+ QMouseEvent* e = static_cast(event);
+ if(e->modifiers() == Qt::NoModifier)
+ {
+ if(d->selection_on)
+ {
+ d->remodel_frame->setOrientation(d->frame->orientation());
+ CGAL::qglviewer::Vec td(d->remodel_frame->position() - d->rf_last_pos);
+ QVector3D dir(td.x, td.y, td.z);
+ d->update_points(dir);
+ d->rf_last_pos=d->remodel_frame->position();
+ d->path_invalidated=true;
+ }
+ d->ready_to_hl= true;
+ d->picked_pixel = e->pos();
+ QTimer::singleShot(0, this,
+ [this, viewer](){
+ highlight(viewer);
+ });
+ }
+ else if(e->modifiers() == Qt::ControlModifier &&
+ e->buttons() == Qt::LeftButton)
+ {
+ QApplication::setOverrideCursor(d->rotate_cursor);
+ }
+ else if(d->selection_on)
+ {
+ d->reset_selection();
+ }
+ d->picked_pixel = e->pos();
+ return false;
+ }
+ else if(event->type() == QEvent::MouseButtonRelease)
+ {
+ d->reset_selection();
+ QApplication::setOverrideCursor(QCursor());
+ viewer->setMouseBinding(
+ Qt::NoModifier,
+ Qt::LeftButton,
+ CGAL::qglviewer::CAMERA,
+ CGAL::qglviewer::ROTATE);
+ }
+ else if(event->type() == QEvent::KeyRelease)
+ {
+ QKeyEvent* e = static_cast(event);
+ if(e->key() == Qt::Key_Control)
+ {
+ QApplication::setOverrideCursor(QCursor());
+ }
+ }
+ return false;
+}
+
+void Locally_shortest_path_item_priv::draw_picking(Viewer_interface* viewer)
+{
+
+ QMatrix4x4 f_matrix;
+ for (int i=0; i<16; ++i){
+ f_matrix.data()[i] = (float)frame->matrix()[i];
+ }
+ GLdouble d_mat[16];
+ QMatrix4x4 mv_mat;
+ viewer->camera()->getModelViewMatrix(d_mat);
+ for (int i=0; i<16; ++i)
+ mv_mat.data()[i] = GLfloat(d_mat[i]);
+ mv_mat = mv_mat*f_matrix;
+
+ // TODO: radius
+ double radius = 0.01 ;
+ Tc* tc = item->getTriangleContainer(P_Spheres);
+ tc->setFrameMatrix(f_matrix);
+ tc->setClipping(false);
+ tc->getVao(viewer)->bind();
+ tc->getVao(viewer)->program->setAttributeValue("radius", (float)radius);
+ tc->getVao(viewer)->release();
+ tc->draw(viewer, false);
+}
+
+void Locally_shortest_path_item_priv::update_points(const QVector3D &dir)
+{
+ CGAL::qglviewer::AxisPlaneConstraint::Type prev_cons = constraint.translationConstraintType();
+ constraint.setTranslationConstraintType(CGAL::qglviewer::AxisPlaneConstraint::FREE);
+ for(Locally_shortest_path_item::vertex* selected_vertex: selected_vertices )
+ {
+ int id = selected_vertex->id;
+ CGAL_assume(id<8 && id >=0);
+ double x = selected_vertex->x + dir.x();
+ double y = selected_vertex->y + dir.y();
+ double z = selected_vertex->z + dir.z();
+
+ // project point onto the mesh
+ auto p = aabb_tree.closest_point(CGAL::Epick::Point_3(x,y,z));
+ selected_vertex->x = p.x();
+ selected_vertex->y = p.y();
+ selected_vertex->z = p.z();
+ }
+ item->invalidateOpenGLBuffers();
+ constraint.setTranslationConstraintType(prev_cons);
+}
+
+//type : 0 = vertex, 1 = edge, 2 = face
+void Locally_shortest_path_item_priv::picking(int& type, int& id, Viewer_interface *viewer)
+{
+ viewer->makeCurrent();
+ type = -1;
+ id = -1;
+ int deviceWidth = viewer->camera()->screenWidth();
+ int deviceHeight = viewer->camera()->screenHeight();
+ QOpenGLFramebufferObject* fbo = new QOpenGLFramebufferObject(deviceWidth, deviceHeight,QOpenGLFramebufferObject::Depth);
+ fbo->bind();
+ viewer->glEnable(GL_DEPTH_TEST);
+ viewer->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ QColor bgColor(viewer->backgroundColor());
+ //draws the image in the fbo
+ viewer->setBackgroundColor(::Qt::white);
+ draw_picking(viewer);
+
+ const auto buffer = read_pixel_as_ubyte_rgba(picked_pixel, viewer, viewer->camera());
+ //decode ID and pick (don't forget the case nothing is picked
+ if(!(buffer[0]==buffer[1] && buffer[1]==buffer[2]))
+ {
+ int r(std::ceil((buffer[0]-10)/20)), g(std::ceil((buffer[1]-10)/20)), b(std::ceil((buffer[2]-10)/20));
+ id = (std::max)(r,g);
+ id = (std::max)(id,b);
+ if(buffer[0] > 0)
+ {
+ if(id <8)
+ type = 0 ;
+ }
+ else if(buffer[1] > 0)
+ {
+ if(id <12)
+ {
+ type = 1;
+ }
+ }
+ else if(buffer[2] > 0)
+ {
+ if(id <6)
+ {
+ type = 2;
+ }
+ }
+ }
+ viewer->setBackgroundColor(bgColor);
+ fbo->release();
+ delete fbo;
+}
+
+void Locally_shortest_path_item::drawHl(Viewer_interface* viewer)const
+{
+ QMatrix4x4 f_matrix;
+ for (int i=0; i<16; ++i){
+ f_matrix.data()[i] = (float)d->frame->matrix()[i];
+ }
+ GLdouble d_mat[16];
+ QMatrix4x4 mv_mat;
+ viewer->camera()->getModelViewMatrix(d_mat);
+ for (int i=0; i<16; ++i)
+ mv_mat.data()[i] = GLfloat(d_mat[i]);
+ mv_mat = mv_mat*f_matrix;
+
+ if(d->hl_type == Locally_shortest_path_item_priv::VERTEX)
+ {
+ Tc* tc = getTriangleContainer(Priv::S_Spheres);
+
+ tc->setFrameMatrix(f_matrix);
+ tc->setMvMatrix(mv_mat);
+ tc->setColor(QColor(Qt::yellow));
+
+ // TODO radius
+ double radius = 0.01 ;
+ tc->setClipping(false);
+ tc->getVao(viewer)->bind();
+ tc->getVao(viewer)->program->setUniformValue("radius", (float)radius);
+ tc->getVao(viewer)->release();
+ tc->draw(viewer, true);
+ }
+}
+
+void Locally_shortest_path_item::invalidateOpenGLBuffers()
+{
+ compute_bbox();
+ setBuffersFilled(false);
+ getTriangleContainer(Priv::Spheres)->reset_vbos(ALL);
+ getTriangleContainer(Priv::P_Spheres)->reset_vbos(ALL);
+}
+
+void Locally_shortest_path_item::computeElements() const
+{
+ d->computeElements();
+
+ Tc* tc = getTriangleContainer(Priv::Spheres);
+ tc->allocate(
+ Tc::Flat_vertices,
+ d->vertex_spheres.data(),
+ static_cast(d->vertex_spheres.size()*sizeof(float)));
+
+ tc->allocate(
+ Tc::Flat_normals,
+ d->normal_spheres.data(),
+ static_cast(d->normal_spheres.size()*sizeof(float)));
+ tc->allocate(
+ Tc::Facet_centers,
+ d->center_spheres.data(),
+ static_cast(d->center_spheres.size()*sizeof(float)));
+
+ tc = getTriangleContainer(Priv::P_Spheres);
+ tc->allocate(
+ Tc::Flat_vertices,
+ d->vertex_spheres.data(),
+ static_cast(d->vertex_spheres.size()*sizeof(float)));
+
+ tc->allocate(
+ Tc::Facet_centers,
+ d->center_spheres.data(),
+ static_cast(d->center_spheres.size()*sizeof(float)));
+
+ tc->allocate(
+ Tc::FColors,
+ d->color_spheres.data(),
+ static_cast(d->color_spheres.size()*sizeof(float)));
+ setBuffersFilled(true);
+}
+
+void Locally_shortest_path_item::initializeBuffers(Viewer_interface *v) const
+{
+ getTriangleContainer(Priv::Spheres)->initializeBuffers(v);
+ getTriangleContainer(Priv::Spheres)->initializeBuffers(v);
+ getTriangleContainer(Priv::P_Spheres)->initializeBuffers(v);
+ getTriangleContainer(Priv::P_Spheres)->initializeBuffers(v);
+
+ getTriangleContainer(Priv::Spheres)->setFlatDataSize(d->vertex_spheres.size());
+ getTriangleContainer(Priv::Spheres)->setCenterSize(d->center_spheres.size());
+ getTriangleContainer(Priv::P_Spheres)->setFlatDataSize(d->vertex_spheres.size());
+ getTriangleContainer(Priv::P_Spheres)->setCenterSize(d->center_spheres.size());
+}
+
+void Locally_shortest_path_item::connectNewViewer(QObject *o)
+{
+ Vi* viewer = qobject_cast(o);
+ if(!viewer)
+ return;
+ viewer->setMouseTracking(true);
+}
diff --git a/Lab/demo/Lab/Plugins/Bsurf/Locally_shortest_path_item.h b/Lab/demo/Lab/Plugins/Bsurf/Locally_shortest_path_item.h
new file mode 100644
index 00000000000..29a131b37d4
--- /dev/null
+++ b/Lab/demo/Lab/Plugins/Bsurf/Locally_shortest_path_item.h
@@ -0,0 +1,70 @@
+#ifndef LOCALLY_SHORTEST_PATH_ITEM_H
+#define LOCALLY_SHORTEST_PATH_ITEM_H
+
+#include
+#include
+#include "Scene_surface_mesh_item.h"
+#include "Scene_polylines_item.h"
+#include "create_sphere.h"
+#include "Locally_shortest_path_item_config.h"
+
+struct Locally_shortest_path_item_priv;
+class LOCALLY_SHORTEST_PATH_ITEM_EXPORT Locally_shortest_path_item:
+ public CGAL::Three::Scene_item_rendering_helper
+{
+ Q_OBJECT
+ public:
+ typedef CGAL::Simple_cartesian Kernel;
+ struct vertex;
+ struct edge;
+ struct face;
+
+ Locally_shortest_path_item(const CGAL::Three::Scene_interface* scene_interface,
+ const Scene_surface_mesh_item* sm_item,
+ Scene_polylines_item* polyline_item,
+ std::size_t nb_pts);
+ ~Locally_shortest_path_item();
+ bool isFinite() const { return true; }
+ bool isEmpty() const { return false; }
+ void compute_bbox() const;
+
+ bool manipulatable() const { return true; }
+ ManipulatedFrame* manipulatedFrame();
+ Locally_shortest_path_item* clone() const {
+ return nullptr;
+ }
+
+ QString toolTip() const;
+ QMenu* contextMenu();
+
+ bool eventFilter(QObject *, QEvent *);
+ // Indicate if rendering mode is supported
+ bool supportsRenderingMode(RenderingMode m) const;
+ void draw(CGAL::Three::Viewer_interface *) const;
+ void drawHl(CGAL::Three::Viewer_interface *) const;
+ //~ void drawEdges(CGAL::Three::Viewer_interface* viewer) const;
+ void drawSpheres(CGAL::Three::Viewer_interface* viewer, const QMatrix4x4 f_matrix) const;
+ void drawPath() const;
+ void invalidateOpenGLBuffers();
+
+ // 5-----6
+ // . | . |
+ // 4------7 |
+ // | | | |
+ // | 1-|---2
+ // | . |.
+ // 0------3
+
+ double point(short i, short j) const;
+ void initializeBuffers(CGAL::Three::Viewer_interface *) const;
+ void computeElements() const;
+public Q_SLOTS:
+ void highlight(CGAL::Three::Viewer_interface* viewer);
+ void clearHL();
+ void connectNewViewer(QObject* o);
+
+protected:
+ friend struct Locally_shortest_path_item_priv;
+ Locally_shortest_path_item_priv* d;
+};
+#endif // SCENE_EDIT_BOX_ITEM_H
diff --git a/Lab/demo/Lab/Plugins/Bsurf/Locally_shortest_path_item_config.h b/Lab/demo/Lab/Plugins/Bsurf/Locally_shortest_path_item_config.h
new file mode 100644
index 00000000000..06796b33eba
--- /dev/null
+++ b/Lab/demo/Lab/Plugins/Bsurf/Locally_shortest_path_item_config.h
@@ -0,0 +1,10 @@
+#ifndef LOCALLY_SHORTEST_PATH_ITEM_CONFIG_H
+#define LOCALLY_SHORTEST_PATH_ITEM_CONFIG_H
+
+#ifdef locally_shortest_path_item_EXPORTS
+# define LOCALLY_SHORTEST_PATH_ITEM_EXPORT Q_DECL_EXPORT
+#else
+# define LOCALLY_SHORTEST_PATH_ITEM_EXPORT Q_DECL_IMPORT
+#endif
+
+#endif // LOCALLY_SHORTEST_PATH_ITEM_CONFIG_H
diff --git a/Lab/demo/Lab/Plugins/Bsurf/Locally_shortest_path_plugin.cpp b/Lab/demo/Lab/Plugins/Bsurf/Locally_shortest_path_plugin.cpp
new file mode 100644
index 00000000000..986975987b4
--- /dev/null
+++ b/Lab/demo/Lab/Plugins/Bsurf/Locally_shortest_path_plugin.cpp
@@ -0,0 +1,151 @@
+#include
+
+#include
+#include
+#include "Locally_shortest_path_item.h"
+#include "Scene_surface_mesh_item.h"
+#include "Scene_polylines_item.h"
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+#include
+#include
+using namespace CGAL::Three;
+class Locally_shortest_path_plugin :
+ public QObject,
+ public CGAL_Lab_plugin_interface
+{
+ Q_OBJECT
+ Q_INTERFACES(CGAL::Three::CGAL_Lab_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*);
+ QList actions() const {
+ return QList() << actionTracePath << actionTraceBezier;
+ }
+
+ bool applicable(QAction*) const {
+ if(scene->numberOfEntries() > 0)
+ {
+ int item_id = scene->mainSelectionIndex();
+ return qobject_cast(scene->item(item_id));
+ }
+ return false;
+ }
+public Q_SLOTS:
+
+ void trace_path();
+ void trace_bezier();
+ void enableAction();
+ void connectNewViewer(QObject* o)
+ {
+ for(int i=0; inumberOfEntries(); ++i)
+ {
+ Locally_shortest_path_item* item = qobject_cast(
+ scene->item(i));
+ if(item)
+ o->installEventFilter(item);
+ }
+ }
+
+private:
+ CGAL::Three::Scene_interface* scene;
+ QMainWindow* mw;
+ QAction* actionTracePath;
+ QAction* actionTraceBezier;
+}; // end Locally_shortest_path_plugin
+
+void Locally_shortest_path_plugin::init(QMainWindow* mainWindow, CGAL::Three::Scene_interface* scene_interface, Messages_interface*)
+{
+ scene = scene_interface;
+ mw = mainWindow;
+ actionTracePath = new QAction(tr("Create Locally Shortest Path"), mainWindow);
+ actionTraceBezier= new QAction(tr("Trace Bezier Curve"), mainWindow);
+ connect(actionTracePath, SIGNAL(triggered()),
+ this, SLOT(trace_path()));
+ connect(actionTraceBezier, SIGNAL(triggered()),
+ this, SLOT(trace_bezier()));
+ connect(mw, SIGNAL(newViewerCreated(QObject*)),
+ this, SLOT(connectNewViewer(QObject*)));
+}
+
+void Locally_shortest_path_plugin::trace_path()
+{
+ for(int i = 0, end = scene->numberOfEntries();
+ i < end; ++i)
+ {
+ if(qobject_cast(scene->item(i)))
+ return;
+ }
+ QApplication::setOverrideCursor(Qt::WaitCursor);
+
+ Scene_surface_mesh_item* sm_item = qobject_cast(scene->item(scene->mainSelectionIndex()));
+
+ Scene_polylines_item* polyline_item = new Scene_polylines_item();
+
+ polyline_item->setName(tr("Locally Shortest Path"));
+ polyline_item->setColor(Qt::red);
+ scene->addItem(polyline_item);
+ polyline_item->invalidateOpenGLBuffers();
+
+ Locally_shortest_path_item* item = new Locally_shortest_path_item(scene, sm_item, polyline_item,2);
+ connect(item, SIGNAL(destroyed()),
+ this, SLOT(enableAction()));
+ item->setName("Source and Target Points");
+ item->setRenderingMode(FlatPlusEdges);
+ for(CGAL::QGLViewer* viewer : CGAL::QGLViewer::QGLViewerPool())
+ viewer->installEventFilter(item);
+
+ scene->addItem(item);
+ actionTracePath->setEnabled(false);
+ actionTraceBezier->setEnabled(false);
+
+ QApplication::restoreOverrideCursor();
+}
+
+void Locally_shortest_path_plugin::trace_bezier()
+{
+ for(int i = 0, end = scene->numberOfEntries();
+ i < end; ++i)
+ {
+ if(qobject_cast(scene->item(i)))
+ return;
+ }
+ QApplication::setOverrideCursor(Qt::WaitCursor);
+
+ Scene_surface_mesh_item* sm_item = qobject_cast(scene->item(scene->mainSelectionIndex()));
+
+ Scene_polylines_item* polyline_item = new Scene_polylines_item();
+
+ polyline_item->setName(tr("Locally Shortest Path"));
+ polyline_item->setColor(Qt::red);
+ scene->addItem(polyline_item);
+ polyline_item->invalidateOpenGLBuffers();
+
+ Locally_shortest_path_item* item = new Locally_shortest_path_item(scene, sm_item, polyline_item,4);
+ connect(item, SIGNAL(destroyed()),
+ this, SLOT(enableAction()));
+ item->setName("Control Points");
+ item->setRenderingMode(FlatPlusEdges);
+ for(CGAL::QGLViewer* viewer : CGAL::QGLViewer::QGLViewerPool())
+ viewer->installEventFilter(item);
+
+ scene->addItem(item);
+ actionTracePath->setEnabled(false);
+ actionTraceBezier->setEnabled(false);
+
+ QApplication::restoreOverrideCursor();
+}
+
+void Locally_shortest_path_plugin::enableAction() {
+ actionTracePath->setEnabled(true);
+ actionTraceBezier->setEnabled(true);
+}
+
+#include "Locally_shortest_path_plugin.moc"
diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/locate.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/locate.h
index c8653c0196d..184c66b2782 100644
--- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/locate.h
+++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/locate.h
@@ -59,6 +59,12 @@ template
using descriptor_variant = std::variant::vertex_descriptor,
typename boost::graph_traits::halfedge_descriptor,
typename boost::graph_traits::face_descriptor>;
+/// \ingroup PMP_locate_grp
+///
+/// A variant used in the function `get_descriptor_from_location()` overload for `Edge_location`.
+template
+using vertex_or_halfedge_variant = std::variant::vertex_descriptor,
+ typename boost::graph_traits::halfedge_descriptor>;
/// \ingroup PMP_locate_grp
///
@@ -81,6 +87,109 @@ template
using Face_location = std::pair::face_descriptor,
Barycentric_coordinates >;
+/// \ingroup PMP_locate_grp
+///
+/// If `pm` is the input surface mesh and given the pair (`e`, `bc`)
+/// such that `bc` is a pair of barycentric coordinates `(w0, w1)`, the correspondence
+/// between the coordinates in `bc` and the vertices of the edge `e` is the following:
+/// - `w0` corresponds to `source(e, pm)`
+/// - `w1` corresponds to `target(e, pm)`
+template
+using Edge_location =
+ std::pair::edge_descriptor,
+ std::array>;
+
+/// \ingroup PMP_locate_grp
+/// returns `true` if `loc1` and `loc2` indicate the same point on `tm`, and `false` otherwise,
+template
+bool
+are_locations_identical(const Face_location& loc1,
+ const Face_location& loc2,
+ const TriangleMesh& tm)
+{
+ if (loc1==loc2) return true;
+
+ using vertex_descriptor = typename boost::graph_traits::vertex_descriptor;
+ using halfedge_descriptor = typename boost::graph_traits::halfedge_descriptor;
+ using face_descriptor = typename boost::graph_traits::face_descriptor;
+ using Barycentric_coordinates = CGAL::Polygon_mesh_processing::Barycentric_coordinates;
+
+ const face_descriptor fd1 = loc1.first;
+ const Barycentric_coordinates& bar1 = loc1.second;
+ const face_descriptor fd2 = loc2.first;
+ const Barycentric_coordinates& bar2 = loc2.second;
+
+ // the first barycentric coordinate corresponds to source(halfedge(fdX, tm), tm)
+ halfedge_descriptor hd1 = prev(halfedge(fd1, tm), tm);
+ halfedge_descriptor hd2 = prev(halfedge(fd2, tm), tm);
+
+ // check if the point is a vertex
+ for(int i=0; i<3; ++i)
+ {
+ if(bar1[i] == FT(1))
+ {
+ vertex_descriptor vd1 = target(hd1, tm);
+ for(int k=0; k<3; ++k)
+ {
+ if(bar2[k] == FT(1))
+ return target(hd2, tm) == vd1;
+ hd2 = next(hd2, tm);
+ }
+ }
+ hd1 = next(hd1, tm);
+ }
+ CGAL_assertion(hd1 == prev(halfedge(fd1, tm), tm));
+
+ // check loc2 is not a vertex
+ for(int k=0; k<3; ++k)
+ {
+ if(bar2[k] == FT(1))
+ return false;
+ hd2 = next(hd2, tm);
+ }
+ CGAL_assertion(hd2 == prev(halfedge(fd2, tm), tm));
+
+
+ // check if the point is on an edge
+ for(int i=0; i<3; ++i)
+ {
+ if(bar1[i] == FT(0)) // coordinate at target(hd1, tm)
+ {
+ for(int k=0; k<3; ++k)
+ {
+ if(bar2[k] == FT(0)) // coordinate at target(hd2, tm)
+ return prev(hd2, tm) == opposite(prev(hd1, tm), tm);
+ hd2 = next(hd2, tm);
+ }
+ }
+ hd1 = next(hd1, tm);
+ }
+
+ return false;
+}
+
+template
+Face_location
+to_face_location(Edge_location loc,
+ const TriangleMesh& tm)
+{
+ auto h = halfedge(loc.first, tm);
+ if (is_border(h, tm))
+ {
+ h=opposite(h, tm);
+ std::swap(loc.second[0], loc.second[1]);
+ }
+
+ auto f = face(h, tm);
+ auto hf = halfedge(f, tm);
+ if (hf == h)
+ return Face_location(f, CGAL::make_array(loc.second[0], loc.second[1], FT(0)));
+ if (next(hf, tm)==h)
+ return Face_location(f, CGAL::make_array(FT(0),loc.second[0], loc.second[1]));
+ CGAL_assertion(prev(hf, tm)==h);
+ return Face_location(f, CGAL::make_array(loc.second[1],FT(0),loc.second[0]));
+}
+
namespace internal {
// The Ray must have the same ambient dimension as the property map's value type (aka, the point type)
@@ -339,6 +448,18 @@ struct Barycentric_point_constructor // 3D version
return P(x, y, z);
}
+
+ P operator()(const P& p, const FT wp, const P& q, const FT wq,
+ const K& /*k*/) const
+ {
+ FT sum = wp + wq;
+ CGAL_assertion(sum != FT(0));
+ FT x = (wp * p.x() + wq * q.x()) / sum;
+ FT y = (wp * p.y() + wq * q.y()) / sum;
+ FT z = (wp * p.z() + wq * q.z()) / sum;
+
+ return P(x, y, z);
+ }
};
} // namespace internal
@@ -503,11 +624,7 @@ random_location_on_mesh(const TriangleMesh& tm,
///
template
descriptor_variant
-#ifdef DOXYGEN_RUNNING // just for convenience because template alias do not allow template deduction
get_descriptor_from_location(const Face_location& loc,
-#else
-get_descriptor_from_location(const Face_location& loc,
-#endif
const TriangleMesh& tm)
{
typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor;
@@ -547,7 +664,123 @@ get_descriptor_from_location(const Face_location& loc,
/// \ingroup PMP_locate_grp
///
-/// \brief Given a location in a face, returns the geometric position described
+/// \brief Given a location, if it designates a vertex, returns the corresponding vertex descriptor and `std::nullopt` otherwise.
+///
+/// \tparam FT must be a model of `FieldNumberType`
+/// \tparam TriangleMesh must be a model of `FaceGraph`
+///
+/// \param loc a location with `loc.first` a face of `tm`
+/// \param tm a triangulated surface mesh
+///
+/// \pre `loc.first` is a face descriptor corresponding to a face of `tm`.
+/// \pre `loc` describes the barycentric coordinates of a point that lives within the face (boundary included),
+/// meaning the barycentric coordinates are all positive.
+///
+template
+std::optional::vertex_descriptor>
+vertex_descriptor_from_location(const Face_location& loc,
+ const TriangleMesh& tm)
+{
+ typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor;
+ typedef typename boost::graph_traits::face_descriptor face_descriptor;
+
+ typedef Barycentric_coordinates Barycentric_coordinates;
+
+ const face_descriptor fd = loc.first;
+ const Barycentric_coordinates& bar = loc.second;
+
+ CGAL_precondition(is_valid_face_descriptor(fd, tm));
+ CGAL_precondition(is_triangle(halfedge(fd, tm), tm));
+ CGAL_precondition(is_in_face(loc, tm));
+
+ // the first barycentric coordinate corresponds to source(halfedge(fd, tm), tm)
+ halfedge_descriptor hd = prev(halfedge(fd, tm), tm);
+
+ // check if the point is a vertex
+ for(int i=0; i<3; ++i)
+ {
+ if(bar[i] == FT(1)) // coordinate at target(hd, tm)
+ return target(hd, tm);
+ hd = next(hd, tm);
+ }
+ return std::nullopt;
+}
+
+/// \ingroup PMP_locate_grp
+///
+/// \brief Given a location, if it designates a vertex, returns the corresponding vertex descriptor and `std::nullopt` otherwise.
+///
+/// \tparam FT must be a model of `FieldNumberType`
+/// \tparam TriangleMesh must be a model of `FaceGraph`
+///
+/// \param loc a location with `loc.first` a face of `tm`
+/// \param tm a triangulated surface mesh
+///
+/// \pre `loc.first` is a face descriptor corresponding to a face of `tm`.
+/// \pre `loc` describes the barycentric coordinates of a point that lives within the face (boundary included),
+/// meaning the barycentric coordinates are all positive.
+///
+template
+std::optional::vertex_descriptor>
+vertex_descriptor_from_location(const Edge_location& loc,
+ const TriangleMesh& tm)
+{
+ typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor;
+
+ vertex_descriptor src = source(loc.first, tm), tgt = target(loc.first, tm);
+ const auto& bar = loc.second;
+
+ if (bar[1]==0) return src;
+ if (bar[0]==0) return tgt;
+
+ return std::nullopt;
+}
+
+
+/// \ingroup PMP_locate_grp
+///
+/// \brief Given a location, returns a descriptor to the simplex of smallest dimension
+/// on which the point corresponding to the location lies.
+///
+/// \details In other words:
+/// - if the point lies on a vertex, this function returns a `boost::graph_traits::%vertex_descriptor` `v`;
+/// - if the point lies on a halfedge, this function returns a `boost::graph_traits::%halfedge_descriptor` `hd`
+/// (note that in that case, `loc.first == face(hd, tm)` holds).
+///
+/// \tparam FT must be a model of `FieldNumberType`
+/// \tparam TriangleMesh must be a model of `FaceGraph`
+///
+/// \param loc a location with `loc.first` a face of `tm`
+/// \param tm a triangulated surface mesh
+///
+/// \pre `loc.first` is an edge descriptor corresponding to an edge of `tm`.
+/// \pre `loc` describes the barycentric coordinates of a point that lives within the edge (boundary included),
+/// meaning the barycentric coordinates are all positive.
+///
+template
+vertex_or_halfedge_variant
+get_descriptor_from_location(const Edge_location& loc,
+ const TriangleMesh& tm)
+{
+ typedef typename boost::graph_traits::edge_descriptor edge_descriptor;
+
+ typedef std::array Barycentric_coordinates;
+
+ const edge_descriptor ed = loc.first;
+ const Barycentric_coordinates& bar = loc.second;
+
+ if (bar[0]==FT(0))
+ return source(ed, tm);
+
+ if (bar[1]==FT(0))
+ return target(ed, tm);
+
+ return halfedge(ed, tm);
+}
+
+/// \ingroup PMP_locate_grp
+///
+/// \brief returns, given a location in a face, the geometric position described
/// by these coordinates, as a point.
///
/// \tparam FT must be a model of `FieldNumberType`
@@ -616,6 +849,83 @@ construct_point(const Face_location& loc,
return bp_constructor(p0, loc.second[0], p1, loc.second[1], p2, loc.second[2], gt);
}
+/// \ingroup PMP_locate_grp
+///
+/// \brief returns, given a location in an edge, the geometric position described
+/// by these coordinates, as a point.
+///
+/// \tparam FT must be a model of `FieldNumberType`
+/// \tparam TriangleMesh must be a model of `FaceGraph`
+/// \tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters"
+///
+/// \param loc the location from which a point is constructed
+/// \param tm a triangulated surface mesh
+/// \param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below
+///
+/// \cgalNamedParamsBegin
+/// \cgalParamNBegin{vertex_point_map}
+/// \cgalParamDescription{a property map associating points to the vertices of `tm`}
+/// \cgalParamType{a class model of `ReadablePropertyMap` with `boost::graph_traits::%vertex_descriptor`
+/// as key type and `%Point_3` as value type}
+/// \cgalParamDefault{`boost::get(CGAL::vertex_point, tm)`}
+/// \cgalParamNEnd
+///
+/// \cgalParamNBegin{geom_traits}
+/// \cgalParamDescription{an instance of a geometric traits class}
+/// \cgalParamType{a class model of `Kernel`}
+/// \cgalParamDefault{a \cgal Kernel deduced from the point type, using `CGAL::Kernel_traits`}
+/// \cgalParamExtra{The geometric traits class must be compatible with the vertex point type.}
+/// \cgalParamExtra{If such traits class is provided, its type `FT` must be identical
+/// to the template parameter `FT` of this function.}
+/// \cgalParamNEnd
+/// \cgalNamedParamsEnd
+///
+/// \pre `loc.first` is a face descriptor corresponding to a face of `tm`.
+///
+/// \returns a point whose type is the same as the value type of the vertex point property map
+/// provided by the user or via named parameters, or the internal point map of the mesh `tm`.
+///
+template
+#ifdef DOXYGEN_RUNNING
+Point construct_point(
+ const Edge_location &loc,
+#else
+typename internal::Location_traits::Point
+construct_point(const Edge_location &loc,
+#endif
+ const TriangleMesh &tm,
+ const NamedParameters &np = parameters::default_values()) {
+ typedef typename boost::graph_traits::edge_descriptor
+ edge_descriptor;
+ typedef
+ typename GetGeomTraits::type Geom_traits;
+
+ typedef typename GetVertexPointMap::const_type
+ VertexPointMap;
+ typedef typename boost::property_traits::value_type Point;
+ typedef typename boost::property_traits::reference
+ Point_reference;
+
+ using parameters::choose_parameter;
+ using parameters::get_parameter;
+
+ CGAL_precondition(CGAL::is_triangle_mesh(tm));
+
+ VertexPointMap vpm = parameters::choose_parameter(
+ parameters::get_parameter(np, internal_np::vertex_point),
+ get_const_property_map(boost::vertex_point, tm));
+ Geom_traits gt = choose_parameter(
+ get_parameter(np, internal_np::geom_traits));
+
+ edge_descriptor ed = loc.first;
+ const Point_reference p0 = get(vpm, source(ed, tm));
+ const Point_reference p1 = get(vpm, target(ed, tm));
+
+ internal::Barycentric_point_constructor bp_constructor;
+ return bp_constructor(p0, loc.second[0], p1, loc.second[1], gt);
+}
+
/// \name Location Predicates
/// @{
diff --git a/Vector_graphics_on_surfaces/doc/Vector_graphics_on_surfaces/Doxyfile.in b/Vector_graphics_on_surfaces/doc/Vector_graphics_on_surfaces/Doxyfile.in
new file mode 100644
index 00000000000..e1d1b533f78
--- /dev/null
+++ b/Vector_graphics_on_surfaces/doc/Vector_graphics_on_surfaces/Doxyfile.in
@@ -0,0 +1,3 @@
+@INCLUDE = ${CGAL_DOC_PACKAGE_DEFAULTS}
+
+PROJECT_NAME = "CGAL ${CGAL_DOC_VERSION} - Vector Graphics on Triangulated Surface Meshes"
diff --git a/Vector_graphics_on_surfaces/doc/Vector_graphics_on_surfaces/PackageDescription.txt b/Vector_graphics_on_surfaces/doc/Vector_graphics_on_surfaces/PackageDescription.txt
new file mode 100644
index 00000000000..c4ce5e4b288
--- /dev/null
+++ b/Vector_graphics_on_surfaces/doc/Vector_graphics_on_surfaces/PackageDescription.txt
@@ -0,0 +1,68 @@
+/// \defgroup PkgVGoS_Ref Vector Graphics on Triangulated Surface Meshes Reference
+/// \defgroup VGSConcepts Concepts
+/// \ingroup PkgVGoS_Ref
+
+/// \defgroup VGSAlgorithmClasses Algorithm Classes
+/// \ingroup PkgVGoS_Ref
+
+/// \defgroup VGSFunctions Functions
+/// \ingroup PkgVGoS_Ref
+
+/// \defgroup VGSTraitsClasses Traits Classes
+/// \ingroup PkgVGoS_Ref
+
+/// \defgroup VGSMiscellaneous Miscellaneous
+/// \ingroup PkgVGoS_Ref
+
+/*!
+\addtogroup PkgVGoS_Ref
+\cgalPkgDescriptionBegin{Vector Graphics on Triangulated Surface Meshes,PkgVGoS}
+\cgalPkgPicture{vgos-small.png}
+\cgalPkgSummaryBegin
+\cgalPkgAuthors{Claudio Mancinelli and Sébastien Loriot}
+\cgalPkgDesc{The package provides functions to draw and manipulate basic geometric primitives such as geodesics curves and Bézier splines
+ on triangulated surface meshes.}
+\cgalPkgManuals{Chapter_VGoS,PkgVGoS_Ref}
+\cgalPkgSummaryEnd
+\cgalPkgShortInfoBegin
+\cgalPkgSince{6.2}
+\cgalPkgDependsOn{\ref PkgPolygonMeshProcessing}
+\cgalPkgBib{cgal:ml-vgos}
+\cgalPkgLicense{\ref licensesGPL "GPL"}
+\cgalPkgDemo{CGAL Lab,CGALlab.zip}
+\cgalPkgShortInfoEnd
+\cgalPkgDescriptionEnd
+
+
+\cgalClassifedRefPages
+
+\cgalCRPSection{Base Functions}
+- `CGAL::Vector_graphics_on_surfaces::locally_shortest_path()`
+- `CGAL::Vector_graphics_on_surfaces::recursive_de_Casteljau()`
+- `CGAL::Vector_graphics_on_surfaces::straightest_geodesic()`
+
+\cgalCRPSection{Higher Level Tracing Functions}
+- `CGAL::Vector_graphics_on_surfaces::trace_geodesic_polygon()`
+- `CGAL::Vector_graphics_on_surfaces::trace_geodesic_polygons()`
+- `CGAL::Vector_graphics_on_surfaces::trace_geodesic_label()`
+- `CGAL::Vector_graphics_on_surfaces::trace_geodesic_label_along_curve`
+- `CGAL::Vector_graphics_on_surfaces::trace_bezier_curves()`
+- `CGAL::Vector_graphics_on_surfaces::trace_polyline_of_bezier_curves()`
+
+\cgalCRPSection{Utilities}
+- `CGAL::Vector_graphics_on_surfaces::path_length()`
+- `CGAL::Vector_graphics_on_surfaces::convert_path_to_polyline()`
+- `CGAL::Vector_graphics_on_surfaces::convert_to_polar_coordinates`
+
+\cgalCRPSection{Miscellaneous}
+- `CGAL::Vector_graphics_on_surfaces::Bezier_segment`
+- `CGAL::Vector_graphics_on_surfaces::Dual_geodesic_solver`
+- `CGAL::Vector_graphics_on_surfaces::init_geodesic_dual_solver()`
+- `CGAL::Vector_graphics_on_surfaces::approximate_geodesic_distance_field()`
+- `CGAL::Vector_graphics_on_surfaces::refine_mesh_along_paths()`
+
+
+\todo tracing functions should probably be merged in only one or two functions, with parameters for the placement of centers + parallel transport, as well as an option for joining with locally shortest paths
+
+
+*/
diff --git a/Vector_graphics_on_surfaces/doc/Vector_graphics_on_surfaces/Vector_graphics_on_surfaces.txt b/Vector_graphics_on_surfaces/doc/Vector_graphics_on_surfaces/Vector_graphics_on_surfaces.txt
new file mode 100644
index 00000000000..0a50533331f
--- /dev/null
+++ b/Vector_graphics_on_surfaces/doc/Vector_graphics_on_surfaces/Vector_graphics_on_surfaces.txt
@@ -0,0 +1,22 @@
+namespace CGAL {
+/*!
+
+\mainpage User Manual
+\anchor Chapter_VGoS
+\cgalAutoToc
+\author Claudio Mancinelli and Sébastien Loriot
+
+This chapter describes the ...
+
+\section vgos_definitions Definitions
+
+Section on definitions here ...
+
+\section vgos_examples Examples
+
+\subsection vgos_examples_first First Example
+
+The following example shows ...
+
+*/
+} /* namespace CGAL */
diff --git a/Vector_graphics_on_surfaces/doc/Vector_graphics_on_surfaces/dependencies b/Vector_graphics_on_surfaces/doc/Vector_graphics_on_surfaces/dependencies
new file mode 100644
index 00000000000..26c05c82d78
--- /dev/null
+++ b/Vector_graphics_on_surfaces/doc/Vector_graphics_on_surfaces/dependencies
@@ -0,0 +1,8 @@
+Manual
+Kernel_23
+STL_Extension
+Algebraic_foundations
+Circulator
+Stream_support
+Polygon_mesh_processing
+BGL
diff --git a/Vector_graphics_on_surfaces/doc/Vector_graphics_on_surfaces/examples.txt b/Vector_graphics_on_surfaces/doc/Vector_graphics_on_surfaces/examples.txt
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/Vector_graphics_on_surfaces/doc/Vector_graphics_on_surfaces/fig/vgos-small.png b/Vector_graphics_on_surfaces/doc/Vector_graphics_on_surfaces/fig/vgos-small.png
new file mode 100644
index 00000000000..aab567220e5
Binary files /dev/null and b/Vector_graphics_on_surfaces/doc/Vector_graphics_on_surfaces/fig/vgos-small.png differ
diff --git a/Vector_graphics_on_surfaces/examples/Vector_graphics_on_surfaces/CMakeLists.txt b/Vector_graphics_on_surfaces/examples/Vector_graphics_on_surfaces/CMakeLists.txt
new file mode 100644
index 00000000000..a7bb19178b5
--- /dev/null
+++ b/Vector_graphics_on_surfaces/examples/Vector_graphics_on_surfaces/CMakeLists.txt
@@ -0,0 +1,41 @@
+# Created by the script cgal_create_CMakeLists
+# This is the CMake script for compiling a set of CGAL applications.
+
+cmake_minimum_required(VERSION 3.12...3.29)
+project(Vector_graphics_on_surfaces_Examples)
+
+# CGAL and its components
+find_package(CGAL REQUIRED)
+
+find_package(NanoSVG)
+include(CGAL_NanoSVG_support)
+
+find_package(Eigen3 3.2.0 QUIET) #(requires 3.2.0 or greater)
+include(CGAL_Eigen3_support)
+if(TARGET CGAL::Eigen3_support)
+ create_single_source_cgal_program("trace_bezier_segment_sm_example.cpp")
+ target_link_libraries(trace_bezier_segment_sm_example PUBLIC CGAL::Eigen3_support)
+ create_single_source_cgal_program("geodesic_circles_sm_example.cpp")
+ target_link_libraries(geodesic_circles_sm_example PUBLIC CGAL::Eigen3_support)
+ create_single_source_cgal_program("straightest_geodesic_sm_example.cpp")
+ target_link_libraries(straightest_geodesic_sm_example PUBLIC CGAL::Eigen3_support)
+ create_single_source_cgal_program("trace_polygon_example.cpp")
+ target_link_libraries(trace_polygon_example PUBLIC CGAL::Eigen3_support)
+ create_single_source_cgal_program("trace_regular_polygons_example.cpp")
+ target_link_libraries(trace_regular_polygons_example PUBLIC CGAL::Eigen3_support)
+ create_single_source_cgal_program("trace_polygon_on_polygonal_curve_example.cpp")
+ target_link_libraries(trace_polygon_on_polygonal_curve_example PUBLIC CGAL::Eigen3_support)
+ create_single_source_cgal_program("locally_shortest_path_sm_example.cpp")
+ target_link_libraries(locally_shortest_path_sm_example PUBLIC CGAL::Eigen3_support)
+ if (NanoSVG_FOUND)
+ create_single_source_cgal_program("trace_svg_example.cpp")
+ target_link_libraries(trace_svg_example PUBLIC CGAL::Eigen3_support CGAL::NanoSVG_support)
+ create_single_source_cgal_program("trace_polygon_on_svg_curve_example.cpp")
+ target_link_libraries(trace_polygon_on_svg_curve_example PUBLIC CGAL::Eigen3_support CGAL::NanoSVG_support)
+ else()
+ message(STATUS "NOTICE: trace_svg_example requires NanoSVG and will not be compiled.")
+ endif()
+else()
+ message(STATUS "NOTICE: Examples that use Eigen will not be compiled.")
+endif()
+
diff --git a/Vector_graphics_on_surfaces/examples/Vector_graphics_on_surfaces/geodesic_circles_sm_example.cpp b/Vector_graphics_on_surfaces/examples/Vector_graphics_on_surfaces/geodesic_circles_sm_example.cpp
new file mode 100644
index 00000000000..c24d173bc38
--- /dev/null
+++ b/Vector_graphics_on_surfaces/examples/Vector_graphics_on_surfaces/geodesic_circles_sm_example.cpp
@@ -0,0 +1,74 @@
+#include
+#include
+#include
+
+#include
+
+namespace VGoS = CGAL::Vector_graphics_on_surfaces;
+namespace PMP = CGAL::Polygon_mesh_processing;
+
+typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
+typedef CGAL::Surface_mesh Mesh;
+typedef PMP::Face_location Face_location;
+typedef PMP::Edge_location Edge_location;
+
+
+int main(int argc, char** argv)
+{
+ std::string filename = (argc > 1) ? std::string(argv[1])
+ : CGAL::data_file_path("meshes/elephant.off");
+
+ Mesh mesh;
+ if(!CGAL::IO::read_polygon_mesh(filename, mesh) || !CGAL::is_triangle_mesh(mesh))
+ {
+ std::cerr << "Invalid input." << std::endl;
+ return 1;
+ }
+
+ std::size_t nb_faces = faces(mesh).size();
+
+ // take two random faces and pick the centroid
+ CGAL::Random rnd = CGAL::get_default_random();
+ // CGAL::Random rnd(1695720148);
+ // CGAL::Random rnd(1695724381);
+ // CGAL::Random rnd(1695813638);
+
+ std::cout << "seed " << rnd.get_seed() << std::endl;
+ Mesh::Face_index f = *std::next(faces(mesh).begin(), rnd.get_int(0, nb_faces));
+ // or pick specific faces
+
+ Face_location src(f, CGAL::make_array(0.3,0.3,0.4));
+ std::vector radii = {0.001, 0.01, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 10};
+
+ std::cout << "src = " << PMP::construct_point(src, mesh) << "\n";
+
+ auto distance_map = mesh.add_property_map("v:dstmap").first;
+ VGoS::approximate_geodesic_distance_field(src, distance_map, mesh);
+
+ std::ofstream out("circles.polylines.txt");
+
+ for (double r : radii)
+ {
+ for (Mesh::Face_index f : faces(mesh))
+ {
+ std::vector pts;
+ Mesh::Halfedge_index h = halfedge(f, mesh);
+ for (int i=0; i<3; ++i)
+ {
+ Mesh::Vertex_index src = source(h, mesh), tgt = target(h, mesh);
+ double ds = get(distance_map, src);
+ double dt = get(distance_map, tgt);
+ if ((ds < r) != (dt < r))
+ {
+ double alpha = (r - dt) / (ds - dt);
+ pts.push_back( CGAL::barycenter(mesh.point(src), alpha, mesh.point(tgt), 1-alpha) );
+ }
+ h=next(h, mesh);
+ }
+ if (pts.size()==2)
+ out << "2 " << pts[0] << " " << pts[1] << "\n";
+ }
+ }
+
+ return 0;
+}
diff --git a/Vector_graphics_on_surfaces/examples/Vector_graphics_on_surfaces/locally_shortest_path_sm_example.cpp b/Vector_graphics_on_surfaces/examples/Vector_graphics_on_surfaces/locally_shortest_path_sm_example.cpp
new file mode 100644
index 00000000000..0347c57b78a
--- /dev/null
+++ b/Vector_graphics_on_surfaces/examples/Vector_graphics_on_surfaces/locally_shortest_path_sm_example.cpp
@@ -0,0 +1,82 @@
+#include
+#include
+#include
+
+#include
+
+namespace VGoS = CGAL::Vector_graphics_on_surfaces;
+namespace PMP = CGAL::Polygon_mesh_processing;
+
+typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
+typedef CGAL::Surface_mesh Mesh;
+typedef PMP::Face_location Face_location;
+typedef PMP::Edge_location Edge_location;
+
+
+int main(int argc, char** argv)
+{
+ std::string filename = (argc > 1) ? std::string(argv[1])
+ : CGAL::data_file_path("meshes/elephant.off");
+
+ Mesh mesh;
+ if(!CGAL::IO::read_polygon_mesh(filename, mesh) || !CGAL::is_triangle_mesh(mesh))
+ {
+ std::cerr << "Invalid input." << std::endl;
+ return 1;
+ }
+
+ std::size_t nb_faces = faces(mesh).size();
+
+ // take two random faces and pick the centroid
+ CGAL::Random rnd = CGAL::get_default_random();
+ // CGAL::Random rnd(1695720148);
+ // CGAL::Random rnd(1695724381);
+ // CGAL::Random rnd(1695813638);
+
+ std::cout << "seed " << rnd.get_seed() << std::endl;
+ Mesh::Face_index f1 = *std::next(faces(mesh).begin(), rnd.get_int(0, nb_faces));
+ Mesh::Face_index f2 = *std::next(faces(mesh).begin(), rnd.get_int(0, nb_faces));
+ // or pick specific faces
+
+ // TODO: add in testsuite: special case
+ // Mesh::Face_index f1( 3268 );
+ // Mesh::Face_index f2( 3014 );
+
+ // TODO: add in testsuite: special case
+ // Mesh::Face_index f2( 3265 );
+ // Mesh::Face_index f1( 3014 );
+
+ // TODO: add in testsuite: special case
+ // Mesh::Face_index f2( 3268 );
+ // Mesh::Face_index f1( 3014 );
+
+ // TODO: add in testsuite: special case
+ // Mesh::Face_index f1( 3265 );
+ // Mesh::Face_index f2( 3014 );
+
+ // TODO: add in testsuite: special case
+ // Mesh::Face_index f1( 3543 );
+ // Mesh::Face_index f2( 4356 );
+
+ Face_location src(f1, CGAL::make_array(0.3,0.3,0.4));
+ Face_location tgt(f2, CGAL::make_array(0.3,0.3,0.4));
+
+ std::cout << "src = " << PMP::construct_point(src, mesh) << "\n";
+ std::cout << "tgt = " << PMP::construct_point(tgt, mesh) << "\n";
+
+ std::vector edge_locations;
+ VGoS::locally_shortest_path(src, tgt, mesh, edge_locations);
+
+
+ std::vector poly;
+ poly.reserve(edge_locations.size()+2);
+ VGoS::convert_path_to_polyline(src, edge_locations, tgt, mesh, std::back_inserter(poly));
+
+ std::ofstream out("locally_shortest_path.polylines.txt");
+ out << poly.size();
+ for (auto p : poly)
+ out << " " << p;
+ out << "\n";
+
+ return 0;
+}
diff --git a/Vector_graphics_on_surfaces/examples/Vector_graphics_on_surfaces/straightest_geodesic_sm_example.cpp b/Vector_graphics_on_surfaces/examples/Vector_graphics_on_surfaces/straightest_geodesic_sm_example.cpp
new file mode 100644
index 00000000000..0a7390c08bc
--- /dev/null
+++ b/Vector_graphics_on_surfaces/examples/Vector_graphics_on_surfaces/straightest_geodesic_sm_example.cpp
@@ -0,0 +1,65 @@
+#include
+#include
+#include
+
+#include
+
+namespace VGoS = CGAL::Vector_graphics_on_surfaces;
+namespace PMP = CGAL::Polygon_mesh_processing;
+
+typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
+typedef CGAL::Surface_mesh Mesh;
+typedef PMP::Face_location Face_location;
+typedef PMP::Edge_location Edge_location;
+
+
+int main(int argc, char** argv)
+{
+ std::string filename = (argc > 1) ? std::string(argv[1])
+ : CGAL::data_file_path("meshes/elephant.off");
+
+ Mesh mesh;
+ if(!CGAL::IO::read_polygon_mesh(filename, mesh) || !CGAL::is_triangle_mesh(mesh))
+ {
+ std::cerr << "Invalid input." << std::endl;
+ return 1;
+ }
+
+ std::size_t nb_faces = faces(mesh).size();
+
+ // take two random faces and pick the centroid
+ CGAL::Random rnd = CGAL::get_default_random();
+ //CGAL::Random rnd(1706709591);
+
+ std::cout << "seed " << rnd.get_seed() << std::endl;
+ Mesh::Face_index f = *std::next(faces(mesh).begin(), rnd.get_int(0, nb_faces));
+ Face_location src(f, CGAL::make_array(0.3,0.3,0.4));
+
+//case opposite edge (for testsuite)
+// Mesh::Face_index f = *std::next(faces(mesh).begin(), 1031);
+// Face_location src(f, CGAL::make_array(0, 0.65258992669550031, 0.34741007330449963));
+
+ K::Point_3 src_pt = PMP::construct_point(src, mesh);
+ std::cout << "src = " << src_pt << "\n";
+
+//case opposite edge (for testsuite)
+// double target_distance = 0.0072766352463858128;
+// K::Vector_2 dir(0.079665117730984697, 0.99682168366107904);
+
+ double target_distance = 0.5;
+ K::Vector_2 dir(1,1);
+ std::vector path = VGoS::straightest_geodesic(src, dir, target_distance, mesh);
+
+ std::vector poly;
+ poly.reserve(path.size());
+ VGoS::convert_path_to_polyline(path, mesh, std::back_inserter(poly));
+
+ std::ofstream out("straightest_geodesic_path.polylines.txt");
+ out << path.size() << " ";
+
+ for (auto p : poly)
+ out << " " << p;
+ out << "\n";
+
+ return 0;
+}
diff --git a/Vector_graphics_on_surfaces/examples/Vector_graphics_on_surfaces/trace_bezier_segment_sm_example.cpp b/Vector_graphics_on_surfaces/examples/Vector_graphics_on_surfaces/trace_bezier_segment_sm_example.cpp
new file mode 100644
index 00000000000..e585fb8b137
--- /dev/null
+++ b/Vector_graphics_on_surfaces/examples/Vector_graphics_on_surfaces/trace_bezier_segment_sm_example.cpp
@@ -0,0 +1,64 @@
+#include
+#include
+#include
+
+#include
+
+namespace VGoS = CGAL::Vector_graphics_on_surfaces;
+namespace PMP = CGAL::Polygon_mesh_processing;
+
+typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
+typedef CGAL::Surface_mesh Mesh;
+typedef PMP::Face_location Face_location;
+typedef PMP::Edge_location Edge_location;
+
+
+int main(int argc, char** argv)
+{
+ std::string filename = (argc > 1) ? std::string(argv[1])
+ : CGAL::data_file_path("meshes/elephant.off");
+
+ Mesh mesh;
+ if(!CGAL::IO::read_polygon_mesh(filename, mesh) || !CGAL::is_triangle_mesh(mesh))
+ {
+ std::cerr << "Invalid input." << std::endl;
+ return 1;
+ }
+
+ std::size_t nb_faces = faces(mesh).size();
+
+ // take two random faces and pick the centroid
+ CGAL::Random rnd = CGAL::get_default_random();
+ // CGAL::Random rnd(1695795785);
+
+ std::cout << "seed " << rnd.get_seed() << std::endl;
+ Mesh::Face_index f1 = *std::next(faces(mesh).begin(), rnd.get_int(0, nb_faces));
+ Mesh::Face_index f2 = *std::next(faces(mesh).begin(), rnd.get_int(0, nb_faces));
+ Mesh::Face_index f3 = *std::next(faces(mesh).begin(), rnd.get_int(0, nb_faces));
+ Mesh::Face_index f4 = *std::next(faces(mesh).begin(), rnd.get_int(0, nb_faces));
+
+
+ Face_location a(f1, CGAL::make_array(0.3,0.3,0.4)),
+ b(f2, CGAL::make_array(0.3,0.3,0.4)),
+ c(f3, CGAL::make_array(0.3,0.3,0.4)),
+ d(f4, CGAL::make_array(0.3,0.3,0.4));
+ VGoS::Bezier_segment control_points=CGAL::make_array(a, b, c, d);
+
+ std::cout << "Using the following control points:\n";
+ std::cout << PMP::construct_point(a, mesh) << "\n";
+ std::cout << PMP::construct_point(b, mesh) << "\n";
+ std::cout << PMP::construct_point(c, mesh) << "\n";
+ std::cout << PMP::construct_point(d, mesh) << "\n";
+
+ std::vector face_locations =
+ VGoS::recursive_de_Casteljau(mesh, control_points, 8);
+
+
+ std::ofstream out("bezier.polylines.txt");
+ out << face_locations.size();
+ for (auto fl : face_locations)
+ out << " " << PMP::construct_point(fl, mesh); // TODO: we should connect points with geodesics and not segments
+ out << "\n";
+
+ return 0;
+}
diff --git a/Vector_graphics_on_surfaces/examples/Vector_graphics_on_surfaces/trace_polygon_example.cpp b/Vector_graphics_on_surfaces/examples/Vector_graphics_on_surfaces/trace_polygon_example.cpp
new file mode 100644
index 00000000000..f1a42ab43e1
--- /dev/null
+++ b/Vector_graphics_on_surfaces/examples/Vector_graphics_on_surfaces/trace_polygon_example.cpp
@@ -0,0 +1,299 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#if 0
+#include
+#else
+
+#endif
+
+namespace VGoS = CGAL::Vector_graphics_on_surfaces;
+namespace PMP = CGAL::Polygon_mesh_processing;
+
+typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
+typedef CGAL::Surface_mesh Mesh;
+typedef PMP::Face_location Face_location;
+typedef PMP::Edge_location Edge_location;
+
+
+int main(int argc, char** argv)
+{
+ std::string filename = (argc > 1) ? std::string(argv[1])
+ : CGAL::data_file_path("meshes/elephant.off");
+
+ std::string filename_poly = (argc > 2) ? std::string(argv[2])
+ : CGAL::data_file_path("XXXXX");
+
+ Mesh mesh;
+ if(!CGAL::IO::read_polygon_mesh(filename, mesh) || !CGAL::is_triangle_mesh(mesh))
+ {
+ std::cerr << "Invalid input." << std::endl;
+ return 1;
+ }
+
+ std::vector> polygons;
+ std::ifstream in(filename_poly);
+ if (!in)
+ {
+ std::cerr << "Error cannot open " << filename_poly << "\n";
+ return 1;
+ }
+
+ int nb_pt;
+ K::Point_3 pt;
+ CGAL::Bbox_2 bb2;
+ while (in >> nb_pt)
+ {
+ polygons.emplace_back();
+ polygons.back().reserve(nb_pt-1);
+ for (int i=0; i> pt;
+ polygons.back().emplace_back(pt.x(), pt.y());
+ bb2+=polygons.back().back().bbox();
+ }
+ in >> pt;
+ if (!in)
+ {
+ std::cerr << "Error reading input polygons\n";
+ return 1;
+ }
+ // check if last point is duplicated
+ if (polygons.back().back().x()!=pt.x() || polygons.back().back().y()!=pt.y())
+ {
+ polygons.back().emplace_back(pt.x(), pt.y());
+ bb2+=polygons.back().back().bbox();
+ }
+ if (!in) break;
+ }
+
+ std::cout << polygons.size() << " polygons read\n";
+
+ // tracing center
+ std::size_t nb_faces = faces(mesh).size();
+ Mesh::Face_index f = *std::next(faces(mesh).begin(), (2154)%nb_faces);
+ Face_location center(f, CGAL::make_array(0.3,0.3,0.4));
+
+ // convert polygons to polar coordinates
+ typename K::Point_2 center_2((bb2.xmax()+bb2.xmin())/2., (bb2.ymax()+bb2.ymin())/2.);
+ double diag = std::sqrt( CGAL::square(bb2.xmin()-bb2.xmax()) + CGAL::square(bb2.xmin()-bb2.xmax()) );
+ const double expected_diag = 0.45; // user parameter for scaling
+ const double scaling = expected_diag/diag;
+
+ std::ofstream out("geodesic_polygon.polylines.txt");
+ out << std::setprecision(17);
+
+ K::Point_3 center_pt = PMP::construct_point(center, mesh);
+ std::cout << "center = " << center_pt << "\n";
+ VGoS::Dual_geodesic_solver solver;
+ VGoS::init_geodesic_dual_solver(solver, mesh);
+
+ for (const std::vector& polygon : polygons)
+ {
+ std::vector> polar_coords =
+ VGoS::convert_to_polar_coordinates(polygon, center_2);
+ if (polygon.front()==polygon.back()) polar_coords.pop_back();
+
+ std::vector directions;
+ std::vector lens;
+ lens.reserve(polar_coords.size());
+ directions.reserve(polar_coords.size());
+
+ for (const std::pair& coord : polar_coords)
+ {
+ lens.push_back(scaling * coord.first);
+ directions.emplace_back(std::cos(coord.second), std::sin(coord.second));
+ }
+
+ // last point is duplicated
+ std::vector out_polygon_path = VGoS::trace_geodesic_polygon(center,directions,lens,mesh, solver);
+ std::vector poly;
+ poly.reserve(out_polygon_path.size());
+ VGoS::convert_path_to_polyline(out_polygon_path, mesh, std::back_inserter(poly));
+
+ out << poly.size();
+ for (auto p : poly)
+ out << " " << p;
+ out << std::endl;
+ }
+
+ // second method
+ out.close();
+ out.open("geodesic_polygons.polylines.txt");
+ out << std::setprecision(17);
+
+ std::vector> polygons_3
+ = VGoS::trace_geodesic_polygons(center, polygons, scaling, mesh, solver);
+
+ for (const auto& polygon : polygons_3)
+ {
+ out << polygon.size();
+ for (auto p : polygon)
+ out << " " << PMP::construct_point(p, mesh);
+ out << std::endl;
+ }
+
+ // third method
+ out.close();
+ out.open("geodesic_label.polylines.txt");
+ out << std::setprecision(17);
+
+ polygons_3.clear();
+ polygons_3 = VGoS::trace_geodesic_label(center, polygons, scaling, mesh, solver);
+
+ for (const auto& polygon : polygons_3)
+ {
+ out << polygon.size();
+ for (auto p : polygon)
+ out << " " << PMP::construct_point(p, mesh);
+ out << std::endl;
+ }
+
+ // now refine the input mesh
+ std::vector cst_hedges;
+ auto vnm = mesh.add_property_map("vnm", K::Vector_3(0,0,0)).first;
+ auto fnm = mesh.add_property_map("fnm", K::Vector_3(0,0,0)).first;
+ using VNM = decltype(vnm);
+
+ PMP::compute_normals(mesh, vnm, fnm);
+ VGoS::refine_mesh_along_paths(polygons_3, mesh, vnm, fnm, std::back_inserter(cst_hedges));
+
+ std::ofstream("mesh_refined.off") << std::setprecision(17) << mesh;
+
+ std::ofstream cst_edges("refinement_edges.polylines.txt");
+ cst_edges.precision(17);
+ for (Mesh::Halfedge_index h : cst_hedges)
+ cst_edges << "2 " << mesh.point(source(h,mesh)) << " " << mesh.point(target(h,mesh)) << "\n";
+
+
+ auto ecm = mesh.add_property_map("ecm", false).first;
+ for (Mesh::Halfedge_index h : cst_hedges)
+ ecm[edge(h, mesh)]=true;
+
+
+ // face index for doing flood fill and mark inside-out
+ Mesh::Face_index out_face(2612);
+ std::vector in_out(num_faces(mesh), -1);
+
+ bool inorout=false;
+ std::vector queue, next_queue;
+ queue.push_back(out_face);
+
+ while(!queue.empty())
+ {
+ Mesh::Face_index f = queue.back();
+ queue.pop_back();
+ if (in_out[f]==-1)
+ {
+ in_out[f]=inorout?1:0;
+ Mesh::Halfedge_index h=halfedge(f, mesh);
+ for (int i=0; i<3; ++i)
+ {
+ Mesh::Face_index nf = face(opposite(h, mesh), mesh);
+ if (nf!=boost::graph_traits::null_face() && in_out[nf]==-1)
+ {
+ if (ecm[edge(h,mesh)])
+ next_queue.push_back(nf);
+ else
+ queue.push_back(nf);
+ }
+ h=next(h, mesh);
+ }
+ }
+ if (queue.empty())
+ {
+ queue.swap(next_queue);
+ inorout=!inorout;
+ }
+ }
+
+
+
+ struct Visitor
+ : public PMP::Corefinement::Default_visitor
+ {
+ VNM vnm;
+ Visitor(VNM vnm) : vnm(vnm) {}
+
+ std::vector > hedge_map;
+ void after_edge_duplicated(Mesh::Halfedge_index h, Mesh::Halfedge_index new_hedge, const Mesh&)
+ {
+ hedge_map.emplace_back(h, new_hedge);
+ }
+
+ void after_vertex_copy(Mesh::Vertex_index v, const Mesh&, Mesh::Vertex_index nv, const Mesh&)
+ {
+ put(vnm, nv, get(vnm, v));
+ }
+ };
+ Visitor visitor(vnm);
+
+ PMP::internal::split_along_edges(mesh, ecm, mesh.points(), visitor);
+
+
+ double delta = -0.005;
+ for (const auto& ph : visitor.hedge_map)
+ {
+ Mesh::Halfedge_index h1 = ph.first;
+ Mesh::Halfedge_index h2 = ph.second;
+ if (is_border(h1, mesh)) h1=opposite(h1, mesh);
+ if (is_border(h2, mesh)) h2=opposite(h2, mesh);
+ Mesh::Halfedge_index h = in_out[face(h1, mesh)]==1 ? h1 : h2;
+
+ Mesh::Vertex_index v = target(h, mesh);
+ K::Vector_3 n = get(vnm, v);
+ mesh.point(v) = mesh.point(v)+delta*n;
+ }
+
+ // interior vertices
+ for (Mesh::Vertex_index v : vertices(mesh))
+ {
+ bool skip=false;
+ Mesh::Halfedge_index h = halfedge(v, mesh);
+ for (Mesh::Halfedge_index h : CGAL::halfedges_around_target(v, mesh))
+ {
+ if (is_border(h, mesh) || in_out[face(h, mesh)]==0)
+ {
+ skip=true;
+ break;
+ }
+ }
+ if (!skip)
+ {
+ K::Vector_3 n = get(vnm, v);
+ mesh.point(v) = mesh.point(v)+delta*n;
+ }
+ }
+
+ std::vector b1(visitor.hedge_map.size());
+ std::vector b2(visitor.hedge_map.size());
+ for (std::size_t i=0; i
+#include
+#include
+
+#include
+
+namespace VGoS = CGAL::Vector_graphics_on_surfaces;
+namespace PMP = CGAL::Polygon_mesh_processing;
+
+typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
+typedef CGAL::Surface_mesh Mesh;
+typedef PMP::Face_location Face_location;
+typedef PMP::Edge_location Edge_location;
+
+std::vector
+get_supporting_curve(std::string filename,
+ const Mesh& mesh)
+{
+ std::ifstream in(filename);
+ if (!in)
+ {
+ std::cerr << "ERROR: cannot open " << filename << "\n";
+ exit(1);
+ }
+
+ int nbp;
+ in >> nbp;
+ std::cout << "Support polyline size: " << nbp << "\n";
+ std::vector polyline(nbp);
+
+ for (int i=0;i> polyline[i];
+
+ std::vector face_locations(nbp);
+
+ using AABB_face_graph_primitive = CGAL::AABB_face_graph_triangle_primitive;
+ using AABB_face_graph_traits = CGAL::AABB_traits_3;
+
+ CGAL::AABB_tree tree;
+ PMP::build_AABB_tree(mesh, tree);
+
+ //Remarks on snapping_tolerance of locate: it is actually a value used as is in barycentric coordinates with no relation to the size of the triangle
+ // and snapping is only done when at least one coordinate is negative (clamping would be a better name, snapping is what I'm doing below).
+ //TODO: the following works only if we don't have a lfs smaller than snap_tol
+ double snap_tol = 0.00001;
+ for(int i=0; i primitives;
+ tree.all_intersected_primitives(query, std::back_inserter(primitives));
+
+ switch (primitives.size())
+ {
+ case 0:
+ std::cerr <<"ERROR points too far!\n";
+ exit(1);
+ break;
+ case 1:
+ face_locations[i] = PMP::locate_in_face(polyline[i],primitives[0], mesh);
+ break;
+ case 2:
+ {
+ // TODO: we assume we don't have any boundary to it's clear it's on an edge
+ Mesh::Halfedge_index h1 = halfedge(primitives[0], mesh),
+ h2 = halfedge(primitives[1], mesh);
+ bool found=false;
+ for (int j=0;j<3;++j)
+ {
+ for (int k=0;k<3; ++k)
+ {
+ if (h1==opposite(h2, mesh))
+ {
+ found = true;
+ break;
+ }
+ h2=next(h2, mesh);
+ }
+ if (found) break;
+ h1=next(h1, mesh);
+ }
+ CGAL_assertion(h1 == opposite(h2, mesh));
+
+ const K::Point_3& src = mesh.point(source(h1, mesh));
+ const K::Point_3& tgt= mesh.point(target(h1, mesh));
+ double t = std::sqrt(CGAL::squared_distance(polyline[i], src)/CGAL::squared_distance(src,tgt));
+ face_locations[i] = PMP::locate_on_halfedge(h1, t, mesh);
+ }
+ break;
+ default:
+ {
+ std::vector vrts;
+ Mesh::Halfedge_index h=halfedge(primitives[0], mesh);
+ vrts.push_back(source(h, mesh));
+ vrts.push_back(target(h, mesh));
+ vrts.push_back(target(next(h, mesh), mesh));
+ std::sort(vrts.begin(), vrts.end());
+ for(std::size_t k=1;k tmp;
+ h=halfedge(primitives[k], mesh);
+ for (int j=0;j<3;++j)
+ {
+ Mesh::Vertex_index v=target(h, mesh);
+ if (std::binary_search(vrts.begin(), vrts.end(), v))
+ tmp.push_back(v);
+ h=next(h, mesh);
+ }
+ tmp.swap(vrts);
+ std::sort(vrts.begin(), vrts.end());
+ }
+ CGAL_assertion(vrts.size()==1);
+
+ int offset=0;
+ h=halfedge(primitives[0], mesh);
+ if (target(h, mesh)==vrts[0])
+ offset=1;
+ else
+ if (target(next(h, mesh), mesh)==vrts[0])
+ offset=2;
+ else
+ CGAL_assertion(source(h, mesh)==vrts[0]);
+ std::array bary = CGAL::make_array(0.,0.,0.);
+ bary[offset]=1.;
+ face_locations[i] = std::make_pair(primitives[0], bary);
+ }
+ }
+ // face_locations[i] = PMP::locate_with_AABB_tree(polyline[i], tree, mesh, CGAL::parameters::snapping_tolerance(snap_tol));
+ for (int k=0;k<3;++k)
+ if (face_locations[i].second[k]<0) face_locations[i].second[k]=0;
+ }
+
+ // DEBUG code to check that the polyline has correctly been converted to face locations (it's not a path as if vertex is hit we don't have the face continuity property)
+ for (int i=0;i 1) ? std::string(argv[1])
+ : CGAL::data_file_path("meshes/elephant.off");
+
+ std::string support_filename = (argc > 2) ? std::string(argv[2])
+ : CGAL::data_file_path("XXXXX");
+
+ std::string filename_poly = (argc > 3) ? std::string(argv[3])
+ : CGAL::data_file_path("XXXXX");
+
+ std::size_t nb_copies = atoi(argv[4]);
+
+ Mesh mesh;
+ if(!CGAL::IO::read_polygon_mesh(filename, mesh) || !CGAL::is_triangle_mesh(mesh))
+ {
+ std::cerr << "Invalid input." << std::endl;
+ return 1;
+ }
+
+ std::vector> polygons;
+ std::ifstream in(filename_poly);
+ if (!in)
+ {
+ std::cerr << "Error cannot open " << filename_poly << "\n";
+ return 1;
+ }
+
+ int nb_pt;
+ K::Point_3 pt;
+ CGAL::Bbox_2 bb2;
+ while (in >> nb_pt)
+ {
+ polygons.emplace_back();
+ polygons.back().reserve(nb_pt-1);
+ for (int i=0; i> pt;
+ polygons.back().emplace_back(pt.x(), pt.y());
+ bb2+=polygons.back().back().bbox();
+ }
+ in >> pt;
+ if (!in)
+ {
+ std::cerr << "Error reading input polygons\n";
+ return 1;
+ }
+ // check if last point is duplicated
+ if (polygons.back().back().x()!=pt.x() || polygons.back().back().y()!=pt.y())
+ {
+ polygons.back().emplace_back(pt.x(), pt.y());
+ bb2+=polygons.back().back().bbox();
+ }
+ if (!in) break;
+ }
+
+ std::cout << polygons.size() << " polygons read\n";
+
+
+ // duplicate them a bit to get longer text while getting another one
+ CGAL::Bbox_2 gbox;
+ for (const auto& polygon : polygons)
+ gbox+=CGAL::bbox_2(polygon.begin(), polygon.end());
+ K::Vector_2 delta(gbox.xmax()-gbox.xmin(), 0);
+ std::size_t nbpoly=polygons.size();
+ polygons.reserve(nbpoly*(nb_copies+1));
+ for (std::size_t c=0; c