mirror of https://github.com/CGAL/cgal
import @afabri files for Boolean using CDT
This commit is contained in:
parent
f91765503c
commit
a41cd1c626
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>&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>&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>&About</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAboutCGAL">
|
||||
<property name="text">
|
||||
<string>About &CGAL</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionQuit">
|
||||
<property name="text">
|
||||
<string>&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>&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>&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>&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&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>&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>
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
<html>
|
||||
<body>
|
||||
<h2>2D Boolean Operations</h2>
|
||||
<p>Copyright © 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>
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue