Solvespace

Форк
0
/
draw.cpp 
954 строки · 33.7 Кб
1
//-----------------------------------------------------------------------------
2
// The root function to paint our graphics window, after setting up all the
3
// views and such appropriately. Also contains all the stuff to manage the
4
// selection.
5
//
6
// Copyright 2008-2013 Jonathan Westhues.
7
//-----------------------------------------------------------------------------
8
#include "solvespace.h"
9

10
bool GraphicsWindow::Selection::Equals(Selection *b) {
11
    if(entity     != b->entity)     return false;
12
    if(constraint != b->constraint) return false;
13
    return true;
14
}
15

16
bool GraphicsWindow::Selection::IsEmpty() {
17
    if(entity.v)        return false;
18
    if(constraint.v)    return false;
19
    return true;
20
}
21

22
bool GraphicsWindow::Selection::HasEndpoints() {
23
    if(!entity.v) return false;
24
    Entity *e = SK.GetEntity(entity);
25
    return e->HasEndpoints();
26
}
27

28
void GraphicsWindow::Selection::Clear() {
29
    entity.v = constraint.v = 0;
30
    emphasized = false;
31
}
32

33
void GraphicsWindow::Selection::Draw(bool isHovered, Canvas *canvas) {
34
    const Camera &camera = canvas->GetCamera();
35

36
    std::vector<Vector> refs;
37
    if(entity.v) {
38
        Entity *e = SK.GetEntity(entity);
39
        e->Draw(isHovered ? Entity::DrawAs::HOVERED :
40
                            Entity::DrawAs::SELECTED,
41
                canvas);
42
        if(emphasized) {
43
            e->GetReferencePoints(&refs);
44
        }
45
    }
46
    if(constraint.v) {
47
        Constraint *c = SK.GetConstraint(constraint);
48
        c->Draw(isHovered ? Constraint::DrawAs::HOVERED :
49
                            Constraint::DrawAs::SELECTED,
50
                canvas);
51
        if(emphasized) {
52
            c->GetReferencePoints(camera, &refs);
53
        }
54
    }
55
    if(emphasized && (constraint.v || entity.v)) {
56
        // We want to emphasize this constraint or entity, by drawing a thick
57
        // line from the top left corner of the screen to the reference point(s)
58
        // of that entity or constraint.
59
        Canvas::Stroke strokeEmphasis = {};
60
        strokeEmphasis.layer  = Canvas::Layer::FRONT;
61
        strokeEmphasis.color  = Style::Color(Style::HOVERED).WithAlpha(50);
62
        strokeEmphasis.width  = 40;
63
        strokeEmphasis.unit   = Canvas::Unit::PX;
64
        Canvas::hStroke hcsEmphasis = canvas->GetStroke(strokeEmphasis);
65

66
        Point2d topLeftScreen;
67
        topLeftScreen.x = -(double)camera.width / 2;
68
        topLeftScreen.y = (double)camera.height / 2;
69
        Vector topLeft = camera.UnProjectPoint(topLeftScreen);
70

71
        auto it = std::unique(refs.begin(), refs.end(),
72
                              [](Vector a, Vector b) { return a.Equals(b); });
73
        refs.erase(it, refs.end());
74
        for(Vector p : refs) {
75
            canvas->DrawLine(topLeft, p, hcsEmphasis);
76
        }
77
    }
78
}
79

80
void GraphicsWindow::ClearSelection() {
81
    selection.Clear();
82
    SS.ScheduleShowTW();
83
    Invalidate();
84
}
85

86
void GraphicsWindow::ClearNonexistentSelectionItems() {
87
    bool change = false;
88
    Selection *s;
89
    selection.ClearTags();
90
    for(s = selection.First(); s; s = selection.NextAfter(s)) {
91
        if(s->constraint.v && !(SK.constraint.FindByIdNoOops(s->constraint))) {
92
            s->tag = 1;
93
            change = true;
94
        }
95
        if(s->entity.v && !(SK.entity.FindByIdNoOops(s->entity))) {
96
            s->tag = 1;
97
            change = true;
98
        }
99
    }
100
    selection.RemoveTagged();
101
    if(change) Invalidate();
102
}
103

104
//-----------------------------------------------------------------------------
105
// Is this entity/constraint selected?
106
//-----------------------------------------------------------------------------
107
bool GraphicsWindow::IsSelected(hEntity he) {
108
    Selection s = {};
109
    s.entity = he;
110
    return IsSelected(&s);
111
}
112
bool GraphicsWindow::IsSelected(Selection *st) {
113
    Selection *s;
114
    for(s = selection.First(); s; s = selection.NextAfter(s)) {
115
        if(s->Equals(st)) {
116
            return true;
117
        }
118
    }
119
    return false;
120
}
121

122
//-----------------------------------------------------------------------------
123
// Unselect an item, if it is selected. We can either unselect just that item,
124
// or also unselect any coincident points. The latter is useful if the user
125
// somehow selects two coincident points (like with select all), because it
126
// would otherwise be impossible to de-select the lower of the two.
127
//-----------------------------------------------------------------------------
128
void GraphicsWindow::MakeUnselected(hEntity he, bool coincidentPointTrick) {
129
    Selection stog = {};
130
    stog.entity = he;
131
    MakeUnselected(&stog, coincidentPointTrick);
132
}
133
void GraphicsWindow::MakeUnselected(Selection *stog, bool coincidentPointTrick){
134
    if(stog->IsEmpty()) return;
135

136
    Selection *s;
137

138
    // If an item was selected, then we just un-select it.
139
    selection.ClearTags();
140
    for(s = selection.First(); s; s = selection.NextAfter(s)) {
141
        if(s->Equals(stog)) {
142
            s->tag = 1;
143
        }
144
    }
145
    // If two points are coincident, then it's impossible to hover one of
146
    // them. But make sure to deselect both, to avoid mysterious seeming
147
    // inability to deselect if the bottom one did somehow get selected.
148
    if(stog->entity.v && coincidentPointTrick) {
149
        Entity *e = SK.GetEntity(stog->entity);
150
        if(e->IsPoint()) {
151
            Vector ep = e->PointGetNum();
152
            for(s = selection.First(); s; s = selection.NextAfter(s)) {
153
                if(!s->entity.v) continue;
154
                if(s->entity == stog->entity)
155
                    continue;
156
                Entity *se = SK.GetEntity(s->entity);
157
                if(!se->IsPoint()) continue;
158
                if(ep.Equals(se->PointGetNum())) {
159
                    s->tag = 1;
160
                }
161
            }
162
        }
163
    }
164
    selection.RemoveTagged();
165
}
166

167
//-----------------------------------------------------------------------------
168
// Select an item, if it isn't selected already.
169
//-----------------------------------------------------------------------------
170
void GraphicsWindow::MakeSelected(hEntity he) {
171
    Selection stog = {};
172
    stog.entity = he;
173
    MakeSelected(&stog);
174
}
175

176
void GraphicsWindow::MakeSelected(hConstraint hc) {
177
    Selection stog = {};
178
    stog.constraint = hc;
179
    MakeSelected(&stog);
180
}
181

182
void GraphicsWindow::MakeSelected(Selection *stog) {
183
    if(stog->IsEmpty()) return;
184
    if(IsSelected(stog)) return;
185

186
    if(stog->entity.v != 0 && SK.GetEntity(stog->entity)->IsFace()) {
187
        // In the interest of speed for the triangle drawing code,
188
        // only MAX_SELECTABLE_FACES faces may be selected at a time.
189
        unsigned int c = 0;
190
        Selection *s;
191
        selection.ClearTags();
192
        for(s = selection.First(); s; s = selection.NextAfter(s)) {
193
            hEntity he = s->entity;
194
            if(he.v != 0 && SK.GetEntity(he)->IsFace()) {
195
                c++;
196
                // See also GraphicsWindow::GroupSelection "if(e->IsFace())"
197
                // and Group::DrawMesh "case DrawMeshAs::SELECTED:"
198
                if(c >= MAX_SELECTABLE_FACES) s->tag = 1;
199
            }
200
        }
201
        selection.RemoveTagged();
202
    }
203

204
    selection.Add(stog);
205
}
206

207
//-----------------------------------------------------------------------------
208
// Select everything that lies within the marquee view-aligned rectangle.
209
//-----------------------------------------------------------------------------
210
void GraphicsWindow::SelectByMarquee() {
211
    Point2d marqueePoint = ProjectPoint(orig.marqueePoint);
212
    BBox marqueeBBox = BBox::From(Vector::From(marqueePoint.x, marqueePoint.y, VERY_NEGATIVE),
213
                                  Vector::From(orig.mouse.x,   orig.mouse.y,   VERY_POSITIVE));
214

215
    for(Entity &e : SK.entity) {
216
        if(e.group != SS.GW.activeGroup) continue;
217
        if(e.IsFace() || e.IsDistance()) continue;
218
        if(!e.IsVisible()) continue;
219

220
        bool entityHasBBox;
221
        BBox entityBBox = e.GetOrGenerateScreenBBox(&entityHasBBox);
222
        if(entityHasBBox && entityBBox.Overlaps(marqueeBBox)) {
223
            if(e.type == Entity::Type::LINE_SEGMENT) {
224
                Vector p0 = SS.GW.ProjectPoint3(e.EndpointStart());
225
                Vector p1 = SS.GW.ProjectPoint3(e.EndpointFinish());
226
                if((!marqueeBBox.Contains({p0.x, p0.y}, 0)) &&
227
                   (!marqueeBBox.Contains({p1.x, p1.y}, 0))) {
228
                    // The selection marquee does not contain either of the line segment end points.
229
                    // This means that either the segment is entirely outside the marquee or that
230
                    // it intersects it. Check if it does...
231
                    if(!Vector::BoundingBoxIntersectsLine(marqueeBBox.maxp, marqueeBBox.minp, p0,
232
                                                          p1, true)) {
233
                        // ... it does not so it is outside.
234
                        continue;
235
                    }
236
                }
237
            }
238
            MakeSelected(e.h);
239
        }
240
    }
241
}
242

243
//-----------------------------------------------------------------------------
244
// Sort the selection according to various criteria: the entities and
245
// constraints separately, counts of certain types of entities (circles,
246
// lines, etc.), and so on.
247
//-----------------------------------------------------------------------------
248
void GraphicsWindow::GroupSelection() {
249
    gs = {};
250
    int i;
251
    for(i = 0; i < selection.n; i++) {
252
        Selection *s = &(selection[i]);
253
        if(s->entity.v) {
254
            (gs.n)++;
255

256
            Entity *e = SK.entity.FindById(s->entity);
257

258
            if(e->IsStylable()) gs.stylables++;
259

260
            // A list of points, and a list of all entities that aren't points.
261
            if(e->IsPoint()) {
262
                gs.points++;
263
                gs.point.push_back(s->entity);
264
            } else {
265
                gs.entities++;
266
                gs.entity.push_back(s->entity);
267
            }
268

269
            // And an auxiliary list of normals, including normals from
270
            // workplanes.
271
            if(e->IsNormal()) {
272
                gs.anyNormals++;
273
                gs.anyNormal.push_back(s->entity);
274
            } else if(e->IsWorkplane()) {
275
                gs.anyNormals++;
276
                gs.anyNormal.push_back(e->Normal()->h);
277
            }
278

279
            // And of vectors (i.e., stuff with a direction to constrain)
280
            if(e->HasVector()) {
281
                gs.vectors++;
282
                gs.vector.push_back(s->entity);
283
            }
284

285
            // Faces (which are special, associated/drawn with triangles)
286
            if(e->IsFace()) {
287
                gs.faces++;
288
                gs.face.push_back(s->entity);
289
            }
290

291
            if(e->HasEndpoints()) {
292
                (gs.withEndpoints)++;
293
            }
294

295
            // And some aux counts too
296
            switch(e->type) {
297
                case Entity::Type::WORKPLANE:      (gs.workplanes)++; break;
298
                case Entity::Type::LINE_SEGMENT:   (gs.lineSegments)++; break;
299
                case Entity::Type::CUBIC:          (gs.cubics)++; break;
300
                case Entity::Type::CUBIC_PERIODIC: (gs.periodicCubics)++; break;
301

302
                case Entity::Type::ARC_OF_CIRCLE:
303
                    (gs.circlesOrArcs)++;
304
                    (gs.arcs)++;
305
                    break;
306

307
                case Entity::Type::CIRCLE:         (gs.circlesOrArcs)++; break;
308

309
                default: break;
310
            }
311
        }
312
        if(s->constraint.v) {
313
            gs.constraints++;
314
            gs.constraint.push_back(s->constraint);
315
            Constraint *c = SK.GetConstraint(s->constraint);
316
            if(c->IsStylable()) gs.stylables++;
317
            if(c->HasLabel()) gs.constraintLabels++;
318
        }
319
    }
320
}
321

322
Camera GraphicsWindow::GetCamera() const {
323
    Camera camera = {};
324
    if(window) {
325
        window->GetContentSize(&camera.width, &camera.height);
326
        camera.pixelRatio = window->GetDevicePixelRatio();
327
        camera.gridFit    = (window->GetDevicePixelRatio() == 1);
328
    } else {    // solvespace-cli
329
        camera.width = 297.0;   // A4? Whatever...
330
        camera.height = 210.0;
331
        camera.pixelRatio = 1.0;
332
        camera.gridFit    = camera.pixelRatio == 1.0;
333
    }
334
    camera.offset     = offset;
335
    camera.projUp     = projUp;
336
    camera.projRight  = projRight;
337
    camera.scale      = scale;
338
    camera.tangent    = SS.CameraTangent();
339
    return camera;
340
}
341

342
Lighting GraphicsWindow::GetLighting() const {
343
    Lighting lighting = {};
344
    lighting.backgroundColor   = SS.backgroundColor;
345
    lighting.ambientIntensity  = SS.ambientIntensity;
346
    lighting.lightIntensity[0] = SS.lightIntensity[0];
347
    lighting.lightIntensity[1] = SS.lightIntensity[1];
348
    lighting.lightDirection[0] = SS.lightDir[0];
349
    lighting.lightDirection[1] = SS.lightDir[1];
350
    return lighting;
351
}
352

353
GraphicsWindow::Selection GraphicsWindow::ChooseFromHoverToSelect() {
354
    Selection sel = {};
355
    if(hoverList.IsEmpty())
356
        return sel;
357

358
    Group *activeGroup = SK.GetGroup(SS.GW.activeGroup);
359
    int bestOrder = -1;
360
    int bestZIndex = 0;
361
    double bestDepth = VERY_POSITIVE;
362
    
363
    for(const Hover &hov : hoverList) {
364
        hGroup hg = {};
365
        if(hov.selection.entity.v != 0) {
366
            hg = SK.GetEntity(hov.selection.entity)->group;
367
        } else if(hov.selection.constraint.v != 0) {
368
            hg = SK.GetConstraint(hov.selection.constraint)->group;
369
        }
370

371
        Group *g = SK.GetGroup(hg);
372
        if(g->order > activeGroup->order) continue;
373
        if(bestOrder != -1 && (bestOrder > g->order || bestZIndex > hov.zIndex)) continue;
374
        // we have hov.zIndex is >= best and hov.group is >= best (but not > active group)
375
        if(hov.depth > bestDepth && bestOrder == g->order && bestZIndex == hov.zIndex) continue;
376
        bestOrder  = g->order;
377
        bestZIndex = hov.zIndex;
378
        bestDepth = hov.depth;
379
        sel = hov.selection;
380
    }
381
    return sel;
382
}
383

384
// This uses the same logic as hovering and static entity selection
385
// but ignores points known not to be draggable
386
GraphicsWindow::Selection GraphicsWindow::ChooseFromHoverToDrag() {
387
    Selection sel = {};
388
    if(hoverList.IsEmpty())
389
        return sel;
390

391
    Group *activeGroup = SK.GetGroup(SS.GW.activeGroup);
392
    int bestOrder = -1;
393
    int bestZIndex = 0;
394
    double bestDepth = VERY_POSITIVE;
395

396
    for(const Hover &hov : hoverList) {
397
        hGroup hg = {};
398
        if(hov.selection.entity.v != 0) {
399
            Entity *e = SK.GetEntity(hov.selection.entity);
400
            if (!e->CanBeDragged()) continue;
401
            hg = e->group;
402
        } else if(hov.selection.constraint.v != 0) {
403
            hg = SK.GetConstraint(hov.selection.constraint)->group;
404
        }
405

406
        Group *g = SK.GetGroup(hg);
407
        if(g->order > activeGroup->order) continue;
408
        if(bestOrder != -1 && (bestOrder > g->order || bestZIndex > hov.zIndex)) continue;
409
        // we have hov.zIndex is >= best and hov.group is >= best (but not > active group)
410
        if(hov.depth > bestDepth && bestOrder == g->order && bestZIndex == hov.zIndex) continue;
411
        bestOrder  = g->order;
412
        bestZIndex = hov.zIndex;
413
        sel = hov.selection;
414
    }
415
    return sel;
416
}
417

418
void GraphicsWindow::HitTestMakeSelection(Point2d mp) {
419
    hoverList = {};
420
    Selection sel = {};
421

422
    // Did the view projection change? If so, invalidate bounding boxes.
423
    if(!offset.EqualsExactly(cached.offset) ||
424
           !projRight.EqualsExactly(cached.projRight) ||
425
           !projUp.EqualsExactly(cached.projUp) ||
426
           EXACT(scale != cached.scale)) {
427
        cached.offset = offset;
428
        cached.projRight = projRight;
429
        cached.projUp = projUp;
430
        cached.scale = scale;
431
        for(Entity &e : SK.entity) {
432
            e.screenBBoxValid = false;
433
        }
434
    }
435

436
    ObjectPicker canvas = {};
437
    canvas.camera    = GetCamera();
438
    canvas.selRadius = 10.0;
439
    canvas.point     = mp;
440
    canvas.maxZIndex = -1;
441

442
    // Always do the entities; we might be dragging something that should
443
    // be auto-constrained, and we need the hover for that.
444
    for(Entity &e : SK.entity) {
445
        if(!e.IsVisible()) continue;
446

447
        // If faces aren't selectable, image entities aren't either.
448
        if(e.type == Entity::Type::IMAGE && !showFaces) continue;
449

450
        // Don't hover whatever's being dragged.
451
        if(IsFromPending(e.h.request())) {
452
            // The one exception is when we're creating a new cubic; we
453
            // want to be able to hover the first point, because that's
454
            // how we turn it into a periodic spline.
455
            if(!e.IsPoint()) continue;
456
            if(!e.h.isFromRequest()) continue;
457
            Request *r = SK.GetRequest(e.h.request());
458
            if(r->type != Request::Type::CUBIC) continue;
459
            if(r->extraPoints < 2) continue;
460
            if(e.h.v != r->h.entity(1).v) continue;
461
        }
462

463
        if(canvas.Pick([&]{ e.Draw(Entity::DrawAs::DEFAULT, &canvas); })) {
464
            Hover hov = {};
465
            hov.distance = canvas.minDistance;
466
            hov.zIndex   = canvas.maxZIndex;
467
            hov.depth    = canvas.minDepth;
468
            hov.selection.entity = e.h;
469
            hoverList.Add(&hov);
470
        }
471
    }
472

473
    // The constraints and faces happen only when nothing's in progress.
474
    if(pending.operation == Pending::NONE) {
475
        // Constraints
476
        for(Constraint &c : SK.constraint) {
477
            if(canvas.Pick([&]{ c.Draw(Constraint::DrawAs::DEFAULT, &canvas); })) {
478
                Hover hov = {};
479
                hov.distance = canvas.minDistance;
480
                hov.zIndex   = canvas.maxZIndex;
481
                hov.selection.constraint = c.h;
482
                hoverList.Add(&hov);
483
            }
484
        }
485
    }
486

487
    std::sort(hoverList.begin(), hoverList.end(),
488
        [](const Hover &a, const Hover &b) {
489
            if(a.zIndex == b.zIndex) return a.distance < b.distance;
490
            return a.zIndex > b.zIndex;
491
        });
492
    sel = ChooseFromHoverToSelect();
493

494
    if(pending.operation == Pending::NONE) {
495
        // Faces, from the triangle mesh; these are lowest priority
496
        if(sel.constraint.v == 0 && sel.entity.v == 0 && showShaded && showFaces) {
497
            Group *g = SK.GetGroup(activeGroup);
498
            SMesh *m = &(g->displayMesh);
499

500
            uint32_t v = m->FirstIntersectionWith(mp);
501
            if(v) {
502
                sel.entity.v = v;
503
            }
504
        }
505
    }
506

507
    canvas.Clear();
508

509
    if(!sel.Equals(&hover)) {
510
        hover = sel;
511
        Invalidate();
512
    }
513
}
514

515
//-----------------------------------------------------------------------------
516
// Project a point in model space to screen space, exactly as gl would; return
517
// units are pixels.
518
//-----------------------------------------------------------------------------
519
Point2d GraphicsWindow::ProjectPoint(Vector p) {
520
    Vector p3 = ProjectPoint3(p);
521
    Point2d p2 = { p3.x, p3.y };
522
    return p2;
523
}
524
//-----------------------------------------------------------------------------
525
// Project a point in model space to screen space, exactly as gl would; return
526
// units are pixels. The z coordinate is also returned, also in pixels.
527
//-----------------------------------------------------------------------------
528
Vector GraphicsWindow::ProjectPoint3(Vector p) {
529
    double w;
530
    Vector r = ProjectPoint4(p, &w);
531
    return r.ScaledBy(scale/w);
532
}
533
//-----------------------------------------------------------------------------
534
// Project a point in model space halfway into screen space. The scale is
535
// not applied, and the perspective divide isn't applied; instead the w
536
// coordinate is returned separately.
537
//-----------------------------------------------------------------------------
538
Vector GraphicsWindow::ProjectPoint4(Vector p, double *w) {
539
    p = p.Plus(offset);
540

541
    Vector r;
542
    r.x = p.Dot(projRight);
543
    r.y = p.Dot(projUp);
544
    r.z = p.Dot(projUp.Cross(projRight));
545

546
    *w = 1 + r.z*SS.CameraTangent()*scale;
547
    return r;
548
}
549

550
//-----------------------------------------------------------------------------
551
// Return a point in the plane parallel to the screen and through the offset,
552
// that projects onto the specified (x, y) coordinates.
553
//-----------------------------------------------------------------------------
554
Vector GraphicsWindow::UnProjectPoint(Point2d p) {
555
    Vector orig = offset.ScaledBy(-1);
556

557
    // Note that we ignoring the effects of perspective. Since our returned
558
    // point has the same component normal to the screen as the offset, it
559
    // will have z = 0 after the rotation is applied, thus w = 1. So this is
560
    // correct.
561
    orig = orig.Plus(projRight.ScaledBy(p.x / scale)).Plus(
562
                     projUp.   ScaledBy(p.y / scale));
563
    return orig;
564
}
565

566
Vector GraphicsWindow::UnProjectPoint3(Vector p) {
567
    p.z = p.z / (scale - p.z * SS.CameraTangent() * scale);
568
    double w = 1 + p.z * SS.CameraTangent() * scale;
569
    p.x *= w / scale;
570
    p.y *= w / scale;
571

572
    Vector orig = offset.ScaledBy(-1);
573
    orig = orig.Plus(projRight.ScaledBy(p.x)).Plus(
574
                     projUp.   ScaledBy(p.y).Plus(
575
                     projRight.Cross(projUp). ScaledBy(p.z)));
576
    return orig;
577
}
578

579
void GraphicsWindow::NormalizeProjectionVectors() {
580
    if(projRight.Magnitude() < LENGTH_EPS) {
581
        projRight = Vector::From(1, 0, 0);
582
    }
583

584
    Vector norm = projRight.Cross(projUp);
585
    // If projRight and projUp somehow ended up parallel, then pick an
586
    // arbitrary projUp normal to projRight.
587
    if(norm.Magnitude() < LENGTH_EPS) {
588
        norm = projRight.Normal(0);
589
    }
590
    projUp = norm.Cross(projRight);
591

592
    projUp = projUp.WithMagnitude(1);
593
    projRight = projRight.WithMagnitude(1);
594
}
595

596
void GraphicsWindow::DrawSnapGrid(Canvas *canvas) {
597
    if(!LockedInWorkplane()) return;
598

599
    const Camera &camera = canvas->GetCamera();
600
    double width  = camera.width,
601
           height = camera.height;
602

603
    hEntity he = ActiveWorkplane();
604
    EntityBase *wrkpl = SK.GetEntity(he),
605
               *norm  = wrkpl->Normal();
606
    Vector n = projUp.Cross(projRight);
607
    Vector wu, wv, wn, wp;
608
    wp = SK.GetEntity(wrkpl->point[0])->PointGetNum();
609
    wu = norm->NormalU();
610
    wv = norm->NormalV();
611
    wn = norm->NormalN();
612

613
    double g = SS.gridSpacing;
614

615
    double umin = VERY_POSITIVE, umax = VERY_NEGATIVE,
616
           vmin = VERY_POSITIVE, vmax = VERY_NEGATIVE;
617
    int a;
618
    for(a = 0; a < 4; a++) {
619
        // Ideally, we would just do +/- half the width and height; but
620
        // allow some extra slop for rounding.
621
        Vector horiz = projRight.ScaledBy((0.6*width)/scale  + 2*g),
622
               vert  = projUp.   ScaledBy((0.6*height)/scale + 2*g);
623
        if(a == 2 || a == 3) horiz = horiz.ScaledBy(-1);
624
        if(a == 1 || a == 3) vert  = vert. ScaledBy(-1);
625
        Vector tp = horiz.Plus(vert).Minus(offset);
626

627
        // Project the point into our grid plane, normal to the screen
628
        // (not to the grid plane). If the plane is on edge then this is
629
        // impossible so don't try to draw the grid.
630
        bool parallel;
631
        Vector tpp = Vector::AtIntersectionOfPlaneAndLine(
632
                                        wn, wn.Dot(wp),
633
                                        tp, tp.Plus(n),
634
                                        &parallel);
635
        if(parallel) return;
636

637
        tpp = tpp.Minus(wp);
638
        double uu = tpp.Dot(wu),
639
               vv = tpp.Dot(wv);
640

641
        umin = min(uu, umin);
642
        umax = max(uu, umax);
643
        vmin = min(vv, vmin);
644
        vmax = max(vv, vmax);
645
    }
646

647
    int i, j, i0, i1, j0, j1;
648

649
    i0 = (int)(umin / g);
650
    i1 = (int)(umax / g);
651
    j0 = (int)(vmin / g);
652
    j1 = (int)(vmax / g);
653

654
    if(i0 > i1 || i1 - i0 > 400) return;
655
    if(j0 > j1 || j1 - j0 > 400) return;
656

657
    Canvas::Stroke stroke = {};
658
    stroke.layer  = Canvas::Layer::BACK;
659
    stroke.color  = Style::Color(Style::DATUM).WithAlpha(75);
660
    stroke.unit   = Canvas::Unit::PX;
661
    stroke.width  = 1.0f;
662
    Canvas::hStroke hcs = canvas->GetStroke(stroke);
663

664
    for(i = i0 + 1; i < i1; i++) {
665
        canvas->DrawLine(wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j0*g)),
666
                         wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j1*g)),
667
                         hcs);
668
    }
669
    for(j = j0 + 1; j < j1; j++) {
670
        canvas->DrawLine(wp.Plus(wu.ScaledBy(i0*g)).Plus(wv.ScaledBy(j*g)),
671
                         wp.Plus(wu.ScaledBy(i1*g)).Plus(wv.ScaledBy(j*g)),
672
                         hcs);
673
    }
674
}
675

676
void GraphicsWindow::DrawEntities(Canvas *canvas, bool persistent) {
677
    for(Entity &e : SK.entity) {
678
        if(persistent == (e.IsNormal() || e.IsWorkplane())) continue;
679
        switch(SS.GW.drawOccludedAs) {
680
            case DrawOccludedAs::VISIBLE:
681
                e.Draw(Entity::DrawAs::OVERLAY, canvas);
682
                break;
683

684
            case DrawOccludedAs::STIPPLED:
685
                e.Draw(Entity::DrawAs::HIDDEN, canvas);
686
                /* fallthrough */
687
            case DrawOccludedAs::INVISIBLE:
688
                e.Draw(Entity::DrawAs::DEFAULT, canvas);
689
                break;
690
        }
691
    }
692
}
693

694
void GraphicsWindow::DrawPersistent(Canvas *canvas) {
695
    // Draw the active group; this does stuff like the mesh and edges.
696
    SK.GetGroup(activeGroup)->Draw(canvas);
697

698
    // Now draw the entities that don't change with viewport.
699
    DrawEntities(canvas, /*persistent=*/true);
700

701
    // Draw filled paths in all groups, when those filled paths were requested
702
    // specially by assigning a style with a fill color, or when the filled
703
    // paths are just being filled by default. This should go last, to make
704
    // the transparency work.
705
    for(hGroup hg : SK.groupOrder) {
706
        Group *g = SK.GetGroup(hg);
707
        if(!(g->IsVisible())) continue;
708
        g->DrawFilledPaths(canvas);
709
    }
710
}
711

712
void GraphicsWindow::Draw(Canvas *canvas) {
713
    const Camera &camera = canvas->GetCamera();
714

715
    // Nasty case when we're reloading the linked files; could be that
716
    // we get an error, so a dialog pops up, and a message loop starts, and
717
    // we have to get called to paint ourselves. If the sketch is screwed
718
    // up, then we could trigger an oops trying to draw.
719
    if(!SS.allConsistent) return;
720

721
    if(showSnapGrid) DrawSnapGrid(canvas);
722

723
    // Draw all the things that don't change when we rotate.
724
    if(persistentCanvas != NULL) {
725
        if(persistentDirty) {
726
            persistentDirty = false;
727

728
            persistentCanvas->Clear();
729
            DrawPersistent(&*persistentCanvas);
730
            persistentCanvas->Finalize();
731
        }
732

733
        persistentCanvas->Draw();
734
    } else {
735
        DrawPersistent(canvas);
736
    }
737

738
    // Draw the entities that do change with viewport.
739
    DrawEntities(canvas, /*persistent=*/false);
740

741
    // Draw the polygon errors.
742
    if(SS.checkClosedContour) {
743
        SK.GetGroup(activeGroup)->DrawPolyError(canvas);
744
    }
745

746
    // Draw the constraints
747
    for(Constraint &c : SK.constraint) {
748
        c.Draw(Constraint::DrawAs::DEFAULT, canvas);
749
    }
750

751
    // Draw areas
752
    if(SS.showContourAreas) {
753
        for(hGroup hg : SK.groupOrder) {
754
            Group *g = SK.GetGroup(hg);
755
            if(g->h != activeGroup) continue;
756
            if(!(g->IsVisible())) continue;
757
            g->DrawContourAreaLabels(canvas);
758
        }
759
    }
760

761
    // Draw the "pending" constraint, i.e. a constraint that would be
762
    // placed on a line that is almost horizontal or vertical.
763
    if(SS.GW.pending.operation == Pending::DRAGGING_NEW_LINE_POINT &&
764
            SS.GW.pending.hasSuggestion) {
765
        Constraint c = {};
766
        c.group = SS.GW.activeGroup;
767
        c.workplane = SS.GW.ActiveWorkplane();
768
        c.type = SS.GW.pending.suggestion;
769
        c.entityA = SS.GW.pending.request.entity(0);
770
        c.Draw(Constraint::DrawAs::DEFAULT, canvas);
771
    }
772

773
    Canvas::Stroke strokeAnalyze = Style::Stroke(Style::ANALYZE);
774
    strokeAnalyze.layer = Canvas::Layer::FRONT;
775
    Canvas::hStroke hcsAnalyze = canvas->GetStroke(strokeAnalyze);
776

777
    // Draw the traced path, if one exists
778
    SEdgeList tracedEdges = {};
779
    SS.traced.path.MakeEdgesInto(&tracedEdges);
780
    canvas->DrawEdges(tracedEdges, hcsAnalyze);
781
    tracedEdges.Clear();
782

783
    Canvas::Stroke strokeError = Style::Stroke(Style::DRAW_ERROR);
784
    strokeError.layer = Canvas::Layer::FRONT;
785
    strokeError.width = 12;
786
    Canvas::hStroke hcsError = canvas->GetStroke(strokeError);
787

788
    // And the naked edges, if the user did Analyze -> Show Naked Edges.
789
    canvas->DrawEdges(SS.nakedEdges, hcsError);
790

791
    // Then redraw whatever the mouse is hovering over, highlighted.
792
    hover.Draw(/*isHovered=*/true, canvas);
793
    SK.GetGroup(activeGroup)->DrawMesh(Group::DrawMeshAs::HOVERED, canvas);
794

795
    // And finally draw the selection, same mechanism.
796
    for(Selection *s = selection.First(); s; s = selection.NextAfter(s)) {
797
        s->Draw(/*isHovered=*/false, canvas);
798
    }
799
    SK.GetGroup(activeGroup)->DrawMesh(Group::DrawMeshAs::SELECTED, canvas);
800

801
    Canvas::Stroke strokeDatum = Style::Stroke(Style::DATUM);
802
    strokeDatum.unit  = Canvas::Unit::PX;
803
    strokeDatum.layer = Canvas::Layer::FRONT;
804
    strokeDatum.width = 1;
805
    Canvas::hStroke hcsDatum = canvas->GetStroke(strokeDatum);
806

807
    // An extra line, used to indicate the origin when rotating within the
808
    // plane of the monitor.
809
    if(SS.extraLine.draw) {
810
        canvas->DrawLine(SS.extraLine.ptA, SS.extraLine.ptB, hcsDatum);
811
    }
812

813
    if(SS.centerOfMass.draw && !SS.centerOfMass.dirty) {
814
        Vector p = SS.centerOfMass.position;
815
        Vector u = camera.projRight;
816
        Vector v = camera.projUp;
817

818
        const double size = 10.0;
819
        const int subdiv = 16;
820
        double h = Style::DefaultTextHeight() / camera.scale;
821
        std::string s =
822
            SS.MmToStringSI(p.x) + ", " +
823
            SS.MmToStringSI(p.y) + ", " +
824
            SS.MmToStringSI(p.z);
825
        canvas->DrawVectorText(s.c_str(), h,
826
                               p.Plus(u.ScaledBy((size + 5.0)/scale)).Minus(v.ScaledBy(h / 2.0)),
827
                               u, v, hcsDatum);
828
        u = u.WithMagnitude(size / scale);
829
        v = v.WithMagnitude(size / scale);
830

831
        canvas->DrawLine(p.Minus(u), p.Plus(u), hcsDatum);
832
        canvas->DrawLine(p.Minus(v), p.Plus(v), hcsDatum);
833
        Vector prev;
834
        for(int i = 0; i <= subdiv; i++) {
835
            double a = (double)i / subdiv * 2.0 * PI;
836
            Vector point = p.Plus(u.ScaledBy(cos(a))).Plus(v.ScaledBy(sin(a)));
837
            if(i > 0) {
838
                canvas->DrawLine(point, prev, hcsDatum);
839
            }
840
            prev = point;
841
        }
842
    }
843

844
    // A note to indicate the origin in the just-exported file.
845
    if(SS.justExportedInfo.draw) {
846
        Vector p, u, v;
847
        if(SS.justExportedInfo.showOrigin) {
848
            p = SS.justExportedInfo.pt,
849
            u = SS.justExportedInfo.u,
850
            v = SS.justExportedInfo.v;
851
        } else {
852
            p = camera.offset.ScaledBy(-1);
853
            u = camera.projRight;
854
            v = camera.projUp;
855
        }
856
        canvas->DrawVectorText("previewing exported geometry; press Esc to return",
857
                              Style::DefaultTextHeight() / camera.scale,
858
                              p.Plus(u.ScaledBy(10/scale)).Plus(v.ScaledBy(10/scale)), u, v,
859
                              hcsDatum);
860

861
        if(SS.justExportedInfo.showOrigin) {
862
            Vector um = p.Plus(u.WithMagnitude(-15/scale)),
863
                   up = p.Plus(u.WithMagnitude(30/scale)),
864
                   vm = p.Plus(v.WithMagnitude(-15/scale)),
865
                   vp = p.Plus(v.WithMagnitude(30/scale));
866
            canvas->DrawLine(um, up, hcsDatum);
867
            canvas->DrawLine(vm, vp, hcsDatum);
868
            canvas->DrawVectorText("(x, y) = (0, 0) for file just exported",
869
                                  Style::DefaultTextHeight() / camera.scale,
870
                                  p.Plus(u.ScaledBy(40/scale)).Plus(
871
                                         v.ScaledBy(-(Style::DefaultTextHeight())/scale)), u, v,
872
                                  hcsDatum);
873
        }
874
    }
875
}
876

877
void GraphicsWindow::Paint() {
878
    ssassert(window != NULL && canvas != NULL,
879
             "Cannot paint without window and canvas");
880

881
    havePainted = true;
882

883
    Camera   camera   = GetCamera();
884
    Lighting lighting = GetLighting();
885

886
    if(!SS.ActiveGroupsOkay()) {
887
        // Draw a different background whenever we're having solve problems.
888
        RgbaColor bgColor = Style::Color(Style::DRAW_ERROR);
889
        bgColor = RgbaColor::FromFloat(0.4f*bgColor.redF(),
890
                                       0.4f*bgColor.greenF(),
891
                                       0.4f*bgColor.blueF());
892
        lighting.backgroundColor = bgColor;
893
        // And show the text window, which has info to debug it
894
        ForceTextWindowShown();
895
    }
896

897
    canvas->SetLighting(lighting);
898
    canvas->SetCamera(camera);
899
    canvas->StartFrame();
900

901
    // Draw the 3d objects.
902
    Draw(canvas.get());
903
    canvas->FlushFrame();
904

905
    // Draw the 2d UI overlay.
906
    camera.LoadIdentity();
907
    camera.offset.x = -(double)camera.width  / 2.0;
908
    camera.offset.y = -(double)camera.height / 2.0;
909
    canvas->SetCamera(camera);
910

911
    UiCanvas uiCanvas = {};
912
    uiCanvas.canvas = canvas;
913

914
    // If a marquee selection is in progress, then draw the selection
915
    // rectangle, as an outline and a transparent fill.
916
    if(pending.operation == Pending::DRAGGING_MARQUEE) {
917
        Point2d begin = ProjectPoint(orig.marqueePoint);
918
        uiCanvas.DrawRect((int)orig.mouse.x + (int)camera.width / 2,
919
                          (int)begin.x + (int)camera.width / 2,
920
                          (int)orig.mouse.y + (int)camera.height / 2,
921
                          (int)begin.y + (int)camera.height / 2,
922
                          /*fillColor=*/Style::Color(Style::HOVERED).WithAlpha(25),
923
                          /*outlineColor=*/Style::Color(Style::HOVERED));
924
    }
925

926
    // If we've had a screenshot requested, take it now, before the UI is overlaid.
927
    if(!SS.screenshotFile.IsEmpty()) {
928
        FILE *f = OpenFile(SS.screenshotFile, "wb");
929
        if(!f || !canvas->ReadFrame()->WritePng(f, /*flip=*/true)) {
930
            Error("Couldn't write to '%s'", SS.screenshotFile.raw.c_str());
931
        }
932
        if(f) fclose(f);
933
        SS.screenshotFile.Clear();
934
    }
935

936
    // And finally the toolbar.
937
    if(SS.showToolbar) {
938
        canvas->SetCamera(camera);
939
        ToolbarDraw(&uiCanvas);
940
    }
941

942
    canvas->FlushFrame();
943
    canvas->FinishFrame();
944
    canvas->Clear();
945
}
946

947
void GraphicsWindow::Invalidate(bool clearPersistent) {
948
    if(window) {
949
        if(clearPersistent) {
950
            persistentDirty = true;
951
        }
952
        window->Invalidate();
953
    }
954
}
955

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

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

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

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