//----------------------------------------------------------------------------- // Operations on triangle meshes, like our mesh Booleans using the BSP, and // the stuff to check for watertightness. // // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- #include "solvespace.h" #include void SMesh::Clear() { 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 = {}; t.meta = meta; t.a = a; t.b = b; t.c = c; AddTriangle(&t); } void SMesh::AddTriangle(const STriangle *st) { l.Add(st); } void SMesh::DoBounding(Vector v, Vector *vmax, Vector *vmin) const { 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) const { int i; *vmin = Vector::From( 1e12, 1e12, 1e12); *vmax = Vector::From(-1e12, -1e12, -1e12); for(i = 0; i < l.n; i++) { const STriangle *st = &(l[i]); DoBounding(st->a, vmax, vmin); DoBounding(st->b, vmax, vmin); DoBounding(st->c, vmax, vmin); } } //---------------------------------------------------------------------------- // Report the edges of the boundary of the region(s) of our mesh that lie // within the plane n dot p = d. //---------------------------------------------------------------------------- void SMesh::MakeEdgesInPlaneInto(SEdgeList *sel, Vector n, double d) { SMesh m = {}; m.MakeFromCopyOf(this); // Delete all triangles in the mesh that do not lie in our export plane. m.l.ClearTags(); int i; for(i = 0; i < m.l.n; i++) { STriangle *tr = &(m.l[i]); if((fabs(n.Dot(tr->a) - d) >= LENGTH_EPS) || (fabs(n.Dot(tr->b) - d) >= LENGTH_EPS) || (fabs(n.Dot(tr->c) - d) >= LENGTH_EPS)) { tr->tag = 1; } } m.l.RemoveTagged(); // Select the naked edges in our resulting open mesh. SKdNode *root = SKdNode::From(&m); root->SnapToMesh(&m); root->MakeCertainEdgesInto(sel, EdgeKind::NAKED_OR_SELF_INTER, /*coplanarIsInter=*/false, NULL, NULL); m.Clear(); } void SMesh::MakeOutlinesInto(SOutlineList *sol, EdgeKind edgeKind) { SKdNode *root = SKdNode::From(this); root->MakeOutlinesInto(sol, edgeKind); } //----------------------------------------------------------------------------- // When we are called, all of the triangles from l[start] to the end must // be coplanar. So we try to find a set of fewer triangles that covers the // exact same area, in order to reduce the number of triangles in the mesh. // We use this after a triangle has been split against the BSP. // // This is really ugly code; basically it just pastes things together to // form convex polygons, merging collinear edges when possible, then // triangulates the convex poly. //----------------------------------------------------------------------------- void SMesh::Simplify(int start) { int maxTriangles = (l.n - start) + 10; STriMeta meta = l[start].meta; STriangle *tout = (STriangle *)MemAlloc(maxTriangles*sizeof(*tout)); int toutc = 0; Vector n = Vector::From(0, 0, 0); Vector *conv = (Vector *)MemAlloc(maxTriangles*3*sizeof(*conv)); int convc = 0; int start0 = start; int i, j; for(i = start; i < l.n; i++) { STriangle *tr = &(l[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[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[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 std::move(conv+j+1, conv+convc, conv+j); 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] std::move_backward(conv+j+1, conv+convc, conv+convc+1); 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])); } MemFree(tout); MemFree(conv); } void SMesh::AddAgainstBsp(SMesh *srcm, SBsp3 *bsp3) { int i; for(i = 0; i < srcm->l.n; i++) { STriangle *st = &(srcm->l[i]); int pn = l.n; atLeastOneDiscarded = false; SBsp3::InsertOrCreate(bsp3, 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::MakeFromUnionOf(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::MakeFromDifferenceOf(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); } void SMesh::MakeFromCopyOf(SMesh *a) { ssassert(this != a, "Can't make from copy of self"); for(int i = 0; i < a->l.n; i++) { AddTriangle(&(a->l[i])); } } void SMesh::MakeFromAssemblyOf(SMesh *a, SMesh *b) { MakeFromCopyOf(a); MakeFromCopyOf(b); } void SMesh::MakeFromTransformationOf(SMesh *a, Vector trans, Quaternion q, double scale) { STriangle *tr; for(tr = a->l.First(); tr; tr = a->l.NextAfter(tr)) { STriangle tt = *tr; tt.a = (tt.a).ScaledBy(scale); tt.b = (tt.b).ScaledBy(scale); tt.c = (tt.c).ScaledBy(scale); if(scale < 0) { // The mirroring would otherwise turn a closed mesh inside out. swap(tt.a, tt.b); } tt.a = (q.Rotate(tt.a)).Plus(trans); tt.b = (q.Rotate(tt.b)).Plus(trans); tt.c = (q.Rotate(tt.c)).Plus(trans); AddTriangle(&tt); } } bool SMesh::IsEmpty() const { return (l.IsEmpty()); } uint32_t SMesh::FirstIntersectionWith(Point2d mp) const { Vector rayPoint = SS.GW.UnProjectPoint3(Vector::From(mp.x, mp.y, 0.0)); Vector rayDir = SS.GW.UnProjectPoint3(Vector::From(mp.x, mp.y, 1.0)).Minus(rayPoint); uint32_t face = 0; double faceT = VERY_NEGATIVE; for(int i = 0; i < l.n; i++) { const STriangle &tr = l[i]; if(tr.meta.face == 0) continue; double t; if(!tr.Raytrace(rayPoint, rayDir, &t, NULL)) continue; if(t > faceT) { face = tr.meta.face; faceT = t; } } return face; } Vector SMesh::GetCenterOfMass() const { Vector center = {}; double vol = 0.0; for(int i = 0; i < l.n; i++) { const STriangle &tr = l[i]; double tvol = tr.SignedVolume(); center = center.Plus(tr.a.Plus(tr.b.Plus(tr.c)).ScaledBy(tvol / 4.0)); vol += tvol; } return center.ScaledBy(1.0 / vol); } STriangleLl *STriangleLl::Alloc() { return (STriangleLl *)AllocTemporary(sizeof(STriangleLl)); } SKdNode *SKdNode::Alloc() { 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[i]; } srand(0); int n = m->l.n; while(n > 1) { int k = rand() % n; n--; swap(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); } SKdNode *SKdNode::From(STriangleLl *tll) { int which = 0; SKdNode *ret = Alloc(); int i; int gtc[3] = { 0, 0, 0 }, ltc[3] = { 0, 0, 0 }, allc = 0; double badness[3] = { 0, 0, 0 }; double split[3] = { 0, 0, 0 }; if(!tll) { goto leaf; } for(i = 0; i < 3; i++) { int tcnt = 0; STriangleLl *ll; for(ll = tll; ll; ll = ll->next) { 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 < 3 || allc == gtc[which] || allc == ltc[which]) { goto leaf; } STriangleLl *ll; STriangleLl *lgt, *llt; lgt = 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); ret->lt = SKdNode::From(llt); return ret; leaf: ret->tris = tll; return ret; } void SKdNode::ClearTags() const { 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) const { 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::ListTrianglesInto(std::vector *tl) const { if(gt) gt->ListTrianglesInto(tl); if(lt) lt->ListTrianglesInto(tl); STriangleLl *ll; for(ll = tris; ll; ll = ll->next) { if(ll->tri->tag) continue; tl->push_back(ll->tri); ll->tri->tag = 1; } } //----------------------------------------------------------------------------- // If any triangles in the mesh have an edge that goes through v (but not // a vertex at v), then split those triangles so that they now have a vertex // there. The existing triangle is modified, and the new triangle appears // in extras. //----------------------------------------------------------------------------- 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++) { double trA = (tr->a).Element(k); double trB = (tr->b).Element(k); double trC = (tr->c).Element(k); double vk = v.Element(k); if(trA < vk - KDTREE_EPS && trB < vk - KDTREE_EPS && trC < vk - KDTREE_EPS) { mightHit = false; break; } if(trA > vk + KDTREE_EPS && trB > vk + KDTREE_EPS && trC > vk + 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(tr->IsDegenerate()) { 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; } } } } //----------------------------------------------------------------------------- // Snap to each vertex of each triangle of the given mesh. If the given mesh // is identical to the mesh used to make this kd tree, then the result should // be a vertex-to-vertex mesh. //----------------------------------------------------------------------------- void SKdNode::SnapToMesh(SMesh *m) { int i, j, k; for(i = 0; i < m->l.n; i++) { STriangle *tr = &(m->l[i]); if(tr->IsDegenerate()) { continue; } for(j = 0; j < 3; j++) { Vector v = tr->vertices[j]; SMesh extra = {}; SnapToVertex(v, &extra); for(k = 0; k < extra.l.n; k++) { STriangle *tra = (STriangle *)AllocTemporary(sizeof(*tra)); *tra = extra.l[k]; AddTriangle(tra); } extra.Clear(); } } } //----------------------------------------------------------------------------- // For all the edges in sel, split them against the given triangle, and test // them for occlusion. sel is both our input and our output. tag indicates // whether an edge is occluded. //----------------------------------------------------------------------------- void SKdNode::SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr) const { SEdgeList seln = {}; Vector tn = tr->Normal().WithMagnitude(1); double td = tn.Dot(tr->a); // Consider front-facing triangles only. if(tn.z > LENGTH_EPS) { // If the edge crosses our triangle's plane, then split into above // and below parts. Note that we must preserve auxA, which contains // the style associated with this line, as well as the tag, which // contains the occlusion status. SEdge *se; for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) { double da = (se->a).Dot(tn) - td, db = (se->b).Dot(tn) - td; if((da < -LENGTH_EPS && db > LENGTH_EPS) || (db < -LENGTH_EPS && da > LENGTH_EPS)) { Vector m = Vector::AtIntersectionOfPlaneAndLine( tn, td, se->a, se->b, NULL); seln.AddEdge(m, se->b, se->auxA, 0, se->tag); se->b = m; } } for(se = seln.l.First(); se; se = seln.l.NextAfter(se)) { sel->AddEdge(se->a, se->b, se->auxA, 0, se->tag); } seln.Clear(); for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) { Vector pt = ((se->a).Plus(se->b)).ScaledBy(0.5); if(pt.Dot(tn) - td > -LENGTH_EPS) { // Edge is in front of or on our plane (remember, tn.z > 0) // so it is exempt from further splitting se->auxB = 1; } else { // Edge is behind our plane, needs further splitting se->auxB = 0; } } // Considering only the (x, y) coordinates, split the edge against our // triangle. Point2d a = (tr->a).ProjectXy(), b = (tr->b).ProjectXy(), c = (tr->c).ProjectXy(); Point2d n[3] = { (b.Minus(a)).Normal().WithMagnitude(1), (c.Minus(b)).Normal().WithMagnitude(1), (a.Minus(c)).Normal().WithMagnitude(1) }; double d[3] = { n[0].Dot(b), n[1].Dot(c), n[2].Dot(a) }; // Split all of the edges where they intersect the triangle edges int i; for(i = 0; i < 3; i++) { for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) { if(se->auxB) continue; Point2d ap = (se->a).ProjectXy(), bp = (se->b).ProjectXy(); double da = n[i].Dot(ap) - d[i], db = n[i].Dot(bp) - d[i]; if((da < -LENGTH_EPS && db > LENGTH_EPS) || (db < -LENGTH_EPS && da > LENGTH_EPS)) { double dab = (db - da); Vector spl = ((se->a).ScaledBy( db/dab)).Plus( (se->b).ScaledBy(-da/dab)); seln.AddEdge(spl, se->b, se->auxA, 0, se->tag); se->b = spl; } } for(se = seln.l.First(); se; se = seln.l.NextAfter(se)) { // The split pieces are all behind the triangle, since only // edges behind the triangle got split. So their auxB is 0. sel->AddEdge(se->a, se->b, se->auxA, 0, se->tag); } seln.Clear(); } for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) { bool occluded; if(se->auxB) { // Lies above or on the triangle plane, so triangle doesn't // occlude it. occluded = false; } else { // Test the segment to see if it lies outside the triangle // (i.e., outside wrt at least one edge), and keep it only // then. Point2d pt = ((se->a).Plus(se->b).ScaledBy(0.5)).ProjectXy(); occluded = true; for(i = 0; i < 3; i++) { // If the test point lies on the boundary of our triangle, // then we still discard the edge. if(n[i].Dot(pt) - d[i] > LENGTH_EPS) occluded = false; } } if(occluded) { se->tag = 1; } } } } //----------------------------------------------------------------------------- // Given an edge orig, occlusion test it against our mesh. We output an edge // list in sel, where only invisible portions of the edge are tagged. //----------------------------------------------------------------------------- void SKdNode::OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt) const { if(gt && lt) { double ac = (orig.a).Element(which), bc = (orig.b).Element(which); // We can ignore triangles that are separated in x or y, but triangles // that are separated in z may still contribute if(ac < c + KDTREE_EPS || bc < c + KDTREE_EPS || which == 2) { lt->OcclusionTestLine(orig, sel, cnt); } if(ac > c - KDTREE_EPS || bc > c - KDTREE_EPS || which == 2) { gt->OcclusionTestLine(orig, sel, cnt); } } else { STriangleLl *ll; for(ll = tris; ll; ll = ll->next) { STriangle *tr = ll->tri; if(tr->tag == cnt) continue; SplitLinesAgainstTriangle(sel, tr); tr->tag = cnt; } } } //----------------------------------------------------------------------------- // Search the mesh for a triangle with an edge from b to a (i.e., the mate // for the edge from a to b), and increment info->count each time that we // find one. If a triangle is found, then report whether it is front- or // back-facing using info->frontFacing. And regardless of whether a mate is // found, report whether the edge intersects the mesh with info->intersectsMesh; // if coplanarIsInter then we count the edge as intersecting if it's coplanar // with a triangle in the mesh, otherwise not. //----------------------------------------------------------------------------- void SKdNode::FindEdgeOn(Vector a, Vector b, int cnt, bool coplanarIsInter, EdgeOnInfo *info) const { 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, cnt, coplanarIsInter, info); } if(ac > c - KDTREE_EPS || bc > c - KDTREE_EPS) { gt->FindEdgeOn(a, b, cnt, coplanarIsInter, info); } return; } // We are a leaf node; so we iterate over all the triangles in our // linked list. 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))) { info->count++; // Record whether this triangle is front- or back-facing. if(tr->Normal().z > LENGTH_EPS) { info->frontFacing = true; } else { info->frontFacing = false; } // Record the triangle info->tr = tr; // And record which vertices a and b correspond to info->ai = a.Equals(tr->a) ? 0 : (a.Equals(tr->b) ? 1 : 2); info->bi = b.Equals(tr->a) ? 0 : (b.Equals(tr->b) ? 1 : 2); } 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)) { if(coplanarIsInter) { info->intersectsMesh = true; } else { Vector p = Vector::AtIntersectionOfPlaneAndLine( n, d, a, b, NULL); Vector ta = tr->a, tb = tr->b, tc = tr->c; if((p.DistanceToLine(ta, tb.Minus(ta)) < LENGTH_EPS) || (p.DistanceToLine(tb, tc.Minus(tb)) < LENGTH_EPS) || (p.DistanceToLine(tc, ta.Minus(tc)) < LENGTH_EPS)) { // Intersection lies on edge. This happens when // our edge is from a triangle coplanar with // another triangle in the mesh. We don't test // the edge against triangles whose plane contains // that edge, but we do end up testing against // the coplanar triangle's neighbours, which we // will intersect on their edges. } else { info->intersectsMesh = true; } } } } } // Ensure that we don't count this triangle twice if it appears // in two buckets of the kd tree. tr->tag = cnt; } } static bool CheckAndAddTrianglePair(std::set> *pairs, STriangle *a, STriangle *b) { if(pairs->find(std::make_pair(a, b)) != pairs->end() || pairs->find(std::make_pair(b, a)) != pairs->end()) return true; pairs->emplace(a, b); return false; } //----------------------------------------------------------------------------- // Pick certain classes of edges out from our mesh. These might be: // * naked edges (i.e., edges with no anti-parallel neighbor) and self- // intersecting edges (i.e., edges that cross another triangle) // * turning edges (i.e., edges where a front-facing triangle joins // a back-facing triangle) // * emphasized edges (i.e., edges where a triangle from one face joins // a triangle from a different face) //----------------------------------------------------------------------------- void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, EdgeKind how, bool coplanarIsInter, bool *inter, bool *leaky, int auxA) const { if(inter) *inter = false; if(leaky) *leaky = false; std::vector tris; ClearTags(); ListTrianglesInto(&tris); std::set> edgeTris; int cnt = 1234; for(STriangle *tr : tris) { for(int j = 0; j < 3; j++) { Vector a = tr->vertices[j]; Vector b = tr->vertices[(j + 1) % 3]; SKdNode::EdgeOnInfo info = {}; FindEdgeOn(a, b, cnt, coplanarIsInter, &info); switch(how) { case EdgeKind::NAKED_OR_SELF_INTER: if(info.count != 1) { sel->AddEdge(a, b, auxA); if(leaky) *leaky = true; } if(info.intersectsMesh) { sel->AddEdge(a, b, auxA); if(inter) *inter = true; } break; case EdgeKind::SELF_INTER: if(info.intersectsMesh) { sel->AddEdge(a, b, auxA); if(inter) *inter = true; } break; case EdgeKind::TURNING: if((tr->Normal().z < LENGTH_EPS) && (info.count == 1) && info.frontFacing) { if(CheckAndAddTrianglePair(&edgeTris, tr, info.tr)) break; // This triangle is back-facing (or on edge), and // this edge has exactly one mate, and that mate is // front-facing. So this is a turning edge. sel->AddEdge(a, b, auxA); } break; case EdgeKind::EMPHASIZED: if(info.count == 1 && tr->meta.face != info.tr->meta.face) { if(CheckAndAddTrianglePair(&edgeTris, tr, info.tr)) break; // The two triangles that join at this edge come from // different faces; either really different faces, // or one is from a face and the other is zero (i.e., // not from a face). sel->AddEdge(a, b, auxA); } break; case EdgeKind::SHARP: if(info.count == 1) { Vector na0 = tr->normals[j].WithMagnitude(1.0); Vector nb0 = tr->normals[(j + 1) % 3].WithMagnitude(1.0); Vector na1 = info.tr->normals[info.ai].WithMagnitude(1.0); Vector nb1 = info.tr->normals[info.bi].WithMagnitude(1.0); if(!((na0.Equals(na1) && nb0.Equals(nb1)) || (na0.Equals(nb1) && nb0.Equals(na1)))) { if(CheckAndAddTrianglePair(&edgeTris, tr, info.tr)) break; // The two triangles that join at this edge meet at a sharp // angle. This implies they come from different faces. sel->AddEdge(a, b, auxA); } } break; } cnt++; } } } void SKdNode::MakeOutlinesInto(SOutlineList *sol, EdgeKind edgeKind) const { std::vector tris; ClearTags(); ListTrianglesInto(&tris); std::set> edgeTris; int cnt = 1234; for(STriangle *tr : tris) { for(int j = 0; j < 3; j++) { Vector a = tr->vertices[j]; Vector b = tr->vertices[(j + 1) % 3]; SKdNode::EdgeOnInfo info = {}; FindEdgeOn(a, b, cnt, /*coplanarIsInter=*/false, &info); cnt++; if(info.count != 1) continue; if(CheckAndAddTrianglePair(&edgeTris, tr, info.tr)) continue; int tag = 0; switch(edgeKind) { case EdgeKind::EMPHASIZED: if(tr->meta.face != info.tr->meta.face) { tag = 1; } break; case EdgeKind::SHARP: { Vector na0 = tr->normals[j].WithMagnitude(1.0); Vector nb0 = tr->normals[(j + 1) % 3].WithMagnitude(1.0); Vector na1 = info.tr->normals[info.ai].WithMagnitude(1.0); Vector nb1 = info.tr->normals[info.bi].WithMagnitude(1.0); if(!((na0.Equals(na1) && nb0.Equals(nb1)) || (na0.Equals(nb1) && nb0.Equals(na1)))) { tag = 1; } } break; default: ssassert(false, "Unexpected edge kind"); } Vector nl = tr->Normal().WithMagnitude(1.0); Vector nr = info.tr->Normal().WithMagnitude(1.0); // We don't add edges with the same left and right // normals because they can't produce outlines. if(tag == 0 && nl.Equals(nr)) continue; sol->AddEdge(a, b, nl, nr, tag); } } } bool SOutline::IsVisible(Vector projDir) const { double ldot = nl.Dot(projDir); double rdot = nr.Dot(projDir); return (ldot > -LENGTH_EPS) == (rdot < LENGTH_EPS) || (rdot > -LENGTH_EPS) == (ldot < LENGTH_EPS); } void SOutlineList::Clear() { l.Clear(); } void SOutlineList::AddEdge(Vector a, Vector b, Vector nl, Vector nr, int tag) { SOutline so = {}; so.a = a; so.b = b; so.nl = nl; so.nr = nr; so.tag = tag; l.Add(&so); } void SOutlineList::ListTaggedInto(SEdgeList *el, int auxA, int auxB) { for(const SOutline &so : l) { if(so.tag == 0) continue; el->AddEdge(so.a, so.b, auxA, auxB); } } void SOutlineList::MakeFromCopyOf(SOutlineList *sol) { for(SOutline *so = sol->l.First(); so; so = sol->l.NextAfter(so)) { l.Add(so); } } void SMesh::PrecomputeTransparency() { std::sort(l.begin(), l.end(), [&](const STriangle &sta, const STriangle &stb) { RgbaColor colora = sta.meta.color, colorb = stb.meta.color; bool opaquea = colora.IsEmpty() || colora.alpha == 255, opaqueb = colorb.IsEmpty() || colorb.alpha == 255; if(!opaquea || !opaqueb) isTransparent = true; return (opaquea != opaqueb && opaquea == true); }); } void SMesh::RemoveDegenerateTriangles() { for(auto &tr : l) { tr.tag = (int)tr.IsDegenerate(); } l.RemoveTagged(); } double SMesh::CalculateVolume() const { double vol = 0; for(STriangle tr : l) { // Translate to place vertex A at (x, y, 0) Vector trans = Vector::From(tr.a.x, tr.a.y, 0); tr.a = (tr.a).Minus(trans); tr.b = (tr.b).Minus(trans); tr.c = (tr.c).Minus(trans); // Rotate to place vertex B on the y-axis. Depending on // whether the triangle is CW or CCW, C is either to the // right or to the left of the y-axis. This handles the // sign of our normal. Vector u = Vector::From(-tr.b.y, tr.b.x, 0); u = u.WithMagnitude(1); Vector v = Vector::From(tr.b.x, tr.b.y, 0); v = v.WithMagnitude(1); Vector n = Vector::From(0, 0, 1); tr.a = (tr.a).DotInToCsys(u, v, n); tr.b = (tr.b).DotInToCsys(u, v, n); tr.c = (tr.c).DotInToCsys(u, v, n); n = tr.Normal().WithMagnitude(1); // Triangles on edge don't contribute if(fabs(n.z) < LENGTH_EPS) continue; // The plane has equation p dot n = a dot n double d = (tr.a).Dot(n); // nx*x + ny*y + nz*z = d // nz*z = d - nx*x - ny*y double A = -n.x/n.z, B = -n.y/n.z, C = d/n.z; double mac = tr.c.y/tr.c.x, mbc = (tr.c.y - tr.b.y)/tr.c.x; double xc = tr.c.x, yb = tr.b.y; // I asked Maple for // int(int(A*x + B*y +C, y=mac*x..(mbc*x + yb)), x=0..xc); double integral = (1.0/3)*( A*(mbc-mac)+ (1.0/2)*B*(mbc*mbc-mac*mac) )*(xc*xc*xc)+ (1.0/2)*(A*yb+B*yb*mbc+C*(mbc-mac))*xc*xc+ C*yb*xc+ (1.0/2)*B*yb*yb*xc; vol += integral; } return vol; } double SMesh::CalculateSurfaceArea(const std::vector &faces) const { double area = 0.0; for(uint32_t f : faces) { for(const STriangle &t : l) { if(f != t.meta.face) continue; area += t.Area(); } } return area; }