Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BGL - add CGAL::shortest_path(vs, vt, mesh) #8724

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions BGL/doc/BGL/PackageDescription.txt
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,9 @@ the requirement for traversal of all faces in a graph.
/// \defgroup PkgBGLPartition Partitioning Operations
/// \ingroup PkgBGLRef

/// \defgroup PkgBGLTraversal Graph Traversal
/// \ingroup PkgBGLRef

/// \defgroup PkgBGLIOFct I/O Functions
/// \ingroup PkgBGLRef

Expand Down Expand Up @@ -761,6 +764,9 @@ user might encounter.
\cgalCRPSection{Conversion Functions}
- `CGAL::split_graph_into_polylines()`

\cgalCRPSection{Graph Traversal}
- `CGAL::shortest_path_between_two_vertices()`

\cgalCRPSection{Graph Adaptors}
- `CGAL::Dual`
- `CGAL::Face_filtered_graph`
Expand Down
1 change: 1 addition & 0 deletions BGL/examples/BGL_surface_mesh/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ create_single_source_cgal_program("seam_mesh.cpp")
create_single_source_cgal_program("write_inp.cpp")
create_single_source_cgal_program("surface_mesh_dual.cpp")
create_single_source_cgal_program("connected_components.cpp")
create_single_source_cgal_program("shortest_path.cpp")

find_package(METIS QUIET)
include(CGAL_METIS_support)
Expand Down
80 changes: 80 additions & 0 deletions BGL/examples/BGL_surface_mesh/shortest_path.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/boost/graph/graph_traits_Surface_mesh.h>

#include <CGAL/boost/graph/shortest_path.h>
#include <CGAL/IO/polygon_mesh_io.h>


#include <string>
#include <vector>
#include <fstream>
#include <exception>
#include <algorithm>

using K = CGAL::Exact_predicates_inexact_constructions_kernel;
using Point = K::Point_3;
using Mesh = CGAL::Surface_mesh<Point>;

using vertex_descriptor = boost::graph_traits<Mesh>::vertex_descriptor;
using edge_descriptor = boost::graph_traits<Mesh>::edge_descriptor;
using halfedge_descriptor = boost::graph_traits<Mesh>::halfedge_descriptor;

namespace PMP = CGAL::Polygon_mesh_processing;
namespace params = CGAL::parameters;


// Example main
int main(int argc, char** argv)
{
const std::string filename = (argc > 1) ? argv[1] : CGAL::data_file_path("meshes/elephant.off");

// Try building a surface_mesh
Mesh sm;
bool ok = CGAL::IO::read_polygon_mesh(filename, sm);
if (!ok || !sm.is_valid() || sm.is_empty())
{
std::cerr << "Error: Invalid facegraph" << std::endl;
std::cerr << "Filename = " << filename << std::endl;
return EXIT_FAILURE;
}

const std::size_t i0 = 0;
const std::size_t i1 = num_vertices(sm) / 2;


// Get the vertex descriptors of the source and target vertices
const vertex_descriptor vs = *vertices(sm).first;
vertex_descriptor vt;
std::size_t vid = 0;
for (const vertex_descriptor v : vertices(sm))
{
if (vid++ == i1)
{
vt = v;
break;
}
}

std::vector<halfedge_descriptor> halfedge_sequence;
CGAL::shortest_path_between_two_vertices(vs, vt, sm,
std::back_inserter(halfedge_sequence));

// dump
std::cout << "Shortest path between vertices " << i0 << " and " << i1
<< " is made of " << halfedge_sequence.size() << " halfedges." << std::endl;

// Get the property map of the points of the mesh
auto vpmap = get(CGAL::vertex_point, sm);

std::ofstream out("shortest_path.polylines.txt");
for (const halfedge_descriptor he : halfedge_sequence)
{
const vertex_descriptor v0 = source(he, sm);
const vertex_descriptor v1 = target(he, sm);
out << "2 " << get(vpmap, v0) << " " << get(vpmap, v1) << std::endl;
}
out.close();

return EXIT_SUCCESS;
}
160 changes: 160 additions & 0 deletions BGL/include/CGAL/boost/graph/shortest_path.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// Copyright (c) 2025 GeometryFactory (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) : Jane Tournois, Andreas Fabri
//

#ifndef CGAL_BOOST_GRAPH_SHORTEST_PATH_H
#define CGAL_BOOST_GRAPH_SHORTEST_PATH_H

#include <boost/graph/dijkstra_shortest_paths.hpp>

#include <CGAL/Named_function_parameters.h>
#include <CGAL/boost/graph/named_params_helper.h>

#include <boost/property_map/property_map.hpp>
#include <CGAL/boost/graph/properties.h>

#include <vector>
#include <unordered_map>

namespace CGAL {
namespace internal {

/// An exception used while catching a throw that stops Dijkstra's algorithm
/// once the shortest path to a target has been found.
class Dijkstra_end_exception : public std::exception
{
const char* what() const throw ()
{
return "Dijkstra shortest path: reached the target vertex.";
}
};

/// Visitor to stop Dijkstra's algorithm once the given target turns 'BLACK',
/// that is when the target has been examined through all its incident edges and
/// the shortest path is thus known.
template<typename Graph>
class Stop_at_target_Dijkstra_visitor : boost::default_dijkstra_visitor
{
using vertex_descriptor = typename boost::graph_traits<Graph>::vertex_descriptor;
using edge_descriptor = typename boost::graph_traits<Graph>::edge_descriptor;

public:
vertex_descriptor destination_vd;

void initialize_vertex(const vertex_descriptor& /*s*/, const Graph& /*g*/) const {}
void examine_vertex(const vertex_descriptor& /*s*/, const Graph& /*g*/) const {}
void examine_edge(const edge_descriptor& /*e*/, const Graph& /*g*/) const {}
void edge_relaxed(const edge_descriptor& /*e*/, const Graph& /*g*/) const {}
void discover_vertex(const vertex_descriptor& /*s*/, const Graph& /*g*/) const {}
void edge_not_relaxed(const edge_descriptor& /*e*/, const Graph& /*g*/) const {}
void finish_vertex(const vertex_descriptor& vd, const Graph& /* g*/) const
{
if (vd == destination_vd)
throw Dijkstra_end_exception();
}
};
} // namespace internal

/*!
* \ingroup PkgBGLTraversal
* Computes the shortest path between two vertices in a graph `g`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to add that the vertices must be in the same connected component

*
*@tparam Graph a model of the concept `HalfedgeListGraph`
* @param vs source vertex
* @param vt target vertex
* @param g the graph
* @param halfedge_sequence_oit the output iterator holding the output sequence
* of halfedges that form the shortest path from `vs` to `vt` on `g`
* @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below
* ** edge_weight_map : @todo deal with input mesh with no internal pmap.
* default in boost is `get(boost::edge_weight, mesh)`
*
*/
template<typename Graph,
typename OutputIterator,
typename NamedParameters = parameters::Default_named_parameters>
OutputIterator shortest_path_between_two_vertices(
const typename boost::graph_traits<Graph>::vertex_descriptor vs,//source
const typename boost::graph_traits<Graph>::vertex_descriptor vt,//target
const Graph& g,
OutputIterator halfedge_sequence_oit,
const NamedParameters& np = parameters::default_values())
{
using vertex_descriptor = typename boost::graph_traits<Graph>::vertex_descriptor;
using halfedge_descriptor = typename boost::graph_traits<Graph>::halfedge_descriptor;

using Pred_umap = std::unordered_map<vertex_descriptor, vertex_descriptor>;
using Pred_pmap = boost::associative_property_map<Pred_umap>;

using parameters::get_parameter;
using parameters::choose_parameter;

const auto w_map = choose_parameter(get_parameter(np, internal_np::edge_weight),
get(boost::edge_weight, g));

Pred_umap predecessor;
Pred_pmap pred_pmap(predecessor);

internal::Stop_at_target_Dijkstra_visitor<Graph> vis;
vis.destination_vd = vt;
try
{
boost::dijkstra_shortest_paths(g, vs,
boost::predecessor_map(pred_pmap)
.visitor(vis)
.weight_map(w_map));
}
catch (const internal::Dijkstra_end_exception& ){}

// Walk back from target to source and collect vertices along the way
struct vertex_on_path
{
vertex_descriptor vertex;
bool is_constrained;
};

std::vector<vertex_descriptor> constrained_vertices = { vs };
vertex_descriptor t = vt;
std::vector<vertex_on_path> path;
do
{
const bool is_new_vertex = (constrained_vertices.end()
== std::find(constrained_vertices.begin(), constrained_vertices.end(), t));

vertex_on_path vop;
vop.vertex = t;
vop.is_constrained = !is_new_vertex;
path.push_back(vop);

t = get(pred_pmap, t);
}
while (t != vs);

// Add the last vertex
vertex_on_path vop;
vop.vertex = constrained_vertices.back();
vop.is_constrained = true;
path.push_back(vop);

// Display path
for (auto path_it = path.begin(); path_it != path.end() - 1; ++path_it)
{
const std::pair<halfedge_descriptor, bool>
h = halfedge((path_it + 1)->vertex, path_it->vertex, g);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we obtain directly the halfedges when running Dijkstra, instead of having a sequence of vertices?

if (h.second)
*halfedge_sequence_oit++ = h.first;
}
return halfedge_sequence_oit;
}

} // namespace CGAL


#endif //CGAL_BOOST_GRAPH_SHORTEST_PATH_H
99 changes: 14 additions & 85 deletions Lab/demo/Lab/Scene_polyhedron_selection_item.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "triangulate_primitive.h"
#include <CGAL/boost/graph/Face_filtered_graph.h>
#include <CGAL/Polygon_mesh_processing/measure.h>
#include <CGAL/boost/graph/shortest_path.h>
#include <CGAL/boost/graph/properties.h>

using namespace CGAL::Three;
Expand Down Expand Up @@ -1486,103 +1487,31 @@ void Scene_polyhedron_selection_item::emitTempInstruct()
Q_EMIT updateInstructions(QString("<font color='black'>%1</font>").arg(d->m_temp_instructs));
}

/// An exception used while catching a throw that stops Dijkstra's algorithm
/// once the shortest path to a target has been found.
class Dijkstra_end_exception : public std::exception
{
const char* what() const throw ()
{
return "Dijkstra shortest path: reached the target vertex.";
}
};

/// Visitor to stop Dijkstra's algorithm once the given target turns 'BLACK',
/// that is when the target has been examined through all its incident edges and
/// the shortest path is thus known.
class Stop_at_target_Dijkstra_visitor : boost::default_dijkstra_visitor
{
fg_vertex_descriptor destination_vd;

public:
Stop_at_target_Dijkstra_visitor(fg_vertex_descriptor destination_vd)
: destination_vd(destination_vd)
{ }

void initialize_vertex(const fg_vertex_descriptor& /*s*/, const Face_graph& /*mesh*/) const { }
void examine_vertex(const fg_vertex_descriptor& /*s*/, const Face_graph& /*mesh*/) const { }
void examine_edge(const fg_edge_descriptor& /*e*/, const Face_graph& /*mesh*/) const { }
void edge_relaxed(const fg_edge_descriptor& /*e*/, const Face_graph& /*mesh*/) const { }
void discover_vertex(const fg_vertex_descriptor& /*s*/, const Face_graph& /*mesh*/) const { }
void edge_not_relaxed(const fg_edge_descriptor& /*e*/, const Face_graph& /*mesh*/) const { }
void finish_vertex(const fg_vertex_descriptor &vd, const Face_graph& /* mesh*/) const
{
if(vd == destination_vd)
throw Dijkstra_end_exception();
}
};

void Scene_polyhedron_selection_item_priv::computeAndDisplayPath()
{
item->temp_selected_edges.clear();
path.clear();

typedef std::unordered_map<fg_vertex_descriptor, fg_vertex_descriptor> Pred_umap;
typedef boost::associative_property_map<Pred_umap> Pred_pmap;

Pred_umap predecessor;
Pred_pmap pred_pmap(predecessor);

vertex_on_path vop;
QList<fg_vertex_descriptor>::iterator it;
for(it = constrained_vertices.begin(); it!=constrained_vertices.end()-1; ++it)
const auto& mesh = *item->polyhedron();
std::vector<fg_halfedge_descriptor> path_halfedges;
for(auto it = constrained_vertices.begin(); it!=constrained_vertices.end()-1; ++it)
{
fg_vertex_descriptor t(*it), s(*(it+1));
Stop_at_target_Dijkstra_visitor vis(t);

try
{
boost::dijkstra_shortest_paths(*item->polyhedron(), s,
boost::predecessor_map(pred_pmap).visitor(vis));
}
catch (const std::exception& e)
{
std::cout << e.what() << std::endl;
}

// Walk back from target to source and collect vertices along the way
do
{
vop.vertex = t;
if(constrained_vertices.contains(t))
{
vop.is_constrained = true;
}
else
vop.is_constrained = false;
path.append(vop);
t = get(pred_pmap, t);
}
while(t != s);
CGAL::shortest_path_between_two_vertices(s, t, mesh,
std::back_inserter(path_halfedges));
}

// Add the last vertex
vop.vertex = constrained_vertices.last();
vop.is_constrained = true;
path.append(vop);
item->temp_selected_edges.clear();
path.clear();

// Display path
double path_length = 0;
QList<vertex_on_path>::iterator path_it;
for(path_it = path.begin(); path_it!=path.end()-1; ++path_it)
VPmap vpm = get(CGAL::vertex_point, mesh);
for(auto h : path_halfedges)
{
std::pair<fg_halfedge_descriptor, bool> h = halfedge((path_it+1)->vertex,path_it->vertex,*item->polyhedron());
if(h.second)
{
VPmap vpm = get(CGAL::vertex_point,*polyhedron());
Point p1(get(vpm, (path_it+1)->vertex)), p2(get(vpm, path_it->vertex));
path_length += CGAL::sqrt(Vector(p1,p2).squared_length());
item->temp_selected_edges.insert(edge(h.first, *item->polyhedron()));
}
const Point& p1 = get(vpm, target(h, mesh));
const Point& p2 = get(vpm, source(h, mesh));
path_length += CGAL::approximate_sqrt(CGAL::squared_distance(p1, p2));
item->temp_selected_edges.insert(edge(h, *item->polyhedron()));
}
item->printMessage(QString("New path length: %1").arg(path_length));
}
Expand Down
Loading