Merge pull request #22 from pierre-dejoue/master

Add documentation and code patches for maintenability
This commit is contained in:
Jan Niklas Hasse 2020-10-26 20:18:51 +00:00 committed by GitHub
commit e9938d9c68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 116 additions and 65 deletions

View File

@ -7,7 +7,8 @@ option(P2T_BUILD_TESTS "Build tests" OFF)
option(P2T_BUILD_TESTBED "Build the testbed application" OFF) option(P2T_BUILD_TESTBED "Build the testbed application" OFF)
file(GLOB SOURCES poly2tri/common/*.cc poly2tri/sweep/*.cc) file(GLOB SOURCES poly2tri/common/*.cc poly2tri/sweep/*.cc)
add_library(poly2tri ${SOURCES}) file(GLOB HEADERS poly2tri/*.h poly2tri/common/*.h poly2tri/sweep/*.h)
add_library(poly2tri ${SOURCES} ${HEADERS})
target_include_directories(poly2tri INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(poly2tri INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
if(P2T_BUILD_TESTS) if(P2T_BUILD_TESTS)

View File

@ -52,6 +52,8 @@ python waf configure
python waf build python waf build
``` ```
Alternatively, the testbed can be built using cmake. See below.
Running the Examples Running the Examples
-------------------- --------------------
@ -59,7 +61,7 @@ Load data points from a file:
``` ```
p2t <filename> <center_x> <center_y> <zoom> p2t <filename> <center_x> <center_y> <zoom>
``` ```
Random distribution of points inside a consrained box: Random distribution of points inside a constrained box:
``` ```
p2t random <num_points> <box_radius> <zoom> p2t random <num_points> <box_radius> <zoom>
``` ```
@ -71,3 +73,42 @@ Examples:
./build/p2t random 10 100 5.0 ./build/p2t random 10 100 5.0
./build/p2t random 1000 20000 0.025 ./build/p2t random 1000 20000 0.025
``` ```
BUILD WITH CMAKE
================
Build the library
-----------------
```
mkdir build && cd build
cmake -GNinja
cmake --build .
```
Build and run the unit tests
----------------------------
```
mkdir build && cd build
cmake -GNinja -DP2T_BUILD_TESTS=ON
cmake --build .
ctest --output-on-failure
```
Build the testbed
-----------------
```
mkdir build && cd build
cmake -GNinja -DP2T_BUILD_TESTBED=ON
cmake --build .
```
References
==========
- Domiter V. and Zalik B. (2008) Sweepline algorithm for constrained Delaunay triangulation
- FlipScan by library author Thomas Åhlén
![FlipScan](doc/FlipScan.png)

BIN
doc/FlipScan.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

View File

@ -243,6 +243,17 @@ Point* Triangle::PointCCW(const Point& point)
return nullptr; return nullptr;
} }
// The neighbor across to given point
Triangle* Triangle::NeighborAcross(const Point& point)
{
if (&point == points_[0]) {
return neighbors_[0];
} else if (&point == points_[1]) {
return neighbors_[1];
}
return neighbors_[2];
}
// The neighbor clockwise to given point // The neighbor clockwise to given point
Triangle* Triangle::NeighborCW(const Point& point) Triangle* Triangle::NeighborCW(const Point& point)
{ {
@ -349,23 +360,6 @@ void Triangle::SetDelunayEdgeCW(const Point& p, bool e)
} }
} }
// The neighbor across to given point
Triangle& Triangle::NeighborAcross(const Point& opoint)
{
Triangle* neighbor = nullptr;
if (&opoint == points_[0]) {
neighbor = neighbors_[0];
} else if (&opoint == points_[1]) {
neighbor = neighbors_[1];
} else {
neighbor = neighbors_[2];
}
if (neighbor == nullptr) {
throw std::runtime_error("NeighborAcross - null neighbor");
}
return *neighbor;
}
void Triangle::DebugPrint() void Triangle::DebugPrint()
{ {
std::cout << *points_[0] << " " << *points_[1] << " " << *points_[2] << std::endl; std::cout << *points_[0] << " " << *points_[1] << " " << *points_[2] << std::endl;

View File

@ -176,6 +176,7 @@ void MarkConstrainedEdge(Point* p, Point* q);
int Index(const Point* p); int Index(const Point* p);
int EdgeIndex(const Point* p1, const Point* p2); int EdgeIndex(const Point* p1, const Point* p2);
Triangle* NeighborAcross(const Point& point);
Triangle* NeighborCW(const Point& point); Triangle* NeighborCW(const Point& point);
Triangle* NeighborCCW(const Point& point); Triangle* NeighborCCW(const Point& point);
bool GetConstrainedEdgeCCW(const Point& p); bool GetConstrainedEdgeCCW(const Point& p);
@ -203,8 +204,6 @@ void ClearDelunayEdges();
inline bool IsInterior(); inline bool IsInterior();
inline void IsInterior(bool b); inline void IsInterior(bool b);
Triangle& NeighborAcross(const Point& opoint);
void DebugPrint(); void DebugPrint();
bool CircumcicleContains(const Point&) const; bool CircumcicleContains(const Point&) const;

View File

@ -108,6 +108,9 @@ void Sweep::EdgeEvent(SweepContext& tcx, Edge* edge, Node* node)
void Sweep::EdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle* triangle, Point& point) void Sweep::EdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle* triangle, Point& point)
{ {
if (triangle == nullptr) {
throw std::runtime_error("EdgeEvent - null triangle");
}
if (IsEdgeSideOfTriangle(*triangle, ep, eq)) { if (IsEdgeSideOfTriangle(*triangle, ep, eq)) {
return; return;
} }
@ -120,7 +123,7 @@ void Sweep::EdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle* triangl
// We are modifying the constraint maybe it would be better to // We are modifying the constraint maybe it would be better to
// not change the given constraint and just keep a variable for the new constraint // not change the given constraint and just keep a variable for the new constraint
tcx.edge_event.constrained_edge->q = p1; tcx.edge_event.constrained_edge->q = p1;
triangle = &triangle->NeighborAcross(point); triangle = triangle->NeighborAcross(point);
EdgeEvent(tcx, ep, *p1, triangle, *p1); EdgeEvent(tcx, ep, *p1, triangle, *p1);
} else { } else {
throw std::runtime_error("EdgeEvent - collinear points not supported"); throw std::runtime_error("EdgeEvent - collinear points not supported");
@ -136,7 +139,7 @@ void Sweep::EdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle* triangl
// We are modifying the constraint maybe it would be better to // We are modifying the constraint maybe it would be better to
// not change the given constraint and just keep a variable for the new constraint // not change the given constraint and just keep a variable for the new constraint
tcx.edge_event.constrained_edge->q = p2; tcx.edge_event.constrained_edge->q = p2;
triangle = &triangle->NeighborAcross(point); triangle = triangle->NeighborAcross(point);
EdgeEvent(tcx, ep, *p2, triangle, *p2); EdgeEvent(tcx, ep, *p2, triangle, *p2);
} else { } else {
throw std::runtime_error("EdgeEvent - collinear points not supported"); throw std::runtime_error("EdgeEvent - collinear points not supported");
@ -155,6 +158,7 @@ void Sweep::EdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle* triangl
EdgeEvent(tcx, ep, eq, triangle, point); EdgeEvent(tcx, ep, eq, triangle, point);
} else { } else {
// This triangle crosses constraint so lets flippin start! // This triangle crosses constraint so lets flippin start!
assert(triangle);
FlipEdgeEvent(tcx, ep, eq, triangle, point); FlipEdgeEvent(tcx, ep, eq, triangle, point);
} }
} }
@ -215,7 +219,6 @@ void Sweep::Fill(SweepContext& tcx, Node& node)
if (!Legalize(tcx, *triangle)) { if (!Legalize(tcx, *triangle)) {
tcx.MapTriangleToNodes(*triangle); tcx.MapTriangleToNodes(*triangle);
} }
} }
void Sweep::FillAdvancingFront(SweepContext& tcx, Node& n) void Sweep::FillAdvancingFront(SweepContext& tcx, Node& n)
@ -610,7 +613,6 @@ void Sweep::FillRightConcaveEdgeEvent(SweepContext& tcx, Edge* edge, Node& node)
} }
} }
} }
} }
void Sweep::FillRightConvexEdgeEvent(SweepContext& tcx, Edge* edge, Node& node) void Sweep::FillRightConvexEdgeEvent(SweepContext& tcx, Edge* edge, Node& node)
@ -691,12 +693,17 @@ void Sweep::FillLeftConcaveEdgeEvent(SweepContext& tcx, Edge* edge, Node& node)
} }
} }
} }
} }
void Sweep::FlipEdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle* t, Point& p) void Sweep::FlipEdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle* t, Point& p)
{ {
Triangle& ot = t->NeighborAcross(p); assert(t);
Triangle* ot_ptr = t->NeighborAcross(p);
if (ot_ptr == nullptr)
{
throw std::runtime_error("FlipEdgeEvent - null neighbor across");
}
Triangle& ot = *ot_ptr;
Point& op = *ot.OppositePoint(*t, p); Point& op = *ot.OppositePoint(*t, p);
if (InScanArea(p, *t->PointCCW(p), *t->PointCW(p), op)) { if (InScanArea(p, *t->PointCCW(p), *t->PointCW(p), op)) {
@ -762,7 +769,11 @@ Point& Sweep::NextFlipPoint(Point& ep, Point& eq, Triangle& ot, Point& op)
void Sweep::FlipScanEdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle& flip_triangle, void Sweep::FlipScanEdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle& flip_triangle,
Triangle& t, Point& p) Triangle& t, Point& p)
{ {
Triangle& ot = t.NeighborAcross(p); Triangle* ot_ptr = t.NeighborAcross(p);
if (ot_ptr == nullptr) {
throw std::runtime_error("FlipScanEdgeEvent - null neighbor across");
}
Triangle& ot = *ot_ptr;
Point& op = *ot.OppositePoint(t, p); Point& op = *ot.OppositePoint(t, p);
if (InScanArea(eq, *flip_triangle.PointCCW(eq), *flip_triangle.PointCW(eq), op)) { if (InScanArea(eq, *flip_triangle.PointCCW(eq), *flip_triangle.PointCW(eq), op)) {
@ -790,5 +801,4 @@ Sweep::~Sweep() {
} }
} } // namespace p2t

View File

@ -33,7 +33,7 @@
* Zalik, B.(2008)'Sweep-line algorithm for constrained Delaunay triangulation', * Zalik, B.(2008)'Sweep-line algorithm for constrained Delaunay triangulation',
* International Journal of Geographical Information Science * International Journal of Geographical Information Science
* *
* "FlipScan" Constrained Edge Algorithm invented by Thomas ?hl?n, thahlen@gmail.com * "FlipScan" Constrained Edge Algorithm invented by Thomas Åhlén, thahlen@gmail.com
*/ */
#ifndef SWEEP_H #ifndef SWEEP_H

View File

@ -87,8 +87,8 @@ void SweepContext::InitTriangulation()
double dx = kAlpha * (xmax - xmin); double dx = kAlpha * (xmax - xmin);
double dy = kAlpha * (ymax - ymin); double dy = kAlpha * (ymax - ymin);
head_ = new Point(xmax + dx, ymin - dy); head_ = new Point(xmin - dx, ymin - dy);
tail_ = new Point(xmin - dx, ymin - dy); tail_ = new Point(xmax + dx, ymin - dy);
// Sort points along y-axis // Sort points along y-axis
std::sort(points_.begin(), points_.end(), cmp); std::sort(points_.begin(), points_.end(), cmp);
@ -124,7 +124,7 @@ void SweepContext::CreateAdvancingFront()
{ {
// Initial triangle // Initial triangle
Triangle* triangle = new Triangle(*points_[0], *tail_, *head_); Triangle* triangle = new Triangle(*points_[0], *head_, *tail_);
map_.push_back(triangle); map_.push_back(triangle);

View File

@ -28,18 +28,22 @@
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
#include <cstdlib> #include <poly2tri/poly2tri.h>
#include <GLFW/glfw3.h>
#include <time.h>
#include <fstream>
#include <string>
#include <sstream>
#include <algorithm>
#include <iterator>
#include <iostream>
using namespace std;
#include "../poly2tri/poly2tri.h" #include <GLFW/glfw3.h>
#include <algorithm>
#include <cstdlib>
#include <ctime>
#include <fstream>
#include <iostream>
#include <iterator>
#include <list>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
using namespace p2t; using namespace p2t;
void Init(); void Init();

View File

@ -3,9 +3,11 @@
#endif #endif
#define BOOST_TEST_MODULE Poly2triTest #define BOOST_TEST_MODULE Poly2triTest
#include <poly2tri/poly2tri.h>
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
#include <poly2tri/poly2tri.h>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <iterator> #include <iterator>
@ -19,7 +21,7 @@ BOOST_AUTO_TEST_CASE(BasicTest)
new p2t::Point(1, 1), new p2t::Point(1, 1),
}; };
p2t::CDT cdt{ polyline }; p2t::CDT cdt{ polyline };
cdt.Triangulate(); BOOST_CHECK_NO_THROW(cdt.Triangulate());
const auto result = cdt.GetTriangles(); const auto result = cdt.GetTriangles();
BOOST_REQUIRE_EQUAL(result.size(), 1); BOOST_REQUIRE_EQUAL(result.size(), 1);
BOOST_CHECK_EQUAL(*result[0]->GetPoint(0), *polyline[0]); BOOST_CHECK_EQUAL(*result[0]->GetPoint(0), *polyline[0]);
@ -36,7 +38,7 @@ BOOST_AUTO_TEST_CASE(QuadTest)
std::vector<p2t::Point*> polyline{ new p2t::Point(0, 0), new p2t::Point(0, 1), std::vector<p2t::Point*> polyline{ new p2t::Point(0, 0), new p2t::Point(0, 1),
new p2t::Point(1, 1), new p2t::Point(1, 0) }; new p2t::Point(1, 1), new p2t::Point(1, 0) };
p2t::CDT cdt{ polyline }; p2t::CDT cdt{ polyline };
cdt.Triangulate(); BOOST_CHECK_NO_THROW(cdt.Triangulate());
const auto result = cdt.GetTriangles(); const auto result = cdt.GetTriangles();
BOOST_REQUIRE_EQUAL(result.size(), 2); BOOST_REQUIRE_EQUAL(result.size(), 2);
BOOST_CHECK(p2t::IsDelaunay(result)); BOOST_CHECK(p2t::IsDelaunay(result));
@ -45,7 +47,7 @@ BOOST_AUTO_TEST_CASE(QuadTest)
} }
} }
BOOST_AUTO_TEST_CASE(QuadTestThrow) BOOST_AUTO_TEST_CASE(NarrowQuadTest)
{ {
// Very narrow quad that demonstrates a failure case during triangulation // Very narrow quad that demonstrates a failure case during triangulation
std::vector<p2t::Point*> polyline { std::vector<p2t::Point*> polyline {
@ -90,7 +92,7 @@ BOOST_AUTO_TEST_CASE(TestbedFilesTest)
polyline.push_back(new p2t::Point(x, y)); polyline.push_back(new p2t::Point(x, y));
} }
p2t::CDT cdt{ polyline }; p2t::CDT cdt{ polyline };
cdt.Triangulate(); BOOST_CHECK_NO_THROW(cdt.Triangulate());
const auto result = cdt.GetTriangles(); const auto result = cdt.GetTriangles();
BOOST_REQUIRE(result.size() * 3 > polyline.size()); BOOST_REQUIRE(result.size() * 3 > polyline.size());
BOOST_CHECK_MESSAGE(p2t::IsDelaunay(result), filename + std::to_string(polyline.size())); BOOST_CHECK_MESSAGE(p2t::IsDelaunay(result), filename + std::to_string(polyline.size()));