solvespace/mesh.cpp
Jonathan Westhues cad77c9c47 Add routines to remove T intersections from the mesh, which are
introduced by the bsp routines. It's usually, though not always,
possible to generate a watertight mesh. The occasions where it's
not look ugly, floating point issues, no quick fix.

And use those to generate a list of edges where two different faces
meet, which I can emphasize for cosmetic reasons (and some UI to
specify whether to do that, and with what color).

And make the right mouse button rotate the model, since that was
previously doing nothing.

[git-p4: depot-paths = "//depot/solvespace/": change = 1821]
2008-07-05 23:56:24 -08:00

631 lines
18 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) {
#define MAX_TRIANGLES 2000
if(l.n - start > MAX_TRIANGLES) oops();
STriMeta meta = l.elem[start].meta;
STriangle tout[MAX_TRIANGLES];
int toutc = 0;
Vector n, conv[MAX_TRIANGLES*3];
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) oops();
}
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]));
}
}
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.
// But as far as the actual model, we just copy everything over.
int i;
for(i = 0; i < srca->l.n; i++) {
AddTriangle(&(srca->l.elem[i]));
}
for(i = 0; i < srcb->l.n; i++) {
AddTriangle(&(srcb->l.elem[i]));
}
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::SnapToVertex(Vector v, SMesh *extras) {
if(gt && lt) {
double vc = v.Element(which);
if(vc < c + KDTREE_EPS) {
lt->SnapToVertex(v, extras);
}
if(vc > c - KDTREE_EPS) {
gt->SnapToVertex(v, extras);
}
// Nothing bad happens if the triangle to be split appears in both
// branches; the first call will split the triangle, so that the
// second call will do nothing, because the modified triangle will
// already contain v
} else {
STriangleLl *ll;
for(ll = tris; ll; ll = ll->next) {
STriangle *tr = ll->tri;
// Do a cheap bbox test first
int k;
bool mightHit = true;
for(k = 0; k < 3; k++) {
if((tr->a).Element(k) < v.Element(k) - KDTREE_EPS &&
(tr->b).Element(k) < v.Element(k) - KDTREE_EPS &&
(tr->c).Element(k) < v.Element(k) - KDTREE_EPS)
{
mightHit = false;
break;
}
if((tr->a).Element(k) > v.Element(k) + KDTREE_EPS &&
(tr->b).Element(k) > v.Element(k) + KDTREE_EPS &&
(tr->c).Element(k) > v.Element(k) + KDTREE_EPS)
{
mightHit = false;
break;
}
}
if(!mightHit) continue;
if(tr->a.Equals(v)) { tr->a = v; continue; }
if(tr->b.Equals(v)) { tr->b = v; continue; }
if(tr->c.Equals(v)) { tr->c = v; continue; }
if(v.OnLineSegment(tr->a, tr->b)) {
STriangle nt = STriangle::From(tr->meta, tr->a, v, tr->c);
extras->AddTriangle(&nt);
tr->a = v;
continue;
}
if(v.OnLineSegment(tr->b, tr->c)) {
STriangle nt = STriangle::From(tr->meta, tr->b, v, tr->a);
extras->AddTriangle(&nt);
tr->b = v;
continue;
}
if(v.OnLineSegment(tr->c, tr->a)) {
STriangle nt = STriangle::From(tr->meta, tr->c, v, tr->b);
extras->AddTriangle(&nt);
tr->c = v;
continue;
}
}
}
}
void SKdNode::SnapToMesh(SMesh *m) {
int i, j, k;
for(i = 0; i < m->l.n; i++) {
STriangle *tr = &(m->l.elem[i]);
for(j = 0; j < 3; j++) {
Vector v = ((j == 0) ? tr->a :
((j == 1) ? tr->b :
tr->c));
SMesh extra;
ZERO(&extra);
SnapToVertex(v, &extra);
for(k = 0; k < extra.l.n; k++) {
STriangle *tra = (STriangle *)AllocTemporary(sizeof(*tra));
*tra = extra.l.elem[k];
AddTriangle(tra);
}
extra.Clear();
}
}
}
void SKdNode::FindEdgeOn(Vector a, Vector b, int *n, int *nOther,
STriMeta m, int cnt)
{
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, nOther, m, cnt);
}
if(ac > c - KDTREE_EPS ||
bc > c - KDTREE_EPS)
{
gt->FindEdgeOn(a, b, n, nOther, m, cnt);
}
} else {
STriangleLl *ll;
for(ll = tris; ll; ll = ll->next) {
STriangle *tr = ll->tri;
if(tr->tag == cnt) continue;
if((a.EqualsExactly(tr->b) && b.EqualsExactly(tr->a)) ||
(a.EqualsExactly(tr->c) && b.EqualsExactly(tr->b)) ||
(a.EqualsExactly(tr->a) && b.EqualsExactly(tr->c)))
{
(*n)++;
if(tr->meta.face != m.face) (*nOther)++;
}
tr->tag = cnt;
}
}
}
void SKdNode::MakeEdgesToEmphasizeInto(SEdgeList *sel) {
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;
FindEdgeOn(a, b, &n, &nOther, tr->meta, cnt++);
if(n != 1) {
dbp("hanging edge: n=%d (%.3f %.3f %.3f) (%.3f %.3f %.3f)",
n, CO(a), CO(b));
}
if(nOther > 0) {
sel->AddEdge(a, b);
}
}
}
m.Clear();
}