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

Generalize Kernel and Point Type #2

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
143 changes: 93 additions & 50 deletions include/PolygonSimplification.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,62 +33,107 @@

namespace com {
namespace geopipe {
namespace PolySimp {
using CGALKernel = CGAL::Exact_predicates_exact_constructions_kernel;
using CGALPoint = CGALKernel::Point_2;
using CGALPolygon = CGAL::Polygon_2<CGALKernel>;
template<typename Kernel = CGAL::Exact_predicates_exact_constructions_kernel, typename Point = typename Kernel::Point_2>
class PolySimp {
using CGALKernel = Kernel;
KermMartian marked this conversation as resolved.
Show resolved Hide resolved
using CGALPoint = Point;
using CGALPolygon = CGAL::Polygon_2<CGALKernel, std::vector<Point>>;
Copy link
Collaborator

Choose a reason for hiding this comment

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

A couple questions:

(a) it doesn't seem like Point should be able to be specified separately from Kernel?
(b) are there any restrictions on the types of kernels that can be used? I would be fairly surprised if an inexact kernel could be used with Nef polyhedra....

Copy link
Author

Choose a reason for hiding this comment

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

(a) In an ideal world only Point would be specified and we would derive the Kernel from the point, but my reading of https://doc.cgal.org/latest/Kernel_23/classCGAL_1_1Point__2.html doesn't show a way to do this.
(b) I'd be surprised as well. The purpose of this PR is really to allow different point types; the different kernels is just a side-effect based on (a).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are you trying to use a point type other than Kernel::Point_2?

Copy link
Collaborator

Choose a reason for hiding this comment

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

But if you really want to just specify Point, you need something like:

namespace detail {
    template<class Point>
    struct cgal_infer_point_kernel { using type = void; }

    template<class Kernel>
    struct cgal_infer_point_kernel< Point_2<Kernel> > { using type = Kernel; }

}

template<class Point, class Kernel = typename detail::cgal_infer_point_kernel<Point>::type >
/* ... */

Copy link
Collaborator

Choose a reason for hiding this comment

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

ah, okay, so, based on our Google Chat discussion, there's no reason to specify point type and kernel type separately. Following up with additional change request.


using CGALBoundedKernel = CGAL::Bounded_kernel<CGALKernel>;
using NefPolyhedron = CGAL::Nef_polyhedron_2<CGALBoundedKernel>;
using PolyExplorer = NefPolyhedron::Explorer;
using PolyExplorer = typename NefPolyhedron::Explorer;

// Private methods - previously ::detail within namespace

namespace detail {
using std::vector;

auto identity = [](vector<CGALPoint> &p){ return p; };
auto points2Poly = [](vector<CGALPoint> &p){ return CGALPolygon(p.cbegin(), p.cend()); };
template<typename T> using BareType = typename std::remove_cv<typename std::remove_reference<T>::type>::type;

template<typename T, typename F = decltype(identity)> void extractFiniteFaces(const PolyExplorer &explorer, T& faces, F f = identity) {
using Face = BareType<decltype(*explorer.faces_begin())>;
std::transform(++(explorer.faces_begin()), explorer.faces_end(), std::back_inserter(faces), [&f](const Face &face){
/*
if (std::distance(face.fc_begin(), face.fc_end()) > 0) {
std::cerr << ("There shouldn't be a hole in the interior of this face, "
"because it's the result of tracing a single polyline into the plane\n") << std::endl;
}
*/

CGAL::Container_from_circulator<PolyExplorer::Halfedge_around_face_const_circulator> edges(face.halfedge());
using Edge = BareType<decltype(*edges.begin())>;
vector<CGALPoint> points;
// As long as we are using a Bounded_kernel, we don't need to worry that the vertex is actually a ray.
std::transform(edges.begin(), edges.end(), std::back_inserter(points), [](const Edge &edge){ return edge.vertex()->point(); });

return f(points);
});
}

void debugNef(const NefPolyhedron &poly) {
vector<CGALPolygon> temp;
PolyExplorer explorer = poly.explorer();
extractFiniteFaces(poly.explorer(), temp, points2Poly);
std::for_each(temp.cbegin(), temp.cend(), [](const CGALPolygon &poly){
std::cout << "\t" << poly << " ( is simple? " << poly.is_simple() << " )" << std::endl;
});
}
}
static constexpr auto identity = [](std::vector<CGALPoint> &p) constexpr { return p; };
static constexpr auto points2Poly = [](std::vector<CGALPoint> &p) constexpr { return CGALPolygon(p.cbegin(), p.cend()); };
template<typename T> using BareType = typename std::remove_cv<typename std::remove_reference<T>::type>::type;

std::vector<CGALPolygon> simplifyPolygon(const CGALPolygon &inp, bool auto_close = true){
/**************************************************
* Extracts the finite faces from a Nef polyhedron
* @arg explorer The `CGAL::Nef_polyhedron_2<CGALBoundedKernel>::Explorer
* to traverse.
* @arg faces The output container, must support
* `std::back_inserter`. Typically `std::vector<E>
* for some face-type `E`.
* @arg f A functor from
* `std::vector<CGAL::Exact_predicates_exact_constructions_kernel::Point_2>`
* to `E`, where `E` is the element type of `faces`.
**************************************************/
template<typename T, typename F = decltype(identity)> static void extractFiniteFaces(const PolyExplorer &explorer, T& faces, F f = identity) {
using Face = BareType<decltype(*explorer.faces_begin())>;
std::transform(++(explorer.faces_begin()), explorer.faces_end(), std::back_inserter(faces), [&f](const Face &face){
/*
if (std::distance(face.fc_begin(), face.fc_end()) > 0) {
std::cerr << ("There shouldn't be a hole in the interior of this face, "
"because it's the result of tracing a single polyline into the plane\n") << std::endl;
}
*/

CGAL::Container_from_circulator<typename PolyExplorer::Halfedge_around_face_const_circulator> edges(face.halfedge());
using Edge = BareType<decltype(*edges.begin())>;
std::vector<CGALPoint> points;
// As long as we are using a Bounded_kernel, we don't need to worry that the vertex is actually a ray.
std::transform(edges.begin(), edges.end(), std::back_inserter(points), [](const Edge &edge){ return edge.vertex()->point(); });
Copy link
Collaborator

Choose a reason for hiding this comment

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

FWIW, I'm pretty sure that the points associated with each vertex will be CGALKernel::Point_2 and not CGALPoint, so this is not going to preserve IDs. If this doesn't work for you, you're going to need to provide a custom kernel that uses your custom points.

Copy link
Author

Choose a reason for hiding this comment

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

@elfprince13 what makes you say that?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Because CGAL::Nef_polyhedron_2 is templated on the kernel type and not the point type, so the internal data structures have no way of knowing about the ids. In principle, that doesn't preclude an interface based on some kind of type-erasure mechanics, but I know enough about CGAL's zero-cost abstractions design philosophy to tell you that's not what's happening here.

Have you actually run tests to confirm if a no-op simplification on an already simple polygon preserves IDs?

Copy link
Author

Choose a reason for hiding this comment

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

No, I have not, but that certainly sounds de rigueur.


return f(points);
});
}
/// Dump some info on `poly` to `std::cout`.
void debugNef(const NefPolyhedron &poly) {
std::vector<CGALPolygon> temp;
PolyExplorer explorer = poly.explorer();
extractFiniteFaces(poly.explorer(), temp, points2Poly);
std::for_each(temp.cbegin(), temp.cend(), [](const CGALPolygon &poly){
std::cout << "\t" << poly << " ( is simple? " << poly.is_simple() << " )" << std::endl;
});
}

public:
/*******************************************************************************
* Converts an edge soup into a set of simple, orientable, finite-area polygons.
*
* 1. Subtract the edges of `inp` from an infinite plane and extract the finite faces.
* 2. Union result back together, and regularize (removing 0- and 1-D holes).
* 3. Take the interior (removing 0-D intersections, no 1-D intersections can
* remain after step 2), and re-extract the finite faces.
*
* Since this loses orientation information, it makes no assumptions
* about whether nested polygons are holes.
*
* If `auto_close = true`, it first makes explicit the implicit edge between
* the first and last pointer of `inp`. Otherwise treats `inp` as a polyline
* instead of a polygon.
*
* <pre class="markdeep">
* # Example 1: Repairing bowties
* ## Input:
* *************************************************************************
* * Input (Unorientable) Output *
* * .--------->. .--------->. *
* * ^ | ^ | *
* * | | | Face 1 | *
* * | | | v *
* * '<------------------' '<--------. .------->. *
* * | ^ ^ | *
* * | | | Face 2 | *
* * | | | v *
* * v.------>' '<--------. *
* * *
* *************************************************************************
*
* </pre>
*******************************************************************************/
static std::vector<CGALPolygon> simplifyPolygon(const CGALPolygon &inp, bool auto_close = true) {
using std::pair;
using std::vector;
using PointIter = vector<CGALPoint>::const_iterator;
using PointIter = typename vector<CGALPoint>::const_iterator;
using PointIterPair = pair<PointIter, PointIter>;

vector<CGALPoint> points(inp.container());
vector<CGALPolygon> polys;

if(auto_close){
if (auto_close) {
points.push_back(inp.container().front());
}

Expand All @@ -100,7 +145,7 @@ namespace com {
NefPolyhedron area_pointset = (whole_plane - boundary_pointset).interior();

vector<vector<CGALPoint> > components;
detail::extractFiniteFaces(area_pointset.explorer(), components);
extractFiniteFaces(area_pointset.explorer(), components);

vector<PointIterPair> component_polys;
std::transform(components.cbegin(), components.cend(), std::back_inserter(component_polys), [](const vector<CGALPoint> &component) {
Expand All @@ -111,17 +156,15 @@ namespace com {
NefPolyhedron remainder = std::accumulate(component_polys.cbegin(), component_polys.cend(), NefPolyhedron(NefPolyhedron::EMPTY), [](const NefPolyhedron& left, const PointIterPair& right){
return left.join(NefPolyhedron(right.first, right.second));
});
//detail::debugNef(remainder);
//debugNef(remainder);

NefPolyhedron remainder_clean = remainder.regularization().interior();
//detail::debugNef(remainder_clean);
//debugNef(remainder_clean);

detail::extractFiniteFaces(remainder_clean.explorer(), polys, detail::points2Poly);
extractFiniteFaces(remainder_clean.explorer(), polys, points2Poly);

return polys;
}


};
};
};
10 changes: 6 additions & 4 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,28 @@

#include <PolygonSimplification.hpp>

using namespace com::geopipe::PolySimp;
using namespace com::geopipe;

int main(int argc, const char* argv[]) {
using simp = PolySimp;

int ret;
if((!argc % 2)){
std::cout << argv[0] << " x0 y0 [... xn yn]" << std::endl;
ret = -1;
} else {
typedef const char * (*CStrPair)[2];
CStrPair arg_pairs = (CStrPair)(argv + 1);
std::vector<CGALPoint> problemNodes;
std::vector<simp.CGALPoint> problemNodes;

std::transform(arg_pairs, arg_pairs + (argc / 2), std::back_inserter(problemNodes),
[](const char *(arg[2])){
return CGALPoint(std::atof(arg[0]), std::atof(arg[1]));
});

CGALPolygon test(problemNodes.cbegin(), problemNodes.cend());
simp.CGALPolygon test(problemNodes.cbegin(), problemNodes.cend());

std::vector<CGALPolygon> fixed = simplifyPolygon(test);
std::vector<simp.CGALPolygon> fixed = simp.simplifyPolygon(test);
std::cout << "Finished set: " << std::endl;
std::for_each(fixed.cbegin(), fixed.cend(), [](const CGALPolygon &poly){
std::cout << "\t" << poly << " ( is simple? " << poly.is_simple() << " )" << std::endl;
Expand Down