//----------------------------------------------------------------------------- // Select everything that lies within the marquee view-aligned rectangle. For // points, we test by the point location. For normals, we test by the normal's // associated point. For anything else, we test by any piecewise linear edge. //----------------------------------------------------------------------------- void GraphicsWindow::SelectByMarquee(void) { Point2d begin = ProjectPoint(orig.marqueePoint); double xmin = min(orig.mouse.x, begin.x), xmax = max(orig.mouse.x, begin.x), ymin = min(orig.mouse.y, begin.y), ymax = max(orig.mouse.y, begin.y); Entity *e; for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { if(e->group.v != SS.GW.activeGroup.v) continue; if(e->IsFace() || e->IsDistance()) continue; if(!e->IsVisible()) continue; if(e->IsPoint() || e->IsNormal()) { Vector p = e->IsPoint() ? e->PointGetNum() : SK.GetEntity(e->point[0])->PointGetNum(); Point2d pp = ProjectPoint(p); if(pp.x >= xmin && pp.x <= xmax && pp.y >= ymin && pp.y <= ymax) { MakeSelected(e->h); } } else { // Use the 3d bounding box test routines, to avoid duplication; // so let our bounding square become a bounding box that certainly // includes the z = 0 plane. Vector ptMin = Vector::From(xmin, ymin, -1), ptMax = Vector::From(xmax, ymax, 1); SEdgeList sel; ZERO(&sel); e->GenerateEdges(&sel, true); SEdge *se; for(se = sel.l.First(); se; se = sel.l.NextAfter(se)) { Point2d ppa = ProjectPoint(se->a), ppb = ProjectPoint(se->b); Vector ptA = Vector::From(ppa.x, ppa.y, 0), ptB = Vector::From(ppb.x, ppb.y, 0); if(Vector::BoundingBoxIntersectsLine(ptMax, ptMin, ptA, ptB, true) || !ptA.OutsideAndNotOn(ptMax, ptMin) || !ptB.OutsideAndNotOn(ptMax, ptMin)) { MakeSelected(e->h); break; } } sel.Clear(); } } }
void SSurface::TriangulateInto(SShell *shell, SMesh *sm) { SEdgeList el = {}; MakeEdgesInto(shell, &el, AS_UV); SPolygon poly = {}; if(el.AssemblePolygon(&poly, NULL, true)) { int i, start = sm->l.n; if(degm == 1 && degn == 1) { // A surface with curvature along one direction only; so // choose the triangulation with chords that lie as much // as possible within the surface. And since the trim curves // have been pwl'd to within the desired chord tol, that will // produce a surface good to within roughly that tol. // // If this is just a plane (degree (1, 1)) then the triangulation // code will notice that, and not bother checking chord tols. poly.UvTriangulateInto(sm, this); } else { // A surface with compound curvature. So we must overlay a // two-dimensional grid, and triangulate around that. poly.UvGridTriangulateInto(sm, this); } STriMeta meta = { face, color }; for(i = start; i < sm->l.n; i++) { STriangle *st = &(sm->l.elem[i]); st->meta = meta; st->an = NormalAt(st->a.x, st->a.y); st->bn = NormalAt(st->b.x, st->b.y); st->cn = NormalAt(st->c.x, st->c.y); st->a = PointAt(st->a.x, st->a.y); st->b = PointAt(st->b.x, st->b.y); st->c = PointAt(st->c.x, st->c.y); // Works out that my chosen contour direction is inconsistent with // the triangle direction, sigh. st->FlipNormal(); } } else { dbp("failed to assemble polygon to trim nurbs surface in uv space"); } el.Clear(); poly.Clear(); }
bool SPolygon::SelfIntersecting(Vector *intersectsAt) { SEdgeList el; ZERO(&el); MakeEdgesInto(&el); SKdNodeEdges *kdtree = SKdNodeEdges::From(&el); int cnt = 1; el.l.ClearTags(); bool ret = false; SEdge *se; for(se = el.l.First(); se; se = el.l.NextAfter(se)) { int inters = kdtree->AnyEdgeCrossings(se->a, se->b, cnt, intersectsAt); if(inters != 1) { ret = true; break; } cnt++; } el.Clear(); return ret; }
void SolveSpaceUI::MenuAnalyze(int id) { SS.GW.GroupSelection(); #define gs (SS.GW.gs) switch(id) { case GraphicsWindow::MNU_STEP_DIM: if(gs.constraints == 1 && gs.n == 0) { Constraint *c = SK.GetConstraint(gs.constraint[0]); if(c->HasLabel() && !c->reference) { SS.TW.shown.dimFinish = c->valA; SS.TW.shown.dimSteps = 10; SS.TW.shown.dimIsDistance = (c->type != Constraint::ANGLE) && (c->type != Constraint::LENGTH_RATIO) && (c->type != Constraint::LENGTH_DIFFERENCE); SS.TW.shown.constraint = c->h; SS.TW.shown.screen = TextWindow::SCREEN_STEP_DIMENSION; // The step params are specified in the text window, // so force that to be shown. SS.GW.ForceTextWindowShown(); SS.ScheduleShowTW(); SS.GW.ClearSelection(); } else { Error("Constraint must have a label, and must not be " "a reference dimension."); } } else { Error("Bad selection for step dimension; select a constraint."); } break; case GraphicsWindow::MNU_NAKED_EDGES: { SS.nakedEdges.Clear(); Group *g = SK.GetGroup(SS.GW.activeGroup); SMesh *m = &(g->displayMesh); SKdNode *root = SKdNode::From(m); bool inters, leaks; root->MakeCertainEdgesInto(&(SS.nakedEdges), SKdNode::NAKED_OR_SELF_INTER_EDGES, true, &inters, &leaks); InvalidateGraphics(); const char *intersMsg = inters ? "The mesh is self-intersecting (NOT okay, invalid)." : "The mesh is not self-intersecting (okay, valid)."; const char *leaksMsg = leaks ? "The mesh has naked edges (NOT okay, invalid)." : "The mesh is watertight (okay, valid)."; std::string cntMsg = ssprintf("\n\nThe model contains %d triangles, from " "%d surfaces.", g->displayMesh.l.n, g->runningShell.surface.n); if(SS.nakedEdges.l.n == 0) { Message("%s\n\n%s\n\nZero problematic edges, good.%s", intersMsg, leaksMsg, cntMsg.c_str()); } else { Error("%s\n\n%s\n\n%d problematic edges, bad.%s", intersMsg, leaksMsg, SS.nakedEdges.l.n, cntMsg.c_str()); } break; } case GraphicsWindow::MNU_INTERFERENCE: { SS.nakedEdges.Clear(); SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh); SKdNode *root = SKdNode::From(m); bool inters, leaks; root->MakeCertainEdgesInto(&(SS.nakedEdges), SKdNode::SELF_INTER_EDGES, false, &inters, &leaks); InvalidateGraphics(); if(inters) { Error("%d edges interfere with other triangles, bad.", SS.nakedEdges.l.n); } else { Message("The assembly does not interfere, good."); } break; } case GraphicsWindow::MNU_VOLUME: { SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh); double vol = 0; int i; for(i = 0; i < m->l.n; i++) { STriangle tr = m->l.elem[i]; // 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; } std::string msg = ssprintf("The volume of the solid model is:\n\n"" %.3f %s^3", vol / pow(SS.MmPerUnit(), 3), SS.UnitName()); if(SS.viewUnits == SolveSpaceUI::UNIT_MM) { msg += ssprintf("\n %.2f mL", vol/(10*10*10)); } msg += "\n\nCurved surfaces have been approximated as triangles.\n" "This introduces error, typically of around 1%."; Message("%s", msg.c_str()); break; } case GraphicsWindow::MNU_AREA: { Group *g = SK.GetGroup(SS.GW.activeGroup); if(g->polyError.how != Group::POLY_GOOD) { Error("This group does not contain a correctly-formed " "2d closed area. It is open, not coplanar, or self-" "intersecting."); break; } SEdgeList sel = {}; g->polyLoops.MakeEdgesInto(&sel); SPolygon sp = {}; sel.AssemblePolygon(&sp, NULL, true); sp.normal = sp.ComputeNormal(); sp.FixContourDirections(); double area = sp.SignedArea(); double scale = SS.MmPerUnit(); Message("The area of the region sketched in this group is:\n\n" " %.3f %s^2\n\n" "Curves have been approximated as piecewise linear.\n" "This introduces error, typically of around 1%%.", area / (scale*scale), SS.UnitName()); sel.Clear(); sp.Clear(); break; } case GraphicsWindow::MNU_SHOW_DOF: // This works like a normal solve, except that it calculates // which variables are free/bound at the same time. SS.GenerateAll(SolveSpaceUI::GENERATE_ALL, true); break; case GraphicsWindow::MNU_TRACE_PT: if(gs.points == 1 && gs.n == 1) { SS.traced.point = gs.point[0]; SS.GW.ClearSelection(); } else { Error("Bad selection for trace; select a single point."); } break; case GraphicsWindow::MNU_STOP_TRACING: { std::string exportFile; if(GetSaveFile(&exportFile, "", CsvFileFilter)) { FILE *f = ssfopen(exportFile, "wb"); if(f) { int i; SContour *sc = &(SS.traced.path); for(i = 0; i < sc->l.n; i++) { Vector p = sc->l.elem[i].p; double s = SS.exportScale; fprintf(f, "%.10f, %.10f, %.10f\r\n", p.x/s, p.y/s, p.z/s); } fclose(f); } else { Error("Couldn't write to '%s'", exportFile.c_str()); } } // Clear the trace, and stop tracing SS.traced.point = Entity::NO_ENTITY; SS.traced.path.l.Clear(); InvalidateGraphics(); break; } default: oops(); } }
//----------------------------------------------------------------------------- // 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 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 SPolygon::UvTriangulateInto(SMesh *m, SSurface *srf) { if(l.n <= 0) return; SDWORD in = GetMilliseconds(); normal = Vector::From(0, 0, 1); while(l.n > 0) { FixContourDirections(); l.ClearTags(); // Find a top-level contour, and start with that. Then build bridges // in order to merge all its islands into a single contour. SContour *top; for(top = l.First(); top; top = l.NextAfter(top)) { if(top->timesEnclosed == 0) { break; } } if(!top) { dbp("polygon has no top-level contours?"); return; } // Start with the outer contour SContour merged; ZERO(&merged); top->tag = 1; top->CopyInto(&merged); (merged.l.n)--; // List all of the edges, for testing whether bridges work. SEdgeList el; ZERO(&el); top->MakeEdgesInto(&el); List<Vector> vl; ZERO(&vl); // And now find all of its holes. Note that we will also find any // outer contours that lie entirely within this contour, and any // holes for those contours. But that's okay, because we can merge // those too. SContour *sc; for(sc = l.First(); sc; sc = l.NextAfter(sc)) { if(sc->timesEnclosed != 1) continue; if(sc->l.n < 2) continue; // Test the midpoint of an edge. Our polygon may not be self- // intersecting, but two contours may share a vertex; so a // vertex could be on the edge of another polygon, in which // case ContainsPointProjdToNormal returns indeterminate. Vector tp = sc->AnyEdgeMidpoint(); if(top->ContainsPointProjdToNormal(normal, tp)) { sc->tag = 2; sc->MakeEdgesInto(&el); sc->FindPointWithMinX(); } } // dbp("finished finding holes: %d ms", GetMilliseconds() - in); for(;;) { double xmin = 1e10; SContour *scmin = NULL; for(sc = l.First(); sc; sc = l.NextAfter(sc)) { if(sc->tag != 2) continue; if(sc->xminPt.x < xmin) { xmin = sc->xminPt.x; scmin = sc; } } if(!scmin) break; if(!merged.BridgeToContour(scmin, &el, &vl)) { dbp("couldn't merge our hole"); return; } // dbp(" bridged to contour: %d ms", GetMilliseconds() - in); scmin->tag = 3; } // dbp("finished merging holes: %d ms", GetMilliseconds() - in); merged.UvTriangulateInto(m, srf); // dbp("finished ear clippping: %d ms", GetMilliseconds() - in); merged.l.Clear(); el.Clear(); vl.Clear(); // Careful, need to free the points within the contours, and not just // the contours themselves. This was a tricky memory leak. for(sc = l.First(); sc; sc = l.NextAfter(sc)) { if(sc->tag) { sc->l.Clear(); } } l.RemoveTagged(); } }
void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB, SShell *into) { Vector amax, amin, bmax, bmin; GetAxisAlignedBounding(&amax, &amin); b->GetAxisAlignedBounding(&bmax, &bmin); if(Vector::BoundingBoxesDisjoint(amax, amin, bmax, bmin)) { // They cannot possibly intersect, no curves to generate return; } Vector alongt, alongb; SBezier oft, ofb; bool isExtdt = this->IsExtrusion(&oft, &alongt), isExtdb = b->IsExtrusion(&ofb, &alongb); if(degm == 1 && degn == 1 && b->degm == 1 && b->degn == 1) { // Line-line intersection; it's a plane or nothing. Vector na = NormalAt(0, 0).WithMagnitude(1), nb = b->NormalAt(0, 0).WithMagnitude(1); double da = na.Dot(PointAt(0, 0)), db = nb.Dot(b->PointAt(0, 0)); Vector dl = na.Cross(nb); if(dl.Magnitude() < LENGTH_EPS) return; // parallel planes dl = dl.WithMagnitude(1); Vector p = Vector::AtIntersectionOfPlanes(na, da, nb, db); // Trim it to the region 0 <= {u,v} <= 1 for each plane; not strictly // necessary, since line will be split and excess edges culled, but // this improves speed and robustness. int i; double tmax = VERY_POSITIVE, tmin = VERY_NEGATIVE; for(i = 0; i < 2; i++) { SSurface *s = (i == 0) ? this : b; Vector tu, tv; s->TangentsAt(0, 0, &tu, &tv); double up, vp, ud, vd; s->ClosestPointTo(p, &up, &vp); ud = (dl.Dot(tu)) / tu.MagSquared(); vd = (dl.Dot(tv)) / tv.MagSquared(); // so u = up + t*ud // v = vp + t*vd if(ud > LENGTH_EPS) { tmin = max(tmin, -up/ud); tmax = min(tmax, (1 - up)/ud); } else if(ud < -LENGTH_EPS) { tmax = min(tmax, -up/ud); tmin = max(tmin, (1 - up)/ud); } else { if(up < -LENGTH_EPS || up > 1 + LENGTH_EPS) { // u is constant, and outside [0, 1] tmax = VERY_NEGATIVE; } } if(vd > LENGTH_EPS) { tmin = max(tmin, -vp/vd); tmax = min(tmax, (1 - vp)/vd); } else if(vd < -LENGTH_EPS) { tmax = min(tmax, -vp/vd); tmin = max(tmin, (1 - vp)/vd); } else { if(vp < -LENGTH_EPS || vp > 1 + LENGTH_EPS) { // v is constant, and outside [0, 1] tmax = VERY_NEGATIVE; } } } if(tmax > tmin + LENGTH_EPS) { SBezier bezier = SBezier::From(p.Plus(dl.ScaledBy(tmin)), p.Plus(dl.ScaledBy(tmax))); AddExactIntersectionCurve(&bezier, b, agnstA, agnstB, into); } } else if((degm == 1 && degn == 1 && isExtdb) || (b->degm == 1 && b->degn == 1 && isExtdt)) { // The intersection between a plane and a surface of extrusion SSurface *splane, *sext; if(degm == 1 && degn == 1) { splane = this; sext = b; } else { splane = b; sext = this; } Vector n = splane->NormalAt(0, 0).WithMagnitude(1), along; double d = n.Dot(splane->PointAt(0, 0)); SBezier bezier; (void)sext->IsExtrusion(&bezier, &along); if(fabs(n.Dot(along)) < LENGTH_EPS) { // Direction of extrusion is parallel to plane; so intersection // is zero or more lines. Build a line within the plane, and // normal to the direction of extrusion, and intersect that line // against the surface; each intersection point corresponds to // a line. Vector pm, alu, p0, dp; // a point halfway along the extrusion pm = ((sext->ctrl[0][0]).Plus(sext->ctrl[0][1])).ScaledBy(0.5); alu = along.WithMagnitude(1); dp = (n.Cross(along)).WithMagnitude(1); // n, alu, and dp form an orthogonal csys; set n component to // place it on the plane, alu component to lie halfway along // extrusion, and dp component doesn't matter so zero p0 = n.ScaledBy(d).Plus(alu.ScaledBy(pm.Dot(alu))); List<SInter> inters = {}; sext->AllPointsIntersecting(p0, p0.Plus(dp), &inters, /*asSegment=*/false, /*trimmed=*/false, /*inclTangent=*/true); SInter *si; for(si = inters.First(); si; si = inters.NextAfter(si)) { Vector al = along.ScaledBy(0.5); SBezier bezier; bezier = SBezier::From((si->p).Minus(al), (si->p).Plus(al)); AddExactIntersectionCurve(&bezier, b, agnstA, agnstB, into); } inters.Clear(); } else { // Direction of extrusion is not parallel to plane; so // intersection is projection of extruded curve into our plane. int i; for(i = 0; i <= bezier.deg; i++) { Vector p0 = bezier.ctrl[i], p1 = p0.Plus(along); bezier.ctrl[i] = Vector::AtIntersectionOfPlaneAndLine(n, d, p0, p1, NULL); } AddExactIntersectionCurve(&bezier, b, agnstA, agnstB, into); } } else if(isExtdt && isExtdb && sqrt(fabs(alongt.Dot(alongb))) > sqrt(alongt.Magnitude() * alongb.Magnitude()) - LENGTH_EPS) { // Two surfaces of extrusion along the same axis. So they might // intersect along some number of lines parallel to the axis. Vector axis = alongt.WithMagnitude(1); List<SInter> inters = {}; List<Vector> lv = {}; double a_axis0 = ( ctrl[0][0]).Dot(axis), a_axis1 = ( ctrl[0][1]).Dot(axis), b_axis0 = (b->ctrl[0][0]).Dot(axis), b_axis1 = (b->ctrl[0][1]).Dot(axis); if(a_axis0 > a_axis1) swap(a_axis0, a_axis1); if(b_axis0 > b_axis1) swap(b_axis0, b_axis1); double ab_axis0 = max(a_axis0, b_axis0), ab_axis1 = min(a_axis1, b_axis1); if(fabs(ab_axis0 - ab_axis1) < LENGTH_EPS) { // The line would be zero-length return; } Vector axis0 = axis.ScaledBy(ab_axis0), axis1 = axis.ScaledBy(ab_axis1), axisc = (axis0.Plus(axis1)).ScaledBy(0.5); oft.MakePwlInto(&lv); int i; for(i = 0; i < lv.n - 1; i++) { Vector pa = lv.elem[i], pb = lv.elem[i+1]; pa = pa.Minus(axis.ScaledBy(pa.Dot(axis))); pb = pb.Minus(axis.ScaledBy(pb.Dot(axis))); pa = pa.Plus(axisc); pb = pb.Plus(axisc); b->AllPointsIntersecting(pa, pb, &inters, /*asSegment=*/true,/*trimmed=*/false, /*inclTangent=*/false); } SInter *si; for(si = inters.First(); si; si = inters.NextAfter(si)) { Vector p = (si->p).Minus(axis.ScaledBy((si->p).Dot(axis))); double ub, vb; b->ClosestPointTo(p, &ub, &vb, /*mustConverge=*/true); SSurface plane; plane = SSurface::FromPlane(p, axis.Normal(0), axis.Normal(1)); b->PointOnSurfaces(this, &plane, &ub, &vb); p = b->PointAt(ub, vb); SBezier bezier; bezier = SBezier::From(p.Plus(axis0), p.Plus(axis1)); AddExactIntersectionCurve(&bezier, b, agnstA, agnstB, into); } inters.Clear(); lv.Clear(); } else { // Try intersecting the surfaces numerically, by a marching algorithm. // First, we find all the intersections between a surface and the // boundary of the other surface. SPointList spl = {}; int a; for(a = 0; a < 2; a++) { SShell *shA = (a == 0) ? agnstA : agnstB; SSurface *srfA = (a == 0) ? this : b, *srfB = (a == 0) ? b : this; SEdgeList el = {}; srfA->MakeEdgesInto(shA, &el, MakeAs::XYZ, NULL); SEdge *se; for(se = el.l.First(); se; se = el.l.NextAfter(se)) { List<SInter> lsi = {}; srfB->AllPointsIntersecting(se->a, se->b, &lsi, /*asSegment=*/true, /*trimmed=*/true, /*inclTangent=*/false); if(lsi.n == 0) continue; // Find the other surface that this curve trims. hSCurve hsc = { (uint32_t)se->auxA }; SCurve *sc = shA->curve.FindById(hsc); hSSurface hother = (sc->surfA.v == srfA->h.v) ? sc->surfB : sc->surfA; SSurface *other = shA->surface.FindById(hother); SInter *si; for(si = lsi.First(); si; si = lsi.NextAfter(si)) { Vector p = si->p; double u, v; srfB->ClosestPointTo(p, &u, &v); srfB->PointOnSurfaces(srfA, other, &u, &v); p = srfB->PointAt(u, v); if(!spl.ContainsPoint(p)) { SPoint sp; sp.p = p; // We also need the edge normal, so that we know in // which direction to march. srfA->ClosestPointTo(p, &u, &v); Vector n = srfA->NormalAt(u, v); sp.auxv = n.Cross((se->b).Minus(se->a)); sp.auxv = (sp.auxv).WithMagnitude(1); spl.l.Add(&sp); } } lsi.Clear(); } el.Clear(); } while(spl.l.n >= 2) { SCurve sc = {}; sc.surfA = h; sc.surfB = b->h; sc.isExact = false; sc.source = SCurve::Source::INTERSECTION; Vector start = spl.l.elem[0].p, startv = spl.l.elem[0].auxv; spl.l.ClearTags(); spl.l.elem[0].tag = 1; spl.l.RemoveTagged(); // Our chord tolerance is whatever the user specified double maxtol = SS.ChordTolMm(); int maxsteps = max(300, SS.GetMaxSegments()*3); // The curve starts at our starting point. SCurvePt padd = {}; padd.vertex = true; padd.p = start; sc.pts.Add(&padd); Point2d pa, pb; Vector np, npc = Vector::From(0, 0, 0); bool fwd = false; // Better to start with a too-small step, so that we don't miss // features of the curve entirely. double tol, step = maxtol; for(a = 0; a < maxsteps; a++) { ClosestPointTo(start, &pa); b->ClosestPointTo(start, &pb); Vector na = NormalAt(pa).WithMagnitude(1), nb = b->NormalAt(pb).WithMagnitude(1); if(a == 0) { Vector dp = nb.Cross(na); if(dp.Dot(startv) < 0) { // We want to march in the more inward direction. fwd = true; } else { fwd = false; } } int i; for(i = 0; i < 20; i++) { Vector dp = nb.Cross(na); if(!fwd) dp = dp.ScaledBy(-1); dp = dp.WithMagnitude(step); np = start.Plus(dp); npc = ClosestPointOnThisAndSurface(b, np); tol = (npc.Minus(np)).Magnitude(); if(tol > maxtol*0.8) { step *= 0.90; } else { step /= 0.90; } if((tol < maxtol) && (tol > maxtol/2)) { // If we meet the chord tolerance test, and we're // not too fine, then we break out. break; } } SPoint *sp; for(sp = spl.l.First(); sp; sp = spl.l.NextAfter(sp)) { if((sp->p).OnLineSegment(start, npc, 2*SS.ChordTolMm())) { sp->tag = 1; a = maxsteps; npc = sp->p; } } padd.p = npc; padd.vertex = (a == maxsteps); sc.pts.Add(&padd); start = npc; } spl.l.RemoveTagged(); // And now we split and insert the curve SCurve split = sc.MakeCopySplitAgainst(agnstA, agnstB, this, b); sc.Clear(); into->curve.AddAndAssignId(&split); } spl.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 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(); }
void SolveSpace::ExportSectionTo(char *filename) { Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp); gn = gn.WithMagnitude(1); Group *g = SK.GetGroup(SS.GW.activeGroup); g->GenerateDisplayItems(); if(g->displayMesh.IsEmpty()) { Error("No solid model present; draw one with extrudes and revolves, " "or use Export 2d View to export bare lines and curves."); return; } // The plane in which the exported section lies; need this because we'll // reorient from that plane into the xy plane before exporting. Vector origin, u, v, n; double d; SS.GW.GroupSelection(); #define gs (SS.GW.gs) if((gs.n == 0 && g->activeWorkplane.v != Entity::FREE_IN_3D.v)) { Entity *wrkpl = SK.GetEntity(g->activeWorkplane); origin = wrkpl->WorkplaneGetOffset(); n = wrkpl->Normal()->NormalN(); u = wrkpl->Normal()->NormalU(); v = wrkpl->Normal()->NormalV(); } else if(gs.n == 1 && gs.faces == 1) { Entity *face = SK.GetEntity(gs.entity[0]); origin = face->FaceGetPointNum(); n = face->FaceGetNormalNum(); if(n.Dot(gn) < 0) n = n.ScaledBy(-1); u = n.Normal(0); v = n.Normal(1); } else if(gs.n == 3 && gs.vectors == 2 && gs.points == 1) { Vector ut = SK.GetEntity(gs.entity[0])->VectorGetNum(), vt = SK.GetEntity(gs.entity[1])->VectorGetNum(); ut = ut.WithMagnitude(1); vt = vt.WithMagnitude(1); if(fabs(SS.GW.projUp.Dot(vt)) < fabs(SS.GW.projUp.Dot(ut))) { SWAP(Vector, ut, vt); } if(SS.GW.projRight.Dot(ut) < 0) ut = ut.ScaledBy(-1); if(SS.GW.projUp. Dot(vt) < 0) vt = vt.ScaledBy(-1); origin = SK.GetEntity(gs.point[0])->PointGetNum(); n = ut.Cross(vt); u = ut.WithMagnitude(1); v = (n.Cross(u)).WithMagnitude(1); } else { Error("Bad selection for export section. Please select:\n\n" " * nothing, with an active workplane " "(workplane is section plane)\n" " * a face (section plane through face)\n" " * a point and two line segments " "(plane through point and parallel to lines)\n"); return; } SS.GW.ClearSelection(); n = n.WithMagnitude(1); d = origin.Dot(n); SEdgeList el; ZERO(&el); SBezierList bl; ZERO(&bl); // If there's a mesh, then grab the edges from it. g->runningMesh.MakeEdgesInPlaneInto(&el, n, d); // If there's a shell, then grab the edges and possibly Beziers. g->runningShell.MakeSectionEdgesInto(n, d, &el, (SS.exportPwlCurves || fabs(SS.exportOffset) > LENGTH_EPS) ? NULL : &bl); // All of these are solid model edges, so use the appropriate style. SEdge *se; for(se = el.l.First(); se; se = el.l.NextAfter(se)) { se->auxA = Style::SOLID_EDGE; } SBezier *sb; for(sb = bl.l.First(); sb; sb = bl.l.NextAfter(sb)) { sb->auxA = Style::SOLID_EDGE; } el.CullExtraneousEdges(); bl.CullIdenticalBeziers(); // And write the edges. VectorFileWriter *out = VectorFileWriter::ForFile(filename); if(out) { // parallel projection (no perspective), and no mesh ExportLinesAndMesh(&el, &bl, NULL, u, v, n, origin, 0, out); } el.Clear(); bl.Clear(); }
void Entity::DrawOrGetDistance(void) { if(!IsVisible()) return; Group *g = SK.GetGroup(group); switch(type) { case POINT_N_COPY: case POINT_N_TRANS: case POINT_N_ROT_TRANS: case POINT_N_ROT_AA: case POINT_IN_3D: case POINT_IN_2D: { Vector v = PointGetNum(); dogd.refp = v; if(dogd.drawing) { double s = 3.5; Vector r = SS.GW.projRight.ScaledBy(s/SS.GW.scale); Vector d = SS.GW.projUp.ScaledBy(s/SS.GW.scale); ssglColorRGB(Style::Color(Style::DATUM)); ssglDepthRangeOffset(6); glBegin(GL_QUADS); ssglVertex3v(v.Plus (r).Plus (d)); ssglVertex3v(v.Plus (r).Minus(d)); ssglVertex3v(v.Minus(r).Minus(d)); ssglVertex3v(v.Minus(r).Plus (d)); glEnd(); ssglDepthRangeOffset(0); } else { Point2d pp = SS.GW.ProjectPoint(v); dogd.dmin = pp.DistanceTo(dogd.mp) - 6; } break; } case NORMAL_N_COPY: case NORMAL_N_ROT: case NORMAL_N_ROT_AA: case NORMAL_IN_3D: case NORMAL_IN_2D: { int i; for(i = 0; i < 2; i++) { if(i == 0 && !SS.GW.showNormals) { // When the normals are hidden, we will continue to show // the coordinate axes at the bottom left corner, but // not at the origin. continue; } hRequest hr = h.request(); // Always draw the x, y, and z axes in red, green, and blue; // brighter for the ones at the bottom left of the screen, // dimmer for the ones at the model origin. int f = (i == 0 ? 100 : 255); if(hr.v == Request::HREQUEST_REFERENCE_XY.v) { ssglColorRGB(RGBi(0, 0, f)); } else if(hr.v == Request::HREQUEST_REFERENCE_YZ.v) { ssglColorRGB(RGBi(f, 0, 0)); } else if(hr.v == Request::HREQUEST_REFERENCE_ZX.v) { ssglColorRGB(RGBi(0, f, 0)); } else { ssglColorRGB(Style::Color(Style::NORMALS)); if(i > 0) break; } Quaternion q = NormalGetNum(); Vector tail; if(i == 0) { tail = SK.GetEntity(point[0])->PointGetNum(); glLineWidth(1); } else { // Draw an extra copy of the x, y, and z axes, that's // always in the corner of the view and at the front. // So those are always available, perhaps useful. double s = SS.GW.scale; double h = 60 - SS.GW.height/2; double w = 60 - SS.GW.width/2; tail = SS.GW.projRight.ScaledBy(w/s).Plus( SS.GW.projUp. ScaledBy(h/s)).Minus(SS.GW.offset); ssglDepthRangeLockToFront(true); glLineWidth(2); } Vector v = (q.RotationN()).WithMagnitude(50/SS.GW.scale); Vector tip = tail.Plus(v); LineDrawOrGetDistance(tail, tip); v = v.WithMagnitude(12/SS.GW.scale); Vector axis = q.RotationV(); LineDrawOrGetDistance(tip,tip.Minus(v.RotatedAbout(axis, 0.6))); LineDrawOrGetDistance(tip,tip.Minus(v.RotatedAbout(axis,-0.6))); } ssglDepthRangeLockToFront(false); break; } case DISTANCE: case DISTANCE_N_COPY: // These are used only as data structures, nothing to display. break; case WORKPLANE: { Vector p; p = SK.GetEntity(point[0])->PointGetNum(); Vector u = Normal()->NormalU(); Vector v = Normal()->NormalV(); double s = (min(SS.GW.width, SS.GW.height))*0.45/SS.GW.scale; Vector us = u.ScaledBy(s); Vector vs = v.ScaledBy(s); Vector pp = p.Plus (us).Plus (vs); Vector pm = p.Plus (us).Minus(vs); Vector mm = p.Minus(us).Minus(vs), mm2 = mm; Vector mp = p.Minus(us).Plus (vs); glLineWidth(1); ssglColorRGB(Style::Color(Style::NORMALS)); glEnable(GL_LINE_STIPPLE); glLineStipple(3, 0x1111); if(!h.isFromRequest()) { mm = mm.Plus(v.ScaledBy(60/SS.GW.scale)); mm2 = mm2.Plus(u.ScaledBy(60/SS.GW.scale)); LineDrawOrGetDistance(mm2, mm); } LineDrawOrGetDistance(pp, pm); LineDrawOrGetDistance(pm, mm2); LineDrawOrGetDistance(mm, mp); LineDrawOrGetDistance(mp, pp); glDisable(GL_LINE_STIPPLE); char *str = DescriptionString()+5; double th = DEFAULT_TEXT_HEIGHT; if(dogd.drawing) { ssglWriteText(str, th, mm2, u, v, NULL, NULL); } else { Vector pos = mm2.Plus(u.ScaledBy(ssglStrWidth(str, th)/2)).Plus( v.ScaledBy(ssglStrHeight(th)/2)); Point2d pp = SS.GW.ProjectPoint(pos); dogd.dmin = min(dogd.dmin, pp.DistanceTo(dogd.mp) - 10); // If a line lies in a plane, then select the line, not // the plane. dogd.dmin += 3; } break; } case LINE_SEGMENT: case CIRCLE: case ARC_OF_CIRCLE: case CUBIC: case CUBIC_PERIODIC: case TTF_TEXT: // Nothing but the curve(s). break; case FACE_NORMAL_PT: case FACE_XPROD: case FACE_N_ROT_TRANS: case FACE_N_TRANS: case FACE_N_ROT_AA: // Do nothing; these are drawn with the triangle mesh break; default: oops(); } // And draw the curves; generate the rational polynomial curves for // everything, then piecewise linearize them, and display those. SEdgeList sel; ZERO(&sel); GenerateEdges(&sel, true); int i; for(i = 0; i < sel.l.n; i++) { SEdge *se = &(sel.l.elem[i]); LineDrawOrGetDistance(se->a, se->b, true); } sel.Clear(); }