// +-------------------------------------------------------------------------
// | mesh.isct.tpp
// |
// | Author: Gilbert Bernstein
// +-------------------------------------------------------------------------
// | COPYRIGHT:
// | Copyright Gilbert Bernstein 2013
// | See the included COPYRIGHT file for further details.
// |
// | This file is part of the Cork library.
// |
// | Cork is free software: you can redistribute it and/or modify
// | it under the terms of the GNU Lesser General Public License as
// | published by the Free Software Foundation, either version 3 of
// | the License, or (at your option) any later version.
// |
// | Cork is distributed in the hope that it will be useful,
// | but WITHOUT ANY WARRANTY; without even the implied warranty of
// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// | GNU Lesser General Public License for more details.
// |
// | You should have received a copy
// | of the GNU Lesser General Public License
// | along with Cork. If not, see .
// +-------------------------------------------------------------------------
#pragma once
#include "mesh.topoCache.tpp"
#include "../math/bbox.h"
#include "../isct/quantization.h"
#include "../isct/empty3d.h"
#include "../accel/aabvh.h"
#define REAL double
extern "C" {
#include "../isct/triangle.h"
}
struct GenericVertType;
struct IsctVertType;
struct OrigVertType;
struct GenericEdgeType;
struct IsctEdgeType;
struct OrigEdgeType;
struct SplitEdgeType;
struct GenericTriType;
struct GluePointMarker;
//using GVptr = GenericVertType*;
// using IVptr = IsctVertType*;
// using OVptr = OrigVertType*;
//using GEptr = GenericEdgeType*;
// using IEptr = IsctEdgeType*;
// using OEptr = OrigEdgeType*;
// using SEptr = SplitEdgeType*;
//using GTptr = GenericTriType*;
//using GluePt = GluePointMarker*;
typedef GenericVertType* GVptr;
typedef IsctVertType* IVptr;
typedef OrigVertType* OVptr;
typedef GenericEdgeType* GEptr;
typedef IsctEdgeType* IEptr;
typedef OrigEdgeType* OEptr;
typedef SplitEdgeType* SEptr;
typedef GenericTriType* GTptr;
typedef GluePointMarker* GluePt;
struct GenericVertType
{
virtual ~GenericVertType() {}
Vptr concrete;
Vec3d coord;
bool boundary;
uint idx; // temporary for triangulation marshalling
ShortVec edges;
};
struct IsctVertType : public GenericVertType
{
GluePt glue_marker;
};
struct OrigVertType : public GenericVertType {};
struct GenericEdgeType
{
virtual ~GenericEdgeType() {}
Eptr concrete;
bool boundary;
uint idx; // temporary for triangulation marshalling
GVptr ends[2];
ShortVec interior;
};
struct IsctEdgeType : public GenericEdgeType
{
public:
// use to detect duplicate instances within a triangle
Tptr other_tri_key;
};
struct OrigEdgeType : public GenericEdgeType {};
struct SplitEdgeType : public GenericEdgeType {};
struct GenericTriType
{
Tptr concrete;
GVptr verts[3];
};
struct GluePointMarker
{
// list of all the vertices to be glued...
ShortVec copies;
bool split_type; // splits are introduced
// manually, not via intersection
// and therefore use only e pointer
bool edge_tri_type; // true if edge-tri intersection
// false if tri-tri-tri
Eptr e;
Tptr t[3];
};
template inline
IEptr find_edge(ShortVec &vec, Tptr key)
{
for(IEptr ie : vec) {
if(ie->other_tri_key == key)
return ie;
}
return nullptr;
}
inline Vptr commonVert(Tptr t0, Tptr t1)
{
for(uint i=0; i<3; i++) {
for(uint j=0; j<3; j++) {
if(t0->verts[i] == t1->verts[j])
return t0->verts[i];
}
}
return nullptr;
}
inline bool hasCommonVert(Tptr t0, Tptr t1)
{
return (t0->verts[0] == t1->verts[0] ||
t0->verts[0] == t1->verts[1] ||
t0->verts[0] == t1->verts[2] ||
t0->verts[1] == t1->verts[0] ||
t0->verts[1] == t1->verts[1] ||
t0->verts[1] == t1->verts[2] ||
t0->verts[2] == t1->verts[0] ||
t0->verts[2] == t1->verts[1] ||
t0->verts[2] == t1->verts[2]);
}
inline bool hasCommonVert(Eptr e, Tptr t)
{
return (e->verts[0] == t->verts[0] ||
e->verts[0] == t->verts[1] ||
e->verts[0] == t->verts[2] ||
e->verts[1] == t->verts[0] ||
e->verts[1] == t->verts[1] ||
e->verts[1] == t->verts[2]);
}
inline void disconnectGE(GEptr ge)
{
ge->ends[0]->edges.erase(ge);
ge->ends[1]->edges.erase(ge);
for(IVptr iv : ge->interior)
iv->edges.erase(ge);
}
// should deal with via pointers
template
class Mesh::TriangleProblem
{
public:
TriangleProblem() {}
~TriangleProblem() {}
inline void init(IsctProblem *iprob, Tptr t) {
the_tri = t;
// extract original edges/verts
for(uint k=0; k<3; k++)
overts[k] = iprob->newOrigVert(the_tri->verts[k]);
for(uint k=0; k<3; k++) {
oedges[k] = iprob->newOrigEdge(the_tri->edges[k],
overts[(k+1)%3],
overts[(k+2)%3]);
}
}
private: // may actually not add edge, but instead just hook up endpoint
inline void addEdge(
IsctProblem *iprob, IVptr iv, Tptr tri_key
) {
IEptr ie = find_edge(iedges, tri_key);
if(ie) { // if the edge is already present
ie->ends[1] = iv;
iv->edges.push_back(ie);
} else { // if the edge is being added
ie = iprob->newIsctEdge(iv, tri_key);
iedges.push_back(ie);
}
}
void addBoundaryHelper(
Eptr edge, IVptr iv
) {
iv->boundary = true;
iverts.push_back(iv);
// hook up point to boundary edge interior!
for(uint k=0; k<3; k++) {
OEptr oe = oedges[k];
if(oe->concrete == edge) {
oe->interior.push_back(iv);
iv->edges.push_back(oe);
break;
}
}
}
public:
// specify reference glue point and edge piercing this triangle.
IVptr addInteriorEndpoint(
IsctProblem *iprob, Eptr edge, GluePt glue
) {
IVptr iv = iprob->newIsctVert(edge, the_tri, glue);
iv->boundary = false;
iverts.push_back(iv);
for(Tptr tri_key : edge->tris) {
addEdge(iprob, iv, tri_key);
}
return iv;
}
// specify the other triangle cutting this one, the edge cut,
// and the resulting point of intersection
void addBoundaryEndpoint(
IsctProblem *iprob, Tptr tri_key, Eptr edge, IVptr iv
) {
iv = iprob->copyIsctVert(iv);
addBoundaryHelper(edge, iv);
// handle edge extending into interior
addEdge(iprob, iv, tri_key);
}
IVptr addBoundaryEndpoint(
IsctProblem *iprob, Tptr tri_key, Eptr edge, Vec3d coord, GluePt glue
) {
IVptr iv = iprob->newSplitIsctVert(coord, glue);
addBoundaryHelper(edge, iv);
// handle edge extending into interior
addEdge(iprob, iv, tri_key);
return iv;
}
// Should only happen for manually inserted split points on
// edges, not for points computed via intersection...
IVptr addBoundaryPointAlone(
IsctProblem *iprob, Eptr edge, Vec3d coord, GluePt glue
) {
IVptr iv = iprob->newSplitIsctVert(coord, glue);
addBoundaryHelper(edge, iv);
return iv;
}
void addInteriorPoint(
IsctProblem *iprob, Tptr t0, Tptr t1, GluePt glue
) {
// note this generates wasted re-computation of coordinates 3X
IVptr iv = iprob->newIsctVert(the_tri, t0, t1, glue);
iv->boundary = false;
iverts.push_back(iv);
// find the 2 interior edges
for(IEptr ie : iedges) {
if(ie->other_tri_key == t0 ||
ie->other_tri_key == t1) {
ie->interior.push_back(iv);
iv->edges.push_back(ie);
}
}
}
// run after we've accumulated all the elements
void consolidate(IsctProblem *iprob) {
// identify all intersection edges missing endpoints
// and check to see if we can assign an original vertex
// as the appropriate endpoint.
for(IEptr ie : iedges) {
if(ie->ends[1] == nullptr) {
// try to figure out which vertex must be the endpoint...
Vptr vert = commonVert(the_tri, ie->other_tri_key);
if(!vert) {
std::cout << "the edge is "
<< ie->ends[0] << ", "
<< ie->ends[1] << std::endl;
IVptr iv = dynamic_cast(ie->ends[0]);
std::cout << " "
<< iv->glue_marker->edge_tri_type
<< std::endl;
std::cout << "the tri is " << the_tri << ": "
<< *the_tri << std::endl;
std::cout << "other tri is " << ie->other_tri_key << ": "
<< *(ie->other_tri_key) << std::endl;
std::cout << "coordinates for triangles" << std::endl;
std::cout << "the tri" << std::endl;
for(uint k=0; k<3; k++)
std::cout << iprob->vPos(the_tri->verts[k])
<< std::endl;
for(uint k=0; k<3; k++)
std::cout << iprob->vPos(ie->other_tri_key->verts[k])
<< std::endl;
std::cout << "degen count:"
<< Empty3d::degeneracy_count << std::endl;
std::cout << "exact count: "
<< Empty3d::exact_count << std::endl;
}
ENSURE(vert); // bad if we can't find a common vertex
// then, find the corresponding OVptr, and connect
for(uint k=0; k<3; k++) {
if(overts[k]->concrete == vert) {
ie->ends[1] = overts[k];
overts[k]->edges.push_back(ie);
break;
}
}
}
}
ENSURE(isValid());
}
bool isValid() const {
ENSURE(the_tri);
return true;
}
void subdivide(IsctProblem *iprob) {
// collect all the points, and create more points as necessary
ShortVec points;
for(uint k=0; k<3; k++) {
points.push_back(overts[k]);
//std::cout << k << ": id " << overts[k]->concrete->ref << std::endl;
}
for(IVptr iv : iverts) {
//iprob->buildConcreteVert(iv);
points.push_back(iv);
/*std::cout << " " << points.size() - 1
<< " (" << iv->glue_marker->edge_tri_type
<< ") ";
if(iv->glue_marker->edge_tri_type) {
Eptr e = iv->glue_marker->e;
Vec3d p0 = iprob->vPos(e->verts[0]);
Vec3d p1 = iprob->vPos(e->verts[1]);
std::cout << " "
<< e->verts[0]->ref << p0
<< " " << e->verts[1]->ref << p1;
Tptr t = iv->glue_marker->t[0];
p0 = iprob->vPos(t->verts[0]);
p1 = iprob->vPos(t->verts[1]);
Vec3d p2 = iprob->vPos(t->verts[2]);
std::cout << " "
<< t->verts[0]->ref << p0
<< " " << t->verts[1]->ref << p1
<< " " << t->verts[2]->ref << p2;
}
std::cout
<< std::endl;*/
}
for(uint i=0; iidx = i;
// split edges and marshall data
// for safety, we zero out references to pre-subdivided edges,
// which may have been destroyed
ShortVec edges;
for(uint k=0; k<3; k++) {
//std::cout << "oedge: "
// << oedges[k]->ends[0]->idx << "; "
// << oedges[k]->ends[1]->idx << std::endl;
//for(IVptr iv : oedges[k]->interior)
// std::cout << " " << iv->idx << std::endl;
subdivideEdge(iprob, oedges[k], edges);
oedges[k] = nullptr;
}
//std::cout << "THE TRI: " << the_tri->verts[0]->ref
// << "; " << the_tri->verts[1]->ref
// << "; " << the_tri->verts[2]->ref
// << std::endl;
for(IEptr &ie : iedges) {
//std::cout << "iedge: "
// << ie->ends[0]->idx << "; "
// << ie->ends[1]->idx << std::endl;
//std::cout << "other tri: " << ie->other_tri_key->verts[0]->ref
// << "; " << ie->other_tri_key->verts[1]->ref
// << "; " << ie->other_tri_key->verts[2]->ref
// << std::endl;
//for(IVptr iv : ie->interior)
// std::cout << " " << iv->idx
// << " (" << iv->glue_marker->edge_tri_type
// << ") " << std::endl;
subdivideEdge(iprob, ie, edges);
ie = nullptr;
}
for(uint i=0; iidx = i;
// find 2 dimensions to project onto
// get normal
Vec3d normal = cross( overts[1]->coord - overts[0]->coord,
overts[2]->coord - overts[0]->coord );
uint normdim = maxDim(abs(normal));
uint dim0 = (normdim+1)%3;
uint dim1 = (normdim+2)%3;
double sign_flip = (normal.v[normdim] < 0.0)? -1.0 : 1.0;
struct triangulateio in, out;
/* Define input points. */
in.numberofpoints = points.size();
in.numberofpointattributes = 0;
in.pointlist = new REAL[in.numberofpoints * 2];
in.pointattributelist = nullptr;
in.pointmarkerlist = new int[in.numberofpoints];
for(int k=0; kcoord.v[dim0];
in.pointlist[k*2 + 1] = points[k]->coord.v[dim1] * sign_flip;
in.pointmarkerlist[k] = (points[k]->boundary)? 1 : 0;
}
/* Define the input segments */
in.numberofsegments = edges.size();
in.numberofholes = 0;// yes, zero
in.numberofregions = 0;// not using regions
in.segmentlist = new int[in.numberofsegments * 2];
in.segmentmarkerlist = new int[in.numberofsegments];
for(int k=0; kends[0]->idx;
in.segmentlist[k*2 + 1] = edges[k]->ends[1]->idx;
in.segmentmarkerlist[k] = (edges[k]->boundary)? 1 : 0;
}
// to be safe... declare 0 triangle attributes on input
in.numberoftriangles = 0;
in.numberoftriangleattributes = 0;
/* set for flags.... */
out.pointlist = nullptr;
out.pointattributelist = nullptr; // not necessary if using -N or 0 attr
out.pointmarkerlist = nullptr;
out.trianglelist = nullptr; // not necessary if using -E
//out.triangleattributelist = null; // not necessary if using -E or 0 attr
//out.trianglearealist = // only needed with -r and -a
//out.neighborlist = null; // only neccesary if -n is used
out.segmentlist = nullptr; // NEED THIS; output segments go here
out.segmentmarkerlist = nullptr; // NEED THIS for OUTPUT SEGMENTS
//out.edgelist = null; // only necessary if -e is used
//out.edgemarkerlist = null; // only necessary if -e is used
// solve the triangulation problem
char *params = (char*)("pzQYY");
//char *debug_params = (char*)("pzYYVC");
triangulate(params, &in, &out, nullptr);
if(out.numberofpoints != in.numberofpoints) {
std::cout << "out.numberofpoints: "
<< out.numberofpoints << std::endl;
std::cout << "points.size(): " << points.size() << std::endl;
std::cout << "dumping out the points' coordinates" << std::endl;
for(uint k=0; kcoord
<< " " << gv->idx << std::endl;
}
std::cout << "dumping out the segments" << std::endl;
for(int k=0; knewGenericTri(gv0, gv1, gv2);
}
// clean up after triangulate...
// in free
free(in.pointlist);
free(in.pointmarkerlist);
free(in.segmentlist);
free(in.segmentmarkerlist);
// out free
free(out.pointlist);
//free(out.pointattributelist);
free(out.pointmarkerlist);
free(out.trianglelist);
//free(out.triangleattributelist);
//free(out.trianglearealist);
//free(out.neighborlist);
free(out.segmentlist);
free(out.segmentmarkerlist);
//free(out.edgelist);
//free(out.edgemarkerlist);
}
private:
void subdivideEdge(IsctProblem *iprob, GEptr ge, ShortVec &edges)
{
if(ge->interior.size() == 0) {
//if(typeid(ge) == typeid(IEptr)) { // generate new edge
// iprob->buildConcreteEdge(ge);
//}
edges.push_back(ge);
} else if(ge->interior.size() == 1) { // common case
SEptr se0 = iprob->newSplitEdge(ge->ends[0],
ge->interior[0],
ge->boundary);
SEptr se1 = iprob->newSplitEdge(ge->interior[0],
ge->ends[1],
ge->boundary);
//iprob->buildConcreteEdge(se0);
//iprob->buildConcreteEdge(se1);
edges.push_back(se0);
edges.push_back(se1);
// get rid of old edge
iprob->releaseEdge(ge);
} else { // sorting is the uncommon case
// determine the primary dimension and direction of the edge
Vec3d dir = ge->ends[1]->coord - ge->ends[0]->coord;
uint dim = (fabs(dir.x) > fabs(dir.y))?
((fabs(dir.x) > fabs(dir.z))? 0 : 2) :
((fabs(dir.y) > fabs(dir.z))? 1 : 2);
double sign = (dir.v[dim] > 0.0)? 1.0 : -1.0;
// pack the interior vertices into a vector for sorting
std::vector< std::pair > verts;
for(IVptr iv : ge->interior) {
verts.push_back(std::make_pair(
// if the sort is ascending, then we're good...
sign * iv->coord.v[dim],
iv
));
}
// ... and sort the vector
std::sort(verts.begin(), verts.end());
// then, write the verts into a new container with the endpoints
std::vector allv(verts.size()+2);
allv[0] = ge->ends[0];
allv[allv.size()-1] = ge->ends[1];
for(uint k=0; knewSplitEdge(allv[i-1],
allv[i],
ge->boundary);
edges.push_back(se);
}
// get rid of old edge
iprob->releaseEdge(ge);
}
}
public: // data
ShortVec iverts;
ShortVec iedges;
// original triangle elements
OVptr overts[3];
OEptr oedges[3];
ShortVec gtris;
Tptr the_tri;
};
template
class Mesh::IsctProblem : public TopoCache
{
public:
IsctProblem(Mesh *owner) : TopoCache(owner)
{
// initialize all the triangles to NOT have an associated tprob
TopoCache::tris.for_each([](Tptr t) {
t->data = nullptr;
});
// Callibrate the quantization unit...
double maxMag = 0.0;
for(VertData &v : TopoCache::mesh->verts) {
maxMag = std::max(maxMag, max(abs(v.pos)));
}
Quantization::callibrate(maxMag);
// and use vertex auxiliary data to store quantized vertex coordinates
uint N = TopoCache::mesh->verts.size();
quantized_coords.resize(N);
uint write = 0;
TopoCache::verts.for_each([&](Vptr v) {
#ifdef _WIN32
Vec3d raw = mesh->verts[v->ref].pos;
#else
Vec3d raw = TopoCache::mesh->verts[v->ref].pos;
#endif
quantized_coords[write].x = Quantization::quantize(raw.x);
quantized_coords[write].y = Quantization::quantize(raw.y);
quantized_coords[write].z = Quantization::quantize(raw.z);
v->data = &(quantized_coords[write]);
write++;
});
}
virtual ~IsctProblem() {}
// access auxiliary quantized coordinates
inline Vec3d vPos(Vptr v) const {
return *(reinterpret_cast(v->data));
}
Tprob getTprob(Tptr t) {
Tprob prob = reinterpret_cast(t->data);
if(!prob) {
t->data = prob = tprobs.alloc();
prob->init(this, t);
}
return prob;
}
GluePt newGluePt() {
GluePt glue = glue_pts.alloc();
glue->split_type = false;
return glue;
}
inline IVptr newIsctVert(Eptr e, Tptr t, GluePt glue) {
IVptr iv = ivpool.alloc();
iv->concrete = nullptr;
iv->coord = computeCoords(e, t);
iv->glue_marker = glue;
glue->copies.push_back(iv);
return iv;
}
inline IVptr newIsctVert(Tptr t0, Tptr t1, Tptr t2, GluePt glue) {
IVptr iv = ivpool.alloc();
iv->concrete = nullptr;
iv->coord = computeCoords(t0, t1, t2);
iv->glue_marker = glue;
glue->copies.push_back(iv);
return iv;
}
inline IVptr newSplitIsctVert(Vec3d coords, GluePt glue) {
IVptr iv = ivpool.alloc();
iv->concrete = nullptr;
iv->coord = coords;
iv->glue_marker = glue;
glue->copies.push_back(iv);
return iv;
}
inline IVptr copyIsctVert(IVptr orig) {
IVptr iv = ivpool.alloc();
iv->concrete = nullptr;
iv->coord = orig->coord;
iv->glue_marker = orig->glue_marker;
orig->glue_marker->copies.push_back(iv);
return iv;
}
inline IEptr newIsctEdge(IVptr endpoint, Tptr tri_key) {
IEptr ie = iepool.alloc();
ie->concrete = nullptr;
ie->boundary = false;
ie->ends[0] = endpoint;
endpoint->edges.push_back(ie);
ie->ends[1] = nullptr; // other end null
ie->other_tri_key = tri_key;
return ie;
}
inline OVptr newOrigVert(Vptr v) {
OVptr ov = ovpool.alloc();
ov->concrete = v;
ov->coord = vPos(v);
ov->boundary = true;
return ov;
}
inline OEptr newOrigEdge(Eptr e, OVptr v0, OVptr v1) {
OEptr oe = oepool.alloc();
oe->concrete = e;
oe->boundary = true;
oe->ends[0] = v0;
oe->ends[1] = v1;
v0->edges.push_back(oe);
v1->edges.push_back(oe);
return oe;
}
inline SEptr newSplitEdge(GVptr v0, GVptr v1, bool boundary) {
SEptr se = sepool.alloc();
se->concrete = nullptr;
se->boundary = boundary;
se->ends[0] = v0;
se->ends[1] = v1;
v0->edges.push_back(se);
v1->edges.push_back(se);
return se;
}
inline GTptr newGenericTri(GVptr v0, GVptr v1, GVptr v2) {
GTptr gt = gtpool.alloc();
gt->verts[0] = v0;
gt->verts[1] = v1;
gt->verts[2] = v2;
gt->concrete = nullptr;
return gt;
}
inline void releaseEdge(GEptr ge) {
disconnectGE(ge);
IEptr ie = dynamic_cast(ge);
if(ie) {
iepool.free(ie);
} else {
OEptr oe = dynamic_cast(ge);
ENSURE(oe);
oepool.free(oe);
}
}
inline void killIsctVert(IVptr iv) {
iv->glue_marker->copies.erase(iv);
if(iv->glue_marker->copies.size() == 0)
glue_pts.free(iv->glue_marker);
for(GEptr ge : iv->edges) {
// disconnect
ge->interior.erase(iv);
if(ge->ends[0] == iv) ge->ends[0] = nullptr;
if(ge->ends[1] == iv) ge->ends[1] = nullptr;
}
ivpool.free(iv);
}
inline void killIsctEdge(IEptr ie) {
// an endpoint may be an original vertex
if(ie->ends[1])
ie->ends[1]->edges.erase(ie);
iepool.free(ie);
}
inline void killOrigVert(OVptr ov) {
ovpool.free(ov);
}
inline void killOrigEdge(OEptr oe) {
oepool.free(oe);
}
/*
inline void buildConcreteVert(GVptr gv) {
gv->concrete = newVert();
// NEED DATA SOMEHOW??
}
// make sure the endpoints have concrete versions first!
inline void buildConcreteEdge(GEptr ge) {
Eptr e = ge->concrete = newEdge();
Vptr v0 = e->verts[0] = ge->ends[0]->concrete;
Vptr v1 = e->verts[1] = ge->ends[1]->concrete;
v0->edges.push_back(e);
v1->edges.push_back(e);
}
inline void buildConcreteTri(GVptr gv0, GVptr gv1, GVptr gv2) {
// create edges as necessary...
}*/
bool hasIntersections(); // test for iscts, exit if one is found
bool findIntersections(const double EPSILON = 1e-5); // perturbation epsilon
void resolveAllIntersections();
private:
// if we encounter ambiguous degeneracies, then this
// routine returns false, indicating that the computation aborted.
bool tryToFindIntersections();
// In that case, we can perturb the positions of points
void perturbPositions(const double EPSILON = 1e-5); // perturbation epsilon
// in order to give things another try, discard partial work
void reset();
public:
void dumpIsctPoints(std::vector *points);
void dumpIsctEdges(std::vector< std::pair > *edges);
protected: // DATA
IterPool glue_pts;
IterPool tprobs;
IterPool ivpool;
IterPool ovpool;
IterPool iepool;
IterPool oepool;
IterPool sepool;
IterPool gtpool;
private:
std::vector quantized_coords;
private:
inline void for_edge_tri(std::function);
inline void bvh_edge_tri(std::function);
inline GeomBlob edge_blob(Eptr e);
inline BBox3d bboxFromTptr(Tptr t);
inline BBox3d buildBox(Eptr e) const;
inline BBox3d buildBox(Tptr t) const;
inline void marshallArithmeticInput(Empty3d::TriIn &input, Tptr t) const;
inline void marshallArithmeticInput(Empty3d::EdgeIn &input, Eptr e) const;
inline void marshallArithmeticInput(
Empty3d::TriEdgeIn &input, Eptr e, Tptr t) const;
inline void marshallArithmeticInput(
Empty3d::TriTriTriIn &input, Tptr t0, Tptr t1, Tptr t2) const;
bool checkIsct(Eptr e, Tptr t) const;
bool checkIsct(Tptr t0, Tptr t1, Tptr t2) const;
Vec3d computeCoords(Eptr e, Tptr t) const;
Vec3d computeCoords(Tptr t0, Tptr t1, Tptr t2) const;
void fillOutVertData(GluePt glue, VertData &data);
void fillOutTriData(Tptr tri, Tptr parent);
private:
class EdgeCache;
private: // functions here to get around a GCC bug...
void createRealPtFromGluePt(GluePt glue);
void createRealTriangles(Tprob tprob, EdgeCache &ecache);
};
template inline
void for_pairs(
ShortVec &vec,
std::function func
) {
for(uint i=0; i inline
void Mesh::IsctProblem::for_edge_tri(
std::function func
) {
bool aborted = false;
TopoCache::tris.for_each([&](Tptr t) {
TopoCache::edges.for_each([&](Eptr e) {
if(!aborted) {
if(!func(e, t))
aborted = true;
}
});
});
}
template inline
GeomBlob Mesh::IsctProblem::edge_blob(
Eptr e
) {
GeomBlob blob;
blob.bbox = buildBox(e);
blob.point = (blob.bbox.minp + blob.bbox.maxp) / 2.0;
blob.id = e;
return blob;
}
template inline
void Mesh::IsctProblem::bvh_edge_tri(
std::function func
) {
std::vector< GeomBlob > edge_geoms;
TopoCache::edges.for_each([&](Eptr e) {
edge_geoms.push_back(edge_blob(e));
});
AABVH edgeBVH(edge_geoms);
// use the acceleration structure
bool aborted = false;
TopoCache::tris.for_each([&](Tptr t) {
// compute BBox
BBox3d bbox = buildBox(t);
if(!aborted) {
edgeBVH.for_each_in_box(bbox, [&](Eptr e) {
if(!func(e,t))
aborted = true;
});
}
});
}
template
bool Mesh::IsctProblem::tryToFindIntersections()
{
Empty3d::degeneracy_count = 0;
// Find all edge-triangle intersection points
//for_edge_tri([&](Eptr eisct, Tptr tisct)->bool{
bvh_edge_tri([&](Eptr eisct, Tptr tisct)->bool{
if(checkIsct(eisct,tisct)) {
GluePt glue = newGluePt();
glue->edge_tri_type = true;
glue->e = eisct;
glue->t[0] = tisct;
// first add point and edges to the pierced triangle
IVptr iv = getTprob(tisct)->addInteriorEndpoint(this, eisct, glue);
for(Tptr tri : eisct->tris) {
getTprob(tri)->addBoundaryEndpoint(this, tisct, eisct, iv);
}
}
if(Empty3d::degeneracy_count > 0)
return false; // break
else
return true; // continue
});
if(Empty3d::degeneracy_count > 0) {
return false; // restart / abort
}
// we're going to peek into the triangle problems in order to
// identify potential candidates for Tri-Tri-Tri intersections
std::vector triples;
tprobs.for_each([&](Tprob tprob) {
Tptr t0 = tprob->the_tri;
// Scan pairs of existing edges to create candidate triples
for_pairs(tprob->iedges, [&](IEptr &ie1, IEptr &ie2){
Tptr t1 = ie1->other_tri_key;
Tptr t2 = ie2->other_tri_key;
// This triple might be considered three times,
// one for each triangle it contains.
// To prevent duplication, only proceed if this is
// the least triangle according to an arbitrary ordering
if(t0 < t1 && t0 < t2) {
// now look for the third edge. We're not
// sure if it exists...
Tprob prob1 = reinterpret_cast(t1->data);
for(IEptr ie : prob1->iedges) {
if(ie->other_tri_key == t2) {
// ADD THE TRIPLE
triples.push_back(TriTripleTemp(t0, t1, t2));
}
}
}
});
});
// Now, we've collected a list of Tri-Tri-Tri intersection candidates.
// Check to see if the intersections actually exist.
for(TriTripleTemp t : triples) {
if(!checkIsct(t.t0, t.t1, t.t2)) continue;
// Abort if we encounter a degeneracy
if(Empty3d::degeneracy_count > 0) break;
GluePt glue = newGluePt();
glue->edge_tri_type = false;
glue->t[0] = t.t0;
glue->t[1] = t.t1;
glue->t[2] = t.t2;
getTprob(t.t0)->addInteriorPoint(this, t.t1, t.t2, glue);
getTprob(t.t1)->addInteriorPoint(this, t.t0, t.t2, glue);
getTprob(t.t2)->addInteriorPoint(this, t.t0, t.t1, glue);
}
if(Empty3d::degeneracy_count > 0) {
return false; // restart / abort
}
return true;
}
template
void Mesh::IsctProblem::perturbPositions(const double EPSILON) // perturbation epsilon
{
for(Vec3d &coord : quantized_coords) {
Vec3d perturbation(Quantization::quantize(drand(-EPSILON, EPSILON)),
Quantization::quantize(drand(-EPSILON, EPSILON)),
Quantization::quantize(drand(-EPSILON, EPSILON)));
coord += perturbation;
}
}
template
void Mesh::IsctProblem::reset()
{
// the data pointer in the triangles points to tproblems
// that we're about to destroy,
// so zero out all those pointers first!
tprobs.for_each([](Tprob tprob) {
Tptr t = tprob->the_tri;
t->data = nullptr;
});
glue_pts.clear();
tprobs.clear();
ivpool.clear();
ovpool.clear();
iepool.clear();
oepool.clear();
sepool.clear();
gtpool.clear();
}
template
bool Mesh::IsctProblem::findIntersections(const double EPSILON) // perturbation epsilon
{
int nTrys = 5;
perturbPositions(EPSILON); // always perturb for safety...
while(nTrys > 0) {
if(!tryToFindIntersections()) {
reset();
perturbPositions(EPSILON);
nTrys--;
} else {
break;
}
}
if(nTrys <= 0) {
CORK_ERROR("Ran out of tries to perturb the mesh");
return false;
}
// ok all points put together,
// all triangle problems assembled.
// Some intersection edges may have original vertices as endpoints
// we consolidate the problems to check for cases like these.
tprobs.for_each([&](Tprob tprob) {
tprob->consolidate(this);
});
return true;
}
template
bool Mesh::IsctProblem::hasIntersections()
{
bool foundIsct = false;
Empty3d::degeneracy_count = 0;
// Find some edge-triangle intersection point...
bvh_edge_tri([&](Eptr eisct, Tptr tisct)->bool{
if(checkIsct(eisct,tisct)) {
foundIsct = true;
return false; // break;
}
if(Empty3d::degeneracy_count > 0) {
return false; // break;
}
return true; // continue
});
if(/*Empty3d::degeneracy_count > 0 ||*/ foundIsct) {
std::cout << "This self-intersection might be spurious. "
"Degeneracies were detected." << std::endl;
return true;
} else {
return false;
}
}
template inline
BBox3d Mesh::IsctProblem::buildBox(Eptr e) const
{
Vec3d p0 = vPos(e->verts[0]);
Vec3d p1 = vPos(e->verts[1]);
return BBox3d(min(p0, p1), max(p0, p1));
}
template inline
BBox3d Mesh::IsctProblem::buildBox(Tptr t) const
{
Vec3d p0 = vPos(t->verts[0]);
Vec3d p1 = vPos(t->verts[1]);
Vec3d p2 = vPos(t->verts[2]);
return BBox3d(min(p0, min(p1, p2)), max(p0, max(p1, p2)));
}
template inline
void Mesh::IsctProblem::marshallArithmeticInput(
Empty3d::EdgeIn &input, Eptr e
) const {
input.p[0] = vPos(e->verts[0]);
input.p[1] = vPos(e->verts[1]);
}
template inline
void Mesh::IsctProblem::marshallArithmeticInput(
Empty3d::TriIn &input, Tptr t
) const {
input.p[0] = vPos(t->verts[0]);
input.p[1] = vPos(t->verts[1]);
input.p[2] = vPos(t->verts[2]);
}
template inline
void Mesh::IsctProblem::marshallArithmeticInput(
Empty3d::TriEdgeIn &input,
Eptr e, Tptr t
) const {
marshallArithmeticInput(input.edge, e);
marshallArithmeticInput(input.tri, t);
}
template inline
void Mesh::IsctProblem::marshallArithmeticInput(
Empty3d::TriTriTriIn &input,
Tptr t0, Tptr t1, Tptr t2
) const {
marshallArithmeticInput(input.tri[0], t0);
marshallArithmeticInput(input.tri[1], t1);
marshallArithmeticInput(input.tri[2], t2);
}
template
bool Mesh::IsctProblem::checkIsct(Eptr e, Tptr t) const
{
// simple bounding box cull; for acceleration, not correctness
BBox3d ebox = buildBox(e);
BBox3d tbox = buildBox(t);
if(!hasIsct(ebox, tbox))
return false;
// must check whether the edge and triangle share a vertex
// if so, then trivially we know they intersect in exactly that vertex
// so we discard this case from consideration.
if(hasCommonVert(e, t))
return false;
Empty3d::TriEdgeIn input;
marshallArithmeticInput(input, e, t);
//bool empty = Empty3d::isEmpty(input);
bool empty = Empty3d::emptyExact(input);
return !empty;
}
template
bool Mesh::IsctProblem::checkIsct(
Tptr t0, Tptr t1, Tptr t2
) const {
// This function should only be called if we've already
// identified that the intersection edges
// (t0,t1), (t0,t2), (t1,t2)
// exist.
// From this, we can conclude that each pair of triangles
// shares no more than a single vertex in common.
// If each of these shared vertices is different from each other,
// then we could legitimately have a triple intersection point,
// but if all three pairs share the same vertex in common, then
// the intersection of the three triangles must be that vertex.
// So, we must check for such a single vertex in common amongst
// the three triangles
Vptr common = commonVert(t0, t1);
if(common) {
for(uint i=0; i<3; i++)
if(common == t2->verts[i])
return false;
}
Empty3d::TriTriTriIn input;
marshallArithmeticInput(input, t0, t1, t2);
//bool empty = Empty3d::isEmpty(input);
bool empty = Empty3d::emptyExact(input);
return !empty;
}
template
Vec3d Mesh::IsctProblem::computeCoords(Eptr e, Tptr t) const
{
Empty3d::TriEdgeIn input;
marshallArithmeticInput(input, e, t);
Vec3d coords = Empty3d::coordsExact(input);
return coords;
}
template
Vec3d Mesh::IsctProblem::computeCoords(
Tptr t0, Tptr t1, Tptr t2
) const {
Empty3d::TriTriTriIn input;
marshallArithmeticInput(input, t0, t1, t2);
Vec3d coords = Empty3d::coordsExact(input);
return coords;
}
template
void Mesh::IsctProblem::fillOutVertData(
GluePt glue, VertData &data
) {
if(glue->split_type) { // manually inserted split point
uint v0i = glue->e->verts[0]->ref;
uint v1i = glue->e->verts[1]->ref;
data.isctInterpolate(TopoCache::mesh->verts[v0i],
TopoCache::mesh->verts[v1i]);
} else
if(glue->edge_tri_type) { // edge-tri type
IsctVertEdgeTriInput input;
for(uint k=0; k<2; k++) {
uint vid = glue->e->verts[k]->ref;
input.e[k] = &(TopoCache::mesh->verts[vid]);
}
for(uint k=0; k<3; k++) {
uint vid = glue->t[0]->verts[k]->ref;
input.t[k] = &(TopoCache::mesh->verts[vid]);
}
data.isct(input);
} else { // tri-tri-tri type
IsctVertTriTriTriInput input;
for(uint i=0; i<3; i++) {
for(uint j=0; j<3; j++) {
uint vid = glue->t[i]->verts[j]->ref;
input.t[i][j] = &(TopoCache::mesh->verts[vid]);
}}
data.isct(input);
}
}
template inline
void Mesh::subdivide_tri(
uint t_piece_ref, uint t_parent_ref
) {
SubdivideTriInput input;
input.pt = &(tris[t_parent_ref].data);
for(uint k=0; k<3; k++) {
input.pv[k] = &(verts[tris[t_parent_ref].v[k]]);
input.v[k] = &(verts[tris[t_piece_ref].v[k]]);
}
tris[t_piece_ref].data.subdivide(input);
}
template
void Mesh::IsctProblem::fillOutTriData(
Tptr piece, Tptr parent
) {
TopoCache::mesh->subdivide_tri(piece->ref, parent->ref);
}
template
class Mesh::IsctProblem::EdgeCache
{
public:
EdgeCache(IsctProblem *ip) : iprob(ip), edges(ip->mesh->verts.size()) {}
Eptr operator()(Vptr v0, Vptr v1) {
uint i = v0->ref;
uint j = v1->ref;
if(i > j) std::swap(i,j);
uint N = edges[i].size();
for(uint k=0; knewEdge();
e->verts[0] = v0;
e->verts[1] = v1;
v0->edges.push_back(e);
v1->edges.push_back(e);
return e;
}
// k = 0, 1, or 2
Eptr getTriangleEdge(GTptr gt, uint k, Tptr big_tri)
{
GVptr gv0 = gt->verts[(k+1)%3];
GVptr gv1 = gt->verts[(k+2)%3];
Vptr v0 = gv0->concrete;
Vptr v1 = gv1->concrete;
// if neither of these are intersection points,
// then this is a pre-existing edge...
Eptr e = nullptr;
if(typeid(gv0) == typeid(OVptr) &&
typeid(gv1) == typeid(OVptr)
) {
// search through edges of original triangle...
for(uint c=0; c<3; c++) {
Vptr corner0 = big_tri->verts[(c+1)%3];
Vptr corner1 = big_tri->verts[(c+2)%3];
if((corner0 == v0 && corner1 == v1) ||
(corner0 == v1 && corner1 == v0)) {
e = big_tri->edges[c];
}
}
ENSURE(e); // Yell if we didn't find an edge
}
// otherwise, we need to check the cache to find this edge
else
{
e = operator()(v0, v1);
}
return e;
}
Eptr maybeEdge(GEptr ge)
{
uint i = ge->ends[0]->concrete->ref;
uint j = ge->ends[1]->concrete->ref;
if(i > j) std::swap(i,j);
uint N = edges[i].size();
for(uint k=0; k > edges;
};
template
void Mesh::IsctProblem::createRealPtFromGluePt(GluePt glue) {
ENSURE(glue->copies.size() > 0);
Vptr v = TopoCache::newVert();
VertData &data = TopoCache::mesh->verts[v->ref];
data.pos = glue->copies[0]->coord;
fillOutVertData(glue, data);
for(IVptr iv : glue->copies)
iv->concrete = v;
}
template
void Mesh::IsctProblem::createRealTriangles(
Tprob tprob, EdgeCache &ecache
) {
for(GTptr gt : tprob->gtris) {
Tptr t = TopoCache::newTri();
gt->concrete = t;
Tri &tri = TopoCache::mesh->tris[t->ref];
for(uint k=0; k<3; k++) {
Vptr v = gt->verts[k]->concrete;
t->verts[k] = v;
v->tris.push_back(t);
tri.v[k] = v->ref;
Eptr e = ecache.getTriangleEdge(gt, k, tprob->the_tri);
e->tris.push_back(t);
t->edges[k] = e;
}
fillOutTriData(t, tprob->the_tri);
}
// Once all the pieces are hooked up, let's kill the old triangle!
TopoCache::deleteTri(tprob->the_tri);
}
template
void Mesh::IsctProblem::resolveAllIntersections()
{
// solve a subdivision problem in each triangle
tprobs.for_each([&](Tprob tprob) {
tprob->subdivide(this);
});
// now we have diced up triangles inside each triangle problem
// Let's go through the glue points and create a new concrete
// vertex object for each of these.
glue_pts.for_each([&](GluePt glue) {
createRealPtFromGluePt(glue);
});
EdgeCache ecache(this);
// Now that we have concrete vertices plugged in, we can
// go through the diced triangle pieces and create concrete triangles
// for each of those.
// Along the way, let's go ahead and hook up edges as appropriate
tprobs.for_each([&](Tprob tprob) {
createRealTriangles(tprob, ecache);
});
// mark all edges as normal by zero-ing out the data pointer
TopoCache::edges.for_each([](Eptr e) {
e->data = 0;
});
// then iterate over the edges formed by intersections
// (i.e. those edges without the boundary flag set in each triangle)
// and mark those by setting the data pointer
iepool.for_each([&](IEptr ie) {
// every ie must be non-boundary
Eptr e = ecache.maybeEdge(ie);
ENSURE(e);
e->data = (void*)1;
});
sepool.for_each([&](SEptr se) {
//if(se->boundary) return; // continue
Eptr e = ecache.maybeEdge(se);
ENSURE(e);
e->data = (void*)1;
});
// This basically takes care of everything EXCEPT one detail
// *) The base mesh data structures still need to be compacted
// This detail should be handled by the calling code...
}
template
void Mesh::IsctProblem::dumpIsctPoints(
std::vector *points
) {
points->resize(glue_pts.size());
uint write = 0;
glue_pts.for_each([&](GluePt glue) {
ENSURE(glue->copies.size() > 0);
IVptr iv = glue->copies[0];
(*points)[write] = iv->coord;
write++;
});
}
template
void Mesh::IsctProblem::dumpIsctEdges(
std::vector< std::pair > *edges
) {
edges->clear();
tprobs.for_each([&](Tprob tprob) {
for(IEptr ie : tprob->iedges) {
GVptr gv0 = ie->ends[0];
GVptr gv1 = ie->ends[1];
edges->push_back(std::make_pair(gv0->coord, gv1->coord));
}
});
}
template
bool Mesh::testingComputeStaticIsctPoints(
std::vector *points,
const double EPSILON
) {
IsctProblem iproblem(this);
if(!iproblem.findIntersections(EPSILON))
return false;
iproblem.dumpIsctPoints(points);
return true;
}
template
bool Mesh::testingComputeStaticIsct(
std::vector *points,
std::vector< std::pair > *edges,
const double EPSILON
) {
IsctProblem iproblem(this);
if(!iproblem.findIntersections(EPSILON))
return false;
iproblem.dumpIsctPoints(points);
iproblem.dumpIsctEdges(edges);
return true;
}
template
bool Mesh::resolveIntersections(const double EPSILON)
{
IsctProblem iproblem(this);
if(!iproblem.findIntersections(EPSILON))
return false;
iproblem.resolveAllIntersections();
iproblem.commit();
//iproblem.print();
return true;
}
template
bool Mesh::isSelfIntersecting()
{
IsctProblem iproblem(this);
return iproblem.hasIntersections();
}