blob: db4911c1c6e504a74d1547a8df038666669e4b31 [file] [log] [blame]
/*
* Authors: kaen, raptor, sam686, watusimoto
*
* Originally from the bitfighter source code
*
* The MIT License (MIT)
*
* Copyright (c) 2014 Bitfighter developers
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "clip2tri.h"
#include <poly2tri.h>
#include <cstdio>
static const double clipperScaleFactor = 1073741822.0;
static const double clipperScaleFactorInv = 1.0 / 1073741822.0;
using namespace p2t;
namespace c2t
{
static const F32 CLIPPER_SCALE_FACT = 1000.0f;
static const F32 CLIPPER_SCALE_FACT_INVERSE = 0.001f;
/////////////////////////////////
Point::Point()
{
x = 0;
y = 0;
}
Point::Point(const Point& pt)
{
x = pt.x;
y = pt.y;
}
/////////////////////////////////
clip2tri::clip2tri() : openSubject(false)
{
// Do nothing!
}
clip2tri::~clip2tri()
{
// Do nothing!
}
void clip2tri::triangulate(const vector<vector<Point> > &inputPolygons, vector<Point> &outputTriangles,
const vector<Point> &boundingPolygon)
{
// Use clipper to clean. This upscales the floating point input
PolyTree solution;
mergePolysToPolyTree(inputPolygons, solution);
Path bounds = upscaleClipperPoints(boundingPolygon);
// This will downscale the Clipper output and use poly2tri to triangulate
triangulateComplex(outputTriangles, bounds, solution);
}
void clip2tri::addClipPolygon(const Path &path)
{
try // prevent any exception to spill into Qt
{
clipper.AddPath(path, ptClip, true);
}
catch(QtClipperLib::clipperException &e)
{
printf("addClipPolygon: %s\n", e.what());
}
}
void clip2tri::addSubjectPath(const Path &path, bool closed)
{
try // prevent any exception to spill into Qt
{
clipper.AddPath(path, ptSubject, closed);
}
catch(QtClipperLib::clipperException &e)
{
printf("addSubjectPath: %s\n", e.what());
return;
}
if (!closed)
openSubject = true;
}
void clip2tri::clearClipper()
{
// clear doesn't throw
clipper.Clear();
openSubject = false;
}
static QtClipperLib::ClipType operation(const clip2tri::Operation &op)
{
switch (op) {
case clip2tri::Intersection:
return QtClipperLib::ctIntersection;
case clip2tri::Union:
return QtClipperLib::ctUnion;
case clip2tri::Difference:
return QtClipperLib::ctDifference;
case clip2tri::Xor:
return QtClipperLib::ctXor;
}
return ctIntersection;
}
static std::string operationName(const clip2tri::Operation &op)
{
switch (op) {
case clip2tri::Intersection:
return std::string("Intersection");
case clip2tri::Union:
return std::string("Union");
case clip2tri::Difference:
return std::string("Difference");
case clip2tri::Xor:
return std::string("Xor");
}
return std::string("Intersection");
}
Paths clip2tri::execute(const clip2tri::Operation op, const PolyFillType subjFillType, const PolyFillType clipFillType)
{
Paths solution;
try // prevent any exception from spilling into Qt
{
if (!openSubject) {
clipper.Execute(operation(op), solution, subjFillType, clipFillType);
} else {
PolyTree res;
clipper.Execute(operation(op), res, subjFillType, clipFillType);
PolyNode *n = res.GetFirst();
if (n) {
solution.push_back(n->Contour);
while ((n = n->GetNext()))
solution.push_back(n->Contour);
}
}
}
catch(QtClipperLib::clipperException &e)
{
printf("executing %s: %s\n", operationName(op).c_str(), e.what());
}
return solution;
}
int clip2tri::pointInPolygon(const IntPoint &pt, const Path &path)
{
return PointInPolygon(pt, path);
}
Path clip2tri::upscaleClipperPoints(const vector<Point> &inputPolygon)
{
Path outputPolygon;
outputPolygon.resize(inputPolygon.size());
for(S32 i = 0; i < inputPolygon.size(); i++)
outputPolygon[i] = IntPoint(S64(inputPolygon[i].x * CLIPPER_SCALE_FACT), S64(inputPolygon[i].y * CLIPPER_SCALE_FACT));
return outputPolygon;
}
Paths clip2tri::upscaleClipperPoints(const vector<vector<Point> > &inputPolygons)
{
Paths outputPolygons;
outputPolygons.resize(inputPolygons.size());
for(S32 i = 0; i < inputPolygons.size(); i++)
{
outputPolygons[i].resize(inputPolygons[i].size());
for(S32 j = 0; j < inputPolygons[i].size(); j++)
outputPolygons[i][j] = IntPoint(S64(inputPolygons[i][j].x * CLIPPER_SCALE_FACT), S64(inputPolygons[i][j].y * CLIPPER_SCALE_FACT));
}
return outputPolygons;
}
vector<vector<Point> > clip2tri::downscaleClipperPoints(const Paths &inputPolygons)
{
vector<vector<Point> > outputPolygons;
outputPolygons.resize(inputPolygons.size());
for(U32 i = 0; i < inputPolygons.size(); i++)
{
outputPolygons[i].resize(inputPolygons[i].size());
for(U32 j = 0; j < inputPolygons[i].size(); j++)
outputPolygons[i][j] = Point(F32(inputPolygons[i][j].X) * CLIPPER_SCALE_FACT_INVERSE, F32(inputPolygons[i][j].Y) * CLIPPER_SCALE_FACT_INVERSE);
}
return outputPolygons;
}
// Use Clipper to merge inputPolygons, placing the result in a Polytree
// NOTE: this does NOT downscale the Clipper points. You must do this afterwards
//
// Here you add all your non-navigatable objects (e.g. walls, barriers, etc.)
bool clip2tri::mergePolysToPolyTree(const vector<vector<Point> > &inputPolygons, PolyTree &solution)
{
Paths input = upscaleClipperPoints(inputPolygons);
// Fire up clipper and union!
Clipper clipper;
clipper.StrictlySimple(true);
try // there is a "throw" in AddPolygon
{
clipper.AddPaths(input, ptSubject, true);
}
catch(QtClipperLib::clipperException &e)
{
printf("mergePolysToPolyTree: %s\n", e.what());
}
return clipper.Execute(ctUnion, solution, pftNonZero, pftNonZero);
}
// Delete all poly2tri points from a vector and clear the vector
static void deleteAndClear(vector<p2t::Point*> &vec)
{
for(U32 i = 0; i < vec.size(); i++)
delete vec[i];
vec.clear();
}
// Shrink large polygons by reducing each coordinate by 1 in the
// general direction of the last point as we wind around
//
// This normally wouldn't work in every case, but our upscaled-by-1000 polygons
// have little chance to create new duplicate points with this method.
//
// For information on why this was needed, see:
//
// https://code.google.com/p/poly2tri/issues/detail?id=90
//
static void edgeShrink(Path &path)
{
U32 prev = path.size() - 1;
for(U32 i = 0; i < path.size(); i++)
{
// Adjust coordinate by 1 depending on the direction
path[i].X - path[prev].X > 0 ? path[i].X-- : path[i].X++;
path[i].Y - path[prev].Y > 0 ? path[i].Y-- : path[i].Y++;
prev = i;
}
}
// This uses poly2tri to triangulate. poly2tri isn't very robust so clipper needs to do
// the cleaning of points before getting here.
//
// A tree structure of polygons is required for doing complex polygons-within-polygons.
// For reference discussion on how this started to be developed, see here:
//
// https://code.google.com/p/poly2tri/issues/detail?id=74
//
// For assistance with a special case crash, see this utility:
// http://javascript.poly2tri.googlecode.com/hg/index.html
//
// FIXME: what is ignoreFills and ignoreHoles for? kaen?
bool clip2tri::triangulateComplex(vector<Point> &outputTriangles, const Path &outline,
const PolyTree &polyTree, bool ignoreFills, bool ignoreHoles)
{
// Keep track of memory for all the poly2tri objects we create
vector<p2t::CDT*> cdtRegistry;
vector<vector<p2t::Point*> > holesRegistry;
vector<vector<p2t::Point*> > polylinesRegistry;
// Let's be tricky and add our outline to the root node (it should have none), it'll be
// our first Clipper hole
PolyNode *rootNode = NULL;
PolyNode tempNode;
if(polyTree.Total() == 0) // Polytree is empty with no root node, e.g. on an empty level
rootNode = &tempNode;
else
rootNode = polyTree.GetFirst()->Parent;
rootNode->Contour = outline;
// Now traverse our polyline nodes and triangulate them with only their children holes
PolyNode *currentNode = rootNode;
while(currentNode != NULL)
{
// A Clipper hole is actually what we want to build zones for; they become our bounding
// polylines. poly2tri holes are therefore the inverse
if((!ignoreHoles && currentNode->IsHole()) ||
(!ignoreFills && !currentNode->IsHole()))
{
// Build up this polyline in poly2tri's format (downscale Clipper points)
vector<p2t::Point*> polyline;
for(U32 j = 0; j < currentNode->Contour.size(); j++)
polyline.push_back(new p2t::Point(F64(currentNode->Contour[j].X), F64(currentNode->Contour[j].Y)));
polylinesRegistry.push_back(polyline); // Memory
// Set our polyline in poly2tri
p2t::CDT* cdt = new p2t::CDT(polyline);
cdtRegistry.push_back(cdt);
for(U32 j = 0; j < currentNode->Childs.size(); j++)
{
PolyNode *childNode = currentNode->Childs[j];
// Slightly modify the polygon to guarantee no duplicate points
edgeShrink(childNode->Contour);
vector<p2t::Point*> hole;
for(U32 k = 0; k < childNode->Contour.size(); k++)
hole.push_back(new p2t::Point(F64(childNode->Contour[k].X), F64(childNode->Contour[k].Y)));
holesRegistry.push_back(hole); // Memory
// Add the holes for this polyline
cdt->AddHole(hole);
}
cdt->Triangulate();
// Add current output triangles to our total
vector<p2t::Triangle*> currentOutput = cdt->GetTriangles();
// Copy our data to TNL::Point and to our output Vector
p2t::Triangle *currentTriangle;
for(U32 j = 0; j < currentOutput.size(); j++)
{
currentTriangle = currentOutput[j];
outputTriangles.push_back(Point(currentTriangle->GetPoint(0)->x * CLIPPER_SCALE_FACT_INVERSE, currentTriangle->GetPoint(0)->y * CLIPPER_SCALE_FACT_INVERSE));
outputTriangles.push_back(Point(currentTriangle->GetPoint(1)->x * CLIPPER_SCALE_FACT_INVERSE, currentTriangle->GetPoint(1)->y * CLIPPER_SCALE_FACT_INVERSE));
outputTriangles.push_back(Point(currentTriangle->GetPoint(2)->x * CLIPPER_SCALE_FACT_INVERSE, currentTriangle->GetPoint(2)->y * CLIPPER_SCALE_FACT_INVERSE));
}
}
currentNode = currentNode->GetNext();
}
// Clean up memory used with poly2tri
//
// Clean-up workers
for(S32 i = 0; i < cdtRegistry.size(); i++)
delete cdtRegistry[i];
// Free the polylines
for(S32 i = 0; i < polylinesRegistry.size(); i++)
{
vector<p2t::Point*> polyline = polylinesRegistry[i];
deleteAndClear(polyline);
}
// Free the holes
for(S32 i = 0; i < holesRegistry.size(); i++)
{
vector<p2t::Point*> hole = holesRegistry[i];
deleteAndClear(hole);
}
// Make sure we have output data
if(outputTriangles.size() == 0)
return false;
return true;
}
} /* namespace c2t */