Solvespace
729 строк · 22.6 Кб
1//-----------------------------------------------------------------------------
2// Binary space partitioning tree, used to represent a volume in 3-space
3// bounded by a triangle mesh. These are used to compute Boolean operations
4// on meshes. These aren't used for anything relating to an SShell of
5// ratpoly surfaces.
6//
7// Copyright 2008-2013 Jonathan Westhues.
8//-----------------------------------------------------------------------------
9#include "solvespace.h"10
11SBsp2 *SBsp2::Alloc() { return (SBsp2 *)AllocTemporary(sizeof(SBsp2)); }12SBsp3 *SBsp3::Alloc() { return (SBsp3 *)AllocTemporary(sizeof(SBsp3)); }13
14SBsp3 *SBsp3::FromMesh(const SMesh *m) {15SMesh mc = {};16for(auto const &elt : m->l) { mc.AddTriangle(&elt); }17
18srand(0); // Let's be deterministic, at least!19int n = mc.l.n;20while(n > 1) {21int k = rand() % n;22n--;23swap(mc.l[k], mc.l[n]);24}25
26SBsp3 *bsp3 = NULL;27for(auto &elt : mc.l) { bsp3 = InsertOrCreate(bsp3, &elt, NULL); }28mc.Clear();29return bsp3;30}
31
32Vector SBsp3::IntersectionWith(Vector a, Vector b) const {33double da = a.Dot(n) - d;34double db = b.Dot(n) - d;35ssassert(da*db < 0, "Expected segment to intersect BSP node");36
37double dab = (db - da);38return (a.ScaledBy(db/dab)).Plus(b.ScaledBy(-da/dab));39}
40
41void SBsp3::InsertInPlane(bool pos2, STriangle *tr, SMesh *m) {42Vector tc = ((tr->a).Plus(tr->b).Plus(tr->c)).ScaledBy(1.0/3);43
44bool onFace = false;45bool sameNormal = false;46double maxNormalMag = -1;47
48Vector lln, trn = tr->Normal();49
50SBsp3 *ll = this;51while(ll) {52if((ll->tri).ContainsPoint(tc)) {53onFace = true;54// If the mesh contains almost-zero-area triangles, and we're55// just on the edge of one of those, then don't trust its normal.56lln = (ll->tri).Normal();57if(lln.Magnitude() > maxNormalMag) {58sameNormal = trn.Dot(lln) > 0;59maxNormalMag = lln.Magnitude();60}61}62ll = ll->more;63}64
65if((!onFace && ((m->keepInsideOtherShell && !pos2) ||66(!m->keepInsideOtherShell && pos2))) ||67(onFace && ((m->keepCoplanar && m->flipNormal && !sameNormal) ||68(m->keepCoplanar && !m->flipNormal && sameNormal)))) {69// We have decided that we need to keep a triangle either inside,70// outside or on the other shell. So add it and flip it if requested.71if(!(m->flipNormal)) {72m->AddTriangle(tr->meta, tr->a, tr->b, tr->c);73} else {74m->AddTriangle(tr->meta, tr->c, tr->b, tr->a);75}76} else {77m->atLeastOneDiscarded = true;78}79}
80
81void SBsp3::InsertHow(BspClass how, STriangle *tr, SMesh *instead) {82switch(how) {83case BspClass::POS:84if(instead && !pos) goto alt;85pos = InsertOrCreate(pos, tr, instead);86break;87
88case BspClass::NEG:89if(instead && !neg) goto alt;90neg = InsertOrCreate(neg, tr, instead);91break;92
93case BspClass::COPLANAR: {94if(instead) goto alt;95SBsp3 *m = Alloc();96m->n = n;97m->d = d;98m->tri = *tr;99m->more = more;100more = m;101break;102}103}104return;105
106alt:107if(((BspClass::POS == how) && !instead->keepInsideOtherShell) ||108((BspClass::NEG == how) && instead->keepInsideOtherShell)) {109// We have decided that we need to keep a triangle (either inside or110// outside the other shell. So add it and flip it if requested.111if(!(instead->flipNormal)) {112instead->AddTriangle(tr->meta, tr->a, tr->b, tr->c);113} else {114instead->AddTriangle(tr->meta, tr->c, tr->b, tr->a);115}116} else if(how == BspClass::COPLANAR) {117if(edges) {118edges->InsertTriangle(tr, instead, this);119} else {120// I suppose this actually is allowed to happen, if the coplanar121// face is the leaf, and all of its neighbors are earlier in tree?122InsertInPlane(/*pos2=*/false, tr, instead);123}124} else {125instead->atLeastOneDiscarded = true;126}127}
128
129class BspUtil {130public:131SBsp3 *bsp;132
133size_t onc;134size_t posc;135size_t negc;136bool *isPos;137bool *isNeg;138bool *isOn;139
140// triangle operations141STriangle *tr;142STriangle *btri; // also as alone143STriangle *ctri;144
145// convex operations146Vector *on;147size_t npos;148size_t nneg;149Vector *vpos; // also as quad150Vector *vneg;151
152static BspUtil *Alloc() {153return (BspUtil *)AllocTemporary(sizeof(BspUtil));154}155
156void AllocOn() {157on = (Vector *)AllocTemporary(sizeof(Vector) * 2);158}159
160void AllocTriangle() {161btri = (STriangle *)AllocTemporary(sizeof(STriangle));162}163
164void AllocTriangles() {165btri = (STriangle *)AllocTemporary(sizeof(STriangle) * 2);166ctri = &btri[1];167}168
169void AllocQuad() {170vpos = (Vector *)AllocTemporary(sizeof(Vector) * 4);171}172
173void AllocClassify(size_t size) {174// Allocate a one big piece is faster than a small ones.175isPos = (bool *)AllocTemporary(sizeof(bool) * size * 3);176isNeg = &isPos[size];177isOn = &isNeg[size];178}179
180void AllocVertices(size_t size) {181vpos = (Vector *)AllocTemporary(sizeof(Vector) * size * 2);182vneg = &vpos[size];183}184
185void ClassifyTriangle(STriangle *tri, SBsp3 *node) {186tr = tri;187bsp = node;188onc = 0;189posc = 0;190negc = 0;191
192AllocClassify(3);193
194double dt[3] = { (tr->a).Dot(bsp->n), (tr->b).Dot(bsp->n), (tr->c).Dot(bsp->n) };195double d = bsp->d;196// Count vertices in the plane197for(int i = 0; i < 3; i++) {198if(dt[i] > d + LENGTH_EPS) {199posc++;200isPos[i] = true;201} else if(dt[i] < d - LENGTH_EPS) {202negc++;203isNeg[i] = true;204} else {205onc++;206isOn[i] = true;207}208}209}210
211bool ClassifyConvex(Vector *vertex, size_t cnt, SBsp3 *node, bool insertEdge) {212bsp = node;213onc = 0;214posc = 0;215negc = 0;216
217AllocClassify(cnt);218AllocOn();219
220for(size_t i = 0; i < cnt; i++) {221double dt = bsp->n.Dot(vertex[i]);222isPos[i] = isNeg[i] = isOn[i] = false;223if(fabs(dt - bsp->d) < LENGTH_EPS) {224isOn[i] = true;225if(onc < 2) {226on[onc] = vertex[i];227}228onc++;229} else if(dt > bsp->d) {230isPos[i] = true;231posc++;232} else {233isNeg[i] = true;234negc++;235}236}237
238if(onc != 2 && onc != 1 && onc != 0) return false;239if(onc == 2) {240if(insertEdge) {241Vector e01 = (vertex[1]).Minus(vertex[0]);242Vector e12 = (vertex[2]).Minus(vertex[1]);243Vector out = e01.Cross(e12);244SEdge se = SEdge::From(on[0], on[1]);245bsp->edges = SBsp2::InsertOrCreateEdge(bsp->edges, &se, bsp->n, out);246}247}248return true;249}250
251bool ClassifyConvexVertices(Vector *vertex, size_t cnt, bool insertEdges) {252Vector inter[2];253int inters = 0;254
255npos = 0;256nneg = 0;257
258// Enlarge vertices list to consider two intersections259AllocVertices(cnt + 4);260
261for(size_t i = 0; i < cnt; i++) {262size_t ip = WRAP((i + 1), cnt);263
264if(isPos[i]) {265vpos[npos++] = vertex[i];266}267if(isNeg[i]) {268vneg[nneg++] = vertex[i];269}270if(isOn[i]) {271vneg[nneg++] = vertex[i];272vpos[npos++] = vertex[i];273}274if((isPos[i] && isNeg[ip]) || (isNeg[i] && isPos[ip])) {275Vector vi = bsp->IntersectionWith(vertex[i], vertex[ip]);276vpos[npos++] = vi;277vneg[nneg++] = vi;278
279if(inters >= 2) return false; // triangulate: XXX shouldn't happen but does280inter[inters++] = vi;281}282}283ssassert(npos <= cnt + 1 && nneg <= cnt + 1, "Impossible");284
285if(insertEdges) {286Vector e01 = (vertex[1]).Minus(vertex[0]);287Vector e12 = (vertex[2]).Minus(vertex[1]);288Vector out = e01.Cross(e12);289if(inters == 2) {290SEdge se = SEdge::From(inter[0], inter[1]);291bsp->edges = SBsp2::InsertOrCreateEdge(bsp->edges, &se, bsp->n, out);292} else if(inters == 1 && onc == 1) {293SEdge se = SEdge::From(inter[0], on[0]);294bsp->edges = SBsp2::InsertOrCreateEdge(bsp->edges, &se, bsp->n, out);295} else if(inters == 0 && onc == 2) {296// We already handled this on-plane existing edge297} else {298return false; //triangulate;299}300}301if(nneg < 3 || npos < 3) return false; // triangulate; // XXX302
303return true;304}305
306void ProcessEdgeInsert() {307ssassert(onc == 2, "Impossible");308
309Vector a, b;310if (!isOn[0]) { a = tr->b; b = tr->c; }311else if(!isOn[1]) { a = tr->c; b = tr->a; }312else if(!isOn[2]) { a = tr->a; b = tr->b; }313else ssassert(false, "Impossible");314
315SEdge se = SEdge::From(a, b);316bsp->edges = SBsp2::InsertOrCreateEdge(bsp->edges, &se, bsp->n, tr->Normal());317}318
319bool SplitIntoTwoTriangles(bool insertEdge) {320ssassert(posc == 1 && negc == 1 && onc == 1, "Impossible");321
322bool bpos;323Vector a, b, c;324
325// Standardize so that a is on the plane326if (isOn[0]) { a = tr->a; b = tr->b; c = tr->c; bpos = isPos[1];327} else if(isOn[1]) { a = tr->b; b = tr->c; c = tr->a; bpos = isPos[2];328} else if(isOn[2]) { a = tr->c; b = tr->a; c = tr->b; bpos = isPos[0];329} else ssassert(false, "Impossible");330
331AllocTriangles();332Vector bPc = bsp->IntersectionWith(b, c);333*btri = STriangle::From(tr->meta, a, b, bPc);334*ctri = STriangle::From(tr->meta, c, a, bPc);335
336if(insertEdge) {337SEdge se = SEdge::From(a, bPc);338bsp->edges = SBsp2::InsertOrCreateEdge(bsp->edges, &se, bsp->n, tr->Normal());339}340
341return bpos;342}343
344bool SplitIntoTwoPieces(bool insertEdge) {345Vector a, b, c;346if(posc == 2 && negc == 1) {347// Standardize so that a is on one side, and b and c are on the other.348if (isNeg[0]) { a = tr->a; b = tr->b; c = tr->c;349} else if(isNeg[1]) { a = tr->b; b = tr->c; c = tr->a;350} else if(isNeg[2]) { a = tr->c; b = tr->a; c = tr->b;351} else ssassert(false, "Impossible");352} else if(posc == 1 && negc == 2) {353if (isPos[0]) { a = tr->a; b = tr->b; c = tr->c;354} else if(isPos[1]) { a = tr->b; b = tr->c; c = tr->a;355} else if(isPos[2]) { a = tr->c; b = tr->a; c = tr->b;356} else ssassert(false, "Impossible");357} else ssassert(false, "Impossible");358
359Vector aPb = bsp->IntersectionWith(a, b);360Vector cPa = bsp->IntersectionWith(c, a);361AllocTriangle();362AllocQuad();363
364*btri = STriangle::From(tr->meta, a, aPb, cPa);365
366vpos[0] = aPb;367vpos[1] = b;368vpos[2] = c;369vpos[3] = cPa;370
371if(insertEdge) {372SEdge se = SEdge::From(aPb, cPa);373bsp->edges = SBsp2::InsertOrCreateEdge(bsp->edges, &se, bsp->n, btri->Normal());374}375
376return posc == 2 && negc == 1;377}378
379static SBsp3 *Triangulate(SBsp3 *bsp, const STriMeta &meta, Vector *vertex,380size_t cnt, SMesh *instead) {381for(size_t i = 0; i < cnt - 2; i++) {382STriangle tr = STriangle::From(meta, vertex[0], vertex[i + 1], vertex[i + 2]);383bsp = SBsp3::InsertOrCreate(bsp, &tr, instead);384}385return bsp;386}387};388
389void SBsp3::InsertConvexHow(BspClass how, STriMeta meta, Vector *vertex, size_t n,390SMesh *instead) {391switch(how) {392case BspClass::POS:393if(pos) {394pos = pos->InsertConvex(meta, vertex, n, instead);395return;396}397break;398
399case BspClass::NEG:400if(neg) {401neg = neg->InsertConvex(meta, vertex, n, instead);402return;403}404break;405
406default: ssassert(false, "Unexpected BSP insert type");407}408
409for(size_t i = 0; i < n - 2; i++) {410STriangle tr = STriangle::From(meta,411vertex[0], vertex[i+1], vertex[i+2]);412InsertHow(how, &tr, instead);413}414}
415
416SBsp3 *SBsp3::InsertConvex(STriMeta meta, Vector *vertex, size_t cnt, SMesh *instead) {417BspUtil *u = BspUtil::Alloc();418if(u->ClassifyConvex(vertex, cnt, this, !instead)) {419if(u->posc == 0) {420InsertConvexHow(BspClass::NEG, meta, vertex, cnt, instead);421return this;422}423if(u->negc == 0) {424InsertConvexHow(BspClass::POS, meta, vertex, cnt, instead);425return this;426}427
428if(u->ClassifyConvexVertices(vertex, cnt, !instead)) {429InsertConvexHow(BspClass::NEG, meta, u->vneg, u->nneg, instead);430InsertConvexHow(BspClass::POS, meta, u->vpos, u->npos, instead);431return this;432}433}434
435// We don't handle the special case for this; do it as triangles436return BspUtil::Triangulate(this, meta, vertex, cnt, instead);437}
438
439SBsp3 *SBsp3::InsertOrCreate(SBsp3 *where, STriangle *tr, SMesh *instead) {440if(where == NULL) {441if(instead) {442// ruevs: I do not think this code is reachable, but in443// principle should we use instead->keepInsideOtherShell444// in place of instead->flipNormal ?445if(instead->flipNormal) {446instead->atLeastOneDiscarded = true;447} else {448instead->AddTriangle(tr->meta, tr->a, tr->b, tr->c);449}450return NULL;451}452
453// Brand new node; so allocate for it, and fill us in.454SBsp3 *r = Alloc();455r->n = (tr->Normal()).WithMagnitude(1);456r->d = (tr->a).Dot(r->n);457r->tri = *tr;458return r;459}460where->Insert(tr, instead);461return where;462}
463
464void SBsp3::Insert(STriangle *tr, SMesh *instead) {465BspUtil *u = BspUtil::Alloc();466u->ClassifyTriangle(tr, this);467
468// All vertices in-plane469if(u->onc == 3) {470InsertHow(BspClass::COPLANAR, tr, instead);471return;472}473
474// No split required475if(u->posc == 0 || u->negc == 0) {476if(!instead && u->onc == 2) {477u->ProcessEdgeInsert();478}479
480if(u->posc > 0) {481InsertHow(BspClass::POS, tr, instead);482} else {483InsertHow(BspClass::NEG, tr, instead);484}485return;486}487
488// The polygon must be split into two triangles, one above, one below.489if(u->posc == 1 && u->negc == 1 && u->onc == 1) {490if(u->SplitIntoTwoTriangles(!instead)) {491InsertHow(BspClass::POS, u->btri, instead);492InsertHow(BspClass::NEG, u->ctri, instead);493} else {494InsertHow(BspClass::POS, u->ctri, instead);495InsertHow(BspClass::NEG, u->btri, instead);496}497return;498}499
500// The polygon must be split into two pieces: a triangle and a quad.501if(u->SplitIntoTwoPieces(!instead)) {502InsertConvexHow(BspClass::POS, tr->meta, u->vpos, 4, instead);503InsertHow(BspClass::NEG, u->btri, instead);504} else {505InsertConvexHow(BspClass::NEG, tr->meta, u->vpos, 4, instead);506InsertHow(BspClass::POS, u->btri, instead);507}508}
509
510void SBsp3::GenerateInPaintOrder(SMesh *m) const {511// Doesn't matter which branch we take if the normal has zero z512// component, so don't need a separate case for that.513if(n.z < 0) {514if(pos) pos->GenerateInPaintOrder(m);515} else {516if(neg) neg->GenerateInPaintOrder(m);517}518
519const SBsp3 *flip = this;520while(flip) {521m->AddTriangle(&(flip->tri));522flip = flip->more;523}524
525if(n.z < 0) {526if(neg) neg->GenerateInPaintOrder(m);527} else {528if(pos) pos->GenerateInPaintOrder(m);529}530}
531
532/////////////////////////////////
533
534Vector SBsp2::IntersectionWith(Vector a, Vector b) const {535double da = a.Dot(no) - d;536double db = b.Dot(no) - d;537ssassert(da*db < 0, "Expected segment to intersect BSP node");538
539double dab = (db - da);540return (a.ScaledBy(db/dab)).Plus(b.ScaledBy(-da/dab));541}
542
543SBsp2 *SBsp2::InsertOrCreateEdge(SBsp2 *where, SEdge *nedge, Vector nnp, Vector out) {544if(where == NULL) {545// Brand new node; so allocate for it, and fill us in.546SBsp2 *r = Alloc();547r->np = nnp;548r->no = ((r->np).Cross((nedge->b).Minus(nedge->a))).WithMagnitude(1);549if(out.Dot(r->no) < 0) {550r->no = (r->no).ScaledBy(-1);551}552r->d = (nedge->a).Dot(r->no);553r->edge = *nedge;554return r;555}556where->InsertEdge(nedge, nnp, out);557return where;558}
559
560void SBsp2::InsertEdge(SEdge *nedge, Vector nnp, Vector out) {561
562double dt[2] = { (nedge->a).Dot(no), (nedge->b).Dot(no) };563
564bool isPos[2] = {}, isNeg[2] = {}, isOn[2] = {};565for(int i = 0; i < 2; i++) {566if(fabs(dt[i] - d) < LENGTH_EPS) {567isOn[i] = true;568} else if(dt[i] > d) {569isPos[i] = true;570} else {571isNeg[i] = true;572}573}574
575if((isPos[0] && isPos[1])||(isPos[0] && isOn[1])||(isOn[0] && isPos[1])) {576pos = InsertOrCreateEdge(pos, nedge, nnp, out);577return;578}579if((isNeg[0] && isNeg[1])||(isNeg[0] && isOn[1])||(isOn[0] && isNeg[1])) {580neg = InsertOrCreateEdge(neg, nedge, nnp, out);581return;582}583if(isOn[0] && isOn[1]) {584SBsp2 *m = Alloc();585
586m->np = nnp;587m->no = ((m->np).Cross((nedge->b).Minus(nedge->a))).WithMagnitude(1);588if(out.Dot(m->no) < 0) {589m->no = (m->no).ScaledBy(-1);590}591m->d = (nedge->a).Dot(m->no);592m->edge = *nedge;593
594m->more = more;595more = m;596return;597}598if((isPos[0] && isNeg[1]) || (isNeg[0] && isPos[1])) {599Vector aPb = IntersectionWith(nedge->a, nedge->b);600
601SEdge ea = SEdge::From(nedge->a, aPb);602SEdge eb = SEdge::From(aPb, nedge->b);603
604if(isPos[0]) {605pos = InsertOrCreateEdge(pos, &ea, nnp, out);606neg = InsertOrCreateEdge(neg, &eb, nnp, out);607} else {608neg = InsertOrCreateEdge(neg, &ea, nnp, out);609pos = InsertOrCreateEdge(pos, &eb, nnp, out);610}611return;612}613ssassert(false, "Impossible");614}
615
616void SBsp2::InsertTriangleHow(BspClass how, STriangle *tr, SMesh *m, SBsp3 *bsp3) {617switch(how) {618case BspClass::POS:619if(pos) {620pos->InsertTriangle(tr, m, bsp3);621} else {622bsp3->InsertInPlane(/*pos2=*/true, tr, m);623}624break;625
626case BspClass::NEG:627if(neg) {628neg->InsertTriangle(tr, m, bsp3);629} else {630bsp3->InsertInPlane(/*pos2=*/false, tr, m);631}632break;633
634default: ssassert(false, "Unexpected BSP insert type");635}636}
637
638void SBsp2::InsertTriangle(STriangle *tr, SMesh *m, SBsp3 *bsp3) {639double dt[3] = { (tr->a).Dot(no), (tr->b).Dot(no), (tr->c).Dot(no) };640
641bool isPos[3] = {}, isNeg[3] = {}, isOn[3] = {};642int inc = 0, posc = 0, negc = 0;643for(int i = 0; i < 3; i++) {644if(fabs(dt[i] - d) < LENGTH_EPS) {645isOn[i] = true;646inc++;647} else if(dt[i] > d) {648isPos[i] = true;649posc++;650} else {651isNeg[i] = true;652negc++;653}654}655
656if(inc == 3) {657// All vertices on-line; so it's a degenerate triangle, to ignore.658return;659}660
661// No split required662if(posc == 0 || negc == 0) {663if(posc > 0) {664InsertTriangleHow(BspClass::POS, tr, m, bsp3);665} else {666InsertTriangleHow(BspClass::NEG, tr, m, bsp3);667}668return;669}670
671// The polygon must be split into two pieces, one above, one below.672Vector a, b, c;673
674if(posc == 1 && negc == 1 && inc == 1) {675bool bpos;676// Standardize so that a is on the plane677if (isOn[0]) { a = tr->a; b = tr->b; c = tr->c; bpos = isPos[1];678} else if(isOn[1]) { a = tr->b; b = tr->c; c = tr->a; bpos = isPos[2];679} else if(isOn[2]) { a = tr->c; b = tr->a; c = tr->b; bpos = isPos[0];680} else ssassert(false, "Impossible");681
682Vector bPc = IntersectionWith(b, c);683STriangle btri = STriangle::From(tr->meta, a, b, bPc);684STriangle ctri = STriangle::From(tr->meta, c, a, bPc);685
686if(bpos) {687InsertTriangleHow(BspClass::POS, &btri, m, bsp3);688InsertTriangleHow(BspClass::NEG, &ctri, m, bsp3);689} else {690InsertTriangleHow(BspClass::POS, &ctri, m, bsp3);691InsertTriangleHow(BspClass::NEG, &btri, m, bsp3);692}693
694return;695}696
697if(posc == 2 && negc == 1) {698// Standardize so that a is on one side, and b and c are on the other.699if (isNeg[0]) { a = tr->a; b = tr->b; c = tr->c;700} else if(isNeg[1]) { a = tr->b; b = tr->c; c = tr->a;701} else if(isNeg[2]) { a = tr->c; b = tr->a; c = tr->b;702} else ssassert(false, "Impossible");703
704} else if(posc == 1 && negc == 2) {705if (isPos[0]) { a = tr->a; b = tr->b; c = tr->c;706} else if(isPos[1]) { a = tr->b; b = tr->c; c = tr->a;707} else if(isPos[2]) { a = tr->c; b = tr->a; c = tr->b;708} else ssassert(false, "Impossible");709} else ssassert(false, "Impossible");710
711Vector aPb = IntersectionWith(a, b);712Vector cPa = IntersectionWith(c, a);713
714STriangle alone = STriangle::From(tr->meta, a, aPb, cPa);715STriangle quad1 = STriangle::From(tr->meta, aPb, b, c );716STriangle quad2 = STriangle::From(tr->meta, aPb, c, cPa);717
718if(posc == 2 && negc == 1) {719InsertTriangleHow(BspClass::POS, &quad1, m, bsp3);720InsertTriangleHow(BspClass::POS, &quad2, m, bsp3);721InsertTriangleHow(BspClass::NEG, &alone, m, bsp3);722} else {723InsertTriangleHow(BspClass::NEG, &quad1, m, bsp3);724InsertTriangleHow(BspClass::NEG, &quad2, m, bsp3);725InsertTriangleHow(BspClass::POS, &alone, m, bsp3);726}727
728return;729}
730