SBsp3 *SBsp3::FromMesh(SMesh *m) { SBsp3 *bsp3 = NULL; int i; SMesh mc; ZERO(&mc); for(i = 0; i < m->l.n; i++) { mc.AddTriangle(&(m->l.elem[i])); } srand(0); // Let's be deterministic, at least! int n = mc.l.n; while(n > 1) { int k = rand() % n; n--; SWAP(STriangle, mc.l.elem[k], mc.l.elem[n]); } for(i = 0; i < mc.l.n; i++) { bsp3 = bsp3->Insert(&(mc.l.elem[i]), NULL); } mc.Clear(); return bsp3; }
void Group::GenerateShellAndMesh(void) { bool prevBooleanFailed = booleanFailed; booleanFailed = false; Group *srcg = this; thisShell.Clear(); thisMesh.Clear(); runningShell.Clear(); runningMesh.Clear(); // Don't attempt a lathe or extrusion unless the source section is good: // planar and not self-intersecting. bool haveSrc = true; if(type == EXTRUDE || type == LATHE) { Group *src = SK.GetGroup(opA); if(src->polyError.how != POLY_GOOD) { haveSrc = false; } } if(type == TRANSLATE || type == ROTATE) { // A step and repeat gets merged against the group's prevous group, // not our own previous group. srcg = SK.GetGroup(opA); GenerateForStepAndRepeat<SShell>(&(srcg->thisShell), &thisShell); GenerateForStepAndRepeat<SMesh> (&(srcg->thisMesh), &thisMesh); } else if(type == EXTRUDE && haveSrc) { Group *src = SK.GetGroup(opA); Vector translate = Vector::From(h.param(0), h.param(1), h.param(2)); Vector tbot, ttop; if(subtype == ONE_SIDED) { tbot = Vector::From(0, 0, 0); ttop = translate.ScaledBy(2); } else { tbot = translate.ScaledBy(-1); ttop = translate.ScaledBy(1); } SBezierLoopSetSet *sblss = &(src->bezierLoops); SBezierLoopSet *sbls; for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) { int is = thisShell.surface.n; // Extrude this outer contour (plus its inner contours, if present) thisShell.MakeFromExtrusionOf(sbls, tbot, ttop, color); // And for any plane faces, annotate the model with the entity for // that face, so that the user can select them with the mouse. Vector onOrig = sbls->point; int i; for(i = is; i < thisShell.surface.n; i++) { SSurface *ss = &(thisShell.surface.elem[i]); hEntity face = Entity::NO_ENTITY; Vector p = ss->PointAt(0, 0), n = ss->NormalAt(0, 0).WithMagnitude(1); double d = n.Dot(p); if(i == is || i == (is + 1)) { // These are the top and bottom of the shell. if(fabs((onOrig.Plus(ttop)).Dot(n) - d) < LENGTH_EPS) { face = Remap(Entity::NO_ENTITY, REMAP_TOP); ss->face = face.v; } if(fabs((onOrig.Plus(tbot)).Dot(n) - d) < LENGTH_EPS) { face = Remap(Entity::NO_ENTITY, REMAP_BOTTOM); ss->face = face.v; } continue; } // So these are the sides if(ss->degm != 1 || ss->degn != 1) continue; Entity *e; for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { if(e->group.v != opA.v) continue; if(e->type != Entity::LINE_SEGMENT) continue; Vector a = SK.GetEntity(e->point[0])->PointGetNum(), b = SK.GetEntity(e->point[1])->PointGetNum(); a = a.Plus(ttop); b = b.Plus(ttop); // Could get taken backwards, so check all cases. if((a.Equals(ss->ctrl[0][0]) && b.Equals(ss->ctrl[1][0])) || (b.Equals(ss->ctrl[0][0]) && a.Equals(ss->ctrl[1][0])) || (a.Equals(ss->ctrl[0][1]) && b.Equals(ss->ctrl[1][1])) || (b.Equals(ss->ctrl[0][1]) && a.Equals(ss->ctrl[1][1]))) { face = Remap(e->h, REMAP_LINE_TO_FACE); ss->face = face.v; break; } } } } } else if(type == LATHE && haveSrc) { Group *src = SK.GetGroup(opA); Vector pt = SK.GetEntity(predef.origin)->PointGetNum(), axis = SK.GetEntity(predef.entityB)->VectorGetNum(); axis = axis.WithMagnitude(1); SBezierLoopSetSet *sblss = &(src->bezierLoops); SBezierLoopSet *sbls; for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) { thisShell.MakeFromRevolutionOf(sbls, pt, axis, color, this); } } else if(type == LINKED) { // The imported shell or mesh are copied over, with the appropriate // transformation applied. We also must remap the face entities. Vector offset = { SK.GetParam(h.param(0))->val, SK.GetParam(h.param(1))->val, SK.GetParam(h.param(2))->val }; Quaternion q = { SK.GetParam(h.param(3))->val, SK.GetParam(h.param(4))->val, SK.GetParam(h.param(5))->val, SK.GetParam(h.param(6))->val }; thisMesh.MakeFromTransformationOf(&impMesh, offset, q, scale); thisMesh.RemapFaces(this, 0); thisShell.MakeFromTransformationOf(&impShell, offset, q, scale); thisShell.RemapFaces(this, 0); } if(srcg->meshCombine != COMBINE_AS_ASSEMBLE) { thisShell.MergeCoincidentSurfaces(); } // So now we've got the mesh or shell for this group. Combine it with // the previous group's mesh or shell with the requested Boolean, and // we're done. Group *prevg = srcg->RunningMeshGroup(); if(prevg->runningMesh.IsEmpty() && thisMesh.IsEmpty() && !forceToMesh) { SShell *prevs = &(prevg->runningShell); GenerateForBoolean<SShell>(prevs, &thisShell, &runningShell, srcg->meshCombine); if(srcg->meshCombine != COMBINE_AS_ASSEMBLE) { runningShell.MergeCoincidentSurfaces(); } // If the Boolean failed, then we should note that in the text screen // for this group. booleanFailed = runningShell.booleanFailed; if(booleanFailed != prevBooleanFailed) { SS.ScheduleShowTW(); } } else { SMesh prevm, thism; prevm = {}; thism = {}; prevm.MakeFromCopyOf(&(prevg->runningMesh)); prevg->runningShell.TriangulateInto(&prevm); thism.MakeFromCopyOf(&thisMesh); thisShell.TriangulateInto(&thism); SMesh outm = {}; GenerateForBoolean<SMesh>(&prevm, &thism, &outm, srcg->meshCombine); // And make sure that the output mesh is vertex-to-vertex. SKdNode *root = SKdNode::From(&outm); root->SnapToMesh(&outm); root->MakeMeshInto(&runningMesh); outm.Clear(); thism.Clear(); prevm.Clear(); } displayDirty = true; }
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(); }