#include "solvespace.h" void SMesh::Clear(void) { l.Clear(); } void SMesh::AddTriangle(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(a, b, c); } else { AddTriangle(c, b, a); } } void SMesh::AddTriangle(Vector a, Vector b, Vector c) { STriangle t; ZERO(&t); 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::MakeFrom( 1e12, 1e12, 1e12); *vmax = Vector::MakeFrom(-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::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->c, st->b, st->a); } else { AddTriangle(st->a, st->b, st->c); } continue; } } } 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 = false; AddAgainstBsp(b, bspa); flipNormal = false; keepCoplanar = false; AddAgainstBsp(a, bspb); } SBsp2 *SBsp2::Alloc(void) { return (SBsp2 *)AllocTemporary(sizeof(SBsp2)); } SBsp3 *SBsp3::Alloc(void) { return (SBsp3 *)AllocTemporary(sizeof(SBsp3)); } SBsp3 *SBsp3::FromMesh(SMesh *m) { SBsp3 *bsp3 = NULL; int i; for(i = 0; i < m->l.n; i++) { bsp3 = bsp3->Insert(&(m->l.elem[i]), NULL); } return bsp3; } Vector SBsp3::IntersectionWith(Vector a, Vector b) { double da = a.Dot(n) - d; double db = b.Dot(n) - d; if(da*db > 0) oops(); double dab = (db - da); return (a.ScaledBy(db/dab)).Plus(b.ScaledBy(-da/dab)); } void SBsp3::InsertInPlane(bool pos2, STriangle *tr, SMesh *m) { Vector tc = ((tr->a).Plus(tr->b).Plus(tr->c)).ScaledBy(1.0/3); bool onFace = false; bool sameNormal; SBsp3 *ll = this; while(ll) { if((ll->tri).ContainsPoint(tc)) { onFace = true; sameNormal = (tr->Normal()).Dot((ll->tri).Normal()) > 0; break; } ll = ll->more; } if(m->flipNormal && ((!pos2 && !onFace) || (onFace && !sameNormal))) { m->AddTriangle(tr->c, tr->b, tr->a); } else if(!(m->flipNormal) && ((pos2 && !onFace) || (onFace && sameNormal && m->keepCoplanar))) { m->AddTriangle(tr->a, tr->b, tr->c); } else { m->atLeastOneDiscarded = true; } } void SBsp3::InsertHow(int how, STriangle *tr, SMesh *instead) { switch(how) { case POS: if(instead && !pos) goto alt; pos = pos->Insert(tr, instead); break; case NEG: if(instead && !neg) goto alt; neg = neg->Insert(tr, instead); break; case COPLANAR: { if(instead) goto alt; SBsp3 *m = Alloc(); m->n = n; m->d = d; m->tri = *tr; m->more = more; more = m; break; } default: oops(); } return; alt: if(how == POS && !(instead->flipNormal)) { instead->AddTriangle(tr->a, tr->b, tr->c); } else if(how == NEG && instead->flipNormal) { instead->AddTriangle(tr->c, tr->b, tr->a); } else if(how == COPLANAR) { if(edges) { edges->InsertTriangle(tr, instead, this); } else { // I suppose this actually is allowed to happen, if the coplanar // face is the leaf, and all of its neighbors are earlier in tree? InsertInPlane(false, tr, instead); } } else { instead->atLeastOneDiscarded = true; } } void SBsp3::InsertConvexHow(int how, Vector *vertex, int n, SMesh *instead) { switch(how) { case POS: if(pos) { pos = pos->InsertConvex(vertex, n, instead); return; } break; case NEG: if(neg) { neg = neg->InsertConvex(vertex, n, instead); return; } break; default: oops(); } int i; for(i = 0; i < n - 2; i++) { STriangle tr = { 0, vertex[0], vertex[i+1], vertex[i+2] }; InsertHow(how, &tr, instead); } } SBsp3 *SBsp3::InsertConvex(Vector *vertex, int cnt, SMesh *instead) { Vector e01 = (vertex[1]).Minus(vertex[0]); Vector e12 = (vertex[2]).Minus(vertex[1]); Vector out = e01.Cross(e12); int i; Vector on[2]; bool *isPos = (bool *)AllocTemporary(cnt*sizeof(bool)); bool *isNeg = (bool *)AllocTemporary(cnt*sizeof(bool)); bool *isOn = (bool *)AllocTemporary(cnt*sizeof(bool)); int posc = 0, negc = 0, onc = 0; for(i = 0; i < cnt; i++) { double dt = n.Dot(vertex[i]); isPos[i] = isNeg[i] = isOn[i] = false; if(fabs(dt - d) < LENGTH_EPS) { isOn[i] = true; if(onc < 2) { on[onc] = vertex[i]; } onc++; } else if(dt > d) { isPos[i] = true; posc++; } else { isNeg[i] = true; negc++; } } if(onc != 2 && onc != 1 && onc != 0) goto triangulate; if(onc == 2) { if(!instead) { SEdge se = { 0, on[0], on[1] }; edges = edges->InsertEdge(&se, n, out); } } if(posc == 0) { InsertConvexHow(NEG, vertex, cnt, instead); return this; } if(negc == 0) { InsertConvexHow(POS, vertex, cnt, instead); return this; } Vector *vpos = (Vector *)AllocTemporary((cnt+1)*sizeof(Vector)); Vector *vneg = (Vector *)AllocTemporary((cnt+1)*sizeof(Vector)); int npos = 0, nneg = 0; Vector inter[2]; int inters = 0; for(i = 0; i < cnt; i++) { int ip = (i + 1) % cnt; if(isPos[i]) { vpos[npos++] = vertex[i]; } if(isNeg[i]) { vneg[nneg++] = vertex[i]; } if(isOn[i]) { vneg[nneg++] = vertex[i]; vpos[npos++] = vertex[i]; } if((isPos[i] && isNeg[ip]) || (isNeg[i] && isPos[ip])) { Vector vi = IntersectionWith(vertex[i], vertex[ip]); vpos[npos++] = vi; vneg[nneg++] = vi; if(inters >= 2) oops(); inter[inters++] = vi; } } if(npos > cnt + 1 || nneg > cnt + 1) oops(); if(!instead) { if(inters == 2) { SEdge se = { 0, inter[0], inter[1] }; edges = edges->InsertEdge(&se, n, out); } else if(inters == 1 && onc == 1) { SEdge se = { 0, inter[0], on[0] }; edges = edges->InsertEdge(&se, n, out); } else if(inters == 0 && onc == 2) { // We already handled this on-plane existing edge } else oops(); } if(nneg < 3 || npos < 3) oops(); InsertConvexHow(NEG, vneg, nneg, instead); InsertConvexHow(POS, vpos, npos, instead); return this; triangulate: // We don't handle the special case for this; do it as triangles SBsp3 *r = this; for(i = 0; i < cnt - 2; i++) { STriangle tr = { 0, vertex[0], vertex[i+1], vertex[i+2] }; r = r->Insert(&tr, instead); } return r; } SBsp3 *SBsp3::Insert(STriangle *tr, SMesh *instead) { if(!this) { // Brand new node; so allocate for it, and fill us in. SBsp3 *r = Alloc(); r->n = tr->Normal(); r->d = (tr->a).Dot(r->n); r->tri = *tr; return r; } double dt[3] = { (tr->a).Dot(n), (tr->b).Dot(n), (tr->c).Dot(n) }; int inc = 0, posc = 0, negc = 0; bool isPos[3], isNeg[3], isOn[3]; ZERO(&isPos); ZERO(&isNeg); ZERO(&isOn); // Count vertices in the plane for(int i = 0; i < 3; i++) { if(fabs(dt[i] - d) < LENGTH_EPS) { inc++; isOn[i] = true; } else if(dt[i] > d) { posc++; isPos[i] = true; } else { negc++; isNeg[i] = true; } } // All vertices in-plane if(inc == 3) { InsertHow(COPLANAR, tr, instead); return this; } // No split required if(posc == 0 || negc == 0) { if(inc == 2) { Vector a, b; if (!isOn[0]) { a = tr->b; b = tr->c; } else if(!isOn[1]) { a = tr->c; b = tr->a; } else if(!isOn[2]) { a = tr->a; b = tr->b; } else oops(); if(!instead) { SEdge se = { 0, a, b }; edges = edges->InsertEdge(&se, n, tr->Normal()); } } if(posc > 0) { InsertHow(POS, tr, instead); } else { InsertHow(NEG, tr, instead); } return this; } // The polygon must be split into two pieces, one above, one below. Vector a, b, c; if(posc == 1 && negc == 1 && inc == 1) { bool bpos; // Standardize so that a is on the plane if (isOn[0]) { a = tr->a; b = tr->b; c = tr->c; bpos = isPos[1]; } else if(isOn[1]) { a = tr->b; b = tr->c; c = tr->a; bpos = isPos[2]; } else if(isOn[2]) { a = tr->c; b = tr->a; c = tr->b; bpos = isPos[0]; } else oops(); Vector bPc = IntersectionWith(b, c); STriangle btri = { 0, a, b, bPc }; STriangle ctri = { 0, c, a, bPc }; if(bpos) { InsertHow(POS, &btri, instead); InsertHow(NEG, &ctri, instead); } else { InsertHow(POS, &ctri, instead); InsertHow(NEG, &btri, instead); } if(!instead) { SEdge se = { 0, a, bPc }; edges = edges->InsertEdge(&se, n, tr->Normal()); } return this; } if(posc == 2 && negc == 1) { // Standardize so that a is on one side, and b and c are on the other. if (isNeg[0]) { a = tr->a; b = tr->b; c = tr->c; } else if(isNeg[1]) { a = tr->b; b = tr->c; c = tr->a; } else if(isNeg[2]) { a = tr->c; b = tr->a; c = tr->b; } else oops(); } else if(posc == 1 && negc == 2) { if (isPos[0]) { a = tr->a; b = tr->b; c = tr->c; } else if(isPos[1]) { a = tr->b; b = tr->c; c = tr->a; } else if(isPos[2]) { a = tr->c; b = tr->a; c = tr->b; } else oops(); } else oops(); Vector aPb = IntersectionWith(a, b); Vector cPa = IntersectionWith(c, a); STriangle alone = { 0, a, aPb, cPa }; Vector quad[4] = { aPb, b, c, cPa }; STriangle quad1 = { 0, aPb, b, c }; STriangle quad2 = { 0, aPb, c, cPa }; if(posc == 2 && negc == 1) { InsertConvexHow(POS, quad, 4, instead); InsertHow(NEG, &alone, instead); } else { InsertConvexHow(NEG, quad, 4, instead); InsertHow(POS, &alone, instead); } if(!instead) { SEdge se = { 0, aPb, cPa }; edges = edges->InsertEdge(&se, n, alone.Normal()); } return this; } void SBsp3::DebugDraw(void) { if(!this) return; pos->DebugDraw(); Vector norm = tri.Normal(); glNormal3d(norm.x, norm.y, norm.z); glEnable(GL_DEPTH_TEST); glEnable(GL_LIGHTING); glBegin(GL_TRIANGLES); glxVertex3v(tri.a); glxVertex3v(tri.b); glxVertex3v(tri.c); glEnd(); glDisable(GL_LIGHTING); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glPolygonOffset(-1, 0); glBegin(GL_TRIANGLES); glxVertex3v(tri.a); glxVertex3v(tri.b); glxVertex3v(tri.c); glEnd(); glDisable(GL_LIGHTING); glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); glPointSize(10); glPolygonOffset(-1, 0); glBegin(GL_TRIANGLES); glxVertex3v(tri.a); glxVertex3v(tri.b); glxVertex3v(tri.c); glEnd(); glPolygonOffset(0, 0); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); more->DebugDraw(); neg->DebugDraw(); edges->DebugDraw(n, d); } ///////////////////////////////// Vector SBsp2::IntersectionWith(Vector a, Vector b) { double da = a.Dot(no) - d; double db = b.Dot(no) - d; if(da*db > 0) oops(); double dab = (db - da); return (a.ScaledBy(db/dab)).Plus(b.ScaledBy(-da/dab)); } SBsp2 *SBsp2::InsertEdge(SEdge *nedge, Vector nnp, Vector out) { if(!this) { // Brand new node; so allocate for it, and fill us in. SBsp2 *r = Alloc(); r->np = nnp; r->no = (r->np).Cross((nedge->b).Minus(nedge->a)); if(out.Dot(r->no) < 0) { r->no = (r->no).ScaledBy(-1); } r->d = (nedge->a).Dot(r->no); r->edge = *nedge; return r; } double dt[2] = { (nedge->a).Dot(no), (nedge->b).Dot(no) }; bool isPos[2], isNeg[2], isOn[2]; ZERO(&isPos); ZERO(&isNeg); ZERO(&isOn); for(int i = 0; i < 2; i++) { if(fabs(dt[i] - d) < LENGTH_EPS) { isOn[i] = true; } else if(dt[i] > d) { isPos[i] = true; } else { isNeg[i] = true; } } if((isPos[0] && isPos[1])||(isPos[0] && isOn[1])||(isOn[0] && isPos[1])) { pos = pos->InsertEdge(nedge, nnp, out); return this; } if((isNeg[0] && isNeg[1])||(isNeg[0] && isOn[1])||(isOn[0] && isNeg[1])) { neg = neg->InsertEdge(nedge, nnp, out); return this; } if(isOn[0] && isOn[1]) { SBsp2 *m = Alloc(); m->np = nnp; m->no = (m->np).Cross((nedge->b).Minus(nedge->a)); if(out.Dot(m->no) < 0) { m->no = (m->no).ScaledBy(-1); } m->d = (nedge->a).Dot(m->no); m->edge = *nedge; m->more = more; more = m; return this; } if((isPos[0] && isNeg[1]) || (isNeg[0] && isPos[1])) { Vector aPb = IntersectionWith(nedge->a, nedge->b); SEdge ea = { 0, nedge->a, aPb }; SEdge eb = { 0, aPb, nedge->b }; if(isPos[0]) { pos = pos->InsertEdge(&ea, nnp, out); neg = neg->InsertEdge(&eb, nnp, out); } else { neg = neg->InsertEdge(&ea, nnp, out); pos = pos->InsertEdge(&eb, nnp, out); } return this; } oops(); } void SBsp2::InsertTriangleHow(int how, STriangle *tr, SMesh *m, SBsp3 *bsp3) { switch(how) { case POS: if(pos) { pos->InsertTriangle(tr, m, bsp3); } else { bsp3->InsertInPlane(true, tr, m); } break; case NEG: if(neg) { neg->InsertTriangle(tr, m, bsp3); } else { bsp3->InsertInPlane(false, tr, m); } break; default: oops(); } } void SBsp2::InsertTriangle(STriangle *tr, SMesh *m, SBsp3 *bsp3) { double dt[3] = { (tr->a).Dot(no), (tr->b).Dot(no), (tr->c).Dot(no) }; bool isPos[3], isNeg[3], isOn[3]; int inc = 0, posc = 0, negc = 0; ZERO(&isPos); ZERO(&isNeg); ZERO(&isOn); for(int i = 0; i < 3; i++) { if(fabs(dt[i] - d) < LENGTH_EPS) { isOn[i] = true; inc++; } else if(dt[i] > d) { isPos[i] = true; posc++; } else { isNeg[i] = true; negc++; } } if(inc == 3) { // All vertices on-line; so it's a degenerate triangle, to ignore. return; } // No split required if(posc == 0 || negc == 0) { if(posc > 0) { InsertTriangleHow(POS, tr, m, bsp3); } else { InsertTriangleHow(NEG, tr, m, bsp3); } return; } // The polygon must be split into two pieces, one above, one below. Vector a, b, c; if(posc == 1 && negc == 1 && inc == 1) { bool bpos; // Standardize so that a is on the plane if (isOn[0]) { a = tr->a; b = tr->b; c = tr->c; bpos = isPos[1]; } else if(isOn[1]) { a = tr->b; b = tr->c; c = tr->a; bpos = isPos[2]; } else if(isOn[2]) { a = tr->c; b = tr->a; c = tr->b; bpos = isPos[0]; } else oops(); Vector bPc = IntersectionWith(b, c); STriangle btri = { 0, a, b, bPc }; STriangle ctri = { 0, c, a, bPc }; if(bpos) { InsertTriangleHow(POS, &btri, m, bsp3); InsertTriangleHow(NEG, &ctri, m, bsp3); } else { InsertTriangleHow(POS, &ctri, m, bsp3); InsertTriangleHow(NEG, &btri, m, bsp3); } return; } if(posc == 2 && negc == 1) { // Standardize so that a is on one side, and b and c are on the other. if (isNeg[0]) { a = tr->a; b = tr->b; c = tr->c; } else if(isNeg[1]) { a = tr->b; b = tr->c; c = tr->a; } else if(isNeg[2]) { a = tr->c; b = tr->a; c = tr->b; } else oops(); } else if(posc == 1 && negc == 2) { if (isPos[0]) { a = tr->a; b = tr->b; c = tr->c; } else if(isPos[1]) { a = tr->b; b = tr->c; c = tr->a; } else if(isPos[2]) { a = tr->c; b = tr->a; c = tr->b; } else oops(); } else oops(); Vector aPb = IntersectionWith(a, b); Vector cPa = IntersectionWith(c, a); STriangle alone = { 0, a, aPb, cPa }; STriangle quad1 = { 0, aPb, b, c }; STriangle quad2 = { 0, aPb, c, cPa }; if(posc == 2 && negc == 1) { InsertTriangleHow(POS, &quad1, m, bsp3); InsertTriangleHow(POS, &quad2, m, bsp3); InsertTriangleHow(NEG, &alone, m, bsp3); } else { InsertTriangleHow(NEG, &quad1, m, bsp3); InsertTriangleHow(NEG, &quad2, m, bsp3); InsertTriangleHow(POS, &alone, m, bsp3); } return; } void SBsp2::DebugDraw(Vector n, double d) { if(!this) return; if(fabs((edge.a).Dot(n) - d) > LENGTH_EPS) oops(); if(fabs((edge.b).Dot(n) - d) > LENGTH_EPS) oops(); glLineWidth(10); glBegin(GL_LINES); glxVertex3v(edge.a); glxVertex3v(edge.b); glEnd(); pos->DebugDraw(n, d); neg->DebugDraw(n, d); more->DebugDraw(n, d); glLineWidth(1); }