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(); }
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(); } }
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); }