import @afabri files for Boolean using CDT

This commit is contained in:
Sébastien Loriot 2024-10-01 12:01:09 +02:00
parent f91765503c
commit a41cd1c626
8 changed files with 1096 additions and 2 deletions

View File

@ -0,0 +1,319 @@
#include <fstream>
// CGAL headers
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Polygon_2.h>
#include <CGAL/point_generators_2.h>
#include <CGAL/Triangulation_2/Boolean.h>
#include <boost/config.hpp>
#include <boost/version.hpp>
#include <CGAL/IO/WKT.h>
// Qt headers
#include <QtGui>
#include <QString>
#include <QFileDialog>
#include <QInputDialog>
#include <QGraphicsLineItem>
// GraphicsView items and event filters (input classes)
#include <CGAL/Qt/GraphicsViewPolylineInput.h>
#include <CGAL/Qt/GraphicsViewPolygonWithHolesInput.h>
#include <CGAL/Qt/PolygonGraphicsItem.h>
#include <CGAL/Qt/PolygonWithHolesGraphicsItem.h>
#include <CGAL/Qt/MultipolygonWithHolesGraphicsItem.h>
#include <CGAL/Qt/LineGraphicsItem.h>
// the two base classes
#include "ui_Boolean_2.h"
#include <CGAL/Qt/DemosMainWindow.h>
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef K::Point_2 Point_2;
typedef K::Segment_2 Segment_2;
typedef K::Line_2 Line_2;
typedef CGAL::Polygon_2<K> Polygon2;
typedef CGAL::Polygon_with_holes_2<K> Polygon_with_holes_2;
typedef CGAL::Multipolygon_with_holes_2<K> Multipolygon_with_holes_2;
typedef CGAL::Triangulations::Boolean<K> Boolean;
typedef std::shared_ptr<Polygon2> PolygonPtr ;
typedef std::vector<PolygonPtr> PolygonPtr_vector ;
class MainWindow :
public CGAL::Qt::DemosMainWindow,
public Ui::Polygon_2
{
Q_OBJECT
private:
bool add_2_A = true;
CGAL::Qt::Converter<K> convert;
Polygon2 poly;
Polygon_with_holes_2 pwhA, pwhB;
Multipolygon_with_holes_2 mpwhA, mpwhB, mpwhC;
QGraphicsScene scene;
CGAL::Qt::MultipolygonWithHolesGraphicsItem<Multipolygon_with_holes_2> *mpwhAgi, *mpwhBgi, *mpwhCgi;
CGAL::Qt::GraphicsViewPolygonWithHolesInput<K> * pi;
public:
MainWindow();
public Q_SLOTS:
void processInput(CGAL::Object o);
void on_actionClear_triggered();
void on_actionLoadPolygon_triggered();
void on_actionSavePolygon_triggered();
void on_actionRecenter_triggered();
void on_actionAdd_to_A_triggered();
void on_actionAdd_to_B_triggered();
void on_actionCreateInputPolygon_toggled(bool);
void clear();
virtual void open(QString);
Q_SIGNALS:
void changed();
};
MainWindow::MainWindow()
: DemosMainWindow()
{
setupUi(this);
this->graphicsView->setAcceptDrops(false);
mpwhAgi = new CGAL::Qt::MultipolygonWithHolesGraphicsItem<Multipolygon_with_holes_2>(&mpwhA);
mpwhAgi->setBrush(QBrush(::Qt::green));
mpwhAgi->setZValue(3);
QObject::connect(this, SIGNAL(changed()),
mpwhAgi, SLOT(modelChanged()));
scene.addItem(mpwhAgi);
mpwhBgi = new CGAL::Qt::MultipolygonWithHolesGraphicsItem<Multipolygon_with_holes_2>(&mpwhB);
mpwhBgi->setZValue(4);
mpwhBgi->setBrush(QBrush(QColor(255, 0, 0, 100)));
QObject::connect(this, SIGNAL(changed()),
mpwhBgi, SLOT(modelChanged()));
scene.addItem(mpwhBgi);
mpwhCgi = new CGAL::Qt::MultipolygonWithHolesGraphicsItem<Multipolygon_with_holes_2>(&mpwhC);
mpwhCgi->setZValue(5);
mpwhCgi->setBrush(QBrush(QColor(211, 211, 211, 150)));
QObject::connect(this, SIGNAL(changed()),
mpwhCgi, SLOT(modelChanged()));
scene.addItem(mpwhCgi);
// Setup input handlers. They get events before the scene gets them
// pi = new CGAL::Qt::GraphicsViewPolylineInput<K>(this, &scene, 0, true);
pi = new CGAL::Qt::GraphicsViewPolygonWithHolesInput<K>(this, &scene);
pi->setZValue(10);
this->actionCreateInputPolygon->setChecked(true);
QObject::connect(pi, SIGNAL(generate(CGAL::Object)),
this, SLOT(processInput(CGAL::Object)));
//
// Manual handling of actions
//
QObject::connect(this->actionQuit, SIGNAL(triggered()),
this, SLOT(close()));
//
// Setup the scene and the view
//
scene.setItemIndexMethod(QGraphicsScene::NoIndex);
scene.setSceneRect(-100, -100, 100, 100);
this->graphicsView->setScene(&scene);
this->graphicsView->setMouseTracking(true);
// Turn the vertical axis upside down
this->graphicsView->scale(1, -1);
// The navigation adds zooming and translation functionality to the
// QGraphicsView
this->addNavigation(this->graphicsView);
this->setupStatusBar();
this->setupOptionsMenu();
this->addAboutDemo(":/cgal/help/about_Polygon_2.html");
this->addAboutCGAL();
this->setupExportSVG(action_Export_SVG, graphicsView);
this->addRecentFiles(this->menuFile, this->actionQuit);
connect(this, SIGNAL(openRecentFile(QString)),
this, SLOT(open(QString)));
}
void
MainWindow::processInput(CGAL::Object o)
{
if(add_2_A){
if(assign(pwhA, o)){
mpwhA.add_polygon_with_holes(pwhA);
}
}else{
if(assign(pwhB, o)){
mpwhB.add_polygon_with_holes(pwhB);
}
}
if((! mpwhA.is_empty()) && (! mpwhB.is_empty())){
Boolean boolean;
boolean.insert(mpwhA, mpwhB);
mpwhC = boolean([](bool a, bool b){ return a && b;});
}
Q_EMIT( changed());
}
/*
* Qt Automatic Connections
* https://doc.qt.io/qt-5/designer-using-a-ui-file.html#automatic-connections
*
* setupUi(this) generates connections to the slots named
* "on_<action_name>_<signal_name>"
*/
void
MainWindow::on_actionAdd_to_A_triggered()
{
this->actionAdd_to_A->setEnabled(false);
this->actionAdd_to_B->setEnabled(true);
add_2_A = true;
}
void
MainWindow::on_actionAdd_to_B_triggered()
{
this->actionAdd_to_B->setEnabled(false);
this->actionAdd_to_A->setEnabled(true);
add_2_A = false;
}
void
MainWindow::on_actionClear_triggered()
{
pwhA.clear();
mpwhA.clear();
clear();
this->actionCreateInputPolygon->setChecked(true);
Q_EMIT( changed());
}
void
MainWindow::on_actionLoadPolygon_triggered()
{
QString fileName = QFileDialog::getOpenFileName(this,
tr("Open Polygon File"),
".",
tr( "WKT files (*.wkt *.WKT);;"
"All file (*)"));
if(! fileName.isEmpty()){
open(fileName);
}
}
void
MainWindow::open(QString fileName)
{
this->actionCreateInputPolygon->setChecked(false);
std::ifstream ifs(qPrintable(fileName));
pwhA.clear();
if(fileName.endsWith(".wkt", Qt::CaseInsensitive))
{
CGAL::IO::read_polygon_WKT(ifs, pwhA);
}
else
{
std::cout << "not supported" << std::endl;
}
clear();
this->addToRecentFiles(fileName);
Q_EMIT( changed());
}
void
MainWindow::on_actionSavePolygon_triggered()
{
QString fileName = QFileDialog::getSaveFileName(this,
tr("Save Polygon"),
".",
tr( "WKT files (*.wkt *.WKT);;"
"All file (*)"));
if(! fileName.isEmpty()){
std::ofstream ofs(qPrintable(fileName));
if(fileName.endsWith(".wkt", Qt::CaseInsensitive))
{
CGAL::IO::write_polygon_WKT(ofs, pwhA);
}
else{
std::cout << "not supported" << std::endl;
}
}
}
void
MainWindow::on_actionCreateInputPolygon_toggled(bool checked)
{
// poly.clear();
clear();
if(checked){
scene.installEventFilter(pi);
} else {
scene.removeEventFilter(pi);
}
Q_EMIT( changed());
}
void
MainWindow::on_actionRecenter_triggered()
{
this->graphicsView->setSceneRect(mpwhAgi->boundingRect());
this->graphicsView->fitInView(mpwhAgi->boundingRect(), Qt::KeepAspectRatio);
}
void
MainWindow::clear()
{}
#include "Boolean_2.moc"
#include <CGAL/Qt/resources.h>
int main(int argc, char **argv)
{
QApplication app(argc, argv);
app.setOrganizationDomain("geometryfactory.com");
app.setOrganizationName("GeometryFactory");
app.setApplicationName("2D Boolean Operations");
// Import resources from libCGAL (Qt6).
// See https://doc.qt.io/qt-5/qdir.html#Q_INIT_RESOURCE
CGAL_QT_INIT_RESOURCES;
Q_INIT_RESOURCE(Boolean_2);
MainWindow mainWindow;
mainWindow.show();
return app.exec();
}

View File

@ -0,0 +1,6 @@
<RCC>
<qresource prefix="/cgal/help" >
<file alias="about_CGAL.html" >../resources/about_CGAL.html</file>
<file>about_Boolean_2.html</file>
</qresource>
</RCC>

View File

@ -0,0 +1,257 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<author>GeometryFactory</author>
<class>Polygon_2</class>
<widget class="QMainWindow" name="Boolean_2">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>568</width>
<height>325</height>
</rect>
</property>
<property name="windowTitle">
<string>CGAL 2D Polygon</string>
</property>
<property name="windowIcon">
<iconset resource="../resources/CGAL.qrc">
<normaloff>:/cgal/logos/cgal_icon</normaloff>:/cgal/logos/cgal_icon</iconset>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QGraphicsView" name="graphicsView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>2</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOn</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOn</enum>
</property>
<property name="transformationAnchor">
<enum>QGraphicsView::NoAnchor</enum>
</property>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QToolBar" name="fileToolBar">
<property name="windowTitle">
<string>File Tools</string>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionClear"/>
<addaction name="actionLoadPolygon"/>
<addaction name="actionSavePolygon"/>
</widget>
<widget class="QToolBar" name="toolBar">
<property name="windowTitle">
<string>Visualization Tools</string>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionRecenter"/>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>568</width>
<height>22</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>&amp;File</string>
</property>
<addaction name="separator"/>
<addaction name="actionClear"/>
<addaction name="actionLoadPolygon"/>
<addaction name="actionSavePolygon"/>
<addaction name="action_Export_SVG"/>
<addaction name="separator"/>
<addaction name="actionQuit"/>
</widget>
<widget class="QMenu" name="menuTools">
<property name="title">
<string>&amp;Algorithms</string>
</property>
<addaction name="separator"/>
<addaction name="actionRecenter"/>
<addaction name="actionAdd_to_A"/>
<addaction name="actionAdd_to_B"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuTools"/>
</widget>
<action name="actionAbout">
<property name="text">
<string>&amp;About</string>
</property>
</action>
<action name="actionAboutCGAL">
<property name="text">
<string>About &amp;CGAL</string>
</property>
</action>
<action name="actionQuit">
<property name="text">
<string>&amp;Quit</string>
</property>
<property name="shortcut">
<string>Ctrl+Q</string>
</property>
</action>
<action name="actionClear">
<property name="icon">
<iconset resource="../icons/File.qrc">
<normaloff>:/cgal/fileToolbar/fileNew.png</normaloff>:/cgal/fileToolbar/fileNew.png</iconset>
</property>
<property name="text">
<string>&amp;Clear</string>
</property>
<property name="shortcut">
<string>Ctrl+C</string>
</property>
</action>
<action name="actionLoadPolygon">
<property name="icon">
<iconset resource="../icons/File.qrc">
<normaloff>:/cgal/fileToolbar/fileOpen.png</normaloff>:/cgal/fileToolbar/fileOpen.png</iconset>
</property>
<property name="text">
<string>&amp;Load Polygon</string>
</property>
<property name="shortcut">
<string>Ctrl+L</string>
</property>
</action>
<action name="actionSavePolygon">
<property name="icon">
<iconset resource="../icons/File.qrc">
<normaloff>:/cgal/fileToolbar/fileSave.png</normaloff>:/cgal/fileToolbar/fileSave.png</iconset>
</property>
<property name="text">
<string>&amp;Save Polygon</string>
</property>
<property name="shortcut">
<string>Ctrl+S</string>
</property>
</action>
<action name="actionRecenter">
<property name="icon">
<iconset resource="../icons/Input.qrc">
<normaloff>:/cgal/Input/zoom-best-fit</normaloff>:/cgal/Input/zoom-best-fit</iconset>
</property>
<property name="text">
<string>Re&amp;center the viewport</string>
</property>
<property name="shortcut">
<string>Ctrl+R</string>
</property>
</action>
<action name="actionYMonotonePartition">
<property name="text">
<string>Y-monotone Partition</string>
</property>
</action>
<action name="actionCreateInputPolygon">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Create Input Polygon</string>
</property>
</action>
<action name="actionSelfMinkowskiSum">
<property name="text">
<string>Minkowski sum with itself</string>
</property>
</action>
<action name="actionInnerSkeleton">
<property name="text">
<string>Inner Skeleton</string>
</property>
</action>
<action name="actionOuterOffset">
<property name="text">
<string>Outer Offset</string>
</property>
</action>
<action name="actionOptimalConvexPartition">
<property name="text">
<string>Optimal Convex Partition</string>
</property>
</action>
<action name="actionApproximateConvexPartition">
<property name="text">
<string>Approximate Convex Partition</string>
</property>
</action>
<action name="actionLinearLeastSquaresFitting">
<property name="text">
<string>Linear Least Squares Fitting of Points</string>
</property>
</action>
<action name="actionLinearLeastSquaresFittingOfSegments">
<property name="text">
<string>Linear Least Squares Fitting of Segments</string>
</property>
</action>
<action name="actionMaximumAreaKGon">
<property name="text">
<string>Maximum Area Triangle</string>
</property>
</action>
<action name="action_Export_SVG">
<property name="text">
<string>&amp;Export SVG...</string>
</property>
</action>
<action name="actionAdd_to_A">
<property name="text">
<string>Add to A</string>
</property>
</action>
<action name="actionAdd_to_B">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Add to B</string>
</property>
</action>
</widget>
<resources>
<include location="Boolean_2.qrc"/>
<include location="../icons/File.qrc"/>
<include location="../resources/CGAL.qrc"/>
<include location="../icons/Input.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -58,8 +58,20 @@ target_link_libraries(Regular_triangulation_2 PRIVATE CGAL::CGAL CGAL::CGAL_Qt6
add_to_cached_list(CGAL_EXECUTABLE_TARGETS Regular_triangulation_2)
#--------------------------------
# The "Boolean" demo: Boolean_2
#--------------------------------
qt_add_executable(
Boolean_2 Boolean_2.cpp
Boolean_2.ui Boolean_2.qrc)
target_link_libraries(Boolean_2 PRIVATE CGAL::CGAL CGAL::CGAL_Qt6
Qt6::Widgets)
add_to_cached_list(CGAL_EXECUTABLE_TARGETS Boolean_2)
include(${CGAL_MODULES_DIR}/CGAL_add_test.cmake)
foreach(target Constrained_Delaunay_triangulation_2 Delaunay_triangulation_2
Regular_triangulation_2)
Regular_triangulation_2 Boolean_2)
cgal_add_compilation_test(${target})
endforeach()

View File

@ -0,0 +1,10 @@
<html>
<body>
<h2>2D Boolean Operations</h2>
<p>Copyright &copy; 2024 GeometryFactory</p>
<p>This application illustrates the 2D Boolean operations based on 2D triangulations
of <a href="https://www.cgal.org/">CGAL</a></p>
<p>See also <a href="https://www.cgal.org/Pkg/Triangulation2">the online
manual</a>.</p>
</body>
</html>

View File

@ -16,7 +16,8 @@ foreach(cppfile ${cppfiles})
endforeach()
if(CGAL_Qt6_FOUND)
target_link_libraries(constrained PUBLIC CGAL::CGAL_Basic_viewer)
target_link_libraries(boolean_constrained_plus PUBLIC CGAL::CGAL_Basic_viewer)
target_link_libraries(constrained PUBLIC CGAL::CGAL_Basic_viewer)
target_link_libraries(draw_triangulation_2 PUBLIC CGAL::CGAL_Basic_viewer)
target_link_libraries(polygon_triangulation PUBLIC CGAL::CGAL_Basic_viewer)
target_link_libraries(star_conflict_zone PUBLIC CGAL::CGAL_Basic_viewer)

View File

@ -0,0 +1,79 @@
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <CGAL/Polygon_with_holes_2.h>
#include <CGAL/Multipolygon_with_holes_2.h>
#include <CGAL/Triangulation_2/Boolean.h>
#include <CGAL/draw_constrained_triangulation_2.h>
#include <CGAL/draw_multipolygon_with_holes_2.h>
#include <CGAL/IO/WKT.h>
#include <iostream>
#include <sstream>
#include <boost/property_map/property_map.hpp>
using K = CGAL::Exact_predicates_exact_constructions_kernel;
using Vector_2 = K::Vector_2;
using Transformation = K::Aff_transformation_2;
using Polygon_with_holes_2 = CGAL::Polygon_with_holes_2<K>;
using Multipolygon_with_holes_2 = CGAL::Multipolygon_with_holes_2<K>;
int
main(int argc, char* argv[])
{
CGAL::Triangulations::Boolean<K> bops;
Multipolygon_with_holes_2 pA, pB;
if(argc == 2) {
std::ifstream in(argv[1]);
CGAL::IO::read_multi_polygon_WKT(in, pA);
CGAL::Bbox_2 bb = pA.bbox();
double w = bb.xmax() - bb.xmin();
Vector_2 vec(w / 10.0, w / 10.0);
pB = CGAL::transform(Transformation(CGAL::TRANSLATION, vec), pA);
}
else if (argc == 3) {
{
std::ifstream in(argv[1]);
CGAL::IO::read_multi_polygon_WKT(in, pA);
}
{
std::ifstream in(argv[2]);
CGAL::IO::read_multi_polygon_WKT(in, pB);
}
}
else {
{
std::istringstream is("MULTIPOLYGON( ((0 0, 20 0, 20 30, 0 30, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1 ) ) , (( 50 0, 60 0, 60 60, 50 60)) )");
//std::istringstream is("MULTIPOLYGON( ((0 0, 2 0, 2 3, 0 3) ) )"); // (0.1 0.1, 0.1 0.4, 0.4 0.1)
CGAL::IO::read_multi_polygon_WKT(is, pA);
}
{
std::istringstream is("MULTIPOLYGON( ((10 1, 30 1, 30 2, 20 2, 20 4, 10 4)) )");
//std::istringstream is("MULTIPOLYGON( ((2 1, 3 1, 3 2, 2 2)) ");
CGAL::IO::read_multi_polygon_WKT(is, pB);
}
}
bops.insert(pA,pB);
Multipolygon_with_holes_2 mpwh = bops([](bool a, bool b){ return a || b;});
std::ofstream out("result.wkt");
CGAL::IO::write_multi_polygon_WKT(out, mpwh);
CGAL::draw(mpwh);
/*
std::map<Boolean_cdt_2::Face_handle,bool> map;
for(auto fh : bops.cdt.finite_face_handles()){
map[fh] = fh->info().in_domain(0) || fh->info().in_domain(1);
assert(map[fh] == (fh->info().label != 0));
}
CGAL::draw(bops.cdt, boost::make_assoc_property_map(map));
*/
return 0;
}

View File

@ -0,0 +1,410 @@
// Copyright (c) 2024 GeometryFactory SARL (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL$
// $Id$
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
//
// Author(s) : Andreas Fabri
#ifndef CGAL_TRIANGULATION_2_BOOLEAN_H
#define CGAL_TRIANGULATION_2_BOOLEAN_H
#include <CGAL/license/Triangulation_2.h>
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <CGAL/Polygon_with_holes_2.h>
#include <CGAL/Multipolygon_with_holes_2.h>
#include <CGAL/Constrained_Delaunay_triangulation_2.h>
#include <CGAL/Constrained_triangulation_plus_2.h>
#include <CGAL/Triangulation_vertex_base_with_info_2.h>
#include <CGAL/Triangulation_face_base_with_info_2.h>
#include <vector>
#include <iostream>
#include <boost/property_map/property_map.hpp>
namespace CGAL {
namespace Triangulations {
/*!
\ingroup PkgTriangulation2Miscellaneous
\tparam Kernel must be
*/
template <typename Kernel>
class Boolean {
private:
struct FaceInfo {
FaceInfo()
{}
int label;
int nesting_level[2];
bool processed;
bool
in_domain(int i) const
{
return nesting_level[i] % 2 == 1;
}
template <typename Fct>
bool
in_domain(const Fct& fct) const
{
return fct(in_domain(0), in_domain(1));
}
};
using K = Kernel;
using Point_2 = typename K::Point_2;
using Polygon_2 = CGAL::Polygon_2<K>;
using Polygon_with_holes_2 = CGAL::Polygon_with_holes_2<K>;
using Multipolygon_with_holes_2 = CGAL::Multipolygon_with_holes_2<K>;
using Itag = std::conditional_t<std::is_floating_point_v<typename K::FT>, Exact_predicates_tag, Exact_intersections_tag>;
using Vb = CGAL::Triangulation_vertex_base_2<K>;
using Fbb = CGAL::Triangulation_face_base_with_info_2<FaceInfo,K>;
using Fb = CGAL::Constrained_triangulation_face_base_2<K,Fbb>;
using Tds = CGAL::Triangulation_data_structure_2<Vb,Fb>;
using CDT = CGAL::Constrained_Delaunay_triangulation_2<K,Tds,Itag>;
using CDTplus = CGAL::Constrained_triangulation_plus_2<CDT>;
using Face_handle = typename CDTplus::Face_handle;
using Face_circulator = typename CDTplus::Face_circulator;
using Vertex_handle = typename CDTplus::Vertex_handle;
using Constraint_id = typename CDTplus::Constraint_id;
using Edge = typename CDTplus::Edge;
using Context = typename CDTplus::Context;
// @todo taken from Polygon_repair should be factorized
struct Polygon_less {
bool
operator()(const Polygon_2& pa, const Polygon_2& pb) const
{
typename Polygon_2::Vertex_iterator va = pa.vertices_begin();
typename Polygon_2::Vertex_iterator vb = pb.vertices_begin();
while (va != pa.vertices_end() && vb != pb.vertices_end()) {
if (*va != *vb) return *va < *vb;
++va;
++vb;
}
if (vb == pb.vertices_end()) return false;
return true;
}
};
// @todo taken from Polygon_repair should be factorized
struct Polygon_with_holes_less {
Polygon_less pl;
bool
operator()(const Polygon_with_holes_2& pa, const Polygon_with_holes_2& pb) const
{
if (pl(pa.outer_boundary(), pb.outer_boundary())) return true;
if (pl(pb.outer_boundary(), pa.outer_boundary())) return false;
typename Polygon_with_holes_2::Hole_const_iterator ha = pa.holes_begin();
typename Polygon_with_holes_2::Hole_const_iterator hb = pb.holes_begin();
while (ha != pa.holes_end() && hb != pb.holes_end()) {
if (pl(*ha, *hb)) return true;
if (pl(*hb, *ha)) return false;
}
if (hb == pb.holes_end()) return false;
return true;
}
};
CDTplus cdt;
std::set<Constraint_id> idA, idB;
public:
/*!
default constructor.
*/
Boolean() = default;
/*!
sets the polygons as input of the %Boolean operation.
*/
void
insert(const Multipolygon_with_holes_2& pA, const Multipolygon_with_holes_2& pB)
{
for(const auto& pwh : pA.polygons_with_holes()){
Constraint_id cidA = cdt.insert_constraint(pwh.outer_boundary().vertices_begin(), pwh.outer_boundary().vertices_end(), true);
idA.insert(cidA);
for(auto const& hole : pwh.holes()){
cidA = cdt.insert_constraint(hole.vertices_begin(), hole.vertices_end(), true);
idA.insert(cidA);
}
}
for(const auto& pwh : pB.polygons_with_holes()){
Constraint_id cidB = cdt.insert_constraint(pwh.outer_boundary().vertices_begin(), pwh.outer_boundary().vertices_end(), true);
idB.insert(cidB);
for(auto const& hole : pwh.holes()){
cidB = cdt.insert_constraint(hole.vertices_begin(), hole.vertices_end(), true);
idB.insert(cidB);
}
}
mark_domains(idA, 0);
mark_domains(idB, 1);
}
private:
void
mark_domains(Face_handle start,
int index,
std::list<Edge>& border,
const std::set<Constraint_id>& cids,
int aorb)
{
if(start->info().nesting_level[aorb] != -1){
return;
}
std::list<Face_handle> queue;
queue.push_back(start);
while(! queue.empty()){
Face_handle fh = queue.front();
queue.pop_front();
if(fh->info().nesting_level[aorb] == -1){
fh->info().nesting_level[aorb] = index;
for(int i = 0; i < 3; i++){
Edge e(fh,i);
Face_handle n = fh->neighbor(i);
if(n->info().nesting_level[aorb] == -1){
if(cdt.is_constrained(e)){
bool found = false;
for(Context c : cdt.contexts(e.first->vertex(cdt.cw(e.second)),
e.first->vertex(cdt.ccw(e.second)))){
if(cids.find(c.id()) != cids.end()){
found = true;
break;
}
}
if (found) {
border.push_back(e);
} else {
queue.push_back(n);
}
}else{
queue.push_back(n);
}
}
}
}
}
}
// this marks the domains for either the first or the second multipolygon
void
mark_domains(const std::set<Constraint_id>& cids, int aorb)
{
for(Face_handle f : cdt.all_face_handles()){
f->info().nesting_level[aorb] = -1;
}
std::list<Edge> border;
mark_domains(cdt.infinite_face(), 0, border, cids, aorb);
while(! border.empty()){
Edge e = border.front();
border.pop_front();
Face_handle n = e.first->neighbor(e.second);
if(n->info().nesting_level[aorb] == -1){
mark_domains(n, e.first->info().nesting_level[aorb]+1, border, cids, aorb);
}
}
}
template <typename Fct>
void
label_domains(Face_handle start, int label, const Fct& fct)
{
std::list<Face_handle> queue;
start->info().label = label;
queue.push_back(start);
while(! queue.empty()){
Face_handle fh = queue.front();
queue.pop_front();
for(int i = 0; i < 3; i++){
Face_handle n = fh->neighbor(i);
if(n->info().in_domain(fct)){
if(n->info().label == 0){
n->info().label = label;
queue.push_back(n);
}
}
}
}
}
// this marks the domain for the Boolean operation applied on the two multipolygons
template <typename Fct>
int
label_domains(const Fct& fct)
{
for (auto const face: cdt.all_face_handles()) {
face->info().processed = false;
face->info().label = 0;
}
int label = 1;
for (auto const face: cdt.all_face_handles()) {
if(face->info().in_domain(fct) && face->info().label == 0){
label_domains(face, label, fct);
++label;
}
}
return label;
}
// @todo taken from Polygon_repair and adapted; might be factorized
// Reconstruct ring boundary starting from an edge (face + opposite vertex) that is part of it
template <typename Fct>
void
reconstruct_ring(std::list<Point_2>& ring,
Face_handle face_adjacent_to_boundary,
int opposite_vertex,
const Fct& fct)
{
// Create ring
Face_handle current_face = face_adjacent_to_boundary;
int current_opposite_vertex = opposite_vertex;
CGAL_assertion(face_adjacent_to_boundary->info().in_domain(fct));
do {
CGAL_assertion(current_face->info().in_domain(fct) == face_adjacent_to_boundary->info().in_domain(fct));
current_face->info().processed = true;
Vertex_handle pivot_vertex = current_face->vertex(current_face->cw(current_opposite_vertex));
// std::cout << "\tAdding point " << pivot_vertex->point() << std::endl;
ring.push_back(pivot_vertex->point());
Face_circulator fc = cdt.incident_faces(pivot_vertex, current_face);
do {
++fc;
} while (fc->info().label != current_face->info().label);
current_face = fc;
current_opposite_vertex = fc->cw(fc->index(pivot_vertex));
} while (current_face != face_adjacent_to_boundary ||
current_opposite_vertex != opposite_vertex);
// Start at lexicographically smallest vertex
typename std::list<Point_2>::iterator smallest_vertex = ring.begin();
for (typename std::list<Point_2>::iterator current_vertex = ring.begin();
current_vertex != ring.end(); ++current_vertex) {
if (*current_vertex < *smallest_vertex) smallest_vertex = current_vertex;
}
if (ring.front() != *smallest_vertex) {
ring.splice(ring.begin(), ring, smallest_vertex, ring.end());
}
}
public:
// @todo taken from Polygon_repair and adapted; might be factorized
/*!
performs the Boolean operation applying `fct` and returns the result as a multipolygon with holes.
\tparam Fct must have the operator `bool operator()(bool, bool)`.
*/
template <typename Fct>
Multipolygon_with_holes_2
operator()(const Fct& fct)
{
int number_of_polygons = label_domains(fct) - 1;
Multipolygon_with_holes_2 mp;
std::vector<Polygon_2> polygons; // outer boundaries
std::vector<std::set<Polygon_2, Polygon_less>> holes; // holes are ordered (per polygon)
polygons.resize(number_of_polygons);
holes.resize(number_of_polygons);
for (auto const face: cdt.all_face_handles()) {
face->info().processed = false;
}
/*
for (auto const face: cdt.all_face_handles()) {
std::cout << face->vertex(0)->point() << " " << face->vertex(1)->point() << " " << face->vertex(2)->point() << std::endl;
std::cout << "label = " << face->info().label << std::endl;
if(face->info().in_domain(fct)) std::cout << "in domain" << std::endl; else std::cout << "not in domain" << std::endl;
}
*/
for (auto const &face: cdt.finite_face_handles()) {
if (! face->info().in_domain(fct)) continue; // exterior triangle
if (face->info().processed) continue; // already reconstructed
for (int opposite_vertex = 0; opposite_vertex < 3; ++opposite_vertex) {
if (face->info().in_domain(fct) == face->neighbor(opposite_vertex)->info().in_domain(fct)) continue; // not adjacent to boundary
// Reconstruct ring
std::list<Point_2> ring;
reconstruct_ring(ring, face, opposite_vertex, fct);
// Put ring in polygons
Polygon_2 polygon;
polygon.reserve(ring.size());
polygon.insert(polygon.vertices_end(), ring.begin(), ring.end());
if (polygon.orientation() == CGAL::COUNTERCLOCKWISE) {
polygons[face->info().label-1] = std::move(polygon);
} else {
holes[face->info().label-1].insert(std::move(polygon));
} break;
}
}
// Create polygons with holes and put in multipolygon
std::set<Polygon_with_holes_2, Polygon_with_holes_less> ordered_polygons;
for (std::size_t i = 0; i < polygons.size(); ++i) {
ordered_polygons.insert(Polygon_with_holes_2(std::move(polygons[i]),
std::make_move_iterator(holes[i].begin()),
std::make_move_iterator(holes[i].end())));
}
for (auto const& polygon: ordered_polygons) {
mp.add_polygon_with_holes(std::move(polygon));
}
return mp;
}
/*!
access to the underlying constrained triangulation.
*/
const CDTplus&
triangulation() const
{
return cdt;
}
};
} // namespace Triangulations
} //namespace CGAL
#endif CGAL_TRIANGULATION_2_BOOLEAN_H