Add O(n*log(n)) self-intersection check for polygons. That was
previously a major bottleneck, and is now at least 10x faster for a practical section. And fix a horrible uninit memory bug in the triangle kd-tree stuff; the split planes were apparently random. This would have slowed things down, but not caused an incorrect result; except when it ends up NaN, which is the reason I noticed. [git-p4: depot-paths = "//depot/solvespace/": change = 2073]solver
parent
f3eeae673f
commit
61da5969f6
25
mesh.cpp
25
mesh.cpp
|
@ -347,8 +347,6 @@ DWORD SMesh::FirstIntersectionWith(Point2d mp) {
|
|||
return face;
|
||||
}
|
||||
|
||||
#define KDTREE_EPS (20*LENGTH_EPS) // nice and sloppy
|
||||
|
||||
STriangleLl *STriangleLl::Alloc(void)
|
||||
{ return (STriangleLl *)AllocTemporary(sizeof(STriangleLl)); }
|
||||
SKdNode *SKdNode::Alloc(void)
|
||||
|
@ -378,18 +376,21 @@ SKdNode *SKdNode::From(SMesh *m) {
|
|||
tll = tn;
|
||||
}
|
||||
|
||||
return SKdNode::From(tll, 0);
|
||||
return SKdNode::From(tll);
|
||||
}
|
||||
|
||||
SKdNode *SKdNode::From(STriangleLl *tll, int which) {
|
||||
SKdNode *SKdNode::From(STriangleLl *tll) {
|
||||
int which = 0;
|
||||
SKdNode *ret = Alloc();
|
||||
|
||||
if(!tll) goto leaf;
|
||||
if(!tll) {
|
||||
goto leaf;
|
||||
}
|
||||
|
||||
int i;
|
||||
int gtc[3] = { 0, 0, 0 }, ltc[3] = { 0, 0, 0 }, allc = 0;
|
||||
double badness[3];
|
||||
double split[3];
|
||||
double badness[3] = { 0, 0, 0 };
|
||||
double split[3] = { 0, 0, 0 };
|
||||
for(i = 0; i < 3; i++) {
|
||||
int tcnt = 0;
|
||||
STriangleLl *ll;
|
||||
|
@ -433,8 +434,9 @@ SKdNode *SKdNode::From(STriangleLl *tll, int which) {
|
|||
which = 2;
|
||||
}
|
||||
|
||||
if(allc < 10) goto leaf;
|
||||
if(allc == gtc[which] || allc == ltc[which]) goto leaf;
|
||||
if(allc < 3 || allc == gtc[which] || allc == ltc[which]) {
|
||||
goto leaf;
|
||||
}
|
||||
|
||||
STriangleLl *ll;
|
||||
STriangleLl *lgt = NULL, *llt = NULL;
|
||||
|
@ -467,12 +469,11 @@ SKdNode *SKdNode::From(STriangleLl *tll, int which) {
|
|||
|
||||
ret->which = which;
|
||||
ret->c = split[which];
|
||||
ret->gt = SKdNode::From(lgt, (which + 1) % 3);
|
||||
ret->lt = SKdNode::From(llt, (which + 1) % 3);
|
||||
ret->gt = SKdNode::From(lgt);
|
||||
ret->lt = SKdNode::From(llt);
|
||||
return ret;
|
||||
|
||||
leaf:
|
||||
// dbp("leaf: allc=%d gtc=%d ltc=%d which=%d", allc, gtc[which], ltc[which], which);
|
||||
ret->tris = tll;
|
||||
return ret;
|
||||
}
|
||||
|
|
276
polygon.cpp
276
polygon.cpp
|
@ -57,6 +57,75 @@ SEdge SEdge::From(Vector a, Vector b) {
|
|||
return se;
|
||||
}
|
||||
|
||||
bool SEdge::EdgeCrosses(Vector ea, Vector eb, Vector *ppi, SPointList *spl) {
|
||||
Vector d = eb.Minus(ea);
|
||||
double t_eps = LENGTH_EPS/d.Magnitude();
|
||||
|
||||
double dist_a, dist_b;
|
||||
double t, tthis;
|
||||
bool skew;
|
||||
Vector pi;
|
||||
bool inOrEdge0, inOrEdge1;
|
||||
|
||||
Vector dthis = b.Minus(a);
|
||||
double tthis_eps = LENGTH_EPS/dthis.Magnitude();
|
||||
|
||||
if(ea.Equals(a) && eb.Equals(b)) return true;
|
||||
if(eb.Equals(a) && ea.Equals(b)) return true;
|
||||
|
||||
dist_a = a.DistanceToLine(ea, d),
|
||||
dist_b = b.DistanceToLine(ea, d);
|
||||
|
||||
// Can't just test if dist_a equals dist_b; they could be on opposite
|
||||
// sides, since it's unsigned.
|
||||
double m = sqrt(d.Magnitude()*dthis.Magnitude());
|
||||
if(sqrt(fabs(d.Dot(dthis))) > (m - LENGTH_EPS)) {
|
||||
// The edges are parallel.
|
||||
if(fabs(dist_a) > LENGTH_EPS) {
|
||||
// and not coincident, so can't be interesecting
|
||||
return false;
|
||||
}
|
||||
// The edges are coincident. Make sure that neither endpoint lies
|
||||
// on the other
|
||||
double t;
|
||||
t = a.Minus(ea).DivPivoting(d);
|
||||
if(t > t_eps && t < (1 - t_eps)) return true;
|
||||
t = b.Minus(ea).DivPivoting(d);
|
||||
if(t > t_eps && t < (1 - t_eps)) return true;
|
||||
t = ea.Minus(a).DivPivoting(dthis);
|
||||
if(t > tthis_eps && t < (1 - tthis_eps)) return true;
|
||||
t = eb.Minus(a).DivPivoting(dthis);
|
||||
if(t > tthis_eps && t < (1 - tthis_eps)) return true;
|
||||
// So coincident but disjoint, okay.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Lines are not parallel, so look for an intersection.
|
||||
pi = Vector::AtIntersectionOfLines(ea, eb, a, b,
|
||||
&skew,
|
||||
&t, &tthis);
|
||||
if(skew) return false;
|
||||
|
||||
inOrEdge0 = (t > -t_eps) && (t < (1 + t_eps));
|
||||
inOrEdge1 = (tthis > -tthis_eps) && (tthis < (1 + tthis_eps));
|
||||
|
||||
if(inOrEdge0 && inOrEdge1) {
|
||||
if(a.Equals(ea) || b.Equals(ea) ||
|
||||
a.Equals(eb) || b.Equals(eb))
|
||||
{
|
||||
// Not an intersection if we share an endpoint with an edge
|
||||
return false;
|
||||
}
|
||||
// But it's an intersection if a vertex of one edge lies on the
|
||||
// inside of the other (or if they cross away from either's
|
||||
// vertex).
|
||||
if(ppi) *ppi = pi;
|
||||
if(spl) spl->Add(pi);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SEdgeList::Clear(void) {
|
||||
l.Clear();
|
||||
}
|
||||
|
@ -151,79 +220,12 @@ bool SEdgeList::AssemblePolygon(SPolygon *dest, SEdge *errorAt, bool keepDir) {
|
|||
int SEdgeList::AnyEdgeCrossings(Vector a, Vector b,
|
||||
Vector *ppi, SPointList *spl)
|
||||
{
|
||||
Vector d = b.Minus(a);
|
||||
double t_eps = LENGTH_EPS/d.Magnitude();
|
||||
|
||||
int cnt = 0;
|
||||
SEdge *se;
|
||||
for(se = l.First(); se; se = l.NextAfter(se)) {
|
||||
double dist_a, dist_b;
|
||||
double t, tse;
|
||||
bool skew;
|
||||
Vector pi;
|
||||
bool inOrEdge0, inOrEdge1;
|
||||
|
||||
Vector dse = (se->b).Minus(se->a);
|
||||
double tse_eps = LENGTH_EPS/dse.Magnitude();
|
||||
|
||||
if(a.Equals(se->a) && b.Equals(se->b)) goto intersects;
|
||||
if(b.Equals(se->a) && a.Equals(se->b)) goto intersects;
|
||||
|
||||
dist_a = (se->a).DistanceToLine(a, d),
|
||||
dist_b = (se->b).DistanceToLine(a, d);
|
||||
|
||||
// Can't just test if dist_a equals dist_b; they could be on opposite
|
||||
// sides, since it's unsigned.
|
||||
double m = sqrt(d.Magnitude()*dse.Magnitude());
|
||||
if(sqrt(fabs(d.Dot(dse))) > (m - LENGTH_EPS)) {
|
||||
// The edges are parallel.
|
||||
if(fabs(dist_a) > LENGTH_EPS) {
|
||||
// and not coincident, so can't be interesecting
|
||||
continue;
|
||||
}
|
||||
// The edges are coincident. Make sure that neither endpoint lies
|
||||
// on the other
|
||||
double t;
|
||||
t = ((se->a).Minus(a)).DivPivoting(d);
|
||||
if(t > t_eps && t < (1 - t_eps)) goto intersects;
|
||||
t = ((se->b).Minus(a)).DivPivoting(d);
|
||||
if(t > t_eps && t < (1 - t_eps)) goto intersects;
|
||||
t = a.Minus(se->a).DivPivoting(dse);
|
||||
if(t > tse_eps && t < (1 - tse_eps)) goto intersects;
|
||||
t = b.Minus(se->a).DivPivoting(dse);
|
||||
if(t > tse_eps && t < (1 - tse_eps)) goto intersects;
|
||||
// So coincident but disjoint, okay.
|
||||
continue;
|
||||
if(se->EdgeCrosses(a, b, ppi, spl)) {
|
||||
cnt++;
|
||||
}
|
||||
|
||||
// Lines are not parallel, so look for an intersection.
|
||||
pi = Vector::AtIntersectionOfLines(a, b, se->a, se->b,
|
||||
&skew,
|
||||
&t, &tse);
|
||||
if(skew) continue;
|
||||
|
||||
inOrEdge0 = (t > -t_eps) && (t < (1 + t_eps));
|
||||
inOrEdge1 = (tse > -tse_eps) && (tse < (1 + tse_eps));
|
||||
|
||||
if(inOrEdge0 && inOrEdge1) {
|
||||
if((se->a).Equals(a) || (se->b).Equals(a) ||
|
||||
(se->a).Equals(b) || (se->b).Equals(b))
|
||||
{
|
||||
// Not an intersection if we share an endpoint with an edge
|
||||
continue;
|
||||
}
|
||||
// But it's an intersection if a vertex of one edge lies on the
|
||||
// inside of the other (or if they cross away from either's
|
||||
// vertex).
|
||||
if(ppi) *ppi = pi;
|
||||
if(spl) spl->Add(pi);
|
||||
goto intersects;
|
||||
}
|
||||
continue;
|
||||
|
||||
intersects:
|
||||
cnt++;
|
||||
// and continue with the loop
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
|
@ -273,6 +275,134 @@ void SEdgeList::CullExtraneousEdges(void) {
|
|||
l.RemoveTagged();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Make a kd-tree of edges. This is used for O(log(n)) implementations of stuff
|
||||
// that would naively be O(n).
|
||||
//-----------------------------------------------------------------------------
|
||||
SKdNodeEdges *SKdNodeEdges::Alloc(void) {
|
||||
return (SKdNodeEdges *)AllocTemporary(sizeof(SKdNodeEdges));
|
||||
}
|
||||
SEdgeLl *SEdgeLl::Alloc(void) {
|
||||
return (SEdgeLl *)AllocTemporary(sizeof(SEdgeLl));
|
||||
}
|
||||
SKdNodeEdges *SKdNodeEdges::From(SEdgeList *sel) {
|
||||
SEdgeLl *sell = NULL;
|
||||
SEdge *se;
|
||||
for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) {
|
||||
SEdgeLl *n = SEdgeLl::Alloc();
|
||||
n->se = se;
|
||||
n->next = sell;
|
||||
sell = n;
|
||||
}
|
||||
return SKdNodeEdges::From(sell);
|
||||
}
|
||||
SKdNodeEdges *SKdNodeEdges::From(SEdgeLl *sell) {
|
||||
SKdNodeEdges *n = SKdNodeEdges::Alloc();
|
||||
|
||||
// Compute the midpoints (just mean, though median would be better) of
|
||||
// each component.
|
||||
Vector ptAve = Vector::From(0, 0, 0);
|
||||
SEdgeLl *flip;
|
||||
int totaln = 0;
|
||||
for(flip = sell; flip; flip = flip->next) {
|
||||
ptAve = ptAve.Plus(flip->se->a);
|
||||
ptAve = ptAve.Plus(flip->se->b);
|
||||
totaln++;
|
||||
}
|
||||
ptAve = ptAve.ScaledBy(1.0 / (2*totaln));
|
||||
|
||||
// For each component, see how it splits.
|
||||
int ltln[3] = { 0, 0, 0 }, gtln[3] = { 0, 0, 0 };
|
||||
double badness[3];
|
||||
for(flip = sell; flip; flip = flip->next) {
|
||||
for(int i = 0; i < 3; i++) {
|
||||
if(flip->se->a.Element(i) < ptAve.Element(i) + KDTREE_EPS ||
|
||||
flip->se->b.Element(i) < ptAve.Element(i) + KDTREE_EPS)
|
||||
{
|
||||
ltln[i]++;
|
||||
}
|
||||
if(flip->se->a.Element(i) > ptAve.Element(i) - KDTREE_EPS ||
|
||||
flip->se->b.Element(i) > ptAve.Element(i) - KDTREE_EPS)
|
||||
{
|
||||
gtln[i]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
for(int i = 0; i < 3; i++) {
|
||||
badness[i] = pow((double)ltln[i], 4) + pow((double)gtln[i], 4);
|
||||
}
|
||||
|
||||
// Choose the least bad coordinate to split along.
|
||||
if(badness[0] < badness[1] && badness[0] < badness[2]) {
|
||||
n->which = 0;
|
||||
} else if(badness[1] < badness[2]) {
|
||||
n->which = 1;
|
||||
} else {
|
||||
n->which = 2;
|
||||
}
|
||||
n->c = ptAve.Element(n->which);
|
||||
|
||||
if(totaln < 3 || totaln == gtln[n->which] || totaln == ltln[n->which]) {
|
||||
n->edges = sell;
|
||||
// and we're a leaf node
|
||||
return n;
|
||||
}
|
||||
|
||||
// Sort the edges according to which side(s) of the split plane they're on.
|
||||
SEdgeLl *gtl = NULL, *ltl = NULL;
|
||||
for(flip = sell; flip; flip = flip->next) {
|
||||
if(flip->se->a.Element(n->which) < n->c + KDTREE_EPS ||
|
||||
flip->se->b.Element(n->which) < n->c + KDTREE_EPS)
|
||||
{
|
||||
SEdgeLl *selln = SEdgeLl::Alloc();
|
||||
selln->se = flip->se;
|
||||
selln->next = ltl;
|
||||
ltl = selln;
|
||||
}
|
||||
if(flip->se->a.Element(n->which) > n->c - KDTREE_EPS ||
|
||||
flip->se->b.Element(n->which) > n->c - KDTREE_EPS)
|
||||
{
|
||||
SEdgeLl *selln = SEdgeLl::Alloc();
|
||||
selln->se = flip->se;
|
||||
selln->next = gtl;
|
||||
gtl = selln;
|
||||
}
|
||||
}
|
||||
|
||||
n->lt = SKdNodeEdges::From(ltl);
|
||||
n->gt = SKdNodeEdges::From(gtl);
|
||||
return n;
|
||||
}
|
||||
|
||||
int SKdNodeEdges::AnyEdgeCrossings(Vector a, Vector b, int cnt,
|
||||
Vector *pi, SPointList *spl)
|
||||
{
|
||||
int inters = 0;
|
||||
if(gt && lt) {
|
||||
if(a.Element(which) < c + KDTREE_EPS ||
|
||||
b.Element(which) < c + KDTREE_EPS)
|
||||
{
|
||||
inters += lt->AnyEdgeCrossings(a, b, cnt, pi, spl);
|
||||
}
|
||||
if(a.Element(which) > c - KDTREE_EPS ||
|
||||
b.Element(which) > c - KDTREE_EPS)
|
||||
{
|
||||
inters += gt->AnyEdgeCrossings(a, b, cnt, pi, spl);
|
||||
}
|
||||
} else {
|
||||
SEdgeLl *sell;
|
||||
for(sell = edges; sell; sell = sell->next) {
|
||||
SEdge *se = sell->se;
|
||||
if(se->tag == cnt) continue;
|
||||
if(se->EdgeCrosses(a, b, pi, spl)) {
|
||||
inters++;
|
||||
}
|
||||
se->tag = cnt;
|
||||
}
|
||||
}
|
||||
return inters;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// We have an edge list that contains only collinear edges, maybe with more
|
||||
// splits than necessary. Merge any collinear segments that join.
|
||||
|
@ -541,16 +671,22 @@ bool SPolygon::SelfIntersecting(Vector *intersectsAt) {
|
|||
SEdgeList el;
|
||||
ZERO(&el);
|
||||
MakeEdgesInto(&el);
|
||||
SKdNodeEdges *kdtree = SKdNodeEdges::From(&el);
|
||||
|
||||
int cnt = 1;
|
||||
el.l.ClearTags();
|
||||
|
||||
bool ret = false;
|
||||
SEdge *se;
|
||||
for(se = el.l.First(); se; se = el.l.NextAfter(se)) {
|
||||
int inters = el.AnyEdgeCrossings(se->a, se->b, intersectsAt);
|
||||
int inters = kdtree->AnyEdgeCrossings(se->a, se->b, cnt, intersectsAt);
|
||||
if(inters != 1) {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
cnt++;
|
||||
}
|
||||
|
||||
el.Clear();
|
||||
return ret;
|
||||
}
|
||||
|
|
38
polygon.h
38
polygon.h
|
@ -15,6 +15,7 @@ public:
|
|||
Vector a, b;
|
||||
|
||||
static SEdge From(Vector a, Vector b);
|
||||
bool EdgeCrosses(Vector a, Vector b, Vector *pi=NULL, SPointList *spl=NULL);
|
||||
};
|
||||
|
||||
class SEdgeList {
|
||||
|
@ -34,6 +35,35 @@ public:
|
|||
void MergeCollinearSegments(Vector a, Vector b);
|
||||
};
|
||||
|
||||
// A kd-tree element needs to go on a side of a node if it's when KDTREE_EPS
|
||||
// of the boundary. So increasing this number never breaks anything, but may
|
||||
// result in more duplicated elements. So it's conservative to be sloppy here.
|
||||
#define KDTREE_EPS (20*LENGTH_EPS)
|
||||
|
||||
class SEdgeLl {
|
||||
public:
|
||||
SEdge *se;
|
||||
SEdgeLl *next;
|
||||
|
||||
static SEdgeLl *Alloc(void);
|
||||
};
|
||||
|
||||
class SKdNodeEdges {
|
||||
public:
|
||||
int which; // whether c is x, y, or z
|
||||
double c;
|
||||
SKdNodeEdges *gt;
|
||||
SKdNodeEdges *lt;
|
||||
|
||||
SEdgeLl *edges;
|
||||
|
||||
static SKdNodeEdges *From(SEdgeList *sel);
|
||||
static SKdNodeEdges *From(SEdgeLl *sell);
|
||||
static SKdNodeEdges *Alloc(void);
|
||||
int AnyEdgeCrossings(Vector a, Vector b, int cnt,
|
||||
Vector *pi=NULL, SPointList *spl=NULL);
|
||||
};
|
||||
|
||||
class SPoint {
|
||||
public:
|
||||
int tag;
|
||||
|
@ -219,7 +249,6 @@ public:
|
|||
// A linked list of triangles
|
||||
class STriangleLl {
|
||||
public:
|
||||
int tag;
|
||||
STriangle *tri;
|
||||
|
||||
STriangleLl *next;
|
||||
|
@ -229,10 +258,7 @@ public:
|
|||
|
||||
class SKdNode {
|
||||
public:
|
||||
static const int BY_X = 0;
|
||||
static const int BY_Y = 1;
|
||||
static const int BY_Z = 2;
|
||||
int which;
|
||||
int which; // whether c is x, y, or z
|
||||
double c;
|
||||
|
||||
SKdNode *gt;
|
||||
|
@ -242,7 +268,7 @@ public:
|
|||
|
||||
static SKdNode *Alloc(void);
|
||||
static SKdNode *From(SMesh *m);
|
||||
static SKdNode *From(STriangleLl *tll, int which);
|
||||
static SKdNode *From(STriangleLl *tll);
|
||||
|
||||
void AddTriangle(STriangle *tr);
|
||||
void MakeMeshInto(SMesh *m);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
|
||||
copy and paste
|
||||
associative entities from solid model, as a special group
|
||||
n*log(n) intersection finding
|
||||
spline splitting
|
||||
|
||||
-----
|
||||
associative entities from solid model, as a special group
|
||||
some kind of import
|
||||
faster triangulation
|
||||
loop detection
|
||||
|
|
Loading…
Reference in New Issue