1271 lines
38 KiB
C++
1271 lines
38 KiB
C++
// Copyright (c) 2019 CNRS and LIRIS' Establishments (France).
|
|
// All rights reserved.
|
|
//
|
|
// This file is part of CGAL (www.cgal.org).
|
|
//
|
|
// $URL: https://github.com/CGAL/cgal/blob/v5.1/Surface_mesh_topology/include/CGAL/Path_on_surface.h $
|
|
// $Id: Path_on_surface.h 52186a0 2020-05-14T11:38:15+02:00 Guillaume Damiand
|
|
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
|
|
//
|
|
// Author(s) : Guillaume Damiand <guillaume.damiand@liris.cnrs.fr>
|
|
//
|
|
#ifndef CGAL_PATH_ON_SURFACE_H
|
|
#define CGAL_PATH_ON_SURFACE_H 1
|
|
|
|
#include <CGAL/license/Surface_mesh_topology.h>
|
|
|
|
#include <CGAL/Combinatorial_map_operations.h>
|
|
#include <CGAL/Combinatorial_map.h>
|
|
#include <CGAL/Random.h>
|
|
#include <CGAL/Face_graph_wrapper.h>
|
|
#include <CGAL/Surface_mesh_topology/internal/Path_on_surface_with_rle.h>
|
|
#include <boost/algorithm/searching/knuth_morris_pratt.hpp>
|
|
#include <utility>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <initializer_list>
|
|
|
|
// A Path_on_surface contains two vectors of equal length n
|
|
// The first one is a vector of darts called m_path and the second one a vector
|
|
// of booleans called m_flip.
|
|
// If n = 0, the path represented by those vectors is the empty path.
|
|
// Else, it is the path represented by the n-1 first elements of both vectors,
|
|
// at the one we add the m_path[n-1] dart if m_flip[n-1] is false and the
|
|
// opposite of this dart if m_flip[n-1] is true i.e. if m_flip[i] is true means
|
|
// that the i-th dart m_path[i] has to be flipped.
|
|
// We use flips because sometimes opposite darts doesn't exist on surfaces with
|
|
// boundaries. But if m_flip[i] is true doesn't necesary mean that
|
|
// m_path[i] is 2-free
|
|
|
|
namespace CGAL {
|
|
namespace Surface_mesh_topology {
|
|
|
|
template<typename Mesh_>
|
|
class Path_on_surface
|
|
{
|
|
public:
|
|
typedef Path_on_surface<Mesh_> Self;
|
|
typedef Mesh_ Mesh;
|
|
typedef typename Get_map<Mesh, Mesh>::type Map; // Mesh seen as a 2-map
|
|
typedef typename Map::Dart_const_handle Dart_const_handle;
|
|
|
|
typedef Dart_const_handle halfedge_descriptor; // To be compatible with BGL
|
|
|
|
Path_on_surface(const Mesh& amesh) : m_map(amesh), m_is_closed(false)
|
|
{}
|
|
|
|
template<class COST>
|
|
Path_on_surface(const internal::Path_on_surface_with_rle<COST>& apath) :
|
|
m_map(apath.get_map()),
|
|
m_is_closed(apath.is_closed())
|
|
{
|
|
for (auto it=apath.m_path.begin(), itend=apath.m_path.end(); it!=itend; ++it)
|
|
{
|
|
push_back(it->begin, false, false);
|
|
if (it->length>0)
|
|
{ extend_straight_positive(it->length, false); }
|
|
else if (it->length<0)
|
|
{ extend_straight_negative(-(it->length), false); }
|
|
}
|
|
update_is_closed();
|
|
CGAL_assertion(is_valid(true));
|
|
}
|
|
|
|
Path_on_surface(const Self& apath) : m_map(apath.m_map),
|
|
m_path(apath.m_path),
|
|
m_is_closed(apath.m_is_closed),
|
|
m_flip(apath.m_flip)
|
|
{}
|
|
|
|
void swap(Self& p2)
|
|
{
|
|
if (this==&p2) { return; }
|
|
|
|
CGAL_assertion(&get_mesh()==&(p2.get_mesh()));
|
|
m_path.swap(p2.m_path);
|
|
std::swap(m_is_closed, p2.m_is_closed);
|
|
m_flip.swap(p2.m_flip);
|
|
}
|
|
|
|
Self& operator=(const Self& other)
|
|
{
|
|
CGAL_assertion(&get_mesh()==&(other.get_mesh()));
|
|
if (this!=&other)
|
|
{
|
|
m_path=other.m_path;
|
|
m_is_closed=other.m_is_closed;
|
|
m_flip=other.m_flip;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
/// @return true iff the path is empty
|
|
bool is_empty() const
|
|
{ return m_path.empty(); }
|
|
|
|
/// @return the length of the path, i.e. its number of darts.
|
|
std::size_t length() const
|
|
{ return m_path.size(); }
|
|
|
|
/// @return true iff the path is closed.
|
|
/// (m_is_closed is updated after each path modification).
|
|
bool is_closed() const
|
|
{ return m_is_closed; }
|
|
|
|
/// @return the combinatorial map supporting this path.
|
|
const Map& get_map() const
|
|
{ return m_map; }
|
|
|
|
/// @return the combinatorial map supporting this path.
|
|
const Mesh& get_mesh() const
|
|
{ return Get_map<Mesh, Mesh>::get_mesh(m_map); }
|
|
|
|
const std::vector<bool>& get_flip() const
|
|
{ return m_flip; }
|
|
|
|
/// clear the path.
|
|
void clear()
|
|
{
|
|
m_path.clear();
|
|
m_flip.clear();
|
|
m_is_closed=false;
|
|
}
|
|
|
|
/// @return true iff the prev index exists
|
|
bool prev_index_exists(std::size_t i) const
|
|
{ return is_closed() || i>0; }
|
|
|
|
/// @return true iff the next index exists
|
|
bool next_index_exists(std::size_t i) const
|
|
{ return is_closed() || i<(m_path.size()-1); }
|
|
|
|
/// @return the index after index i.
|
|
std::size_t next_index(std::size_t i) const
|
|
{ return ((is_closed() && i==(m_path.size()-1))?0:(i+1)); }
|
|
|
|
/// @return the index before index i.
|
|
std::size_t prev_index(std::size_t i) const
|
|
{ return ((is_closed() && i==0)?(m_path.size()-1):(i-1)); }
|
|
|
|
/// @return the ith dart of the path.
|
|
Dart_const_handle get_ith_dart(std::size_t i) const
|
|
{
|
|
CGAL_assertion(i<m_path.size());
|
|
return m_path[i];
|
|
}
|
|
|
|
/// @return true iff the ith dart is flipped
|
|
bool get_ith_flip(std::size_t i) const
|
|
{
|
|
CGAL_assertion(i<m_path.size());
|
|
return m_flip[i];
|
|
}
|
|
|
|
/// @return the ith dart of the path.
|
|
Dart_const_handle operator[] (std::size_t i) const
|
|
{ return get_ith_dart(i); }
|
|
|
|
/// @return the dart before the ith dart of the path,
|
|
/// Map::null_handle if such a dart does not exist.
|
|
Dart_const_handle get_prev_dart(std::size_t i) const
|
|
{
|
|
CGAL_assertion(i<m_path.size());
|
|
if (i==0 && !is_closed()) return Map::null_handle;
|
|
return m_path[prev_index(i)];
|
|
}
|
|
|
|
/// @return the dart after the ith dart of the path,
|
|
/// Map::null_handle if such a dart does not exist.
|
|
Dart_const_handle get_next_dart(std::size_t i) const
|
|
{
|
|
CGAL_assertion(i<m_path.size());
|
|
if (i==m_path.size()-1 && !is_closed()) return Map::null_handle;
|
|
return m_path[next_index(i)];
|
|
}
|
|
|
|
/// @return the flip before the ith flip of the path,
|
|
/// false if such a flip does not exist.
|
|
bool get_prev_flip(std::size_t i) const
|
|
{
|
|
CGAL_assertion(i<m_path.size());
|
|
if (i==0 && !is_closed()) return false;
|
|
return m_flip[prev_index(i)];
|
|
}
|
|
|
|
/// @return the flip after the ith flip of the path,
|
|
/// false if such a flip does not exist.
|
|
bool get_next_flip(std::size_t i) const
|
|
{
|
|
CGAL_assertion(i<m_path.size());
|
|
if (i==m_path.size()-1 && !is_closed()) return false;
|
|
return m_flip[next_index(i)];
|
|
}
|
|
|
|
/// @return the first dart of the path.
|
|
/// @pre !is_empty()
|
|
Dart_const_handle front() const
|
|
{
|
|
CGAL_assertion(!is_empty());
|
|
return m_path.front();
|
|
}
|
|
|
|
/// @return the last dart of the path.
|
|
/// @pre !is_empty()
|
|
Dart_const_handle back() const
|
|
{
|
|
CGAL_assertion(!is_empty());
|
|
return m_path.back();
|
|
}
|
|
|
|
/// @return the first flip of the path.
|
|
/// @pre !is_empty()
|
|
bool front_flip() const
|
|
{
|
|
CGAL_assertion(!is_empty());
|
|
return m_flip.front();
|
|
}
|
|
|
|
/// @return the last flip of the path.
|
|
/// @pre !is_empty()
|
|
bool back_flip() const
|
|
{
|
|
CGAL_assertion(!is_empty());
|
|
return m_flip.back();
|
|
}
|
|
|
|
/// @return the index of the first dart of the path.
|
|
/// @pre !is_empty()
|
|
std::size_t front_index() const
|
|
{ return get_map().darts().index(front()); }
|
|
|
|
/// @return the index of the last dart of the path.
|
|
/// @pre !is_empty()
|
|
std::size_t back_index() const
|
|
{ return get_map().darts().index(back()); }
|
|
|
|
/// @return the ith dart of the path taking into account flip.
|
|
/// return null_handle if flip and there is no beta2
|
|
Dart_const_handle get_ith_real_dart(std::size_t i) const
|
|
{
|
|
CGAL_assertion(i<m_path.size());
|
|
return (get_ith_flip(i)?get_map().opposite2(get_ith_dart(i)):
|
|
get_ith_dart(i));
|
|
}
|
|
|
|
/// @return the opposite of the ith dart of the path taking into account flip.
|
|
/// return null_handle if !flip and there is no beta2
|
|
Dart_const_handle get_opposite_ith_real_dart(std::size_t i) const
|
|
{
|
|
CGAL_assertion(i<m_path.size());
|
|
return (get_ith_flip(i)?get_ith_dart(i):
|
|
get_map().opposite2(get_ith_dart(i)));
|
|
}
|
|
|
|
/// @return the first dart of the path, taking into account flip.
|
|
/// @pre !is_empty()
|
|
Dart_const_handle real_front() const
|
|
{
|
|
CGAL_assertion(!is_empty());
|
|
return get_ith_real_dart(0);
|
|
}
|
|
|
|
/// @return the last dart of the path, taking into account flip.
|
|
/// @pre !is_empty()
|
|
Dart_const_handle real_back() const
|
|
{
|
|
CGAL_assertion(!is_empty());
|
|
return get_ith_real_dart(length()-1);
|
|
}
|
|
|
|
/// @return true iff df can be added at the end of the path.
|
|
bool can_be_pushed(Dart_const_handle dh, bool flip=false) const
|
|
{
|
|
// This assert is too long CGAL_assertion(m_map.darts().owns(dh));
|
|
|
|
if (is_empty()) return true;
|
|
|
|
return m_map.template belong_to_same_cell<0>
|
|
(m_flip.back() ? back() : m_map.other_extremity(back()),
|
|
flip ? m_map.other_extremity(dh) : dh);
|
|
}
|
|
|
|
/// Add the given dart at the end of this path.
|
|
/// @pre can_be_pushed(dh)
|
|
void push_back(Dart_const_handle dh, bool flip=false,
|
|
bool update_isclosed=true)
|
|
{
|
|
CGAL_assertion(dh!=Map::null_handle);
|
|
/* This assert is too long, it is tested in the is_valid method. */
|
|
// CGAL_assertion(can_be_pushed(dh, flip));
|
|
|
|
m_path.push_back(dh);
|
|
m_flip.push_back(flip);
|
|
if (update_isclosed) { update_is_closed(); }
|
|
}
|
|
|
|
/// @return true iff the ith dart can be added at the end of the path.
|
|
bool can_be_pushed_by_index(typename Map::size_type i, bool flip=false,
|
|
bool update_isclosed=true) const
|
|
{ return can_be_pushed(get_map().dart_handle(i), flip, update_isclosed); }
|
|
|
|
/// Add the given ith dart at the end of this path.
|
|
void push_back_by_index(typename Map::size_type i, bool flip=false,
|
|
bool update_isclosed=true)
|
|
{ push_back(get_map().dart_handle(i), flip, update_isclosed); }
|
|
|
|
void push_back_by_index(std::initializer_list<typename Map::size_type> l,
|
|
bool update_isclosed=true)
|
|
{
|
|
for (std::size_t i : l)
|
|
{ push_back_by_index(i, false, update_isclosed); }
|
|
}
|
|
|
|
/// @return true iff the dart labeled e can be added at the end of the path.
|
|
bool can_be_pushed_by_label(const std::string& e, bool flip=false) const
|
|
{
|
|
Dart_const_handle dh=get_map().get_dart_labeled(e);
|
|
if (dh==Map::null_handle) { return false; }
|
|
return can_be_pushed(dh, flip);
|
|
}
|
|
|
|
/// Add the dart having the given labels at the end of this path.
|
|
/// Each label is a word, possibly starting by -, words are separated by spaces
|
|
void push_back_by_label(const std::string& s, bool update_isclosed=true)
|
|
{
|
|
std::istringstream iss(s);
|
|
for (std::string e; std::getline(iss, e, ' '); )
|
|
{
|
|
Dart_const_handle dh=get_map().get_dart_labeled(e);
|
|
if (dh!=Map::null_handle) { push_back(dh, false, update_isclosed); }
|
|
}
|
|
}
|
|
|
|
void push_back_by_label(std::initializer_list<const char*> l,
|
|
bool update_isclosed=true)
|
|
{
|
|
for (const char* e : l)
|
|
{ push_back_by_label(e, false, update_isclosed); }
|
|
}
|
|
|
|
Self& operator+=(const Self& other)
|
|
{
|
|
m_path.reserve(m_path.size()+other.m_path.size());
|
|
// Be careful to the special case when *this==other
|
|
// this is the reason of the iend.
|
|
for (std::size_t i=0, iend=other.length(); i<iend; ++i)
|
|
{ push_back(other[i], other.m_flip[i], false); }
|
|
update_is_closed();
|
|
return *this;
|
|
}
|
|
|
|
Self operator+(const Self& other) const
|
|
{
|
|
Self res=*this;
|
|
res+=other;
|
|
return res;
|
|
}
|
|
|
|
/// change m_path and m_flip in order to get the lower number of flips possible
|
|
void simplify_flips(bool show_flips_left=false)
|
|
{
|
|
if (show_flips_left)
|
|
{ std::cout<<"Flips left (maybe none) : "<<std::flush; }
|
|
for(unsigned int i=0; i<length(); ++i)
|
|
{
|
|
if (m_flip[i] && !get_map().template is_free<2>(m_path[i]))
|
|
{
|
|
m_path[i]=get_map().opposite2(m_path[i]);
|
|
m_flip[i]=!m_flip[i];
|
|
}
|
|
else if (show_flips_left)
|
|
{ std::cout<<i<<" "<<std::flush; }
|
|
}
|
|
if (show_flips_left)
|
|
{ std::cout<<std::endl; }
|
|
}
|
|
|
|
/// @return the number of flips of this path.
|
|
unsigned int nb_flips()
|
|
{
|
|
unsigned int res=0;
|
|
for (unsigned int i=0; i<length(); ++i)
|
|
{ if (m_flip[i]) ++res; }
|
|
return res;
|
|
}
|
|
|
|
/// Cut this path to keep only the n first darts.
|
|
void cut(std::size_t n, bool update_isclosed=true)
|
|
{
|
|
if (n>=length()) return;
|
|
m_path.resize(n);
|
|
m_flip.resize(n);
|
|
if (update_isclosed) { update_is_closed(); }
|
|
}
|
|
|
|
/// copy all darts starting from begin and going to the dart before end
|
|
/// from this path to new_path.
|
|
void copy_rest_of_path(std::size_t begin, std::size_t end,
|
|
Self& new_path)
|
|
{
|
|
CGAL_assertion(begin<=end);
|
|
CGAL_assertion(end<=length());
|
|
new_path.m_path.reserve(new_path.m_path.size()+end-begin+1);
|
|
while(begin!=end)
|
|
{
|
|
new_path.push_back(get_ith_dart(begin), get_ith_flip(begin), false);
|
|
++begin;
|
|
}
|
|
update_is_closed();
|
|
}
|
|
|
|
/// Debugging method.
|
|
void display_failed_extention(const std::string& /*name_of_function*/)
|
|
{
|
|
// std::cout<<"Cant extend the path this way ("<<name_of_function<<")"
|
|
// <<std::endl;
|
|
}
|
|
|
|
/// Extend the path straight positive.
|
|
/// @pre must be non empty.
|
|
void extend_straight_positive(std::size_t nb=1, bool update_isclosed=true)
|
|
{
|
|
if (is_empty() || nb==0)
|
|
{ display_failed_extention("extend_straight_positive"); return; }
|
|
|
|
Dart_const_handle dh=back();
|
|
if(back_flip())
|
|
{
|
|
if (get_map().template is_free<2>(dh))
|
|
{ display_failed_extention("extend_straight_positive"); return; }
|
|
else
|
|
{ dh=get_map().opposite2(dh); }
|
|
}
|
|
|
|
for (unsigned int i=0; i<nb; ++i)
|
|
{
|
|
dh=get_map().next(dh);
|
|
|
|
if (get_map().template is_free<2>(dh))
|
|
{ display_failed_extention("extend_straight_positive"); return; }
|
|
dh=get_map().next(get_map().opposite2(dh));
|
|
|
|
push_back(dh, false, false);
|
|
}
|
|
|
|
if (update_isclosed) { update_is_closed(); }
|
|
}
|
|
|
|
/// Extend the path straight negative.
|
|
/// @pre must be non empty.
|
|
void extend_straight_negative(std::size_t nb=1, bool update_isclosed=true)
|
|
{
|
|
if (is_empty() || nb==0)
|
|
{ display_failed_extention("extend_straight_negative"); return; }
|
|
|
|
Dart_const_handle dh=back();
|
|
if(!back_flip())
|
|
{
|
|
if (get_map().template is_free<2>(dh))
|
|
{ display_failed_extention("extend_straight_positive"); return; }
|
|
else
|
|
{ dh=get_map().opposite2(dh); }
|
|
}
|
|
|
|
for (unsigned int i=0; i<nb; ++i)
|
|
{
|
|
dh=get_map().previous(dh);
|
|
|
|
if (get_map().template is_free<2>(dh))
|
|
{ display_failed_extention("extend_straight_negative"); return; }
|
|
dh=get_map().previous(get_map().opposite2(dh));
|
|
|
|
push_back(dh, true, false);
|
|
}
|
|
|
|
if (update_isclosed) { update_is_closed(); }
|
|
}
|
|
|
|
/// Extend the path given a positive turn.
|
|
/// @pre must be non empty.
|
|
void extend_positive_turn(std::size_t nb=1, bool update_isclosed=true)
|
|
{
|
|
if (is_empty())
|
|
{ display_failed_extention("extend_positive_turn"); return; }
|
|
|
|
if (nb==0)
|
|
{
|
|
push_back(back(), !back_flip(), update_isclosed);
|
|
return;
|
|
}
|
|
|
|
Dart_const_handle dh=back();
|
|
if(back_flip())
|
|
{
|
|
if (get_map().template is_free<2>(dh))
|
|
{ display_failed_extention("extend_positive_turn"); return; }
|
|
else
|
|
{ dh=get_map().opposite2(dh); }
|
|
}
|
|
dh=get_map().next(dh);
|
|
|
|
for (unsigned int i=1; i<nb; ++i)
|
|
{
|
|
if (get_map().template is_free<2>(dh))
|
|
{ display_failed_extention("extend_positive_turn"); return; }
|
|
dh=get_map().next(get_map().opposite2(dh));
|
|
}
|
|
|
|
push_back(dh, false, update_isclosed);
|
|
}
|
|
|
|
/// Extend the path given a negative turn.
|
|
/// @pre must be non empty.
|
|
void extend_negative_turn(std::size_t nb=1, bool update_isclosed=true)
|
|
{
|
|
if (is_empty()) { display_failed_extention("extend_negative_turn"); return; }
|
|
|
|
if (nb==0)
|
|
{
|
|
push_back(back(), !back_flip(), update_isclosed);
|
|
return;
|
|
}
|
|
|
|
Dart_const_handle dh=back();
|
|
if(!back_flip())
|
|
{
|
|
if (get_map().template is_free<2>(dh))
|
|
{ display_failed_extention("extend_negative_turn"); return; }
|
|
else
|
|
{ dh=get_map().opposite2(dh); }
|
|
}
|
|
dh=get_map().previous(dh);
|
|
|
|
for (unsigned int i=1; i<nb; ++i)
|
|
{
|
|
if (get_map().template is_free<2>(dh))
|
|
{ display_failed_extention("extend_negative_turn"); return; }
|
|
dh=get_map().previous(get_map().opposite2(dh));
|
|
}
|
|
|
|
push_back(dh, true, update_isclosed);
|
|
}
|
|
|
|
/// Initializes this path to a random starting path.
|
|
/// @pre must be empty.
|
|
bool initialize_random_starting_dart(CGAL::Random& random,
|
|
bool update_isclosed=true)
|
|
{
|
|
if (!is_empty() || get_map().is_empty()) { return false; }
|
|
|
|
// first select a random edge by taking the lower index of
|
|
// the two darts when it is not a boundary
|
|
typename Map::size_type index=static_cast<typename Map::size_type>
|
|
(random.get_int(0, static_cast<int>(get_map().darts().capacity())));
|
|
while (!get_map().darts().is_used(index) ||
|
|
(!get_map().template is_free<2>(get_map().dart_handle(index)) &&
|
|
get_map().dart_handle(index)>get_map().
|
|
opposite2(get_map().dart_handle(index))))
|
|
{
|
|
++index;
|
|
if (index==get_map().darts().capacity()) index=0;
|
|
}
|
|
|
|
// second we take randomly one of the two darts of this edge
|
|
// (potentially with the help of a flip)
|
|
bool heads_or_tails=random.get_bool();
|
|
if (get_map().template is_free<2>(get_map().dart_handle(index)))
|
|
{
|
|
push_back(get_map().dart_handle(index), heads_or_tails, update_isclosed);
|
|
}
|
|
else
|
|
{
|
|
if (heads_or_tails)
|
|
{ push_back(get_map().dart_handle(index), false, update_isclosed); }
|
|
else
|
|
{ push_back(get_map().opposite2(get_map().dart_handle(index)),
|
|
false, update_isclosed); }
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Initializes this path to a random starting path.
|
|
/// @pre must be empty.
|
|
bool initialize_random_starting_dart(bool update_isclosed=true)
|
|
{
|
|
CGAL::Random& random=get_default_random();
|
|
return initialize_random_starting_dart(random, update_isclosed);
|
|
}
|
|
|
|
/// Extends this path with a random dart.
|
|
/// @pre must be non empty.
|
|
bool extend_path_randomly(CGAL::Random& random,
|
|
bool allow_half_turn=true,
|
|
bool update_isclosed=true)
|
|
{
|
|
if (is_empty())
|
|
{ return initialize_random_starting_dart(random, update_isclosed); }
|
|
|
|
if(get_map().template is_free<1>(back()))
|
|
{ return false; }
|
|
|
|
Dart_const_handle next_vertex;
|
|
if (back_flip())
|
|
{ next_vertex=back(); }
|
|
else if (get_map().template is_free<2>(back()))
|
|
{ next_vertex=get_map().next(back()); }
|
|
else
|
|
{ next_vertex=get_map().opposite2(back()); }
|
|
|
|
std::vector<std::pair<Dart_const_handle, bool> > candidats;
|
|
for (auto it=get_map().template darts_of_cell<0>(next_vertex).begin(),
|
|
itend=get_map().template darts_of_cell<0>(next_vertex).end();
|
|
it!=itend; ++it )
|
|
{
|
|
if (back_flip() || !get_map().template is_free<2>(back()))
|
|
{
|
|
candidats.push_back(std::make_pair(it, false));
|
|
if (get_map().template is_free<2>(get_map().previous(it)))
|
|
{ candidats.push_back
|
|
(std::make_pair(get_map().previous(it), true)); }
|
|
}
|
|
else
|
|
{
|
|
if (get_map().template is_free<2>(get_map().previous(it)))
|
|
{ candidats.push_back
|
|
(std::make_pair(get_map().previous(it), true)); }
|
|
candidats.push_back(std::make_pair(it, false));
|
|
}
|
|
}
|
|
//candidats is now the list of all the darts that can be pushed back to
|
|
// the path (maybe with a flip) the first of them in the list is the
|
|
// opposite of back(), or back() itself if it is 2-free
|
|
|
|
std::size_t i=static_cast<std::size_t>
|
|
(random.get_int(allow_half_turn?0:1,static_cast<int>(candidats.size())));
|
|
auto it=candidats.begin();
|
|
for (std::size_t nb=0; nb<i; ++nb, ++it) {}
|
|
push_back(it->first, it->second, update_isclosed);
|
|
return true;
|
|
}
|
|
|
|
/// Extends this path with a random dart.
|
|
/// @pre must be non empty.
|
|
bool extend_path_randomly(bool allow_half_turn=false,
|
|
bool update_isclosed=true)
|
|
{
|
|
CGAL::Random& random=get_default_random();
|
|
return extend_path_randomly(random, allow_half_turn, update_isclosed);
|
|
}
|
|
|
|
/// Generates a random path, with a number of darts >= length.
|
|
void generate_random_path(std::size_t length,
|
|
CGAL::Random& random=get_default_random(),
|
|
bool allow_half_turns=true,
|
|
bool update_isclosed=true)
|
|
{
|
|
m_path.reserve(m_path.size()+length);
|
|
for (std::size_t i=0; i<length; ++i)
|
|
{ extend_path_randomly(random, allow_half_turns, true); }
|
|
if (update_isclosed) { update_is_closed(); }
|
|
}
|
|
|
|
/// Generates a random path.
|
|
template<typename Path>
|
|
void generate_random_path(CGAL::Random& random,
|
|
bool update_isclosed=true)
|
|
{ generate_random_path(random.get_int(1, 10000),
|
|
random, true, update_isclosed); }
|
|
|
|
/// Generates a random path.
|
|
template<typename Path>
|
|
void generate_random_path(std::size_t length,
|
|
bool update_isclosed=true)
|
|
{
|
|
CGAL::Random& random=get_default_random();
|
|
generate_random_path(length, random, true, update_isclosed);
|
|
}
|
|
|
|
/// Generates a random path.
|
|
template<typename Path>
|
|
void generate_random_path(bool update_isclosed=true)
|
|
{
|
|
CGAL::Random& random=get_default_random();
|
|
generate_random_path(random, update_isclosed);
|
|
}
|
|
|
|
/// Generates a random closed path.
|
|
void generate_random_closed_path(std::size_t length, CGAL::Random& random)
|
|
{
|
|
m_path.reserve(m_path.size()+length);
|
|
std::size_t i=0;
|
|
while(i<length || !is_closed())
|
|
{
|
|
extend_path_randomly(random, true, true);
|
|
++i;
|
|
}
|
|
}
|
|
|
|
/// Generates a random closed path.
|
|
void generate_random_closed_path(std::size_t length)
|
|
{
|
|
CGAL::Random& random=get_default_random();
|
|
generate_random_closed_path(length, random);
|
|
}
|
|
|
|
/// Generates a random closed path.
|
|
void generate_random_closed_path(CGAL::Random& random)
|
|
{ generate_random_closed_path(random.get_int(1, 10000), random); }
|
|
|
|
/// Generates a random closed path.
|
|
void generate_random_closed_path()
|
|
{
|
|
CGAL::Random& random=get_default_random();
|
|
generate_random_closed_path(random.get_int(1, 10000), random);
|
|
}
|
|
|
|
/// Replace edge [i] by the path of darts along the face.
|
|
/// If this face does not exist (if it is a boundary) then replace the edge
|
|
/// by the face on the other side. Problem of complexity when used many times
|
|
/// (like in update_path_randomly).
|
|
bool push_around_face(std::size_t i, bool update_isclosed=true)
|
|
{
|
|
CGAL_assertion(i<length());
|
|
|
|
// It is not possible to push around a perforated face since it changes
|
|
// the homotopy of the path.
|
|
if (get_map().is_perforated(get_ith_dart(i))) { return false; }
|
|
|
|
Self p2(get_mesh());
|
|
|
|
// 1) We add in p2 the part of the path which is pushed.
|
|
if (get_ith_flip(i))
|
|
{
|
|
Dart_const_handle dh=get_map().next(get_ith_dart(i));
|
|
do
|
|
{
|
|
p2.push_back(dh, false, false);
|
|
dh=get_map().next(dh);
|
|
}
|
|
while(dh!=get_ith_dart(i));
|
|
}
|
|
else
|
|
{
|
|
Dart_const_handle dh=get_map().previous(get_ith_dart(i));
|
|
do
|
|
{
|
|
p2.push_back(dh, true, false);
|
|
dh=get_map().previous(dh);
|
|
}
|
|
while(dh!=get_ith_dart(i));
|
|
}
|
|
|
|
// 2) We copy the end of the path.
|
|
p2.m_path.reserve(p2.length()+length()-i);
|
|
for (std::size_t j=i+1; j<length(); ++j)
|
|
{ p2.push_back(get_ith_dart(j), get_ith_flip(j), false); }
|
|
|
|
// 3) We cut this path to keep the first i darts.
|
|
cut(i, false);
|
|
m_path.reserve(length()+p2.length());
|
|
for (std::size_t j=0; j<p2.length(); ++j)
|
|
{ push_back(p2[j], p2.get_ith_flip(j), false); }
|
|
|
|
if (update_isclosed) { update_is_closed(); }
|
|
return true;
|
|
|
|
//CGAL_assertion(is_valid());
|
|
}
|
|
|
|
/// Transform the current path by pushing some dart around faces.
|
|
/// At the end, the new path is homotopic to the original one.
|
|
void update_path_randomly(std::size_t nb, CGAL::Random& random,
|
|
bool update_isclosed=true)
|
|
{
|
|
if (is_empty()) return;
|
|
|
|
for (unsigned int i=0; i<nb; ++i)
|
|
{
|
|
std::size_t dartn=static_cast<std::size_t>
|
|
(random.get_int(0, static_cast<int>(length())));
|
|
std::size_t j=dartn;
|
|
while(!push_around_face(dartn, false) && dartn!=j)
|
|
{ ++dartn; }
|
|
}
|
|
if (update_isclosed) { update_is_closed(); }
|
|
}
|
|
|
|
void update_path_randomly(CGAL::Random& random,
|
|
bool update_isclosed=true)
|
|
{ update_path_randomly(random.get_int(0, 10000), update_isclosed); }
|
|
|
|
void update_path_randomly(std::size_t nb, bool update_isclosed=true)
|
|
{
|
|
CGAL::Random random;
|
|
update_path_randomly(nb, random, update_isclosed);
|
|
}
|
|
|
|
void update_path_randomly(bool update_isclosed=true)
|
|
{
|
|
CGAL::Random& random=get_default_random();
|
|
update_path_randomly(random, update_isclosed);
|
|
}
|
|
|
|
/// @return true iff the i-th dart of the path and the j-th dart of the other
|
|
/// are the same (taking into account the flips !)
|
|
bool are_same_step(std::size_t i, const Self& other, std::size_t j) const
|
|
{
|
|
if (get_ith_flip(i)==other.get_ith_flip(j))
|
|
{ return get_ith_dart(i)==other[j]; }
|
|
|
|
if (get_map().template is_free<2>(get_ith_dart(i)) ||
|
|
get_map().template is_free<2>(other[j]))
|
|
{ return false; }
|
|
|
|
return get_ith_dart(i)==get_map().opposite2(other[j]);
|
|
}
|
|
|
|
/// @return true if this path is equal to other path, identifying dart 0 of
|
|
/// this path with dart start in other path.
|
|
bool are_same_paths_from(const Self& other, std::size_t start) const
|
|
{
|
|
CGAL_assertion(start==0 || start<length());
|
|
CGAL_assertion(is_closed() || start==0);
|
|
CGAL_assertion(length()==other.length() && is_closed()==other.is_closed());
|
|
|
|
for(std::size_t i=0; i<length(); ++i)
|
|
{
|
|
if (!are_same_step(i, other, start))
|
|
{ return false; }
|
|
start=next_index(start);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// @return true if this path is equal to other path. For closed paths, test
|
|
/// all possible starting darts. Old quadratic version, new version
|
|
/// (operator==) use linear version based on Knuth, Morris, Pratt
|
|
bool are_paths_equals(const Self& other) const
|
|
{
|
|
if (length()!=other.length() || is_closed()!=other.is_closed())
|
|
{ return false; }
|
|
|
|
if (!is_closed())
|
|
{ return are_same_paths_from(other, 0); }
|
|
|
|
for(std::size_t start=0; start<length(); ++start)
|
|
{
|
|
if (are_same_paths_from(other, start))
|
|
{ return true; }
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// @return true if this path is equal to other path. For closed paths,
|
|
/// equality is achieved whatever the first dart.
|
|
bool operator==(const Self& other) const
|
|
{
|
|
if (length()!=other.length() || is_closed()!=other.is_closed())
|
|
{ return false; }
|
|
|
|
if (!is_closed())
|
|
{ return are_same_paths_from(other, 0); }
|
|
|
|
Self pp1=*this;
|
|
pp1.simplify_flips();
|
|
Self pp2=other;
|
|
pp2.simplify_flips();
|
|
pp2+=pp2;
|
|
// Now we search if pp1 is a sub-motif of pp2 <=> *this==other
|
|
|
|
return boost::algorithm::knuth_morris_pratt_search(pp2.m_path.begin(),
|
|
pp2.m_path.end(),
|
|
pp1.m_path.begin(),
|
|
pp1.m_path.end())
|
|
#if BOOST_VERSION>=106200
|
|
.first
|
|
#endif
|
|
!=pp2.m_path.end();
|
|
}
|
|
bool operator!=(const Self& other) const
|
|
{ return !(operator==(other)); }
|
|
|
|
/// @Return true if this path is equal to other path, identifying dart 0 of
|
|
/// this path with dart start in other path. other path is given
|
|
/// by index of its darts, in text format.
|
|
bool are_same_paths_from(const char* other, std::size_t start) const
|
|
{
|
|
CGAL_assertion(start==0 || start<length());
|
|
CGAL_assertion(is_closed() || start==0);
|
|
|
|
std::string sother(other);
|
|
std::istringstream iss(sother);
|
|
uint64_t nb;
|
|
|
|
for(std::size_t i=0; i<length(); ++i)
|
|
{
|
|
if (!iss.good())
|
|
{ return false; }
|
|
iss>>nb;
|
|
if (nb!=m_map.darts().index(get_ith_dart(start)))
|
|
{ return false; }
|
|
start=next_index(start);
|
|
}
|
|
iss>>nb;
|
|
if (iss.good())
|
|
{ return false; } // There are more elements in other than in this path
|
|
|
|
return true;
|
|
}
|
|
/// @return true if this path is equal to other path. For closed paths, test
|
|
/// all possible starting darts. other path is given by index of its
|
|
/// darts, in text format.
|
|
bool operator==(const char* other) const
|
|
{
|
|
if (!is_closed())
|
|
{ return are_same_paths_from(other, 0); }
|
|
|
|
for(std::size_t start=0; start<length(); ++start)
|
|
{
|
|
if (are_same_paths_from(other, start))
|
|
{ return true; }
|
|
}
|
|
return false;
|
|
}
|
|
bool operator!=(const char* other) const
|
|
{ return !(operator==(other)); }
|
|
|
|
|
|
/// @return true iff the path is valid; i.e. a sequence of edges two by
|
|
/// two adjacent.
|
|
bool is_valid(bool display_error=false) const
|
|
{
|
|
if (is_empty()) { return !is_closed(); } // an empty past is not closed
|
|
Dart_const_handle last_vertex;
|
|
|
|
for (unsigned int i=1; i<m_path.size(); ++i)
|
|
{
|
|
/* This assert is long if (!m_map.darts().owns(m_path[i]))
|
|
{ return false; } */
|
|
|
|
if (m_path[i]==Map::null_handle || m_path[i]==m_map.null_dart_handle)
|
|
{ return false; }
|
|
|
|
last_vertex=m_flip[i-1]?m_path[i-1]:get_map().next(m_path[i-1]);
|
|
if (last_vertex==Map::null_handle)
|
|
{
|
|
if (display_error)
|
|
{ std::cout<<"Invalid path: one of the vertices doesn't exist"
|
|
<<std::endl; }
|
|
return false;
|
|
}
|
|
|
|
if (!m_map.template belong_to_same_cell<0>
|
|
(m_flip[i]?get_map().next(m_path[i]):m_path[i], last_vertex))
|
|
{
|
|
if (display_error)
|
|
{ std::cout<<"Invalid path: dart "<<i-1<<" and dart "<<i
|
|
<<" are not adjacents"<<std::endl; }
|
|
return false;
|
|
}
|
|
}
|
|
last_vertex=back_flip()?back():get_map().next(back());
|
|
if (is_closed())
|
|
{
|
|
if (last_vertex==Map::null_handle)
|
|
{
|
|
if (display_error)
|
|
{ std::cout<<"Invalid path: one of the vertices doesn't exist"
|
|
<<std::endl; }
|
|
return false;
|
|
}
|
|
if (!m_map.template belong_to_same_cell<0>
|
|
(front_flip()?get_map().next(front()):front(), last_vertex))
|
|
{
|
|
if (display_error)
|
|
{ std::cout<<"Invalid path: m_is_closed is true but the path is "
|
|
<<"not closed"<<std::endl; }
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (last_vertex==Map::null_handle)
|
|
{
|
|
if (display_error)
|
|
{ std::cout<<"Invalid path: one of the vertices doesn't exist"
|
|
<<std::endl; }
|
|
return false;
|
|
}
|
|
if (m_map.template belong_to_same_cell<0>
|
|
(front_flip()?get_map().next(front()):front(), last_vertex))
|
|
{
|
|
if (display_error)
|
|
{ std::cout<<"Invalid path: m_is_closed is false but the path "
|
|
<<"is closed"<<std::endl; }
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Update m_is_closed to true iff the path is closed (i.e. the second
|
|
/// extremity of the last dart of the path is the same vertex than the one
|
|
/// of the first dart of the path).
|
|
void update_is_closed()
|
|
{
|
|
// CGAL_assertion(is_valid());
|
|
if (is_empty()) { m_is_closed=false; }
|
|
else
|
|
{
|
|
Dart_const_handle
|
|
pend=m_flip.back()?back():m_map.other_extremity(back());
|
|
if (pend==Map::null_handle) { m_is_closed=false; }
|
|
else
|
|
{
|
|
Dart_const_handle
|
|
pbegin=m_flip[0]?m_map.other_extremity(m_path[0]):m_path[0];
|
|
m_is_closed=m_map.template belong_to_same_cell<0>(pbegin, pend);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// @return true iff the path does not pass twice through a same edge
|
|
/// or a same vertex.
|
|
bool is_simple() const
|
|
{
|
|
typename Map::size_type markvertex=m_map.get_new_mark();
|
|
typename Map::size_type markedge=m_map.get_new_mark();
|
|
|
|
bool res=true;
|
|
Dart_const_handle dh_vertex;
|
|
unsigned int i=0;
|
|
for (i=0; res && i<m_path.size(); ++i)
|
|
{
|
|
dh_vertex=m_flip[i]?get_map().next(m_path[i]):m_path[i];
|
|
if (m_map.is_marked(dh_vertex, markvertex)) { res=false; }
|
|
else { CGAL::mark_cell<Map, 0>(m_map, dh_vertex, markvertex); }
|
|
|
|
if (m_map.is_marked(m_path[i], markedge)) { res=false; }
|
|
else { CGAL::mark_cell<Map, 1>(m_map, m_path[i], markedge); }
|
|
}
|
|
|
|
i=0;
|
|
while(m_map.number_of_marked_darts(markedge)>0 ||
|
|
m_map.number_of_marked_darts(markvertex)>0)
|
|
{
|
|
CGAL_assertion(i<m_path.size());
|
|
dh_vertex=m_flip[i]?get_map().next(m_path[i]):m_path[i];
|
|
if (m_map.is_marked(dh_vertex, markvertex))
|
|
{ CGAL::unmark_cell<Map, 0>(m_map, dh_vertex, markvertex); }
|
|
if (m_map.is_marked(m_path[i], markedge))
|
|
{ CGAL::unmark_cell<Map, 1>(m_map, m_path[i], markedge); }
|
|
++i;
|
|
}
|
|
|
|
m_map.free_mark(markvertex);
|
|
m_map.free_mark(markedge);
|
|
|
|
return res;
|
|
}
|
|
|
|
/// Reverse the path (i.e. negate its orientation).
|
|
void reverse()
|
|
{
|
|
bool tmpbool;
|
|
for (unsigned int i=0; i<length()/2; ++i)
|
|
{
|
|
std::swap(m_path[i], m_path[length()-1-i]);
|
|
tmpbool=m_flip[i]; // Cannot swap in vector bool
|
|
m_flip[i]=m_flip[length()-1-i];
|
|
m_flip[length()-1-i]=tmpbool;
|
|
}
|
|
|
|
for (unsigned int i=0; i<length(); ++i)
|
|
{ m_flip[i]=!m_flip[i]; }
|
|
}
|
|
|
|
/// If the given path is opened, close it by doing the same path that the
|
|
/// first one in reverse direction.
|
|
void close()
|
|
{ // TODO follow shortest path ?
|
|
if (!is_closed())
|
|
{
|
|
for (int i=m_path.size()-1; i>=0; --i)
|
|
{ m_path.push_back(m_path[i], !m_flip[i], false); }
|
|
m_is_closed=true;
|
|
}
|
|
}
|
|
|
|
/// @return the turn between dart number i and dart number i+1.
|
|
/// (turn is position of the second edge in the cyclic ordering of
|
|
/// edges starting from the first edge around the second extremity
|
|
/// of the first dart)
|
|
std::size_t next_positive_turn(std::size_t i) const
|
|
{
|
|
// CGAL_assertion(is_valid());
|
|
CGAL_assertion(i<m_path.size());
|
|
CGAL_assertion (is_closed() || i<length()-1);
|
|
|
|
if ((get_ith_flip(i) && get_map().template is_free<2>(get_ith_dart(i))) ||
|
|
(get_next_flip(i) && get_map().template is_free<2>(get_next_dart(i))))
|
|
{ return (std::numeric_limits<std::size_t>::max)(); }
|
|
|
|
return m_map.positive_turn(get_ith_real_dart(i),
|
|
get_ith_real_dart(next_index(i)));
|
|
}
|
|
|
|
/// Same than next_positive_turn but turning in reverse orientation
|
|
/// around vertex.
|
|
std::size_t next_negative_turn(std::size_t i) const
|
|
{
|
|
// CGAL_assertion(is_valid());
|
|
CGAL_assertion(i<m_path.size());
|
|
CGAL_assertion (is_closed() || i<length()-1);
|
|
|
|
if ((!get_ith_flip(i) && get_map().template is_free<2>(get_ith_dart(i))) ||
|
|
(!get_next_flip(i) && get_map().template is_free<2>(get_next_dart(i))))
|
|
{ return (std::numeric_limits<std::size_t>::max)(); }
|
|
|
|
return m_map.positive_turn(get_opposite_ith_real_dart(next_index(i)),
|
|
get_opposite_ith_real_dart(i));
|
|
}
|
|
|
|
/// Computes all positive turns of this path.
|
|
std::vector<std::size_t> compute_positive_turns() const
|
|
{
|
|
std::vector<std::size_t> res;
|
|
if (is_empty()) return res;
|
|
|
|
std::size_t i;
|
|
for (i=0; i<m_path.size()-1; ++i)
|
|
{ res.push_back(next_positive_turn(i)); }
|
|
if (is_closed())
|
|
{ res.push_back(next_positive_turn(i)); }
|
|
return res;
|
|
}
|
|
|
|
/// Computes all negative turns of this path.
|
|
std::vector<std::size_t> compute_negative_turns() const
|
|
{
|
|
std::vector<std::size_t> res;
|
|
if (is_empty()) return res;
|
|
|
|
std::size_t i;
|
|
for (i=0; i<m_path.size()-1; ++i)
|
|
{ res.push_back(next_negative_turn(i)); }
|
|
if (is_closed())
|
|
{ res.push_back(next_negative_turn(i)); }
|
|
return res;
|
|
}
|
|
|
|
/// Computes all positive or negative turns of this path, depending on p.
|
|
std::vector<std::size_t> compute_turns(bool p) const
|
|
{ return (p?compute_positive_turns():compute_negative_turns()); }
|
|
|
|
bool same_turns_from(const char* turns,
|
|
const std::vector<std::size_t>& resplus,
|
|
const std::vector<std::size_t>& resmoins,
|
|
std::size_t start) const
|
|
{
|
|
CGAL_assertion(start==0 || start<resplus.size());
|
|
CGAL_assertion(resplus.size()==resmoins.size());
|
|
|
|
std::string sturns(turns);
|
|
std::istringstream iss(sturns);
|
|
int64_t nb;
|
|
|
|
for(std::size_t i=0; i<resplus.size(); ++i)
|
|
{
|
|
if (!iss.good())
|
|
{ return false; }
|
|
iss>>nb;
|
|
if ((nb>=0 && resplus[start]!=static_cast<std::size_t>(nb)) ||
|
|
(nb<0 && resmoins[start]!=static_cast<std::size_t>(-nb)))
|
|
{ return false; }
|
|
|
|
start=next_index(start);
|
|
}
|
|
iss>>nb;
|
|
if (iss.good())
|
|
{ return false; } // There are more elements in turns than in res
|
|
|
|
return true;
|
|
}
|
|
|
|
bool same_turns(const char* turns) const
|
|
{
|
|
std::vector<std::size_t> resplus=compute_positive_turns();
|
|
std::vector<std::size_t> resmoins=compute_negative_turns();
|
|
|
|
if (!is_closed())
|
|
{ return same_turns_from(turns, resplus, resmoins, 0); }
|
|
|
|
for (std::size_t start=0; start<length(); ++start)
|
|
{
|
|
if (same_turns_from(turns, resplus, resmoins, start))
|
|
{ return true; }
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void display_positive_turns() const
|
|
{
|
|
std::cout<<"+(";
|
|
std::vector<std::size_t> res=compute_positive_turns();
|
|
for (std::size_t i=0; i<res.size(); ++i)
|
|
{ std::cout<<res[i]<<(i<res.size()-1?" ":""); }
|
|
std::cout<<")";
|
|
}
|
|
|
|
void display_negative_turns() const
|
|
{
|
|
std::cout<<"-(";
|
|
std::vector<std::size_t> res=compute_negative_turns();
|
|
for (std::size_t i=0; i<res.size(); ++i)
|
|
{ std::cout<<res[i]<<(i<res.size()-1?" ":""); }
|
|
std::cout<<")";
|
|
}
|
|
|
|
void display_pos_and_neg_turns() const
|
|
{
|
|
display_positive_turns();
|
|
std::cout<<" ";
|
|
display_negative_turns();
|
|
}
|
|
|
|
void display() const
|
|
{
|
|
for (std::size_t i=0; i<length(); ++i)
|
|
{
|
|
std::cout<<m_map.darts().index(get_ith_dart(i));
|
|
if (m_flip[i])
|
|
{ std::cout<<"f"; }
|
|
if (i<length()-1) { std::cout<<" "; }
|
|
}
|
|
if (is_closed())
|
|
{ std::cout<<" c "; } //<<m_map.darts().index(get_ith_dart(0)); }
|
|
}
|
|
|
|
friend std::ostream& operator<<(std::ostream& os, const Self& p)
|
|
{
|
|
p.display();
|
|
return os;
|
|
}
|
|
|
|
protected:
|
|
const typename Get_map<Mesh, Mesh>::storage_type m_map; // The underlying map
|
|
std::vector<Dart_const_handle> m_path; /// The sequence of darts
|
|
bool m_is_closed; /// True iff the path is a cycle
|
|
std::vector<bool> m_flip; /// The sequence of flips
|
|
};
|
|
|
|
} // namespace Surface_mesh_topology
|
|
} // namespace CGAL
|
|
|
|
#endif // CGAL_PATH_ON_SURFACE_H //
|
|
// EOF //
|