Solvespace

Форк
0
/
groupmesh.cpp 
799 строк · 29.8 Кб
1
//-----------------------------------------------------------------------------
2
// Routines to generate our watertight brep shells from the operations
3
// and entities specified by the user in each group; templated to work either
4
// on an SShell of ratpoly surfaces or on an SMesh of triangles.
5
//
6
// Copyright 2008-2013 Jonathan Westhues.
7
//-----------------------------------------------------------------------------
8
#include "solvespace.h"
9

10
void Group::AssembleLoops(bool *allClosed,
11
                          bool *allCoplanar,
12
                          bool *allNonZeroLen)
13
{
14
    SBezierList sbl = {};
15

16
    int i;
17
    for(auto &e : SK.entity) {
18
        if(e.group != h)
19
            continue;
20
        if(e.construction)
21
            continue;
22
        if(e.forceHidden)
23
            continue;
24

25
        e.GenerateBezierCurves(&sbl);
26
    }
27

28
    SBezier *sb;
29
    *allNonZeroLen = true;
30
    for(sb = sbl.l.First(); sb; sb = sbl.l.NextAfter(sb)) {
31
        for(i = 1; i <= sb->deg; i++) {
32
            if(!(sb->ctrl[i]).Equals(sb->ctrl[0])) {
33
                break;
34
            }
35
        }
36
        if(i > sb->deg) {
37
            // This is a zero-length edge.
38
            *allNonZeroLen = false;
39
            polyError.errorPointAt = sb->ctrl[0];
40
            goto done;
41
        }
42
    }
43

44
    // Try to assemble all these Beziers into loops. The closed loops go into
45
    // bezierLoops, with the outer loops grouped with their holes. The
46
    // leftovers, if any, go in bezierOpens.
47
    bezierLoops.FindOuterFacesFrom(&sbl, &polyLoops, NULL,
48
                                   SS.ChordTolMm(),
49
                                   allClosed, &(polyError.notClosedAt),
50
                                   allCoplanar, &(polyError.errorPointAt),
51
                                   &bezierOpens);
52
    done:
53
    sbl.Clear();
54
}
55

56
void Group::GenerateLoops() {
57
    polyLoops.Clear();
58
    bezierLoops.Clear();
59
    bezierOpens.Clear();
60

61
    if(type == Type::DRAWING_3D || type == Type::DRAWING_WORKPLANE ||
62
       type == Type::ROTATE || type == Type::TRANSLATE || type == Type::LINKED)
63
    {
64
        bool allClosed = false, allCoplanar = false, allNonZeroLen = false;
65
        AssembleLoops(&allClosed, &allCoplanar, &allNonZeroLen);
66
        if(!allNonZeroLen) {
67
            polyError.how = PolyError::ZERO_LEN_EDGE;
68
        } else if(!allCoplanar) {
69
            polyError.how = PolyError::NOT_COPLANAR;
70
        } else if(!allClosed) {
71
            polyError.how = PolyError::NOT_CLOSED;
72
        } else {
73
            polyError.how = PolyError::GOOD;
74
            // The self-intersecting check is kind of slow, so don't run it
75
            // unless requested.
76
            if(SS.checkClosedContour) {
77
                if(polyLoops.SelfIntersecting(&(polyError.errorPointAt))) {
78
                    polyError.how = PolyError::SELF_INTERSECTING;
79
                }
80
            }
81
        }
82
    }
83
}
84

85
void SShell::RemapFaces(Group *g, int remap) {
86
    for(SSurface &ss : surface){
87
        hEntity face = { ss.face };
88
        if(face == Entity::NO_ENTITY) continue;
89

90
        face = g->Remap(face, remap);
91
        ss.face = face.v;
92
    }
93
}
94

95
void SMesh::RemapFaces(Group *g, int remap) {
96
    STriangle *tr;
97
    for(tr = l.First(); tr; tr = l.NextAfter(tr)) {
98
        hEntity face = { tr->meta.face };
99
        if(face == Entity::NO_ENTITY) continue;
100

101
        face = g->Remap(face, remap);
102
        tr->meta.face = face.v;
103
    }
104
}
105

106
template<class T>
107
void Group::GenerateForStepAndRepeat(T *steps, T *outs, Group::CombineAs forWhat) {
108

109
    int n = (int)valA, a0 = 0;
110
    if(subtype == Subtype::ONE_SIDED && skipFirst) {
111
        a0++; n++;
112
    }
113

114
    int a;
115
    // create all the transformed copies
116
    std::vector <T> transd(n);
117
    std::vector <T> workA(n);
118
    workA[0] = {};
119
    // first generate a shell/mesh with each transformed copy
120
#pragma omp parallel for
121
    for(a = a0; a < n; a++) {
122
        transd[a] = {};
123
        workA[a] = {};
124
        int ap = a*2 - (subtype == Subtype::ONE_SIDED ? 0 : (n-1));
125

126
        if(type == Type::TRANSLATE) {
127
            Vector trans = Vector::From(h.param(0), h.param(1), h.param(2));
128
            trans = trans.ScaledBy(ap);
129
            transd[a].MakeFromTransformationOf(steps,
130
                trans, Quaternion::IDENTITY, 1.0);
131
        } else {
132
            Vector trans = Vector::From(h.param(0), h.param(1), h.param(2));
133
            double theta = ap * SK.GetParam(h.param(3))->val;
134
            double c = cos(theta), s = sin(theta);
135
            Vector axis = Vector::From(h.param(4), h.param(5), h.param(6));
136
            Quaternion q = Quaternion::From(c, s*axis.x, s*axis.y, s*axis.z);
137
            // Rotation is centered at t; so A(x - t) + t = Ax + (t - At)
138
            transd[a].MakeFromTransformationOf(steps,
139
                trans.Minus(q.Rotate(trans)), q, 1.0);
140
        }
141
    }
142
    for(a = a0; a < n; a++) {
143
        // We need to rewrite any plane face entities to the transformed ones.
144
        int remap = (a == (n - 1)) ? REMAP_LAST : a;
145
        transd[a].RemapFaces(this, remap);
146
    }
147

148
    std::vector<T> *soFar = &transd;
149
    std::vector<T> *scratch = &workA;
150
    // do the boolean operations on pairs of equal size
151
    while(n > 1) {
152
        for(a = 0; a < n; a+=2) {
153
            scratch->at(a/2).Clear();
154
            // combine a pair of shells
155
            if((a==0) && (a0==1)) { // if the first was skipped just copy the 2nd
156
                scratch->at(a/2).MakeFromCopyOf(&(soFar->at(a+1)));
157
                (soFar->at(a+1)).Clear();
158
                a0 = 0;
159
            } else if (a == n-1) { // for an odd number just copy the last one
160
                scratch->at(a/2).MakeFromCopyOf(&(soFar->at(a)));
161
                (soFar->at(a)).Clear();
162
            } else if(forWhat == CombineAs::ASSEMBLE) {
163
                scratch->at(a/2).MakeFromAssemblyOf(&(soFar->at(a)), &(soFar->at(a+1)));
164
                (soFar->at(a)).Clear();
165
                (soFar->at(a+1)).Clear();
166
            } else {
167
                scratch->at(a/2).MakeFromUnionOf(&(soFar->at(a)), &(soFar->at(a+1)));
168
                (soFar->at(a)).Clear();
169
                (soFar->at(a+1)).Clear();
170
            }
171
        }
172
        swap(scratch, soFar);
173
        n = (n+1)/2;
174
    }
175
    outs->Clear();
176
    *outs = soFar->at(0);
177
}
178

179
template<class T>
180
void Group::GenerateForBoolean(T *prevs, T *thiss, T *outs, Group::CombineAs how) {
181
    // If this group contributes no new mesh, then our running mesh is the
182
    // same as last time, no combining required. Likewise if we have a mesh
183
    // but it's suppressed.
184
    if(thiss->IsEmpty() || suppress) {
185
        outs->MakeFromCopyOf(prevs);
186
        return;
187
    }
188

189
    // So our group's shell appears in thisShell. Combine this with the
190
    // previous group's shell, using the requested operation.
191
    switch(how) {
192
        case CombineAs::UNION:
193
            outs->MakeFromUnionOf(prevs, thiss);
194
            break;
195

196
        case CombineAs::DIFFERENCE:
197
            outs->MakeFromDifferenceOf(prevs, thiss);
198
            break;
199

200
        case CombineAs::INTERSECTION:
201
            outs->MakeFromIntersectionOf(prevs, thiss);
202
            break;
203

204
        case CombineAs::ASSEMBLE:
205
            outs->MakeFromAssemblyOf(prevs, thiss);
206
            break;
207
    }
208
}
209

210
void Group::GenerateShellAndMesh() {
211
    bool prevBooleanFailed = booleanFailed;
212
    booleanFailed = false;
213

214
    Group *srcg = this;
215

216
    thisShell.Clear();
217
    thisMesh.Clear();
218
    runningShell.Clear();
219
    runningMesh.Clear();
220

221
    // Don't attempt a lathe or extrusion unless the source section is good:
222
    // planar and not self-intersecting.
223
    bool haveSrc = true;
224
    if(type == Type::EXTRUDE || type == Type::LATHE || type == Type::REVOLVE) {
225
        Group *src = SK.GetGroup(opA);
226
        if(src->polyError.how != PolyError::GOOD) {
227
            haveSrc = false;
228
        }
229
    }
230

231
    if(type == Type::TRANSLATE || type == Type::ROTATE) {
232
        // A step and repeat gets merged against the group's previous group,
233
        // not our own previous group.
234
        srcg = SK.GetGroup(opA);
235

236
        if(!srcg->suppress) {
237
            if(!IsForcedToMesh()) {
238
                GenerateForStepAndRepeat<SShell>(&(srcg->thisShell), &thisShell, srcg->meshCombine);
239
            } else {
240
                SMesh prevm = {};
241
                prevm.MakeFromCopyOf(&srcg->thisMesh);
242
                srcg->thisShell.TriangulateInto(&prevm);
243
                GenerateForStepAndRepeat<SMesh> (&prevm, &thisMesh, srcg->meshCombine);
244
            }
245
        }
246
    } else if(type == Type::EXTRUDE && haveSrc) {
247
        Group *src = SK.GetGroup(opA);
248
        Vector translate = Vector::From(h.param(0), h.param(1), h.param(2));
249

250
        Vector tbot, ttop;
251
        if(subtype == Subtype::ONE_SIDED) {
252
            tbot = Vector::From(0, 0, 0); ttop = translate.ScaledBy(2);
253
        } else {
254
            tbot = translate.ScaledBy(-1); ttop = translate.ScaledBy(1);
255
        }
256

257
        SBezierLoopSetSet *sblss = &(src->bezierLoops);
258
        SBezierLoopSet *sbls;
259
        for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) {
260
            int is = thisShell.surface.n;
261
            // Extrude this outer contour (plus its inner contours, if present)
262
            thisShell.MakeFromExtrusionOf(sbls, tbot, ttop, color);
263

264
            // And for any plane faces, annotate the model with the entity for
265
            // that face, so that the user can select them with the mouse.
266
            Vector onOrig = sbls->point;
267
            int i;
268
            // Not using range-for here because we're starting at a different place and using
269
            // indices for meaning.
270
            for(i = is; i < thisShell.surface.n; i++) {
271
                SSurface *ss = &(thisShell.surface[i]);
272
                hEntity face = Entity::NO_ENTITY;
273

274
                Vector p = ss->PointAt(0, 0),
275
                       n = ss->NormalAt(0, 0).WithMagnitude(1);
276
                double d = n.Dot(p);
277

278
                if(i == is || i == (is + 1)) {
279
                    // These are the top and bottom of the shell.
280
                    if(fabs((onOrig.Plus(ttop)).Dot(n) - d) < LENGTH_EPS) {
281
                        face = Remap(Entity::NO_ENTITY, REMAP_TOP);
282
                        ss->face = face.v;
283
                    }
284
                    if(fabs((onOrig.Plus(tbot)).Dot(n) - d) < LENGTH_EPS) {
285
                        face = Remap(Entity::NO_ENTITY, REMAP_BOTTOM);
286
                        ss->face = face.v;
287
                    }
288
                    continue;
289
                }
290

291
                // So these are the sides
292
                if(ss->degm != 1 || ss->degn != 1) continue;
293

294
                for(Entity &e : SK.entity) {
295
                    if(e.group != opA) continue;
296
                    if(e.type != Entity::Type::LINE_SEGMENT) continue;
297

298
                    Vector a = SK.GetEntity(e.point[0])->PointGetNum(),
299
                           b = SK.GetEntity(e.point[1])->PointGetNum();
300
                    a = a.Plus(ttop);
301
                    b = b.Plus(ttop);
302
                    // Could get taken backwards, so check all cases.
303
                    if((a.Equals(ss->ctrl[0][0]) && b.Equals(ss->ctrl[1][0])) ||
304
                       (b.Equals(ss->ctrl[0][0]) && a.Equals(ss->ctrl[1][0])) ||
305
                       (a.Equals(ss->ctrl[0][1]) && b.Equals(ss->ctrl[1][1])) ||
306
                       (b.Equals(ss->ctrl[0][1]) && a.Equals(ss->ctrl[1][1])))
307
                    {
308
                        face = Remap(e.h, REMAP_LINE_TO_FACE);
309
                        ss->face = face.v;
310
                        break;
311
                    }
312
                }
313
            }
314
        }
315
    } else if(type == Type::LATHE && haveSrc) {
316
        Group *src = SK.GetGroup(opA);
317

318
        Vector pt   = SK.GetEntity(predef.origin)->PointGetNum(),
319
               axis = SK.GetEntity(predef.entityB)->VectorGetNum();
320
        axis = axis.WithMagnitude(1);
321

322
        SBezierLoopSetSet *sblss = &(src->bezierLoops);
323
        SBezierLoopSet *sbls;
324
        for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) {
325
            thisShell.MakeFromRevolutionOf(sbls, pt, axis, color, this);
326
        }
327
    } else if(type == Type::REVOLVE && haveSrc) {
328
        Group *src    = SK.GetGroup(opA);
329
        double anglef = SK.GetParam(h.param(3))->val * 4; // why the 4 is needed?
330
        double dists = 0, distf = 0;
331
        double angles = 0.0;
332
        if(subtype != Subtype::ONE_SIDED) {
333
            anglef *= 0.5;
334
            angles = -anglef;
335
        }
336
        Vector pt   = SK.GetEntity(predef.origin)->PointGetNum(),
337
               axis = SK.GetEntity(predef.entityB)->VectorGetNum();
338
        axis        = axis.WithMagnitude(1);
339

340
        SBezierLoopSetSet *sblss = &(src->bezierLoops);
341
        SBezierLoopSet *sbls;
342
        for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) {
343
            if(fabs(anglef - angles) < 2 * PI) {
344
                thisShell.MakeFromHelicalRevolutionOf(sbls, pt, axis, color, this,
345
                                                      angles, anglef, dists, distf);
346
            } else {
347
                thisShell.MakeFromRevolutionOf(sbls, pt, axis, color, this);
348
            }
349
        }
350
    } else if(type == Type::HELIX && haveSrc) {
351
        Group *src    = SK.GetGroup(opA);
352
        double anglef = SK.GetParam(h.param(3))->val * 4; // why the 4 is needed?
353
        double dists = 0, distf = 0;
354
        double angles = 0.0;
355
        distf = SK.GetParam(h.param(7))->val * 2; // dist is applied twice
356
        if(subtype != Subtype::ONE_SIDED) {
357
            anglef *= 0.5;
358
            angles = -anglef;
359
            distf *= 0.5;
360
            dists = -distf;
361
        }
362
        Vector pt   = SK.GetEntity(predef.origin)->PointGetNum(),
363
               axis = SK.GetEntity(predef.entityB)->VectorGetNum();
364
        axis        = axis.WithMagnitude(1);
365

366
        SBezierLoopSetSet *sblss = &(src->bezierLoops);
367
        SBezierLoopSet *sbls;
368
        for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) {
369
            thisShell.MakeFromHelicalRevolutionOf(sbls, pt, axis, color, this,
370
                                                  angles, anglef, dists, distf);
371
        }
372
    } else if(type == Type::LINKED) {
373
        // The imported shell or mesh are copied over, with the appropriate
374
        // transformation applied. We also must remap the face entities.
375
        Vector offset = {
376
            SK.GetParam(h.param(0))->val,
377
            SK.GetParam(h.param(1))->val,
378
            SK.GetParam(h.param(2))->val };
379
        Quaternion q = {
380
            SK.GetParam(h.param(3))->val,
381
            SK.GetParam(h.param(4))->val,
382
            SK.GetParam(h.param(5))->val,
383
            SK.GetParam(h.param(6))->val };
384

385
        thisMesh.MakeFromTransformationOf(&impMesh, offset, q, scale);
386
        thisMesh.RemapFaces(this, 0);
387

388
        thisShell.MakeFromTransformationOf(&impShell, offset, q, scale);
389
        thisShell.RemapFaces(this, 0);
390
    }
391

392
    if(srcg->meshCombine != CombineAs::ASSEMBLE) {
393
        thisShell.MergeCoincidentSurfaces();
394
    }
395

396
    // So now we've got the mesh or shell for this group. Combine it with
397
    // the previous group's mesh or shell with the requested Boolean, and
398
    // we're done.
399

400
    Group *prevg = srcg->RunningMeshGroup();
401

402
    if(!IsForcedToMesh()) {
403
        SShell *prevs = &(prevg->runningShell);
404
        GenerateForBoolean<SShell>(prevs, &thisShell, &runningShell,
405
            srcg->meshCombine);
406

407
        if(srcg->meshCombine != CombineAs::ASSEMBLE) {
408
            runningShell.MergeCoincidentSurfaces();
409
        }
410

411
        // If the Boolean failed, then we should note that in the text screen
412
        // for this group.
413
        booleanFailed = runningShell.booleanFailed;
414
        if(booleanFailed != prevBooleanFailed) {
415
            SS.ScheduleShowTW();
416
        }
417
    } else {
418
        SMesh prevm, thism;
419
        prevm = {};
420
        thism = {};
421

422
        prevm.MakeFromCopyOf(&(prevg->runningMesh));
423
        prevg->runningShell.TriangulateInto(&prevm);
424

425
        thism.MakeFromCopyOf(&thisMesh);
426
        thisShell.TriangulateInto(&thism);
427

428
        SMesh outm = {};
429
        GenerateForBoolean<SMesh>(&prevm, &thism, &outm, srcg->meshCombine);
430

431
        // Remove degenerate triangles; if we don't, they'll get split in SnapToMesh
432
        // in every generated group, resulting in polynomial increase in triangle count,
433
        // and corresponding slowdown.
434
        outm.RemoveDegenerateTriangles();
435

436
        if(srcg->meshCombine != CombineAs::ASSEMBLE) {
437
            // And make sure that the output mesh is vertex-to-vertex.
438
            SKdNode *root = SKdNode::From(&outm);
439
            root->SnapToMesh(&outm);
440
            root->MakeMeshInto(&runningMesh);
441
        } else {
442
            runningMesh.MakeFromCopyOf(&outm);
443
        }
444

445
        outm.Clear();
446
        thism.Clear();
447
        prevm.Clear();
448
    }
449

450
    displayDirty = true;
451
}
452

453
void Group::GenerateDisplayItems() {
454
    // This is potentially slow (since we've got to triangulate a shell, or
455
    // to find the emphasized edges for a mesh), so we will run it only
456
    // if its inputs have changed.
457
    if(displayDirty) {
458
        Group *pg = RunningMeshGroup();
459
        if(pg && thisMesh.IsEmpty() && thisShell.IsEmpty()) {
460
            // We don't contribute any new solid model in this group, so our
461
            // display items are identical to the previous group's; which means
462
            // that we can just display those, and stop ourselves from
463
            // recalculating for those every time we get a change in this group.
464
            //
465
            // Note that this can end up recursing multiple times (if multiple
466
            // groups that contribute no solid model exist in sequence), but
467
            // that's okay.
468
            pg->GenerateDisplayItems();
469

470
            displayMesh.Clear();
471
            displayMesh.MakeFromCopyOf(&(pg->displayMesh));
472

473
            displayOutlines.Clear();
474
            if(SS.GW.showEdges || SS.GW.showOutlines) {
475
                displayOutlines.MakeFromCopyOf(&pg->displayOutlines);
476
            }
477
        } else {
478
            // We do contribute new solid model, so we have to triangulate the
479
            // shell, and edge-find the mesh.
480
            displayMesh.Clear();
481
            runningShell.TriangulateInto(&displayMesh);
482
            STriangle *t;
483
            for(t = runningMesh.l.First(); t; t = runningMesh.l.NextAfter(t)) {
484
                STriangle trn = *t;
485
                Vector n = trn.Normal();
486
                trn.an = n;
487
                trn.bn = n;
488
                trn.cn = n;
489
                displayMesh.AddTriangle(&trn);
490
            }
491

492
            displayOutlines.Clear();
493

494
            if(SS.GW.showEdges || SS.GW.showOutlines) {
495
                SOutlineList rawOutlines = {};
496
                if(!runningMesh.l.IsEmpty()) {
497
                    // Triangle mesh only; no shell or emphasized edges.
498
                    runningMesh.MakeOutlinesInto(&rawOutlines, EdgeKind::EMPHASIZED);
499
                } else {
500
                    displayMesh.MakeOutlinesInto(&rawOutlines, EdgeKind::SHARP);
501
                }
502

503
                PolylineBuilder builder;
504
                builder.MakeFromOutlines(rawOutlines);
505
                builder.GenerateOutlines(&displayOutlines);
506
                rawOutlines.Clear();
507
            }
508
        }
509

510
        // If we render this mesh, we need to know whether it's transparent,
511
        // and we'll want all transparent triangles last, to make the depth test
512
        // work correctly.
513
        displayMesh.PrecomputeTransparency();
514

515
        // Recalculate mass center if needed
516
        if(SS.centerOfMass.draw && SS.centerOfMass.dirty && h == SS.GW.activeGroup) {
517
            SS.UpdateCenterOfMass();
518
        }
519
        displayDirty = false;
520
    }
521
}
522

523
Group *Group::PreviousGroup() const {
524
    Group *prev = nullptr;
525
    for(auto const &gh : SK.groupOrder) {
526
        Group *g = SK.GetGroup(gh);
527
        if(g->h == h) {
528
            return prev;
529
        }
530
        prev = g;
531
    }
532
    return nullptr;
533
}
534

535
Group *Group::RunningMeshGroup() const {
536
    if(type == Type::TRANSLATE || type == Type::ROTATE) {
537
        return SK.GetGroup(opA)->RunningMeshGroup();
538
    } else {
539
        return PreviousGroup();
540
    }
541
}
542

543
bool Group::IsMeshGroup() {
544
    switch(type) {
545
        case Group::Type::EXTRUDE:
546
        case Group::Type::LATHE:
547
        case Group::Type::REVOLVE:
548
        case Group::Type::HELIX:
549
        case Group::Type::ROTATE:
550
        case Group::Type::TRANSLATE:
551
            return true;
552

553
        default:
554
            return false;
555
    }
556
}
557

558
void Group::DrawMesh(DrawMeshAs how, Canvas *canvas) {
559
    if(!(SS.GW.showShaded ||
560
         SS.GW.drawOccludedAs != GraphicsWindow::DrawOccludedAs::VISIBLE)) return;
561

562
    switch(how) {
563
        case DrawMeshAs::DEFAULT: {
564
            // Force the shade color to something dim to not distract from
565
            // the sketch.
566
            Canvas::Fill fillFront = {};
567
            if(!SS.GW.showShaded) {
568
                fillFront.layer = Canvas::Layer::DEPTH_ONLY;
569
            }
570
            if((type == Type::DRAWING_3D || type == Type::DRAWING_WORKPLANE)
571
               && SS.GW.dimSolidModel) {
572
                fillFront.color = Style::Color(Style::DIM_SOLID);
573
            }
574
            Canvas::hFill hcfFront = canvas->GetFill(fillFront);
575

576
            // The back faces are drawn in red; should never seem them, since we
577
            // draw closed shells, so that's a debugging aid.
578
            Canvas::hFill hcfBack = {};
579
            if(SS.drawBackFaces && !displayMesh.isTransparent) {
580
                Canvas::Fill fillBack = {};
581
                fillBack.layer = fillFront.layer;
582
                fillBack.color = RgbaColor::FromFloat(1.0f, 0.1f, 0.1f);
583
                hcfBack = canvas->GetFill(fillBack);
584
            } else {
585
                hcfBack = hcfFront;
586
            }
587

588
            // Draw the shaded solid into the depth buffer for hidden line removal,
589
            // and if we're actually going to display it, to the color buffer too.
590
            canvas->DrawMesh(displayMesh, hcfFront, hcfBack);
591

592
            // Draw mesh edges, for debugging.
593
            if(SS.GW.showMesh) {
594
                Canvas::Stroke strokeTriangle = {};
595
                strokeTriangle.zIndex = 1;
596
                strokeTriangle.color  = RgbaColor::FromFloat(0.0f, 1.0f, 0.0f);
597
                strokeTriangle.width  = 1;
598
                strokeTriangle.unit   = Canvas::Unit::PX;
599
                Canvas::hStroke hcsTriangle = canvas->GetStroke(strokeTriangle);
600
                SEdgeList edges = {};
601
                for(const STriangle &t : displayMesh.l) {
602
                    edges.AddEdge(t.a, t.b);
603
                    edges.AddEdge(t.b, t.c);
604
                    edges.AddEdge(t.c, t.a);
605
                }
606
                canvas->DrawEdges(edges, hcsTriangle);
607
                edges.Clear();
608
            }
609
            break;
610
        }
611

612
        case DrawMeshAs::HOVERED: {
613
            Canvas::Fill fill = {};
614
            fill.color   = Style::Color(Style::HOVERED);
615
            fill.pattern = Canvas::FillPattern::CHECKERED_A;
616
            fill.zIndex  = 2;
617
            Canvas::hFill hcf = canvas->GetFill(fill);
618

619
            std::vector<uint32_t> faces;
620
            hEntity he = SS.GW.hover.entity;
621
            if(he.v != 0 && SK.GetEntity(he)->IsFace()) {
622
                faces.push_back(he.v);
623
            }
624
            canvas->DrawFaces(displayMesh, faces, hcf);
625
            break;
626
        }
627

628
        case DrawMeshAs::SELECTED: {
629
            Canvas::Fill fill = {};
630
            fill.color   = Style::Color(Style::SELECTED);
631
            fill.pattern = Canvas::FillPattern::CHECKERED_B;
632
            fill.zIndex  = 1;
633
            Canvas::hFill hcf = canvas->GetFill(fill);
634

635
            std::vector<uint32_t> faces;
636
            SS.GW.GroupSelection();
637
            auto const &gs = SS.GW.gs;
638
            // See also GraphicsWindow::MakeSelected "if(c >= MAX_SELECTABLE_FACES)"
639
            // and GraphicsWindow::GroupSelection "if(e->IsFace())"
640
            for(auto &fc : gs.face) {
641
                faces.push_back(fc.v);
642
            }
643
            canvas->DrawFaces(displayMesh, faces, hcf);
644
            break;
645
        }
646
    }
647
}
648

649
void Group::Draw(Canvas *canvas) {
650
    // Everything here gets drawn whether or not the group is hidden; we
651
    // can control this stuff independently, with show/hide solids, edges,
652
    // mesh, etc.
653

654
    GenerateDisplayItems();
655
    DrawMesh(DrawMeshAs::DEFAULT, canvas);
656

657
    if(SS.GW.showEdges) {
658
        Canvas::Stroke strokeEdge = Style::Stroke(Style::SOLID_EDGE);
659
        strokeEdge.zIndex = 1;
660
        Canvas::hStroke hcsEdge = canvas->GetStroke(strokeEdge);
661

662
        canvas->DrawOutlines(displayOutlines, hcsEdge,
663
                             SS.GW.showOutlines
664
                             ? Canvas::DrawOutlinesAs::EMPHASIZED_WITHOUT_CONTOUR
665
                             : Canvas::DrawOutlinesAs::EMPHASIZED_AND_CONTOUR);
666

667
        if(SS.GW.drawOccludedAs != GraphicsWindow::DrawOccludedAs::INVISIBLE) {
668
            Canvas::Stroke strokeHidden = Style::Stroke(Style::HIDDEN_EDGE);
669
            if(SS.GW.drawOccludedAs == GraphicsWindow::DrawOccludedAs::VISIBLE) {
670
                strokeHidden.stipplePattern = StipplePattern::CONTINUOUS;
671
            }
672
            strokeHidden.layer  = Canvas::Layer::OCCLUDED;
673
            Canvas::hStroke hcsHidden = canvas->GetStroke(strokeHidden);
674

675
            canvas->DrawOutlines(displayOutlines, hcsHidden,
676
                                 Canvas::DrawOutlinesAs::EMPHASIZED_AND_CONTOUR);
677
        }
678
    }
679

680
    if(SS.GW.showOutlines) {
681
        Canvas::Stroke strokeOutline = Style::Stroke(Style::OUTLINE);
682
        strokeOutline.zIndex = 1;
683
        Canvas::hStroke hcsOutline = canvas->GetStroke(strokeOutline);
684

685
        canvas->DrawOutlines(displayOutlines, hcsOutline,
686
                             Canvas::DrawOutlinesAs::CONTOUR_ONLY);
687
    }
688
}
689

690
void Group::DrawPolyError(Canvas *canvas) {
691
    const Camera &camera = canvas->GetCamera();
692

693
    Canvas::Stroke strokeUnclosed = Style::Stroke(Style::DRAW_ERROR);
694
    strokeUnclosed.color = strokeUnclosed.color.WithAlpha(50);
695
    Canvas::hStroke hcsUnclosed = canvas->GetStroke(strokeUnclosed);
696

697
    Canvas::Stroke strokeError = Style::Stroke(Style::DRAW_ERROR);
698
    strokeError.layer = Canvas::Layer::FRONT;
699
    strokeError.width = 1.0f;
700
    Canvas::hStroke hcsError = canvas->GetStroke(strokeError);
701

702
    double textHeight = Style::DefaultTextHeight() / camera.scale;
703

704
    // And finally show the polygons too, and any errors if it's not possible
705
    // to assemble the lines into closed polygons.
706
    if(polyError.how == PolyError::NOT_CLOSED) {
707
        // Report this error only in sketch-in-workplane groups; otherwise
708
        // it's just a nuisance.
709
        if(type == Type::DRAWING_WORKPLANE) {
710
            canvas->DrawVectorText(_("not closed contour, or not all same style!"),
711
                                   textHeight,
712
                                   polyError.notClosedAt.b, camera.projRight, camera.projUp,
713
                                   hcsError);
714
            canvas->DrawLine(polyError.notClosedAt.a, polyError.notClosedAt.b, hcsUnclosed);
715
        }
716
    } else if(polyError.how == PolyError::NOT_COPLANAR ||
717
              polyError.how == PolyError::SELF_INTERSECTING ||
718
              polyError.how == PolyError::ZERO_LEN_EDGE) {
719
        // These errors occur at points, not lines
720
        if(type == Type::DRAWING_WORKPLANE) {
721
            const char *msg;
722
            if(polyError.how == PolyError::NOT_COPLANAR) {
723
                msg = _("points not all coplanar!");
724
            } else if(polyError.how == PolyError::SELF_INTERSECTING) {
725
                msg = _("contour is self-intersecting!");
726
            } else {
727
                msg = _("zero-length edge!");
728
            }
729
            canvas->DrawVectorText(msg, textHeight,
730
                                   polyError.errorPointAt, camera.projRight, camera.projUp,
731
                                   hcsError);
732
        }
733
    } else {
734
        // The contours will get filled in DrawFilledPaths.
735
    }
736
}
737

738
void Group::DrawFilledPaths(Canvas *canvas) {
739
    for(const SBezierLoopSet &sbls : bezierLoops.l) {
740
        if(sbls.l.IsEmpty() || sbls.l[0].l.IsEmpty())
741
            continue;
742

743
        // In an assembled loop, all the styles should be the same; so doesn't
744
        // matter which one we grab.
745
        const SBezier *sb = &(sbls.l[0].l[0]);
746
        Style *s = Style::Get({ (uint32_t)sb->auxA });
747

748
        Canvas::Fill fill = {};
749
        fill.zIndex = 1;
750
        if(s->filled) {
751
            // This is a filled loop, where the user specified a fill color.
752
            fill.color = s->fillColor;
753
        } else if(h == SS.GW.activeGroup && SS.checkClosedContour &&
754
                    polyError.how == PolyError::GOOD) {
755
            // If this is the active group, and we are supposed to check
756
            // for closed contours, and we do indeed have a closed and
757
            // non-intersecting contour, then fill it dimly.
758
            fill.color = Style::Color(Style::CONTOUR_FILL).WithAlpha(127);
759
        } else continue;
760
        Canvas::hFill hcf = canvas->GetFill(fill);
761

762
        SPolygon sp = {};
763
        sbls.MakePwlInto(&sp);
764
        canvas->DrawPolygon(sp, hcf);
765
        sp.Clear();
766
    }
767
}
768

769
void Group::DrawContourAreaLabels(Canvas *canvas) {
770
    const Camera &camera = canvas->GetCamera();
771
    Vector gr = camera.projRight.ScaledBy(1 / camera.scale);
772
    Vector gu = camera.projUp.ScaledBy(1 / camera.scale);
773

774
    for(SBezierLoopSet &sbls : bezierLoops.l) {
775
        if(sbls.l.IsEmpty() || sbls.l[0].l.IsEmpty())
776
            continue;
777

778
        Vector min = sbls.l[0].l[0].ctrl[0];
779
        Vector max = min;
780
        Vector zero = Vector::From(0.0, 0.0, 0.0);
781
        sbls.GetBoundingProjd(Vector::From(1.0, 0.0, 0.0), zero, &min.x, &max.x);
782
        sbls.GetBoundingProjd(Vector::From(0.0, 1.0, 0.0), zero, &min.y, &max.y);
783
        sbls.GetBoundingProjd(Vector::From(0.0, 0.0, 1.0), zero, &min.z, &max.z);
784

785
        Vector mid = min.Plus(max).ScaledBy(0.5);
786

787
        hStyle hs = { Style::CONSTRAINT };
788
        Canvas::Stroke stroke = Style::Stroke(hs);
789
        stroke.layer = Canvas::Layer::FRONT;
790

791
        std::string label = SS.MmToStringSI(fabs(sbls.SignedArea()), /*dim=*/2);
792
        double fontHeight = Style::TextHeight(hs);
793
        double textWidth  = VectorFont::Builtin()->GetWidth(fontHeight, label),
794
               textHeight = VectorFont::Builtin()->GetCapHeight(fontHeight);
795
        Vector pos = mid.Minus(gr.ScaledBy(textWidth / 2.0))
796
                        .Minus(gu.ScaledBy(textHeight / 2.0));
797
        canvas->DrawVectorText(label, fontHeight, pos, gr, gu, canvas->GetStroke(stroke));
798
    }
799
}
800

801

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.