//----------------------------------------------------------------------------- // Trim this surface against the specified shell, in the way that's appropriate // for the specified Boolean operation type (and which operand we are). We // also need a pointer to the shell that contains our own surface, since that // contains our original trim curves. //----------------------------------------------------------------------------- SSurface SSurface::MakeCopyTrimAgainst(SShell *parent, SShell *sha, SShell *shb, SShell *into, int type) { bool opA = (parent == sha); SShell *agnst = opA ? shb : sha; SSurface ret; // The returned surface is identical, just the trim curves change ret = *this; ret.trim = {}; // First, build a list of the existing trim curves; update them to use // the split curves. STrimBy *stb; for(stb = trim.First(); stb; stb = trim.NextAfter(stb)) { STrimBy stn = *stb; stn.curve = (parent->curve.FindById(stn.curve))->newH; ret.trim.Add(&stn); } if(type == SShell::AS_DIFFERENCE && !opA) { // The second operand of a Boolean difference gets turned inside out ret.Reverse(); } // Build up our original trim polygon; remember the coordinates could // be changed if we just flipped the surface normal, and we are using // the split curves (not the original curves). SEdgeList orig = {}; ret.MakeEdgesInto(into, &orig, AS_UV); ret.trim.Clear(); // which means that we can't necessarily use the old BSP... SBspUv *origBsp = SBspUv::From(&orig, &ret); // And now intersect the other shell against us SEdgeList inter = {}; SSurface *ss; for(ss = agnst->surface.First(); ss; ss = agnst->surface.NextAfter(ss)) { SCurve *sc; for(sc = into->curve.First(); sc; sc = into->curve.NextAfter(sc)) { if(sc->source != SCurve::FROM_INTERSECTION) continue; if(opA) { if(sc->surfA.v != h.v || sc->surfB.v != ss->h.v) continue; } else { if(sc->surfB.v != h.v || sc->surfA.v != ss->h.v) continue; } int i; for(i = 1; i < sc->pts.n; i++) { Vector a = sc->pts.elem[i-1].p, b = sc->pts.elem[i].p; Point2d auv, buv; ss->ClosestPointTo(a, &(auv.x), &(auv.y)); ss->ClosestPointTo(b, &(buv.x), &(buv.y)); int c = (ss->bsp) ? ss->bsp->ClassifyEdge(auv, buv, ss) : SBspUv::OUTSIDE; if(c != SBspUv::OUTSIDE) { Vector ta = Vector::From(0, 0, 0); Vector tb = Vector::From(0, 0, 0); ret.ClosestPointTo(a, &(ta.x), &(ta.y)); ret.ClosestPointTo(b, &(tb.x), &(tb.y)); Vector tn = ret.NormalAt(ta.x, ta.y); Vector sn = ss->NormalAt(auv.x, auv.y); // We are subtracting the portion of our surface that // lies in the shell, so the in-plane edge normal should // point opposite to the surface normal. bool bkwds = true; if((tn.Cross(b.Minus(a))).Dot(sn) < 0) bkwds = !bkwds; if(type == SShell::AS_DIFFERENCE && !opA) bkwds = !bkwds; if(bkwds) { inter.AddEdge(tb, ta, sc->h.v, 1); } else { inter.AddEdge(ta, tb, sc->h.v, 0); } } } } } // Record all the points where more than two edges join, which I will call // the choosing points. If two edges join at a non-choosing point, then // they must either both be kept or both be discarded (since that would // otherwise create an open contour). SPointList choosing = {}; SEdge *se; for(se = orig.l.First(); se; se = orig.l.NextAfter(se)) { choosing.IncrementTagFor(se->a); choosing.IncrementTagFor(se->b); } for(se = inter.l.First(); se; se = inter.l.NextAfter(se)) { choosing.IncrementTagFor(se->a); choosing.IncrementTagFor(se->b); } SPoint *sp; for(sp = choosing.l.First(); sp; sp = choosing.l.NextAfter(sp)) { if(sp->tag == 2) { sp->tag = 1; } else { sp->tag = 0; } } choosing.l.RemoveTagged(); // The list of edges to trim our new surface, a combination of edges from // our original and intersecting edge lists. SEdgeList final = {}; while(orig.l.n > 0) { SEdgeList chain = {}; FindChainAvoiding(&orig, &chain, &choosing); // Arbitrarily choose an edge within the chain to classify; they // should all be the same, though. se = &(chain.l.elem[chain.l.n/2]); Point2d auv = (se->a).ProjectXy(), buv = (se->b).ProjectXy(); Vector pt, enin, enout, surfn; ret.EdgeNormalsWithinSurface(auv, buv, &pt, &enin, &enout, &surfn, se->auxA, into, sha, shb); int indir_shell, outdir_shell, indir_orig, outdir_orig; indir_orig = SShell::INSIDE; outdir_orig = SShell::OUTSIDE; agnst->ClassifyEdge(&indir_shell, &outdir_shell, ret.PointAt(auv), ret.PointAt(buv), pt, enin, enout, surfn); if(KeepEdge(type, opA, indir_shell, outdir_shell, indir_orig, outdir_orig)) { for(se = chain.l.First(); se; se = chain.l.NextAfter(se)) { final.AddEdge(se->a, se->b, se->auxA, se->auxB); } } chain.Clear(); }
void SolveSpace::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *sm, Vector u, Vector v, Vector n, Vector origin, double cameraTan, VectorFileWriter *out) { double s = 1.0 / SS.exportScale; // Project into the export plane; so when we're done, z doesn't matter, // and x and y are what goes in the DXF. SEdge *e; for(e = sel->l.First(); e; e = sel->l.NextAfter(e)) { // project into the specified csys, and apply export scale (e->a) = e->a.InPerspective(u, v, n, origin, cameraTan).ScaledBy(s); (e->b) = e->b.InPerspective(u, v, n, origin, cameraTan).ScaledBy(s); } SBezier *b; if(sbl) { for(b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) { *b = b->InPerspective(u, v, n, origin, cameraTan); int i; for(i = 0; i <= b->deg; i++) { b->ctrl[i] = (b->ctrl[i]).ScaledBy(s); } } } // If cutter radius compensation is requested, then perform it now if(fabs(SS.exportOffset) > LENGTH_EPS) { // assemble those edges into a polygon, and clear the edge list SPolygon sp; ZERO(&sp); sel->AssemblePolygon(&sp, NULL); sel->Clear(); SPolygon compd; ZERO(&compd); sp.normal = Vector::From(0, 0, -1); sp.FixContourDirections(); sp.OffsetInto(&compd, SS.exportOffset*s); sp.Clear(); compd.MakeEdgesInto(sel); compd.Clear(); } // Now the triangle mesh; project, then build a BSP to perform // occlusion testing and generated the shaded surfaces. SMesh smp; ZERO(&smp); if(sm) { Vector l0 = (SS.lightDir[0]).WithMagnitude(1), l1 = (SS.lightDir[1]).WithMagnitude(1); STriangle *tr; for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) { STriangle tt = *tr; tt.a = (tt.a).InPerspective(u, v, n, origin, cameraTan).ScaledBy(s); tt.b = (tt.b).InPerspective(u, v, n, origin, cameraTan).ScaledBy(s); tt.c = (tt.c).InPerspective(u, v, n, origin, cameraTan).ScaledBy(s); // And calculate lighting for the triangle Vector n = tt.Normal().WithMagnitude(1); double lighting = SS.ambientIntensity + max(0, (SS.lightIntensity[0])*(n.Dot(l0))) + max(0, (SS.lightIntensity[1])*(n.Dot(l1))); double r = min(1, REDf (tt.meta.color)*lighting), g = min(1, GREENf(tt.meta.color)*lighting), b = min(1, BLUEf (tt.meta.color)*lighting); tt.meta.color = RGBf(r, g, b); smp.AddTriangle(&tt); } } // Use the BSP routines to generate the split triangles in paint order. SBsp3 *bsp = SBsp3::FromMesh(&smp); SMesh sms; ZERO(&sms); bsp->GenerateInPaintOrder(&sms); // And cull the back-facing triangles STriangle *tr; sms.l.ClearTags(); for(tr = sms.l.First(); tr; tr = sms.l.NextAfter(tr)) { Vector n = tr->Normal(); if(n.z < 0) { tr->tag = 1; } } sms.l.RemoveTagged(); // And now we perform hidden line removal if requested SEdgeList hlrd; ZERO(&hlrd); if(sm && !SS.GW.showHdnLines) { SKdNode *root = SKdNode::From(&smp); // Generate the edges where a curved surface turns from front-facing // to back-facing. if(SS.GW.showEdges) { root->MakeCertainEdgesInto(sel, SKdNode::TURNING_EDGES, false, NULL, NULL); } root->ClearTags(); int cnt = 1234; SEdge *se; for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) { if(se->auxA == Style::CONSTRAINT) { // Constraints should not get hidden line removed; they're // always on top. hlrd.AddEdge(se->a, se->b, se->auxA); continue; } SEdgeList out; ZERO(&out); // Split the original edge against the mesh out.AddEdge(se->a, se->b, se->auxA); root->OcclusionTestLine(*se, &out, cnt); // the occlusion test splits unnecessarily; so fix those out.MergeCollinearSegments(se->a, se->b); cnt++; // And add the results to our output SEdge *sen; for(sen = out.l.First(); sen; sen = out.l.NextAfter(sen)) { hlrd.AddEdge(sen->a, sen->b, sen->auxA); } out.Clear(); } sel = &hlrd; } // We kept the line segments and Beziers separate until now; but put them // all together, and also project everything into the xy plane, since not // all export targets ignore the z component of the points. for(e = sel->l.First(); e; e = sel->l.NextAfter(e)) { SBezier sb = SBezier::From(e->a, e->b); sb.auxA = e->auxA; sbl->l.Add(&sb); } for(b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) { for(int i = 0; i <= b->deg; i++) { b->ctrl[i].z = 0; } } // If possible, then we will assemble these output curves into loops. They // will then get exported as closed paths. SBezierLoopSetSet sblss; ZERO(&sblss); SBezierList leftovers; ZERO(&leftovers); SSurface srf = SSurface::FromPlane(Vector::From(0, 0, 0), Vector::From(1, 0, 0), Vector::From(0, 1, 0)); SPolygon spxyz; ZERO(&spxyz); bool allClosed; SEdge notClosedAt; sbl->l.ClearTags(); sblss.FindOuterFacesFrom(sbl, &spxyz, &srf, SS.ChordTolMm()*s, &allClosed, ¬ClosedAt, NULL, NULL, &leftovers); for(b = leftovers.l.First(); b; b = leftovers.l.NextAfter(b)) { sblss.AddOpenPath(b); } // Now write the lines and triangles to the output file out->Output(&sblss, &sms); leftovers.Clear(); spxyz.Clear(); sblss.Clear(); smp.Clear(); sms.Clear(); hlrd.Clear(); }
void SPolygon::UvGridTriangulateInto(SMesh *mesh, SSurface *srf) { SEdgeList orig; ZERO(&orig); MakeEdgesInto(&orig); SEdgeList holes; ZERO(&holes); normal = Vector::From(0, 0, 1); FixContourDirections(); // Build a rectangular grid, with horizontal and vertical lines in the // uv plane. The spacing of these lines is adaptive, so calculate that. List<double> li, lj; ZERO(&li); ZERO(&lj); double v = 0; li.Add(&v); srf->MakeTriangulationGridInto(&li, 0, 1, true); lj.Add(&v); srf->MakeTriangulationGridInto(&lj, 0, 1, false); // Now iterate over each quad in the grid. If it's outside the polygon, // or if it intersects the polygon, then we discard it. Otherwise we // generate two triangles in the mesh, and cut it out of our polygon. int i, j; for(i = 0; i < (li.n - 1); i++) { for(j = 0; j < (lj.n - 1); j++) { double us = li.elem[i], uf = li.elem[i+1], vs = lj.elem[j], vf = lj.elem[j+1]; Vector a = Vector::From(us, vs, 0), b = Vector::From(us, vf, 0), c = Vector::From(uf, vf, 0), d = Vector::From(uf, vs, 0); if(orig.AnyEdgeCrossings(a, b, NULL) || orig.AnyEdgeCrossings(b, c, NULL) || orig.AnyEdgeCrossings(c, d, NULL) || orig.AnyEdgeCrossings(d, a, NULL)) { continue; } // There's no intersections, so it doesn't matter which point // we decide to test. if(!this->ContainsPoint(a)) { continue; } // Add the quad to our mesh STriangle tr; ZERO(&tr); tr.a = a; tr.b = b; tr.c = c; mesh->AddTriangle(&tr); tr.a = a; tr.b = c; tr.c = d; mesh->AddTriangle(&tr); holes.AddEdge(a, b); holes.AddEdge(b, c); holes.AddEdge(c, d); holes.AddEdge(d, a); } } holes.CullExtraneousEdges(); SPolygon hp; ZERO(&hp); holes.AssemblePolygon(&hp, NULL, true); SContour *sc; for(sc = hp.l.First(); sc; sc = hp.l.NextAfter(sc)) { l.Add(sc); } orig.Clear(); holes.Clear(); li.Clear(); lj.Clear(); hp.l.Clear(); UvTriangulateInto(mesh, srf); }
void SolveSpace::ExportViewOrWireframeTo(char *filename, bool wireframe) { int i; SEdgeList edges; ZERO(&edges); SBezierList beziers; ZERO(&beziers); SMesh *sm = NULL; if(SS.GW.showShaded) { Group *g = SK.GetGroup(SS.GW.activeGroup); g->GenerateDisplayItems(); sm = &(g->displayMesh); } if(sm && sm->IsEmpty()) { sm = NULL; } for(i = 0; i < SK.entity.n; i++) { Entity *e = &(SK.entity.elem[i]); if(!e->IsVisible()) continue; if(e->construction) continue; if(SS.exportPwlCurves || (sm && !SS.GW.showHdnLines) || fabs(SS.exportOffset) > LENGTH_EPS) { // We will be doing hidden line removal, which we can't do on // exact curves; so we need things broken down to pwls. Same // problem with cutter radius compensation. e->GenerateEdges(&edges); } else { e->GenerateBezierCurves(&beziers); } } if(SS.GW.showEdges) { Group *g = SK.GetGroup(SS.GW.activeGroup); g->GenerateDisplayItems(); SEdgeList *selr = &(g->displayEdges); SEdge *se; for(se = selr->l.First(); se; se = selr->l.NextAfter(se)) { edges.AddEdge(se->a, se->b, Style::SOLID_EDGE); } } if(SS.GW.showConstraints) { Constraint *c; for(c = SK.constraint.First(); c; c = SK.constraint.NextAfter(c)) { c->GetEdges(&edges); } } if(wireframe) { VectorFileWriter *out = VectorFileWriter::ForFile(filename); if(out) { ExportWireframeCurves(&edges, &beziers, out); } } else { Vector u = SS.GW.projRight, v = SS.GW.projUp, n = u.Cross(v), origin = SS.GW.offset.ScaledBy(-1); VectorFileWriter *out = VectorFileWriter::ForFile(filename); if(out) { ExportLinesAndMesh(&edges, &beziers, sm, u, v, n, origin, SS.CameraTangent()*SS.GW.scale, out); } if(out && !out->HasCanvasSize()) { // These file formats don't have a canvas size, so they just // get exported in the raw coordinate system. So indicate what // that was on-screen. SS.justExportedInfo.draw = true; SS.justExportedInfo.pt = origin; SS.justExportedInfo.u = u; SS.justExportedInfo.v = v; InvalidateGraphics(); } } edges.Clear(); beziers.Clear(); }