575 lines
16 KiB
C++
575 lines
16 KiB
C++
#include "solvespace.h"
|
|
|
|
void SMesh::Clear(void) {
|
|
l.Clear();
|
|
}
|
|
|
|
void SMesh::AddTriangle(STriMeta meta, Vector n, Vector a, Vector b, Vector c) {
|
|
Vector ab = b.Minus(a), bc = c.Minus(b);
|
|
Vector np = ab.Cross(bc);
|
|
if(np.Magnitude() < 1e-10) {
|
|
// ugh; gl sometimes tesselates to collinear triangles
|
|
return;
|
|
}
|
|
if(np.Dot(n) > 0) {
|
|
AddTriangle(meta, a, b, c);
|
|
} else {
|
|
AddTriangle(meta, c, b, a);
|
|
}
|
|
}
|
|
void SMesh::AddTriangle(STriMeta meta, Vector a, Vector b, Vector c) {
|
|
STriangle t; ZERO(&t);
|
|
t.meta = meta;
|
|
t.a = a;
|
|
t.b = b;
|
|
t.c = c;
|
|
AddTriangle(&t);
|
|
}
|
|
void SMesh::AddTriangle(STriangle *st) {
|
|
l.Add(st);
|
|
}
|
|
|
|
void SMesh::DoBounding(Vector v, Vector *vmax, Vector *vmin) {
|
|
vmax->x = max(vmax->x, v.x);
|
|
vmax->y = max(vmax->y, v.y);
|
|
vmax->z = max(vmax->z, v.z);
|
|
|
|
vmin->x = min(vmin->x, v.x);
|
|
vmin->y = min(vmin->y, v.y);
|
|
vmin->z = min(vmin->z, v.z);
|
|
}
|
|
void SMesh::GetBounding(Vector *vmax, Vector *vmin) {
|
|
int i;
|
|
*vmin = Vector::From( 1e12, 1e12, 1e12);
|
|
*vmax = Vector::From(-1e12, -1e12, -1e12);
|
|
for(i = 0; i < l.n; i++) {
|
|
STriangle *st = &(l.elem[i]);
|
|
DoBounding(st->a, vmax, vmin);
|
|
DoBounding(st->b, vmax, vmin);
|
|
DoBounding(st->c, vmax, vmin);
|
|
}
|
|
}
|
|
|
|
void SMesh::Simplify(int start) {
|
|
int maxTriangles = (l.n - start) + 10;
|
|
|
|
STriMeta meta = l.elem[start].meta;
|
|
|
|
STriangle *tout = (STriangle *)AllocTemporary(maxTriangles*sizeof(*tout));
|
|
int toutc = 0;
|
|
|
|
Vector n, *conv = (Vector *)AllocTemporary(maxTriangles*3*sizeof(*conv));
|
|
int convc = 0;
|
|
|
|
int start0 = start;
|
|
|
|
int i, j;
|
|
for(i = start; i < l.n; i++) {
|
|
STriangle *tr = &(l.elem[i]);
|
|
if(tr->MinAltitude() < LENGTH_EPS) {
|
|
tr->tag = 1;
|
|
} else {
|
|
tr->tag = 0;
|
|
}
|
|
}
|
|
|
|
for(;;) {
|
|
bool didAdd;
|
|
convc = 0;
|
|
for(i = start; i < l.n; i++) {
|
|
STriangle *tr = &(l.elem[i]);
|
|
if(tr->tag) continue;
|
|
|
|
tr->tag = 1;
|
|
n = (tr->Normal()).WithMagnitude(1);
|
|
conv[convc++] = tr->a;
|
|
conv[convc++] = tr->b;
|
|
conv[convc++] = tr->c;
|
|
|
|
start = i+1;
|
|
break;
|
|
}
|
|
if(i >= l.n) break;
|
|
|
|
do {
|
|
didAdd = false;
|
|
|
|
for(j = 0; j < convc; j++) {
|
|
Vector a = conv[WRAP((j-1), convc)],
|
|
b = conv[j],
|
|
d = conv[WRAP((j+1), convc)],
|
|
e = conv[WRAP((j+2), convc)];
|
|
|
|
Vector c;
|
|
for(i = start; i < l.n; i++) {
|
|
STriangle *tr = &(l.elem[i]);
|
|
if(tr->tag) continue;
|
|
|
|
if((tr->a).Equals(d) && (tr->b).Equals(b)) {
|
|
c = tr->c;
|
|
} else if((tr->b).Equals(d) && (tr->c).Equals(b)) {
|
|
c = tr->a;
|
|
} else if((tr->c).Equals(d) && (tr->a).Equals(b)) {
|
|
c = tr->b;
|
|
} else {
|
|
continue;
|
|
}
|
|
// The vertex at C must be convex; but the others must
|
|
// be tested
|
|
Vector ab = b.Minus(a);
|
|
Vector bc = c.Minus(b);
|
|
Vector cd = d.Minus(c);
|
|
Vector de = e.Minus(d);
|
|
|
|
double bDot = (ab.Cross(bc)).Dot(n);
|
|
double dDot = (cd.Cross(de)).Dot(n);
|
|
|
|
bDot /= min(ab.Magnitude(), bc.Magnitude());
|
|
dDot /= min(cd.Magnitude(), de.Magnitude());
|
|
|
|
if(fabs(bDot) < LENGTH_EPS && fabs(dDot) < LENGTH_EPS) {
|
|
conv[WRAP((j+1), convc)] = c;
|
|
// and remove the vertex at j, which is a dup
|
|
memmove(conv+j, conv+j+1,
|
|
(convc - j - 1)*sizeof(conv[0]));
|
|
convc--;
|
|
} else if(fabs(bDot) < LENGTH_EPS && dDot > 0) {
|
|
conv[j] = c;
|
|
} else if(fabs(dDot) < LENGTH_EPS && bDot > 0) {
|
|
conv[WRAP((j+1), convc)] = c;
|
|
} else if(bDot > 0 && dDot > 0) {
|
|
// conv[j] is unchanged, conv[j+1] goes to [j+2]
|
|
memmove(conv+j+2, conv+j+1,
|
|
(convc - j - 1)*sizeof(conv[0]));
|
|
conv[j+1] = c;
|
|
convc++;
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
didAdd = true;
|
|
tr->tag = 1;
|
|
break;
|
|
}
|
|
}
|
|
} while(didAdd);
|
|
|
|
// I need to debug why this is required; sometimes the above code
|
|
// still generates a convex polygon
|
|
for(i = 0; i < convc; i++) {
|
|
Vector a = conv[WRAP((i-1), convc)],
|
|
b = conv[i],
|
|
c = conv[WRAP((i+1), convc)];
|
|
Vector ab = b.Minus(a);
|
|
Vector bc = c.Minus(b);
|
|
double bDot = (ab.Cross(bc)).Dot(n);
|
|
bDot /= min(ab.Magnitude(), bc.Magnitude());
|
|
|
|
if(bDot < 0) return; // XXX, shouldn't happen
|
|
}
|
|
|
|
for(i = 0; i < convc - 2; i++) {
|
|
STriangle tr = STriangle::From(meta, conv[0], conv[i+1], conv[i+2]);
|
|
if(tr.MinAltitude() > LENGTH_EPS) {
|
|
tout[toutc++] = tr;
|
|
}
|
|
}
|
|
}
|
|
|
|
l.n = start0;
|
|
for(i = 0; i < toutc; i++) {
|
|
AddTriangle(&(tout[i]));
|
|
}
|
|
FreeTemporary(tout);
|
|
FreeTemporary(conv);
|
|
}
|
|
|
|
void SMesh::AddAgainstBsp(SMesh *srcm, SBsp3 *bsp3) {
|
|
int i;
|
|
|
|
for(i = 0; i < srcm->l.n; i++) {
|
|
STriangle *st = &(srcm->l.elem[i]);
|
|
int pn = l.n;
|
|
atLeastOneDiscarded = false;
|
|
bsp3->Insert(st, this);
|
|
if(!atLeastOneDiscarded && (l.n != (pn+1))) {
|
|
l.n = pn;
|
|
if(flipNormal) {
|
|
AddTriangle(st->meta, st->c, st->b, st->a);
|
|
} else {
|
|
AddTriangle(st->meta, st->a, st->b, st->c);
|
|
}
|
|
}
|
|
if(l.n - pn > 1) {
|
|
Simplify(pn);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SMesh::MakeFromUnion(SMesh *a, SMesh *b) {
|
|
SBsp3 *bspa = SBsp3::FromMesh(a);
|
|
SBsp3 *bspb = SBsp3::FromMesh(b);
|
|
|
|
flipNormal = false;
|
|
keepCoplanar = false;
|
|
AddAgainstBsp(b, bspa);
|
|
|
|
flipNormal = false;
|
|
keepCoplanar = true;
|
|
AddAgainstBsp(a, bspb);
|
|
}
|
|
|
|
void SMesh::MakeFromDifference(SMesh *a, SMesh *b) {
|
|
SBsp3 *bspa = SBsp3::FromMesh(a);
|
|
SBsp3 *bspb = SBsp3::FromMesh(b);
|
|
|
|
flipNormal = true;
|
|
keepCoplanar = true;
|
|
AddAgainstBsp(b, bspa);
|
|
|
|
flipNormal = false;
|
|
keepCoplanar = false;
|
|
AddAgainstBsp(a, bspb);
|
|
}
|
|
|
|
bool SMesh::MakeFromInterferenceCheck(SMesh *srca, SMesh *srcb, SMesh *error) {
|
|
SBsp3 *bspa = SBsp3::FromMesh(srca);
|
|
SBsp3 *bspb = SBsp3::FromMesh(srcb);
|
|
|
|
error->Clear();
|
|
error->flipNormal = true;
|
|
error->keepCoplanar = false;
|
|
|
|
error->AddAgainstBsp(srcb, bspa);
|
|
error->AddAgainstBsp(srca, bspb);
|
|
// Now we have a list of all the triangles (or fragments thereof) from
|
|
// A that lie inside B, or vice versa. That's the interference, and
|
|
// we report it so that it can be flagged.
|
|
|
|
// For the actual output, take the union.
|
|
flipNormal = false;
|
|
keepCoplanar = false;
|
|
AddAgainstBsp(srcb, bspa);
|
|
|
|
flipNormal = false;
|
|
keepCoplanar = true;
|
|
AddAgainstBsp(srca, bspb);
|
|
|
|
// And we're successful if the intersection was empty.
|
|
return (error->l.n == 0);
|
|
}
|
|
|
|
void SMesh::MakeFromCopy(SMesh *a) {
|
|
int i;
|
|
for(i = 0; i < a->l.n; i++) {
|
|
AddTriangle(&(a->l.elem[i]));
|
|
}
|
|
}
|
|
|
|
DWORD SMesh::FirstIntersectionWith(Point2d mp) {
|
|
Vector p0 = Vector::From(mp.x, mp.y, 0);
|
|
Vector gn = Vector::From(0, 0, 1);
|
|
|
|
double maxT = -1e12;
|
|
DWORD face = 0;
|
|
|
|
int i;
|
|
for(i = 0; i < l.n; i++) {
|
|
STriangle tr = l.elem[i];
|
|
tr.a = SS.GW.ProjectPoint3(tr.a);
|
|
tr.b = SS.GW.ProjectPoint3(tr.b);
|
|
tr.c = SS.GW.ProjectPoint3(tr.c);
|
|
|
|
Vector n = tr.Normal();
|
|
|
|
if(n.Dot(gn) < LENGTH_EPS) continue; // back-facing or on edge
|
|
|
|
if(tr.ContainsPointProjd(gn, p0)) {
|
|
// Let our line have the form r(t) = p0 + gn*t
|
|
double t = -(n.Dot((tr.a).Minus(p0)))/(n.Dot(gn));
|
|
if(t > maxT) {
|
|
maxT = t;
|
|
face = tr.meta.face;
|
|
}
|
|
}
|
|
}
|
|
return face;
|
|
}
|
|
|
|
#define KDTREE_EPS (20*LENGTH_EPS) // nice and sloppy
|
|
|
|
STriangleLl *STriangleLl::Alloc(void)
|
|
{ return (STriangleLl *)AllocTemporary(sizeof(STriangleLl)); }
|
|
SKdNode *SKdNode::Alloc(void)
|
|
{ return (SKdNode *)AllocTemporary(sizeof(SKdNode)); }
|
|
|
|
SKdNode *SKdNode::From(SMesh *m) {
|
|
int i;
|
|
STriangle *tra = (STriangle *)AllocTemporary((m->l.n) * sizeof(*tra));
|
|
|
|
for(i = 0; i < m->l.n; i++) {
|
|
tra[i] = m->l.elem[i];
|
|
}
|
|
|
|
srand(0);
|
|
int n = m->l.n;
|
|
while(n > 1) {
|
|
int k = rand() % n;
|
|
n--;
|
|
SWAP(STriangle, tra[k], tra[n]);
|
|
}
|
|
|
|
STriangleLl *tll = NULL;
|
|
for(i = 0; i < m->l.n; i++) {
|
|
STriangleLl *tn = STriangleLl::Alloc();
|
|
tn->tri = &(tra[i]);
|
|
tn->next = tll;
|
|
tll = tn;
|
|
}
|
|
|
|
return SKdNode::From(tll, 0);
|
|
}
|
|
|
|
SKdNode *SKdNode::From(STriangleLl *tll, int which) {
|
|
SKdNode *ret = Alloc();
|
|
|
|
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];
|
|
for(i = 0; i < 3; i++) {
|
|
int tcnt = 0;
|
|
STriangleLl *ll;
|
|
for(ll = tll; ll; ll = ll->next) {
|
|
STriangle *tr = ll->tri;
|
|
split[i] += (ll->tri->a).Element(i);
|
|
split[i] += (ll->tri->b).Element(i);
|
|
split[i] += (ll->tri->c).Element(i);
|
|
tcnt++;
|
|
}
|
|
split[i] /= (tcnt*3);
|
|
|
|
for(ll = tll; ll; ll = ll->next) {
|
|
STriangle *tr = ll->tri;
|
|
|
|
double a = (tr->a).Element(i),
|
|
b = (tr->b).Element(i),
|
|
c = (tr->c).Element(i);
|
|
|
|
if(a < split[i] + KDTREE_EPS ||
|
|
b < split[i] + KDTREE_EPS ||
|
|
c < split[i] + KDTREE_EPS)
|
|
{
|
|
ltc[i]++;
|
|
}
|
|
if(a > split[i] - KDTREE_EPS ||
|
|
b > split[i] - KDTREE_EPS ||
|
|
c > split[i] - KDTREE_EPS)
|
|
{
|
|
gtc[i]++;
|
|
}
|
|
if(i == 0) allc++;
|
|
}
|
|
badness[i] = pow((double)ltc[i], 4) + pow((double)gtc[i], 4);
|
|
}
|
|
if(badness[0] < badness[1] && badness[0] < badness[2]) {
|
|
which = 0;
|
|
} else if(badness[1] < badness[2]) {
|
|
which = 1;
|
|
} else {
|
|
which = 2;
|
|
}
|
|
|
|
if(allc < 10) goto leaf;
|
|
if(allc == gtc[which] || allc == ltc[which]) goto leaf;
|
|
|
|
STriangleLl *ll;
|
|
STriangleLl *lgt = NULL, *llt = NULL;
|
|
for(ll = tll; ll; ll = ll->next) {
|
|
STriangle *tr = ll->tri;
|
|
|
|
double a = (tr->a).Element(which),
|
|
b = (tr->b).Element(which),
|
|
c = (tr->c).Element(which);
|
|
|
|
if(a < split[which] + KDTREE_EPS ||
|
|
b < split[which] + KDTREE_EPS ||
|
|
c < split[which] + KDTREE_EPS)
|
|
{
|
|
STriangleLl *n = STriangleLl::Alloc();
|
|
*n = *ll;
|
|
n->next = llt;
|
|
llt = n;
|
|
}
|
|
if(a > split[which] - KDTREE_EPS ||
|
|
b > split[which] - KDTREE_EPS ||
|
|
c > split[which] - KDTREE_EPS)
|
|
{
|
|
STriangleLl *n = STriangleLl::Alloc();
|
|
*n = *ll;
|
|
n->next = lgt;
|
|
lgt = n;
|
|
}
|
|
}
|
|
|
|
ret->which = which;
|
|
ret->c = split[which];
|
|
ret->gt = SKdNode::From(lgt, (which + 1) % 3);
|
|
ret->lt = SKdNode::From(llt, (which + 1) % 3);
|
|
return ret;
|
|
|
|
leaf:
|
|
// dbp("leaf: allc=%d gtc=%d ltc=%d which=%d", allc, gtc[which], ltc[which], which);
|
|
ret->tris = tll;
|
|
return ret;
|
|
}
|
|
|
|
void SKdNode::ClearTags(void) {
|
|
if(gt && lt) {
|
|
gt->ClearTags();
|
|
lt->ClearTags();
|
|
} else {
|
|
STriangleLl *ll;
|
|
for(ll = tris; ll; ll = ll->next) {
|
|
ll->tri->tag = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SKdNode::AddTriangle(STriangle *tr) {
|
|
if(gt && lt) {
|
|
double ta = (tr->a).Element(which),
|
|
tb = (tr->b).Element(which),
|
|
tc = (tr->c).Element(which);
|
|
if(ta < c + KDTREE_EPS ||
|
|
tb < c + KDTREE_EPS ||
|
|
tc < c + KDTREE_EPS)
|
|
{
|
|
lt->AddTriangle(tr);
|
|
}
|
|
if(ta > c - KDTREE_EPS ||
|
|
tb > c - KDTREE_EPS ||
|
|
tc > c - KDTREE_EPS)
|
|
{
|
|
gt->AddTriangle(tr);
|
|
}
|
|
} else {
|
|
STriangleLl *tn = STriangleLl::Alloc();
|
|
tn->tri = tr;
|
|
tn->next = tris;
|
|
tris = tn;
|
|
}
|
|
}
|
|
|
|
void SKdNode::MakeMeshInto(SMesh *m) {
|
|
if(gt) gt->MakeMeshInto(m);
|
|
if(lt) lt->MakeMeshInto(m);
|
|
|
|
STriangleLl *ll;
|
|
for(ll = tris; ll; ll = ll->next) {
|
|
if(ll->tri->tag) continue;
|
|
|
|
m->AddTriangle(ll->tri);
|
|
ll->tri->tag = 1;
|
|
}
|
|
}
|
|
|
|
void SKdNode::FindEdgeOn(Vector a, Vector b, int *n, int cnt, bool *inter) {
|
|
if(gt && lt) {
|
|
double ac = a.Element(which),
|
|
bc = b.Element(which);
|
|
if(ac < c + KDTREE_EPS ||
|
|
bc < c + KDTREE_EPS)
|
|
{
|
|
lt->FindEdgeOn(a, b, n, cnt, inter);
|
|
}
|
|
if(ac > c - KDTREE_EPS ||
|
|
bc > c - KDTREE_EPS)
|
|
{
|
|
gt->FindEdgeOn(a, b, n, cnt, inter);
|
|
}
|
|
} else {
|
|
STriangleLl *ll;
|
|
for(ll = tris; ll; ll = ll->next) {
|
|
STriangle *tr = ll->tri;
|
|
|
|
if(tr->tag == cnt) continue;
|
|
|
|
// Test if this triangle matches up with the given edge
|
|
if((a.Equals(tr->b) && b.Equals(tr->a)) ||
|
|
(a.Equals(tr->c) && b.Equals(tr->b)) ||
|
|
(a.Equals(tr->a) && b.Equals(tr->c)))
|
|
{
|
|
(*n)++;
|
|
} else if(((a.Equals(tr->a) && b.Equals(tr->b)) ||
|
|
(a.Equals(tr->b) && b.Equals(tr->c)) ||
|
|
(a.Equals(tr->c) && b.Equals(tr->a))))
|
|
{
|
|
// It's an edge of this triangle, okay.
|
|
} else {
|
|
// Check for self-intersection
|
|
Vector n = (tr->Normal()).WithMagnitude(1);
|
|
double d = (tr->a).Dot(n);
|
|
double pa = a.Dot(n) - d, pb = b.Dot(n) - d;
|
|
// It's an intersection if neither point lies in-plane,
|
|
// and the edge crosses the plane (should handle in-plane
|
|
// intersections separately but don't yet).
|
|
if((pa < -LENGTH_EPS || pa > LENGTH_EPS) &&
|
|
(pb < -LENGTH_EPS || pb > LENGTH_EPS) &&
|
|
(pa*pb < 0))
|
|
{
|
|
// The edge crosses the plane of the triangle; now see if
|
|
// it crosses inside the triangle.
|
|
if(tr->ContainsPointProjd(b.Minus(a), a)) {
|
|
*inter = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure that we don't count this triangle twice if it appears
|
|
// in two buckets of the kd tree.
|
|
tr->tag = cnt;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SKdNode::MakeNakedEdgesInto(SEdgeList *sel, bool *inter, bool *leaky) {
|
|
if(inter) *inter = false;
|
|
if(leaky) *leaky = false;
|
|
|
|
SMesh m;
|
|
ZERO(&m);
|
|
ClearTags();
|
|
MakeMeshInto(&m);
|
|
|
|
int cnt = 1234;
|
|
int i, j;
|
|
for(i = 0; i < m.l.n; i++) {
|
|
STriangle *tr = &(m.l.elem[i]);
|
|
|
|
for(j = 0; j < 3; j++) {
|
|
Vector a = (j == 0) ? tr->a : ((j == 1) ? tr->b : tr->c);
|
|
Vector b = (j == 0) ? tr->b : ((j == 1) ? tr->c : tr->a);
|
|
|
|
int n = 0, nOther = 0;
|
|
bool thisIntersects = false;
|
|
FindEdgeOn(a, b, &n, cnt, &thisIntersects);
|
|
if(n != 1) {
|
|
sel->AddEdge(a, b);
|
|
if(leaky) *leaky = true;
|
|
}
|
|
if(thisIntersects) {
|
|
sel->AddEdge(a, b);
|
|
if(inter) *inter = true;
|
|
}
|
|
|
|
cnt++;
|
|
}
|
|
}
|
|
|
|
m.Clear();
|
|
}
|
|
|