mirror of https://github.com/vitalif/openscad
245 lines
7.4 KiB
C++
245 lines
7.4 KiB
C++
#include "GeometryUtils.h"
|
|
#include "tesselator.h"
|
|
#include "printutils.h"
|
|
#include "Reindexer.h"
|
|
#include "grid.h"
|
|
#include <boost/foreach.hpp>
|
|
|
|
static void *stdAlloc(void* userData, unsigned int size) {
|
|
TESS_NOTUSED(userData);
|
|
return malloc(size);
|
|
}
|
|
|
|
static void stdFree(void* userData, void* ptr) {
|
|
TESS_NOTUSED(userData);
|
|
free(ptr);
|
|
}
|
|
|
|
/*!
|
|
Tessellates input contours into a triangle mesh.
|
|
|
|
The input contours may consist of positive (CCW) and negative (CW)
|
|
contours. These define holes, and possibly islands.
|
|
|
|
The output will be written as indices into the input vertex vector.
|
|
|
|
This function should be robust wrt. malformed input.
|
|
|
|
It will only use existing vertices and is guaranteed to maintain
|
|
connectivity if the input polygon is part of a polygon mesh.
|
|
|
|
One requirement: The input vertices must be distinct
|
|
(i.e. duplicated must resolve to the same index).
|
|
|
|
Returns true on error, false on success.
|
|
*/
|
|
bool GeometryUtils::tessellatePolygonWithHoles(const IndexedPolygons &polygons,
|
|
std::vector<IndexedTriangle> &triangles,
|
|
const Vector3f *normal)
|
|
{
|
|
// No polygon. FIXME: Will this ever happen or can we assert here?
|
|
if (polygons.faces.empty()) return false;
|
|
|
|
// Remove consecutive equal vertices, as well as null ears
|
|
std::vector<IndexedFace> faces = polygons.faces;
|
|
BOOST_FOREACH(IndexedFace &face, faces) {
|
|
int i=0;
|
|
while (i < face.size()) {
|
|
if (face[i] == face[(i+1)%face.size()]) { // Two consecutively equal indices
|
|
face.erase(face.begin()+i);
|
|
}
|
|
else if (face[i] == face[(i+2)%face.size()]) { // Null ear
|
|
face.erase(face.begin() + (i+1)%face.size());
|
|
}
|
|
else {
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
// First polygon has < 3 points - no output
|
|
if (faces[0].size() < 3) return false;
|
|
// Remove collapsed holes
|
|
for (int i=1;i<faces.size();i++) {
|
|
if (faces[i].size() < 3) {
|
|
faces.erase(faces.begin() + i);
|
|
i--;
|
|
}
|
|
}
|
|
|
|
if (faces.size() == 1 && faces[0].size() == 3) {
|
|
// Input polygon has 3 points. shortcut tessellation.
|
|
triangles.push_back(IndexedTriangle(faces[0][0], faces[0][1], faces[0][2]));
|
|
return false;
|
|
}
|
|
|
|
const Vector3f *verts = &polygons.vertices.front();
|
|
TESSreal *normalvec = NULL;
|
|
TESSreal passednormal[3];
|
|
if (normal) {
|
|
passednormal[0] = (*normal)[0];
|
|
passednormal[1] = (*normal)[1];
|
|
passednormal[2] = (*normal)[2];
|
|
normalvec = passednormal;
|
|
}
|
|
|
|
TESSalloc ma;
|
|
TESStesselator* tess = 0;
|
|
|
|
memset(&ma, 0, sizeof(ma));
|
|
ma.memalloc = stdAlloc;
|
|
ma.memfree = stdFree;
|
|
ma.extraVertices = 256; // realloc not provided, allow 256 extra vertices.
|
|
|
|
if (!(tess = tessNewTess(&ma))) return true;
|
|
|
|
int numContours = 0;
|
|
std::vector<TESSreal> contour;
|
|
// Since libtess2's indices is based on the running number of points added, we need to map back
|
|
// to our indices. allindices does the mapping.
|
|
std::vector<int> allindices;
|
|
BOOST_FOREACH(const IndexedFace &face, faces) {
|
|
contour.clear();
|
|
BOOST_FOREACH(int idx, face) {
|
|
const Vector3f &v = verts[idx];
|
|
contour.push_back(v[0]);
|
|
contour.push_back(v[1]);
|
|
contour.push_back(v[2]);
|
|
allindices.push_back(idx);
|
|
}
|
|
assert(face.size() >= 3);
|
|
PRINTDB("Contour: %d\n", face.size());
|
|
tessAddContour(tess, 3, &contour.front(), sizeof(TESSreal) * 3, face.size());
|
|
numContours++;
|
|
}
|
|
|
|
if (!tessTesselate(tess, TESS_WINDING_ODD, TESS_CONSTRAINED_DELAUNAY_TRIANGLES, 3, 3, normalvec)) return -1;
|
|
|
|
const TESSindex *vindices = tessGetVertexIndices(tess);
|
|
const TESSindex *elements = tessGetElements(tess);
|
|
int numelems = tessGetElementCount(tess);
|
|
|
|
/*
|
|
At this point, we have a delaunay triangle mesh.
|
|
|
|
However, as libtess2 might merge vertices, as well as insert new
|
|
vertices for intersecting edges, we need to detect these and
|
|
insert dummy triangles to maintain external connectivity.
|
|
|
|
FIXME: This currently only works for polygons without holes.
|
|
*/
|
|
if (faces.size() == 1) { // Only works for polygons without holes
|
|
|
|
/*
|
|
Algorithm:
|
|
A) Collect all triangles using _only_ existing vertices -> triangles
|
|
B) Locate all unused vertices
|
|
C) For each unused vertex, create a triangle connecting it to the existing mesh
|
|
*/
|
|
int inputSize = allindices.size(); // inputSize is number of points added to libtess2
|
|
std::vector<int> vflags(inputSize); // Inits with 0's
|
|
|
|
IndexedTriangle tri;
|
|
IndexedTriangle mappedtri;
|
|
for (int t=0;t<numelems;t++) {
|
|
bool err = false;
|
|
mappedtri.fill(-1);
|
|
for (int i=0;i<3;i++) {
|
|
int vidx = vindices[elements[t*3 + i]];
|
|
if (vidx == TESS_UNDEF) {
|
|
err = true;
|
|
}
|
|
else {
|
|
tri[i] = vidx; // A)
|
|
mappedtri[i] = allindices[vidx];
|
|
}
|
|
}
|
|
PRINTDB("%d (%d) %d (%d) %d (%d)",
|
|
elements[t*3 + 0] % mappedtri[0] %
|
|
elements[t*3 + 1] % mappedtri[1] %
|
|
elements[t*3 + 2] % mappedtri[2]);
|
|
// FIXME: We ignore self-intersecting triangles rather than detecting and handling this
|
|
if (!err) {
|
|
vflags[tri[0]]++; // B)
|
|
vflags[tri[1]]++;
|
|
vflags[tri[2]]++;
|
|
triangles.push_back(mappedtri);
|
|
}
|
|
}
|
|
|
|
for (int i=0;i<inputSize;i++) {
|
|
if (!vflags[i]) { // vertex missing in output: C)
|
|
int starti = (i+inputSize-1)%inputSize;
|
|
int j;
|
|
for (j = i; j < inputSize && !vflags[j]; j++) {
|
|
// Create triangle fan from vertex i-1 to the first existing vertex
|
|
PRINTDB("(%d) (%d) (%d)\n", allindices[starti] % allindices[j] % allindices[((j+1)%inputSize)]);
|
|
tri[0] = allindices[starti];
|
|
tri[1] = allindices[j];
|
|
tri[2] = allindices[(j+1)%inputSize];
|
|
vflags[tri[0]]++;
|
|
vflags[tri[1]]++;
|
|
vflags[tri[2]]++;
|
|
triangles.push_back(tri);
|
|
}
|
|
i = j;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
IndexedTriangle tri;
|
|
for (int t=0;t<numelems;t++) {
|
|
bool err = false;
|
|
for (int i=0;i<3;i++) {
|
|
int vidx = vindices[elements[t*3 + i]];
|
|
if (vidx == TESS_UNDEF) err = true;
|
|
else tri[i] = allindices[vidx];
|
|
}
|
|
PRINTDB("%d (%d) %d (%d) %d (%d)",
|
|
elements[t*3 + 0] % allindices[vindices[elements[t*3 + 0]]] %
|
|
elements[t*3 + 1] % allindices[vindices[elements[t*3 + 1]]] %
|
|
elements[t*3 + 2] % allindices[vindices[elements[t*3 + 2]]]);
|
|
// FIXME: We ignore self-intersecting triangles rather than detecting and handling this
|
|
if (!err) {
|
|
triangles.push_back(tri);
|
|
}
|
|
else PRINT("WARNING: Self-intersecting polygon encountered - ignoring");
|
|
}
|
|
}
|
|
|
|
tessDeleteTess(tess);
|
|
|
|
return false;
|
|
}
|
|
|
|
/*!
|
|
Tessellates a single contour. Non-indexed version.
|
|
*/
|
|
bool GeometryUtils::tessellatePolygon(const Polygon &polygon, Polygons &triangles,
|
|
const Vector3f *normal)
|
|
{
|
|
bool err = false;
|
|
Reindexer<Vector3f> uniqueVertices;
|
|
IndexedPolygons indexedpolygons;
|
|
indexedpolygons.faces.push_back(IndexedFace());
|
|
IndexedFace &currface = indexedpolygons.faces.back();
|
|
BOOST_FOREACH (const Vector3d &v, polygon) {
|
|
int idx = uniqueVertices.lookup(v.cast<float>());
|
|
if (currface.empty() || idx != currface.back()) currface.push_back(idx);
|
|
}
|
|
if (currface.front() == currface.back()) currface.pop_back();
|
|
if (currface.size() >= 3) { // Cull empty triangles
|
|
uniqueVertices.copy(std::back_inserter(indexedpolygons.vertices));
|
|
std::vector<IndexedTriangle> indexedtriangles;
|
|
err = tessellatePolygonWithHoles(indexedpolygons, indexedtriangles, normal);
|
|
Vector3f *verts = &indexedpolygons.vertices.front();
|
|
BOOST_FOREACH(const IndexedTriangle &t, indexedtriangles) {
|
|
triangles.push_back(Polygon());
|
|
Polygon &p = triangles.back();
|
|
p.push_back(verts[t[0]].cast<double>());
|
|
p.push_back(verts[t[1]].cast<double>());
|
|
p.push_back(verts[t[2]].cast<double>());
|
|
}
|
|
}
|
|
return err;
|
|
}
|