mirror of
https://github.com/jhasse/poly2tri.git
synced 2024-11-29 16:53:30 +01:00
8388a74a9f
Data file format now has optional sections for holes polylines and Steiner points identified with tokens "HOLE" and "STEINER". Rework the data file dude.dat accordingly. Add data file steiner.dat for an example with Steiner points.
505 lines
13 KiB
C++
505 lines
13 KiB
C++
/*
|
|
* Poly2Tri Copyright (c) 2009-2018, Poly2Tri Contributors
|
|
* https://github.com/jhasse/poly2tri
|
|
*
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without modification,
|
|
* are permitted provided that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
* * Neither the name of Poly2Tri nor the names of its contributors may be
|
|
* used to endorse or promote products derived from this software without specific
|
|
* prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
#include <poly2tri/poly2tri.h>
|
|
|
|
#include <GLFW/glfw3.h>
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cstdlib>
|
|
#include <ctime>
|
|
#include <exception>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <iterator>
|
|
#include <list>
|
|
#include <numeric>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
using namespace std;
|
|
using namespace p2t;
|
|
|
|
bool ParseFile(string filename, vector<Point*>& out_polyline, vector<vector<Point*>>& out_holes,
|
|
vector<Point*>& out_steiner);
|
|
void GenerateRandomPointDistribution(size_t num_points, double min, double max,
|
|
vector<Point*>& out_polyline,
|
|
vector<vector<Point*>>& out_holes,
|
|
vector<Point*>& out_steiner);
|
|
void Init();
|
|
void ShutDown(int return_code);
|
|
void MainLoop(const double zoom);
|
|
void Draw(const double zoom);
|
|
void DrawMap(const double zoom);
|
|
void ConstrainedColor(bool constrain);
|
|
double StringToDouble(const std::string& s);
|
|
double Random(double (*fun)(double), double xmin, double xmax);
|
|
double Fun(double x);
|
|
|
|
double rotate_y = 0.0,
|
|
rotate_z = 0.0;
|
|
const double rotations_per_tick = 0.2;
|
|
|
|
/// Screen center x
|
|
double cx = 0.0;
|
|
/// Screen center y
|
|
double cy = 0.0;
|
|
|
|
/// Constrained triangles
|
|
vector<Triangle*> triangles;
|
|
/// Triangle map
|
|
list<Triangle*> map;
|
|
/// Polylines
|
|
vector<Point*> polyline;
|
|
vector<vector<Point*>> holes;
|
|
vector<Point*> steiner;
|
|
|
|
/// Draw the entire triangle map?
|
|
bool draw_map = false;
|
|
/// Create a random distribution of points?
|
|
bool random_distribution = false;
|
|
|
|
GLFWwindow* window = NULL;
|
|
|
|
template <class C> void FreeClear(C& cntr)
|
|
{
|
|
for (typename C::iterator it = cntr.begin(); it != cntr.end(); ++it) {
|
|
delete *it;
|
|
}
|
|
cntr.clear();
|
|
}
|
|
|
|
int main(int argc, char* argv[])
|
|
{
|
|
string filename;
|
|
size_t num_points = 0u;
|
|
double max, min;
|
|
double zoom;
|
|
|
|
if (argc != 5) {
|
|
cout << "-== USAGE ==-" << endl;
|
|
cout << "Load Data File: p2t filename center_x center_y zoom" << endl;
|
|
cout << "Example: ./build/p2t testbed/data/dude.dat 500 500 1" << endl;
|
|
return 1;
|
|
}
|
|
|
|
if (string(argv[1]) == "random") {
|
|
num_points = atoi(argv[2]);
|
|
random_distribution = true;
|
|
char* pEnd;
|
|
max = strtod(argv[3], &pEnd);
|
|
min = -max;
|
|
cx = cy = 0.0;
|
|
zoom = atof(argv[4]);
|
|
} else {
|
|
filename = string(argv[1]);
|
|
cx = atof(argv[2]);
|
|
cy = atof(argv[3]);
|
|
zoom = atof(argv[4]);
|
|
}
|
|
|
|
if (random_distribution) {
|
|
GenerateRandomPointDistribution(num_points, min, max, polyline, holes, steiner);
|
|
} else {
|
|
// Load pointset from file
|
|
if (!ParseFile(filename, polyline, holes, steiner)) {
|
|
return 2;
|
|
}
|
|
}
|
|
|
|
Init();
|
|
|
|
/*
|
|
* Perform triangulation!
|
|
*/
|
|
|
|
double init_time = glfwGetTime();
|
|
|
|
/*
|
|
* STEP 1: Create CDT and add primary polyline
|
|
* NOTE: polyline must be a simple polygon. The polyline's points
|
|
* constitute constrained edges. No repeat points!!!
|
|
*/
|
|
CDT* cdt = new CDT(polyline);
|
|
|
|
/*
|
|
* STEP 2: Add holes or Steiner points
|
|
*/
|
|
for (const auto& hole : holes) {
|
|
assert(!hole.empty());
|
|
cdt->AddHole(hole);
|
|
}
|
|
for (const auto& s : steiner) {
|
|
cdt->AddPoint(s);
|
|
}
|
|
|
|
/*
|
|
* STEP 3: Triangulate!
|
|
*/
|
|
cdt->Triangulate();
|
|
|
|
double dt = glfwGetTime() - init_time;
|
|
|
|
triangles = cdt->GetTriangles();
|
|
map = cdt->GetMap();
|
|
const size_t points_in_holes =
|
|
std::accumulate(holes.cbegin(), holes.cend(), size_t(0),
|
|
[](size_t cumul, const vector<Point*>& hole) { return cumul + hole.size(); });
|
|
|
|
cout << "Number of primary constrained edges = " << polyline.size() << endl;
|
|
cout << "Number of holes = " << holes.size() << endl;
|
|
cout << "Number of constrained edges in holes = " << points_in_holes << endl;
|
|
cout << "Number of Steiner points = " << steiner.size() << endl;
|
|
cout << "Total number of points = " << (polyline.size() + points_in_holes + steiner.size())
|
|
<< endl;
|
|
cout << "Number of triangles = " << triangles.size() << endl;
|
|
cout << "Elapsed time (ms) = " << dt * 1000.0 << endl;
|
|
|
|
MainLoop(zoom);
|
|
|
|
// Cleanup
|
|
delete cdt;
|
|
FreeClear(polyline);
|
|
for (vector<Point*>& hole : holes) {
|
|
FreeClear(hole);
|
|
}
|
|
FreeClear(steiner);
|
|
|
|
ShutDown(0);
|
|
return 0;
|
|
}
|
|
|
|
bool ParseFile(string filename, vector<Point*>& out_polyline, vector<vector<Point*>>& out_holes,
|
|
vector<Point*>& out_steiner)
|
|
{
|
|
enum ParserState {
|
|
Polyline,
|
|
Hole,
|
|
Steiner,
|
|
};
|
|
ParserState state = Polyline;
|
|
vector<Point*>* hole = nullptr;
|
|
try {
|
|
string line;
|
|
ifstream myfile(filename);
|
|
if (myfile.is_open()) {
|
|
while (!myfile.eof()) {
|
|
getline(myfile, line);
|
|
if (line.empty()) {
|
|
break;
|
|
}
|
|
istringstream iss(line);
|
|
vector<string> tokens;
|
|
copy(istream_iterator<string>(iss), istream_iterator<string>(), back_inserter(tokens));
|
|
if (tokens.empty()) {
|
|
break;
|
|
} else if (tokens.size() == 1u) {
|
|
const auto token = tokens[0];
|
|
if (token == "HOLE") {
|
|
state = Hole;
|
|
out_holes.emplace_back();
|
|
hole = &out_holes.back();
|
|
} else if (token == "STEINER") {
|
|
state = Steiner;
|
|
} else {
|
|
throw runtime_error("Invalid token [" + token + "]");
|
|
}
|
|
} else {
|
|
double x = StringToDouble(tokens[0]);
|
|
double y = StringToDouble(tokens[1]);
|
|
switch (state) {
|
|
case Polyline:
|
|
out_polyline.push_back(new Point(x, y));
|
|
break;
|
|
case Hole:
|
|
assert(hole != nullptr);
|
|
hole->push_back(new Point(x, y));
|
|
break;
|
|
case Steiner:
|
|
out_steiner.push_back(new Point(x, y));
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
throw runtime_error("File not opened");
|
|
}
|
|
} catch (exception& e) {
|
|
cerr << "Error parsing file: " << e.what() << endl;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void GenerateRandomPointDistribution(size_t num_points, double min, double max,
|
|
vector<Point*>& out_polyline,
|
|
vector<vector<Point*>>& out_holes, vector<Point*>& out_steiner)
|
|
{
|
|
out_polyline.push_back(new Point(min, min));
|
|
out_polyline.push_back(new Point(min, max));
|
|
out_polyline.push_back(new Point(max, max));
|
|
out_polyline.push_back(new Point(max, min));
|
|
|
|
max -= (1e-4);
|
|
min += (1e-4);
|
|
for (int i = 0; i < num_points; i++) {
|
|
double x = Random(Fun, min, max);
|
|
double y = Random(Fun, min, max);
|
|
out_steiner.push_back(new Point(x, y));
|
|
}
|
|
}
|
|
|
|
void Init()
|
|
{
|
|
const int window_width = 800,
|
|
window_height = 600;
|
|
|
|
if (glfwInit() != GL_TRUE)
|
|
ShutDown(1);
|
|
// 800 x 600, 16 bit color, no depth, alpha or stencil buffers, windowed
|
|
window = glfwCreateWindow(window_width, window_height, "Poly2Tri - C++", NULL, NULL);
|
|
if (!window)
|
|
ShutDown(1);
|
|
|
|
glfwMakeContextCurrent(window);
|
|
glfwSwapInterval(1);
|
|
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glClearColor(0.0, 0.0, 0.0, 0.0);
|
|
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
|
|
}
|
|
|
|
void ShutDown(int return_code)
|
|
{
|
|
glfwTerminate();
|
|
exit(return_code);
|
|
}
|
|
|
|
void MainLoop(const double zoom)
|
|
{
|
|
// the time of the previous frame
|
|
double old_time = glfwGetTime();
|
|
// this just loops as long as the program runs
|
|
bool running = true;
|
|
|
|
while (running) {
|
|
glfwPollEvents();
|
|
|
|
// calculate time elapsed, and the amount by which stuff rotates
|
|
double current_time = glfwGetTime(),
|
|
delta_rotate = (current_time - old_time) * rotations_per_tick * 360.0;
|
|
old_time = current_time;
|
|
|
|
// escape to quit, arrow keys to rotate view
|
|
// Check if ESC key was pressed or window was closed
|
|
running = !glfwGetKey(window, GLFW_KEY_ESCAPE) && !glfwWindowShouldClose(window);
|
|
|
|
if (glfwGetKey(window, GLFW_KEY_LEFT) == GLFW_PRESS)
|
|
rotate_y += delta_rotate;
|
|
if (glfwGetKey(window, GLFW_KEY_RIGHT) == GLFW_PRESS)
|
|
rotate_y -= delta_rotate;
|
|
// z axis always rotates
|
|
rotate_z += delta_rotate;
|
|
|
|
// Draw the scene
|
|
if (draw_map) {
|
|
DrawMap(zoom);
|
|
} else {
|
|
Draw(zoom);
|
|
}
|
|
|
|
// swap back and front buffers
|
|
glfwSwapBuffers(window);
|
|
}
|
|
}
|
|
|
|
void ResetZoom(double zoom, double cx, double cy, double width, double height)
|
|
{
|
|
double left = -width / zoom;
|
|
double right = width / zoom;
|
|
double bottom = -height / zoom;
|
|
double top = height / zoom;
|
|
|
|
// Reset viewport
|
|
glLoadIdentity();
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadIdentity();
|
|
|
|
// Reset ortho view
|
|
glOrtho(left, right, bottom, top, 1.0, -1.0);
|
|
glTranslated(-cx, -cy, 0.0);
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glDisable(GL_DEPTH_TEST);
|
|
glLoadIdentity();
|
|
|
|
// Clear the screen
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
}
|
|
|
|
void Draw(const double zoom)
|
|
{
|
|
// reset zoom
|
|
Point center = Point(cx, cy);
|
|
|
|
ResetZoom(zoom, center.x, center.y, 800, 600);
|
|
|
|
for (int i = 0; i < triangles.size(); i++) {
|
|
Triangle& t = *triangles[i];
|
|
Point& a = *t.GetPoint(0);
|
|
Point& b = *t.GetPoint(1);
|
|
Point& c = *t.GetPoint(2);
|
|
|
|
// Red
|
|
glColor3f(1, 0, 0);
|
|
|
|
glBegin(GL_LINE_LOOP);
|
|
glVertex2d(a.x, a.y);
|
|
glVertex2d(b.x, b.y);
|
|
glVertex2d(c.x, c.y);
|
|
glEnd();
|
|
}
|
|
|
|
// green
|
|
glColor3f(0, 1, 0);
|
|
|
|
vector<vector<Point*>*> polylines;
|
|
polylines.push_back(&polyline);
|
|
for (vector<Point*>& hole : holes) {
|
|
polylines.push_back(&hole);
|
|
}
|
|
for(int i = 0; i < polylines.size(); i++) {
|
|
const vector<Point*>& poly = *polylines[i];
|
|
glBegin(GL_LINE_LOOP);
|
|
for(int j = 0; j < poly.size(); j++) {
|
|
glVertex2d(poly[j]->x, poly[j]->y);
|
|
}
|
|
glEnd();
|
|
}
|
|
}
|
|
|
|
void DrawMap(const double zoom)
|
|
{
|
|
// reset zoom
|
|
Point center = Point(cx, cy);
|
|
|
|
ResetZoom(zoom, center.x, center.y, 800, 600);
|
|
|
|
list<Triangle*>::iterator it;
|
|
for (it = map.begin(); it != map.end(); it++) {
|
|
Triangle& t = **it;
|
|
Point& a = *t.GetPoint(0);
|
|
Point& b = *t.GetPoint(1);
|
|
Point& c = *t.GetPoint(2);
|
|
|
|
ConstrainedColor(t.constrained_edge[2]);
|
|
glBegin(GL_LINES);
|
|
glVertex2d(a.x, a.y);
|
|
glVertex2d(b.x, b.y);
|
|
glEnd( );
|
|
|
|
ConstrainedColor(t.constrained_edge[0]);
|
|
glBegin(GL_LINES);
|
|
glVertex2d(b.x, b.y);
|
|
glVertex2d(c.x, c.y);
|
|
glEnd( );
|
|
|
|
ConstrainedColor(t.constrained_edge[1]);
|
|
glBegin(GL_LINES);
|
|
glVertex2d(c.x, c.y);
|
|
glVertex2d(a.x, a.y);
|
|
glEnd( );
|
|
}
|
|
}
|
|
|
|
void ConstrainedColor(bool constrain)
|
|
{
|
|
if (constrain) {
|
|
// Green
|
|
glColor3f(0, 1, 0);
|
|
} else {
|
|
// Red
|
|
glColor3f(1, 0, 0);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
double StringToDouble(const std::string& s)
|
|
{
|
|
std::istringstream i(s);
|
|
double x;
|
|
if (!(i >> x))
|
|
return 0;
|
|
return x;
|
|
}
|
|
|
|
double Fun(double x)
|
|
{
|
|
return 2.5 + sin(10 * x) / x;
|
|
}
|
|
|
|
double Random(double (*fun)(double), double xmin = 0, double xmax = 1)
|
|
{
|
|
static double (*Fun)(double) = NULL, YMin, YMax;
|
|
static bool First = true;
|
|
|
|
// Initialises random generator for first call
|
|
if (First)
|
|
{
|
|
First = false;
|
|
srand((unsigned) time(NULL));
|
|
}
|
|
|
|
// Evaluates maximum of function
|
|
if (fun != Fun)
|
|
{
|
|
Fun = fun;
|
|
YMin = 0, YMax = Fun(xmin);
|
|
for (int iX = 1; iX < RAND_MAX; iX++)
|
|
{
|
|
double X = xmin + (xmax - xmin) * iX / RAND_MAX;
|
|
double Y = Fun(X);
|
|
YMax = Y > YMax ? Y : YMax;
|
|
}
|
|
}
|
|
|
|
// Gets random values for X & Y
|
|
double X = xmin + (xmax - xmin) * rand() / RAND_MAX;
|
|
double Y = YMin + (YMax - YMin) * rand() / RAND_MAX;
|
|
|
|
// Returns if valid and try again if not valid
|
|
return Y < fun(X) ? X : Random(Fun, xmin, xmax);
|
|
}
|