Solvespace

Форк
0
/
drawconstraint.cpp 
1380 строк · 53.6 Кб
1
//-----------------------------------------------------------------------------
2
// Given a constraint, draw a graphical and user-selectable representation
3
// of that constraint on-screen. We can either draw with gl, or compute the
4
// distance from a point (the location of the mouse pointer) to the lines
5
// that we would have drawn, for selection.
6
//
7
// Copyright 2008-2013 Jonathan Westhues.
8
//-----------------------------------------------------------------------------
9
#include "solvespace.h"
10

11
std::string Constraint::Label() const {
12
    std::string result;
13
    if(type == Type::ANGLE) {
14
        result = SS.DegreeToString(valA) + "°";
15
    } else if(type == Type::LENGTH_RATIO || type == Type::ARC_ARC_LEN_RATIO || type == Type::ARC_LINE_LEN_RATIO) {
16
        result = ssprintf("%.3f:1", valA);
17
    } else if(type == Type::COMMENT) {
18
        result = comment;
19
    } else if(type == Type::DIAMETER) {
20
        if(!other) {
21
            result = "⌀" + SS.MmToStringSI(valA);
22
        } else {
23
            result = "R" + SS.MmToStringSI(valA / 2);
24
        }
25
    } else {
26
        // valA has units of distance
27
        result = SS.MmToStringSI(fabs(valA));
28
    }
29
    if(reference) {
30
        result += " REF";
31
    }
32
    return result;
33
}
34

35
void Constraint::DoLine(Canvas *canvas, Canvas::hStroke hcs, Vector a, Vector b) {
36
    const Camera &camera = canvas->GetCamera();
37

38
    a = camera.AlignToPixelGrid(a);
39
    b = camera.AlignToPixelGrid(b);
40
    canvas->DrawLine(a, b, hcs);
41
}
42

43
void Constraint::DoStippledLine(Canvas *canvas, Canvas::hStroke hcs, Vector a, Vector b) {
44
    Canvas::Stroke strokeStippled = *canvas->strokes.FindById(hcs);
45
    strokeStippled.stipplePattern = StipplePattern::SHORT_DASH;
46
    strokeStippled.stippleScale   = 4.0;
47
    Canvas::hStroke hcsStippled = canvas->GetStroke(strokeStippled);
48
    DoLine(canvas, hcsStippled, a, b);
49
}
50

51
void Constraint::DoLabel(Canvas *canvas, Canvas::hStroke hcs,
52
                         Vector ref, Vector *labelPos, Vector gr, Vector gu) {
53
    const Camera &camera = canvas->GetCamera();
54

55
    std::string s = Label();
56
    double textHeight = Style::TextHeight(GetStyle()) / camera.scale;
57
    double swidth  = VectorFont::Builtin()->GetWidth(textHeight, s),
58
           sheight = VectorFont::Builtin()->GetCapHeight(textHeight);
59

60
    // By default, the reference is from the center; but the style could
61
    // specify otherwise if one is present, and it could also specify a
62
    // rotation.
63
    if(type == Type::COMMENT && disp.style.v) {
64
        Style *st = Style::Get(disp.style);
65
        // rotation first
66
        double rads = st->textAngle*PI/180;
67
        double c = cos(rads), s = sin(rads);
68
        Vector pr = gr, pu = gu;
69
        gr = pr.ScaledBy( c).Plus(pu.ScaledBy(s));
70
        gu = pr.ScaledBy(-s).Plus(pu.ScaledBy(c));
71
        // then origin
72
        uint32_t o = (uint32_t)st->textOrigin;
73
        if(o & (uint32_t)Style::TextOrigin::LEFT) ref = ref.Plus(gr.WithMagnitude(swidth/2));
74
        if(o & (uint32_t)Style::TextOrigin::RIGHT) ref = ref.Minus(gr.WithMagnitude(swidth/2));
75
        if(o & (uint32_t)Style::TextOrigin::BOT) ref = ref.Plus(gu.WithMagnitude(sheight/2));
76
        if(o & (uint32_t)Style::TextOrigin::TOP) ref = ref.Minus(gu.WithMagnitude(sheight/2));
77
    }
78

79
    Vector o = ref.Minus(gr.WithMagnitude(swidth/2)).Minus(
80
                         gu.WithMagnitude(sheight/2));
81
    canvas->DrawVectorText(s, textHeight, o, gr.WithMagnitude(1), gu.WithMagnitude(1), hcs);
82
    if(labelPos) *labelPos = o;
83
}
84

85
void Constraint::DoProjectedPoint(Canvas *canvas, Canvas::hStroke hcs,
86
                                  Vector *r, Vector n, Vector o) {
87
    double d = r->DistanceToPlane(n, o);
88
    Vector p = r->Minus(n.ScaledBy(d));
89
    DoStippledLine(canvas, hcs, p, *r);
90
    *r = p;
91
}
92

93
void Constraint::DoProjectedPoint(Canvas *canvas, Canvas::hStroke hcs, Vector *r) {
94
    Vector p = r->ProjectInto(workplane);
95
    DoStippledLine(canvas, hcs, p, *r);
96
    *r = p;
97
}
98

99
//-----------------------------------------------------------------------------
100
// There is a rectangular box, aligned to our display axes (projRight, projUp)
101
// centered at ref. This is where a dimension label will be drawn. We want to
102
// draw a line from A to B. If that line would intersect the label box, then
103
// trim the line to leave a gap for it, and return zero. If not, then extend
104
// the line to almost meet the box, and return either positive or negative,
105
// depending whether that extension was from A or from B.
106
//-----------------------------------------------------------------------------
107
int Constraint::DoLineTrimmedAgainstBox(Canvas *canvas, Canvas::hStroke hcs,
108
                                        Vector ref, Vector a, Vector b, bool extend) {
109
    const Camera &camera = canvas->GetCamera();
110
    double th      = Style::TextHeight(GetStyle()) / camera.scale;
111
    double pixels  = 1.0 / camera.scale;
112
    double swidth  = VectorFont::Builtin()->GetWidth(th, Label()) + 8 * pixels,
113
           sheight = VectorFont::Builtin()->GetCapHeight(th) + 8 * pixels;
114
    Vector gu = camera.projUp.WithMagnitude(1),
115
           gr = camera.projRight.WithMagnitude(1);
116
    return DoLineTrimmedAgainstBox(canvas, hcs, ref, a, b, extend, gr, gu, swidth, sheight);
117
}
118

119
int Constraint::DoLineTrimmedAgainstBox(Canvas *canvas, Canvas::hStroke hcs,
120
                                        Vector ref, Vector a, Vector b, bool extend,
121
                                        Vector gr, Vector gu, double swidth, double sheight) {
122
    struct {
123
        Vector n;
124
        double d;
125
    } planes[4];
126
    // reference pos is the center of box occupied by text; build a rectangle
127
    // around that, aligned to axes gr and gu, from four planes will all four
128
    // normals pointing inward
129
    planes[0].n = gu.ScaledBy(-1); planes[0].d = -(gu.Dot(ref) + sheight/2);
130
    planes[1].n = gu;              planes[1].d =   gu.Dot(ref) - sheight/2;
131
    planes[2].n = gr;              planes[2].d =   gr.Dot(ref) - swidth/2;
132
    planes[3].n = gr.ScaledBy(-1); planes[3].d = -(gr.Dot(ref) + swidth/2);
133

134
    double tmin = VERY_POSITIVE, tmax = VERY_NEGATIVE;
135
    Vector dl = b.Minus(a);
136

137
    for(int i = 0; i < 4; i++) {
138
        bool parallel;
139
        Vector p = Vector::AtIntersectionOfPlaneAndLine(
140
                                planes[i].n, planes[i].d,
141
                                a, b, &parallel);
142
        if(parallel) continue;
143

144
        int j;
145
        for(j = 0; j < 4; j++) {
146
            double d = (planes[j].n).Dot(p) - planes[j].d;
147
            if(d < -LENGTH_EPS) break;
148
        }
149
        if(j < 4) continue;
150

151
        double t = (p.Minus(a)).DivProjected(dl);
152
        tmin = min(t, tmin);
153
        tmax = max(t, tmax);
154
    }
155

156
    // Both in range; so there's pieces of the line on both sides of the label box.
157
    if(tmin >= 0.0 && tmin <= 1.0 && tmax >= 0.0 && tmax <= 1.0) {
158
        DoLine(canvas, hcs, a, a.Plus(dl.ScaledBy(tmin)));
159
        DoLine(canvas, hcs, a.Plus(dl.ScaledBy(tmax)), b);
160
        return 0;
161
    }
162

163
    // Only one intersection in range; so the box is right on top of the endpoint
164
    if(tmin >= 0.0 && tmin <= 1.0) {
165
        DoLine(canvas, hcs, a, a.Plus(dl.ScaledBy(tmin)));
166
        return 0;
167
    }
168

169
    // Likewise.
170
    if(tmax >= 0.0 && tmax <= 1.0) {
171
        DoLine(canvas, hcs, a.Plus(dl.ScaledBy(tmax)), b);
172
        return 0;
173
    }
174

175
    // The line does not intersect the label; so the line should get
176
    // extended to just barely meet the label.
177
    // 0 means the label lies within the line, negative means it's outside
178
    // and closer to b, positive means outside and closer to a.
179
    if(tmax < 0.0) {
180
        if(extend) a = a.Plus(dl.ScaledBy(tmax));
181
        DoLine(canvas, hcs, a, b);
182
        return 1;
183
    }
184

185
    if(tmin > 1.0) {
186
        if(extend) b = a.Plus(dl.ScaledBy(tmin));
187
        DoLine(canvas, hcs, a, b);
188
        return -1;
189
    }
190

191
    // This will happen if the entire line lies within the box.
192
    return 0;
193
}
194

195
void Constraint::DoArrow(Canvas *canvas, Canvas::hStroke hcs,
196
                         Vector p, Vector dir, Vector n, double width, double angle, double da) {
197
    dir = dir.WithMagnitude(width / cos(angle));
198
    dir = dir.RotatedAbout(n, da);
199
    DoLine(canvas, hcs, p, p.Plus(dir.RotatedAbout(n,  angle)));
200
    DoLine(canvas, hcs, p, p.Plus(dir.RotatedAbout(n, -angle)));
201
}
202

203
//-----------------------------------------------------------------------------
204
// Draw a line with arrows on both ends, and possibly a gap in the middle for
205
// the dimension. We will use these for most length dimensions. The length
206
// being dimensioned is from A to B; but those points get extended perpendicular
207
// to the line AB, until the line between the extensions crosses ref (the
208
// center of the label).
209
//-----------------------------------------------------------------------------
210
void Constraint::DoLineWithArrows(Canvas *canvas, Canvas::hStroke hcs,
211
                                  Vector ref, Vector a, Vector b,
212
                                  bool onlyOneExt)
213
{
214
    const Camera &camera = canvas->GetCamera();
215
    double pixels = 1.0 / camera.scale;
216

217
    Vector ab   = a.Minus(b);
218
    Vector ar   = a.Minus(ref);
219
    // Normal to a plane containing the line and the label origin.
220
    Vector n    = ab.Cross(ar);
221
    // Within that plane, and normal to the line AB; so that's our extension
222
    // line.
223
    Vector out  = ab.Cross(n).WithMagnitude(1);
224
    out = out.ScaledBy(-out.Dot(ar));
225

226
    Vector ae = a.Plus(out), be = b.Plus(out);
227

228
    // Extension lines extend 10 pixels beyond where the arrows get
229
    // drawn (which is at the same offset perpendicular from AB as the
230
    // label).
231
    DoLine(canvas, hcs, a, ae.Plus(out.WithMagnitude(10*pixels)));
232
    if(!onlyOneExt) {
233
        DoLine(canvas, hcs, b, be.Plus(out.WithMagnitude(10*pixels)));
234
    }
235

236
    int within = DoLineTrimmedAgainstBox(canvas, hcs, ref, ae, be);
237

238
    // Arrow heads are 13 pixels long, with an 18 degree half-angle.
239
    double theta = 18*PI/180;
240
    Vector arrow = (be.Minus(ae)).WithMagnitude(13*pixels);
241

242
    if(within != 0) {
243
        arrow = arrow.ScaledBy(-1);
244
        Vector seg = (be.Minus(ae)).WithMagnitude(18*pixels);
245
        if(within < 0) DoLine(canvas, hcs, ae, ae.Minus(seg));
246
        if(within > 0) DoLine(canvas, hcs, be, be.Plus(seg));
247
    }
248

249
    DoArrow(canvas, hcs, ae, arrow, n, 13.0 * pixels, theta, 0.0);
250
    DoArrow(canvas, hcs, be, arrow.Negated(), n, 13.0 * pixels, theta, 0.0);
251
}
252

253
void Constraint::DoEqualLenTicks(Canvas *canvas, Canvas::hStroke hcs,
254
                                 Vector a, Vector b, Vector gn, Vector *refp) {
255
    const Camera &camera = canvas->GetCamera();
256

257
    Vector m = (a.ScaledBy(1.0/3)).Plus(b.ScaledBy(2.0/3));
258
    if(refp) *refp = m;
259
    Vector ab = a.Minus(b);
260
    Vector n = (gn.Cross(ab)).WithMagnitude(10/camera.scale);
261

262
    DoLine(canvas, hcs, m.Minus(n), m.Plus(n));
263
}
264

265
void Constraint::DoEqualRadiusTicks(Canvas *canvas, Canvas::hStroke hcs,
266
                                    hEntity he, Vector *refp) {
267
    const Camera &camera = canvas->GetCamera();
268
    Entity *circ = SK.GetEntity(he);
269

270
    Vector center = SK.GetEntity(circ->point[0])->PointGetDrawNum();
271
    double r = circ->CircleGetRadiusNum();
272
    Quaternion q = circ->Normal()->NormalGetNum();
273
    Vector u = q.RotationU(), v = q.RotationV();
274

275
    double theta;
276
    if(circ->type == Entity::Type::CIRCLE) {
277
        theta = PI/2;
278
    } else if(circ->type == Entity::Type::ARC_OF_CIRCLE) {
279
        double thetaa, thetab, dtheta;
280
        circ->ArcGetAngles(&thetaa, &thetab, &dtheta);
281
        theta = thetaa + dtheta/2;
282
    } else ssassert(false, "Unexpected entity type");
283

284
    Vector d = u.ScaledBy(cos(theta)).Plus(v.ScaledBy(sin(theta)));
285
    d = d.ScaledBy(r);
286
    Vector p = center.Plus(d);
287
    if(refp) *refp = p;
288
    Vector tick = d.WithMagnitude(10/camera.scale);
289
    DoLine(canvas, hcs, p.Plus(tick), p.Minus(tick));
290
}
291

292
void Constraint::DoArcForAngle(Canvas *canvas, Canvas::hStroke hcs,
293
                               Vector a0, Vector da, Vector b0, Vector db,
294
                               Vector offset, Vector *ref, bool trim,
295
                               Vector explodeOffset)
296
{
297
    const Camera &camera = canvas->GetCamera();
298
    double pixels = 1.0 / camera.scale;
299
    Vector gr = camera.projRight.ScaledBy(1.0);
300
    Vector gu = camera.projUp.ScaledBy(1.0);
301

302
    if(workplane != Entity::FREE_IN_3D) {
303
        a0 = a0.ProjectInto(workplane);
304
        b0 = b0.ProjectInto(workplane);
305
        da = da.ProjectVectorInto(workplane);
306
        db = db.ProjectVectorInto(workplane);
307
    }
308

309
    a0 = a0.Plus(explodeOffset);
310
    b0 = b0.Plus(explodeOffset);
311

312
    Vector a1 = a0.Plus(da);
313
    Vector b1 = b0.Plus(db);
314

315
    bool skew;
316
    Vector pi = Vector::AtIntersectionOfLines(a0, a0.Plus(da),
317
                                              b0, b0.Plus(db), &skew);
318

319
    if(!skew) {
320
        *ref = pi.Plus(offset);
321
        // We draw in a coordinate system centered at the intersection point.
322
        // One basis vector is da, and the other is normal to da and in
323
        // the plane that contains our lines (so normal to its normal).
324
        da = da.WithMagnitude(1);
325
        db = db.WithMagnitude(1);
326

327
        Vector norm = da.Cross(db);
328

329
        Vector dna = norm.Cross(da).WithMagnitude(1.0);
330
        Vector dnb = norm.Cross(db).WithMagnitude(1.0);
331

332
        // da and db magnitudes are 1.0
333
        double thetaf = acos(da.Dot(db));
334

335
        // Calculate median
336
        Vector m = da.ScaledBy(cos(thetaf/2)).Plus(
337
                   dna.ScaledBy(sin(thetaf/2)));
338
        Vector rm = (*ref).Minus(pi);
339

340
        // Test which side we have to place an arc
341
        if(m.Dot(rm) < 0) {
342
            da = da.ScaledBy(-1); dna = dna.ScaledBy(-1);
343
            db = db.ScaledBy(-1); dnb = dnb.ScaledBy(-1);
344
        }
345

346
        double rda = rm.Dot(da), rdna = rm.Dot(dna);
347

348
        // Introduce minimal arc radius in pixels
349
        double r = max(sqrt(rda*rda + rdna*rdna), 15.0 * pixels);
350

351
        double th = Style::TextHeight(GetStyle()) / camera.scale;
352
        double swidth   = VectorFont::Builtin()->GetWidth(th, Label()) + 8*pixels,
353
               sheight  = VectorFont::Builtin()->GetCapHeight(th) + 6*pixels;
354
        double textR = sqrt(swidth * swidth + sheight * sheight) / 2.0;
355
        *ref = pi.Plus(rm.WithMagnitude(std::max(rm.Magnitude(), 15 * pixels + textR)));
356

357
        // Arrow points
358
        Vector apa = da. ScaledBy(r).Plus(pi);
359
        Vector apb = da. ScaledBy(r*cos(thetaf)).Plus(
360
                     dna.ScaledBy(r*sin(thetaf))).Plus(pi);
361

362
        double arrowW = 13 * pixels;
363
        double arrowA = 18.0 * PI / 180.0;
364
        bool arrowVisible = apb.Minus(apa).Magnitude() > 2.5 * arrowW;
365
        // Arrow reversing indicator
366
        bool arrowRev = false;
367

368
        // The minimal extension length in angular representation
369
        double extAngle = 18 * pixels / r;
370

371
        // Arc additional angle
372
        double addAngle = 0.0;
373
        // Arc start angle
374
        double startAngle = 0.0;
375

376
        // Arc extension to db.
377
        // We have just enlarge angle value.
378
        if(HasLabel() && rm.Dot(dnb) > 0.0) {
379
            // rm direction projected to plane with u = da, v = dna
380
            Vector rmp = da.ScaledBy(rda).Plus(dna.ScaledBy(rdna)).WithMagnitude(1.0);
381
            // rmp and db magnitudes are 1.0
382
            addAngle = std::max(acos(rmp.Dot(db)), extAngle);
383

384
            if(arrowVisible) {
385
                startAngle = -extAngle;
386
                addAngle += extAngle;
387
                arrowRev = true;
388
            }
389
        }
390

391
        // Arc extension to da.
392
        // We are enlarge angle value and rewrite basis to align along rm projection.
393
        if(HasLabel() && rm.Dot(dna) < 0.0) {
394
            // rm direction projected to plane with u = da, v = dna
395
            Vector rmp = da.ScaledBy(rda).Plus(dna.ScaledBy(rdna)).WithMagnitude(1.0);
396
            // rmp and da magnitudes are 1.0
397
            startAngle = -std::max(acos(rmp.Dot(da)), extAngle);
398
            addAngle = -startAngle;
399

400
            if(arrowVisible) {
401
                addAngle += extAngle;
402
                arrowRev = true;
403
            }
404
        }
405

406
        Vector prev;
407
        int n = 30;
408
        for(int i = 0; i <= n; i++) {
409
            double theta = startAngle + (i*(thetaf + addAngle))/n;
410
            Vector p =  da.ScaledBy(r*cos(theta)).Plus(
411
                       dna.ScaledBy(r*sin(theta))).Plus(pi);
412
            if(i > 0) {
413
                if(trim) {
414
                    DoLineTrimmedAgainstBox(canvas, hcs, *ref, prev, p,
415
                                            /*extend=*/false, gr, gu, swidth, sheight + 2*pixels);
416
                } else {
417
                    DoLine(canvas, hcs, prev, p);
418
                }
419
            }
420
            prev = p;
421
        }
422

423
        DoLineExtend(canvas, hcs, a0, a1, apa, 5.0 * pixels);
424
        DoLineExtend(canvas, hcs, b0, b1, apb, 5.0 * pixels);
425

426
        // Draw arrows only when we have enough space.
427
        if(arrowVisible) {
428
            double angleCorr = arrowW / (2.0 * r);
429
            if(arrowRev) {
430
                dna = dna.ScaledBy(-1.0);
431
                angleCorr = -angleCorr;
432
            }
433
            DoArrow(canvas, hcs, apa, dna, norm, arrowW, arrowA, angleCorr);
434
            DoArrow(canvas, hcs, apb, dna, norm, arrowW, arrowA, thetaf + PI - angleCorr);
435
        }
436
    } else {
437
        // The lines are skew; no wonderful way to illustrate that.
438

439
        *ref = a0.Plus(b0);
440
        *ref = (*ref).ScaledBy(0.5).Plus(disp.offset);
441
        gu = gu.WithMagnitude(1);
442
        double textHeight = Style::TextHeight(GetStyle()) / camera.scale;
443
        Vector trans =
444
            (*ref).Plus(gu.ScaledBy(-1.5*VectorFont::Builtin()->GetCapHeight(textHeight)));
445
        canvas->DrawVectorText("angle between skew lines", textHeight,
446
                               trans, gr.WithMagnitude(1), gu.WithMagnitude(1),
447
                               hcs);
448
    }
449
}
450

451
bool Constraint::IsVisible() const {
452
    if(SS.GW.showConstraints == GraphicsWindow::ShowConstraintMode::SCM_NOSHOW) 
453
        return false;
454
    bool isDim = false;
455

456
    if(SS.GW.showConstraints == GraphicsWindow::ShowConstraintMode::SCM_SHOW_DIM)
457
        switch(type) {
458
        case ConstraintBase::Type::ANGLE:
459
        case ConstraintBase::Type::DIAMETER:
460
        case ConstraintBase::Type::PT_PT_DISTANCE:
461
        case ConstraintBase::Type::PT_FACE_DISTANCE:
462
        case ConstraintBase::Type::PT_LINE_DISTANCE:
463
        case ConstraintBase::Type::PT_PLANE_DISTANCE: isDim = true; break;
464
        default:;
465
        }
466

467
    if(SS.GW.showConstraints == GraphicsWindow::ShowConstraintMode::SCM_SHOW_ALL || isDim ) {
468
        Group *g = SK.GetGroup(group);
469
        // If the group is hidden, then the constraints are hidden and not
470
        // able to be selected.
471
        if(!(g->visible)) return false;
472
        // And likewise if the group is not the active group; except for comments
473
        // with an assigned style.
474
        if(g->h != SS.GW.activeGroup && !(type == Type::COMMENT && disp.style.v)) {
475
            return false;
476
        }
477
        if(disp.style.v) {
478
            Style *s = Style::Get(disp.style);
479
            if(!s->visible) return false;
480
        }
481
        return true;
482
    }
483
    return false;
484
}
485

486
bool Constraint::DoLineExtend(Canvas *canvas, Canvas::hStroke hcs,
487
                              Vector p0, Vector p1, Vector pt, double salient) {
488
    Vector dir = p1.Minus(p0);
489
    double k = dir.Dot(pt.Minus(p0)) / dir.Dot(dir);
490
    Vector ptOnLine = p0.Plus(dir.ScaledBy(k));
491

492
    // Draw projection line.
493
    DoLine(canvas, hcs, pt, ptOnLine);
494

495
    // Calculate salient direction.
496
    Vector sd = dir.WithMagnitude(1.0).ScaledBy(salient);
497

498
    Vector from;
499
    Vector to;
500

501
    if(k < 0.0) {
502
        from = p0;
503
        to = ptOnLine.Minus(sd);
504
    } else if(k > 1.0) {
505
        from = p1;
506
        to = ptOnLine.Plus(sd);
507
    } else {
508
        return false;
509
    }
510

511
    // Draw extension line.
512
    DoLine(canvas, hcs, from, to);
513
    return true;
514
}
515

516
void Constraint::DoLayout(DrawAs how, Canvas *canvas,
517
                          Vector *labelPos, std::vector<Vector> *refs) {
518
    if(!(how == DrawAs::HOVERED || how == DrawAs::SELECTED) &&
519
       !IsVisible()) return;
520

521
    // Unit vectors that describe our current view of the scene. One pixel
522
    // long, not one actual unit.
523
    const Camera &camera = canvas->GetCamera();
524
    Vector gr = camera.projRight.ScaledBy(1/camera.scale);
525
    Vector gu = camera.projUp.ScaledBy(1/camera.scale);
526
    Vector gn = (gr.Cross(gu)).WithMagnitude(1/camera.scale);
527

528
    double textHeight = Style::TextHeight(GetStyle()) / camera.scale;
529

530
    RgbaColor color = {};
531
    switch(how) {
532
        case DrawAs::DEFAULT:  color = Style::Color(GetStyle()); break;
533
        case DrawAs::HOVERED:  color = Style::Color(Style::HOVERED);    break;
534
        case DrawAs::SELECTED: color = Style::Color(Style::SELECTED);   break;
535
    }
536
    Canvas::Stroke stroke = Style::Stroke(GetStyle());
537
    stroke.layer    = Canvas::Layer::FRONT;
538
    stroke.color    = color;
539
    stroke.zIndex   = 4;
540
    Canvas::hStroke hcs = canvas->GetStroke(stroke);
541

542
    Canvas::Fill fill = {};
543
    fill.layer      = Canvas::Layer::FRONT;
544
    fill.color      = color;
545
    fill.zIndex     = stroke.zIndex;
546
    Canvas::hFill hcf = canvas->GetFill(fill);
547

548
    switch(type) {
549
        case Type::PT_PT_DISTANCE: {
550
            Vector ap = SK.GetEntity(ptA)->PointGetNum();
551
            Vector bp = SK.GetEntity(ptB)->PointGetNum();
552

553
            if(workplane != Entity::FREE_IN_3D) {
554
                DoProjectedPoint(canvas, hcs, &ap);
555
                DoProjectedPoint(canvas, hcs, &bp);
556
            }
557

558
            if(ShouldDrawExploded()) {
559
                // Offset A and B by the same offset so the constraint is drawn
560
                // in the plane of one of the exploded points (rather than at an
561
                // angle)
562
                Vector offset = SK.GetEntity(ptA)->ExplodeOffset();
563
                ap = ap.Plus(offset);
564
                bp = bp.Plus(offset);
565
            }
566

567
            Vector ref = ((ap.Plus(bp)).ScaledBy(0.5)).Plus(disp.offset);
568
            if(refs) refs->push_back(ref);
569

570
            DoLineWithArrows(canvas, hcs, ref, ap, bp, /*onlyOneExt=*/false);
571
            DoLabel(canvas, hcs, ref, labelPos, gr, gu);
572
            return;
573
        }
574

575
        case Type::PROJ_PT_DISTANCE: {
576
            Vector ap = SK.GetEntity(ptA)->PointGetNum(),
577
                   bp = SK.GetEntity(ptB)->PointGetNum(),
578
                   dp = (bp.Minus(ap)),
579
                   pp = SK.GetEntity(entityA)->VectorGetNum();
580

581
            if(ShouldDrawExploded()) {
582
                // explode for whichever point is in the workplane (or the first if both are) 
583
                Entity *pt = SK.GetEntity(ptA);
584
                if(pt->group != group) {
585
                    pt = SK.GetEntity(ptB);
586
                }
587
                if(pt->group == group) {
588
                    Vector offset = pt->ExplodeOffset();
589
                    ap = ap.Plus(offset);
590
                    bp = bp.Plus(offset);
591
                }
592
            }
593

594
            Vector ref = ((ap.Plus(bp)).ScaledBy(0.5)).Plus(disp.offset);
595
            if(refs) refs->push_back(ref);
596

597
            pp = pp.WithMagnitude(1);
598
            double d = dp.Dot(pp);
599
            Vector bpp = ap.Plus(pp.ScaledBy(d));
600
            DoStippledLine(canvas, hcs, ap, bpp);
601
            DoStippledLine(canvas, hcs, bp, bpp);
602

603
            DoLineWithArrows(canvas, hcs, ref, ap, bpp, /*onlyOneExt=*/false);
604
            DoLabel(canvas, hcs, ref, labelPos, gr, gu);
605
            return;
606
        }
607

608
        case Type::PT_FACE_DISTANCE:
609
        case Type::PT_PLANE_DISTANCE: {
610
            Vector pt = SK.GetEntity(ptA)->PointGetDrawNum();
611
            Entity *enta = SK.GetEntity(entityA);
612
            Vector n, p;
613
            if(type == Type::PT_PLANE_DISTANCE) {
614
                n = enta->Normal()->NormalN();
615
                p = enta->WorkplaneGetOffset();
616
            } else {
617
                n = enta->FaceGetNormalNum();
618
                p = enta->FaceGetPointNum();
619
            }
620

621
            double d = (p.Minus(pt)).Dot(n);
622
            Vector closest = pt.Plus(n.WithMagnitude(d));
623

624
            Vector ref = ((closest.Plus(pt)).ScaledBy(0.5)).Plus(disp.offset);
625
            if(refs) refs->push_back(ref);
626

627
            if(!pt.Equals(closest)) {
628
                DoLineWithArrows(canvas, hcs, ref, pt, closest, /*onlyOneExt=*/true);
629
            }
630

631
            DoLabel(canvas, hcs, ref, labelPos, gr, gu);
632
            return;
633
        }
634

635
        case Type::PT_LINE_DISTANCE: {
636
            Entity *ptEntity = SK.GetEntity(ptA);
637
            Vector pt = ptEntity->PointGetNum();
638
            Entity *line = SK.GetEntity(entityA);
639
            Vector lA = SK.GetEntity(line->point[0])->PointGetNum();
640
            Vector lB = SK.GetEntity(line->point[1])->PointGetNum();
641
            Vector dl = lB.Minus(lA);
642

643
            if(workplane != Entity::FREE_IN_3D) {
644
                lA = lA.ProjectInto(workplane);
645
                lB = lB.ProjectInto(workplane);
646
                DoProjectedPoint(canvas, hcs, &pt);
647
            }
648

649
            // Only explode if the point and line are in the same group (and that group is a sketch
650
            // with explode enabled) otherwise it's too visually confusing to figure out what the
651
            // correct projections should be.
652
            bool shouldExplode = ShouldDrawExploded()
653
                && ptEntity->group == group
654
                && line->group == group;
655
            if(shouldExplode) {
656
                Vector explodeOffset = ptEntity->ExplodeOffset();
657
                pt = pt.Plus(explodeOffset);
658
                lA = lA.Plus(explodeOffset);
659
                lB = lB.Plus(explodeOffset);
660
            }
661

662
            // Find the closest point on the line
663
            Vector closest = pt.ClosestPointOnLine(lA, dl);
664

665
            Vector ref = ((closest.Plus(pt)).ScaledBy(0.5)).Plus(disp.offset);
666
            if(refs) refs->push_back(ref);
667
            DoLabel(canvas, hcs, ref, labelPos, gr, gu);
668

669
            if(!pt.Equals(closest)) {
670
                DoLineWithArrows(canvas, hcs, ref, pt, closest, /*onlyOneExt=*/true);
671

672
                // Draw projected point
673
                Vector a    = pt;
674
                Vector b    = closest;
675
                Vector ab   = a.Minus(b);
676
                Vector ar   = a.Minus(ref);
677
                Vector n    = ab.Cross(ar);
678
                Vector out  = ab.Cross(n).WithMagnitude(1);
679
                out = out.ScaledBy(-out.Dot(ar));
680
                Vector be   = b.Plus(out);
681
                Vector np   = lA.Minus(pt).Cross(lB.Minus(pt)).WithMagnitude(1.0);
682
                DoProjectedPoint(canvas, hcs, &be, np, pt);
683

684
                // Extensions to line
685
                double pixels = 1.0 / camera.scale;
686
                Vector refClosest = ref.ClosestPointOnLine(lA, dl);
687
                double ddl = dl.Dot(dl);
688
                if(fabs(ddl) > LENGTH_EPS * LENGTH_EPS) {
689
                    double t = refClosest.Minus(lA).Dot(dl) / ddl;
690
                    if(t < 0.0) {
691
                        DoLine(canvas, hcs, refClosest.Minus(dl.WithMagnitude(10.0 * pixels)), lA);
692
                    } else if(t > 1.0) {
693
                        DoLine(canvas, hcs, refClosest.Plus(dl.WithMagnitude(10.0 * pixels)), lB);
694
                    }
695
                }
696
            }
697

698
            if(workplane != Entity::FREE_IN_3D) {
699
                // Draw the projection marker from the closest point on the
700
                // projected line to the projected point on the real line.
701
                Vector lAB = (lA.Minus(lB));
702
                double t = (lA.Minus(closest)).DivProjected(lAB);
703

704
                Vector lA = SK.GetEntity(line->point[0])->PointGetNum();
705
                Vector lB = SK.GetEntity(line->point[1])->PointGetNum();
706

707
                Vector c2 = (lA.ScaledBy(1-t)).Plus(lB.ScaledBy(t));
708
                DoProjectedPoint(canvas, hcs, &c2);
709
            }
710
            return;
711
        }
712

713
        case Type::DIAMETER: {
714
            Entity *circle = SK.GetEntity(entityA);
715
            Vector center = SK.GetEntity(circle->point[0])->PointGetDrawNum();
716
            Quaternion q = SK.GetEntity(circle->normal)->NormalGetNum();
717
            Vector n = q.RotationN().WithMagnitude(1);
718
            double r = circle->CircleGetRadiusNum();
719

720
            Vector ref = center.Plus(disp.offset);
721
            // Force the label into the same plane as the circle.
722
            ref = ref.Minus(n.ScaledBy(n.Dot(ref) - n.Dot(center)));
723
            if(refs) refs->push_back(ref);
724

725
            Vector mark = ref.Minus(center);
726
            mark = mark.WithMagnitude(mark.Magnitude()-r);
727
            DoLineTrimmedAgainstBox(canvas, hcs, ref, ref, ref.Minus(mark));
728

729
            Vector topLeft;
730
            DoLabel(canvas, hcs, ref, &topLeft, gr, gu);
731
            if(labelPos) *labelPos = topLeft;
732
            return;
733
        }
734

735
        case Type::POINTS_COINCIDENT: {
736
            if(how == DrawAs::DEFAULT) {
737
                // Let's adjust the color of this constraint to have the same
738
                // rough luma as the point color, so that the constraint does not
739
                // stand out in an ugly way.
740
                RgbaColor cd = Style::Color(Style::DATUM),
741
                          cc = Style::Color(Style::CONSTRAINT);
742
                // convert from 8-bit color to a vector
743
                Vector vd = Vector::From(cd.redF(), cd.greenF(), cd.blueF()),
744
                       vc = Vector::From(cc.redF(), cc.greenF(), cc.blueF());
745
                // and scale the constraint color to have the same magnitude as
746
                // the datum color, maybe a bit dimmer
747
                vc = vc.WithMagnitude(vd.Magnitude()*0.9);
748
                // and set the color to that.
749
                fill.color = RGBf(vc.x, vc.y, vc.z);
750
                hcf = canvas->GetFill(fill);
751
            }
752

753
            for(int a = 0; a < 2; a++) {
754
                Vector r = camera.projRight.ScaledBy((a+1)/camera.scale);
755
                Vector d = camera.projUp.ScaledBy((2-a)/camera.scale);
756
                for(int i = 0; i < 2; i++) {
757
                    Vector p = SK.GetEntity(i == 0 ? ptA : ptB)->PointGetDrawNum();
758
                    if(refs) refs->push_back(p);
759
                    canvas->DrawQuad(p.Plus (r).Plus (d),
760
                                     p.Plus (r).Minus(d),
761
                                     p.Minus(r).Minus(d),
762
                                     p.Minus(r).Plus (d),
763
                                     hcf);
764
                }
765

766
            }
767
            return;
768
        }
769

770
        case Type::PT_ON_CIRCLE:
771
        case Type::PT_ON_LINE:
772
        case Type::PT_ON_FACE:
773
        case Type::PT_IN_PLANE: {
774
            double s = 8/camera.scale;
775
            Vector p = SK.GetEntity(ptA)->PointGetDrawNum();
776
            if(refs) refs->push_back(p);
777
            Vector r, d;
778
            if(type == Type::PT_ON_FACE) {
779
                Vector n = SK.GetEntity(entityA)->FaceGetNormalNum();
780
                r = n.Normal(0);
781
                d = n.Normal(1);
782
            } else if(type == Type::PT_IN_PLANE) {
783
                EntityBase *n = SK.GetEntity(entityA)->Normal();
784
                r = n->NormalU();
785
                d = n->NormalV();
786
            } else {
787
                r = gr;
788
                d = gu;
789
                s *= (6.0/8); // draw these a little smaller
790
            }
791
            r = r.WithMagnitude(s); d = d.WithMagnitude(s);
792
            DoLine(canvas, hcs, p.Plus (r).Plus (d), p.Plus (r).Minus(d));
793
            DoLine(canvas, hcs, p.Plus (r).Minus(d), p.Minus(r).Minus(d));
794
            DoLine(canvas, hcs, p.Minus(r).Minus(d), p.Minus(r).Plus (d));
795
            DoLine(canvas, hcs, p.Minus(r).Plus (d), p.Plus (r).Plus (d));
796
            return;
797
        }
798

799
        case Type::WHERE_DRAGGED: {
800
            Vector p = SK.GetEntity(ptA)->PointGetDrawNum();
801
            if(refs) refs->push_back(p);
802
            Vector u = p.Plus(gu.WithMagnitude(8/camera.scale)).Plus(
803
                              gr.WithMagnitude(8/camera.scale)),
804
                   uu = u.Minus(gu.WithMagnitude(5/camera.scale)),
805
                   ur = u.Minus(gr.WithMagnitude(5/camera.scale));
806
            // Draw four little crop marks, uniformly spaced (by ninety
807
            // degree rotations) around the point.
808
            int i;
809
            for(i = 0; i < 4; i++) {
810
                DoLine(canvas, hcs, u, uu);
811
                DoLine(canvas, hcs, u, ur);
812
                u = u.RotatedAbout(p, gn, PI/2);
813
                ur = ur.RotatedAbout(p, gn, PI/2);
814
                uu = uu.RotatedAbout(p, gn, PI/2);
815
            }
816
            return;
817
        }
818

819
        case Type::SAME_ORIENTATION: {
820
            for(int i = 0; i < 2; i++) {
821
                Entity *e = SK.GetEntity(i == 0 ? entityA : entityB);
822
                Quaternion q = e->NormalGetNum();
823
                Vector n = q.RotationN().WithMagnitude(25/camera.scale);
824
                Vector u = q.RotationU().WithMagnitude(6/camera.scale);
825
                Vector p = SK.GetEntity(e->point[0])->PointGetNum();
826
                p = p.Plus(n.WithMagnitude(10/camera.scale));
827
                if(refs) refs->push_back(p);
828

829
                DoLine(canvas, hcs, p.Plus(u), p.Minus(u).Plus(n));
830
                DoLine(canvas, hcs, p.Minus(u), p.Plus(u).Plus(n));
831
            }
832
            return;
833
        }
834

835
        case Type::EQUAL_ANGLE: {
836
            Vector ref;
837
            Entity *a = SK.GetEntity(entityA);
838
            Entity *b = SK.GetEntity(entityB);
839
            Entity *c = SK.GetEntity(entityC);
840
            Entity *d = SK.GetEntity(entityD);
841

842
            Vector a0 = a->VectorGetStartPoint();
843
            Vector b0 = b->VectorGetStartPoint();
844
            Vector c0 = c->VectorGetStartPoint();
845
            Vector d0 = d->VectorGetStartPoint();
846
            Vector da = a->VectorGetNum();
847
            Vector db = b->VectorGetNum();
848
            Vector dc = c->VectorGetNum();
849
            Vector dd = d->VectorGetNum();
850

851
            if(other) {
852
                a0 = a0.Plus(da);
853
                da = da.ScaledBy(-1);
854
            }
855

856
            DoArcForAngle(canvas, hcs, a0, da, b0, db,
857
                da.WithMagnitude(40/camera.scale), &ref, /*trim=*/false, a->ExplodeOffset());
858
            if(refs) refs->push_back(ref);
859
            DoArcForAngle(canvas, hcs, c0, dc, d0, dd,
860
                dc.WithMagnitude(40/camera.scale), &ref, /*trim=*/false, c->ExplodeOffset());
861
            if(refs) refs->push_back(ref);
862

863
            return;
864
        }
865

866
        case Type::ANGLE: {
867
            Entity *a = SK.GetEntity(entityA);
868
            Entity *b = SK.GetEntity(entityB);
869

870
            Vector a0 = a->VectorGetStartPoint();
871
            Vector b0 = b->VectorGetStartPoint();
872
            Vector da = a->VectorGetNum();
873
            Vector db = b->VectorGetNum();
874
            if(other) {
875
                a0 = a0.Plus(da);
876
                da = da.ScaledBy(-1);
877
            }
878

879
            Vector ref;
880
            DoArcForAngle(canvas, hcs, a0, da, b0, db, disp.offset, &ref, /*trim=*/true, a->ExplodeOffset());
881
            DoLabel(canvas, hcs, ref, labelPos, gr, gu);
882
            if(refs) refs->push_back(ref);
883
            return;
884
        }
885

886
        case Type::PERPENDICULAR: {
887
            Vector u = Vector::From(0, 0, 0), v = Vector::From(0, 0, 0);
888
            Vector rn, ru;
889
            if(workplane == Entity::FREE_IN_3D) {
890
                rn = gn;
891
                ru = gu;
892
            } else {
893
                EntityBase *normal = SK.GetEntity(workplane)->Normal();
894
                rn = normal->NormalN();
895
                ru = normal->NormalV(); // ru meaning r_up, not u/v
896
            }
897

898
            for(int i = 0; i < 2; i++) {
899
                Entity *e = SK.GetEntity(i == 0 ? entityA : entityB);
900

901
                if(i == 0) {
902
                    // Calculate orientation of perpendicular sign only
903
                    // once, so that it's the same both times it's drawn
904
                    u = e->VectorGetNum();
905
                    u = u.WithMagnitude(16/camera.scale);
906
                    v = (rn.Cross(u)).WithMagnitude(16/camera.scale);
907
                    // a bit of bias to stop it from flickering between the
908
                    // two possibilities
909
                    if(fabs(u.Dot(ru)) < fabs(v.Dot(ru)) + LENGTH_EPS) {
910
                        swap(u, v);
911
                    }
912
                    if(u.Dot(ru) < 0) u = u.ScaledBy(-1);
913
                }
914

915
                Vector p = e->VectorGetRefPoint().Plus(e->ExplodeOffset());
916
                Vector s = p.Plus(u).Plus(v);
917
                DoLine(canvas, hcs, s, s.Plus(v));
918
                Vector m = s.Plus(v.ScaledBy(0.5));
919
                DoLine(canvas, hcs, m, m.Plus(u));
920
                if(refs) refs->push_back(m);
921
            }
922
            return;
923
        }
924

925
        case Type::CURVE_CURVE_TANGENT:
926
        case Type::CUBIC_LINE_TANGENT:
927
        case Type::ARC_LINE_TANGENT: {
928
            Vector textAt, u, v;
929

930
            if(type == Type::ARC_LINE_TANGENT) {
931
                Entity *arc = SK.GetEntity(entityA);
932
                Entity *norm = SK.GetEntity(arc->normal);
933
                Vector c = SK.GetEntity(arc->point[0])->PointGetDrawNum();
934
                Vector p =
935
                    SK.GetEntity(arc->point[other ? 2 : 1])->PointGetDrawNum();
936
                Vector r = p.Minus(c);
937
                textAt = p.Plus(r.WithMagnitude(14/camera.scale));
938
                u = norm->NormalU();
939
                v = norm->NormalV();
940
            } else if(type == Type::CUBIC_LINE_TANGENT) {
941
                Vector n;
942
                if(workplane == Entity::FREE_IN_3D) {
943
                    u = gr;
944
                    v = gu;
945
                    n = gn;
946
                } else {
947
                    EntityBase *wn = SK.GetEntity(workplane)->Normal();
948
                    u = wn->NormalU();
949
                    v = wn->NormalV();
950
                    n = wn->NormalN();
951
                }
952

953
                Entity *cubic = SK.GetEntity(entityA);
954
                Vector p = other ? cubic->CubicGetFinishNum() :
955
                                   cubic->CubicGetStartNum();
956
                p = p.Plus(cubic->ExplodeOffset());
957
                Vector dir = SK.GetEntity(entityB)->VectorGetNum();
958
                Vector out = n.Cross(dir);
959
                textAt = p.Plus(out.WithMagnitude(14/camera.scale));
960
            } else {
961
                Vector n, dir;
962
                EntityBase *wn = SK.GetEntity(workplane)->Normal();
963
                u = wn->NormalU();
964
                v = wn->NormalV();
965
                n = wn->NormalN();
966
                Entity *eA = SK.GetEntity(entityA);
967
                // Big pain; we have to get a vector tangent to the curve
968
                // at the shared point, which could be from either a cubic
969
                // or an arc.
970
                if(other) {
971
                    textAt = eA->EndpointFinish().Plus(eA->ExplodeOffset());
972
                    if(eA->type == Entity::Type::CUBIC) {
973
                        dir = eA->CubicGetFinishTangentNum();
974
                    } else {
975
                        dir = SK.GetEntity(eA->point[0])->PointGetNum().Minus(
976
                              SK.GetEntity(eA->point[2])->PointGetNum());
977
                        dir = n.Cross(dir);
978
                    }
979
                } else {
980
                    textAt = eA->EndpointStart().Plus(eA->ExplodeOffset());
981
                    if(eA->type == Entity::Type::CUBIC) {
982
                        dir = eA->CubicGetStartTangentNum();
983
                    } else {
984
                        dir = SK.GetEntity(eA->point[0])->PointGetNum().Minus(
985
                              SK.GetEntity(eA->point[1])->PointGetNum());
986
                        dir = n.Cross(dir);
987
                    }
988
                }
989
                dir = n.Cross(dir);
990
                textAt = textAt.Plus(dir.WithMagnitude(14/camera.scale));
991
            }
992

993
            Vector ex = VectorFont::Builtin()->GetExtents(textHeight, "T");
994
            canvas->DrawVectorText("T", textHeight, textAt.Minus(ex.ScaledBy(0.5)),
995
                                   u.WithMagnitude(1), v.WithMagnitude(1), hcs);
996
            if(refs) refs->push_back(textAt);
997
            return;
998
        }
999

1000
        case Type::PARALLEL: {
1001
            for(int i = 0; i < 2; i++) {
1002
                Entity *e = SK.GetEntity(i == 0 ? entityA : entityB);
1003
                Vector n = e->VectorGetNum();
1004
                n = n.WithMagnitude(25/camera.scale);
1005
                Vector u = (gn.Cross(n)).WithMagnitude(4/camera.scale);
1006
                Vector p = e->VectorGetRefPoint();
1007

1008
                if(ShouldDrawExploded()) {
1009
                    p = p.Plus(e->ExplodeOffset());
1010
                }
1011

1012
                DoLine(canvas, hcs, p.Plus(u), p.Plus(u).Plus(n));
1013
                DoLine(canvas, hcs, p.Minus(u), p.Minus(u).Plus(n));
1014
                if(refs) refs->push_back(p.Plus(n.ScaledBy(0.5)));
1015
            }
1016
            return;
1017
        }
1018

1019
        case Type::EQUAL_RADIUS: {
1020
            for(int i = 0; i < 2; i++) {
1021
                Vector ref;
1022
                DoEqualRadiusTicks(canvas, hcs, i == 0 ? entityA : entityB, &ref);
1023
                if(refs) refs->push_back(ref);
1024
            }
1025
            return;
1026
        }
1027

1028
        case Type::EQUAL_LINE_ARC_LEN: {
1029
            Entity *line = SK.GetEntity(entityA);
1030
            Vector ref;
1031
            DoEqualLenTicks(canvas, hcs,
1032
                SK.GetEntity(line->point[0])->PointGetDrawNum(),
1033
                SK.GetEntity(line->point[1])->PointGetDrawNum(),
1034
                gn, &ref);
1035
            if(refs) refs->push_back(ref);
1036
            DoEqualRadiusTicks(canvas, hcs, entityB, &ref);
1037
            if(refs) refs->push_back(ref);
1038
            return;
1039
        }
1040

1041
        case Type::LENGTH_RATIO:
1042
        case Type::LENGTH_DIFFERENCE:
1043
        case Type::EQUAL_LENGTH_LINES: {
1044
            Vector a, b = Vector::From(0, 0, 0);
1045
            for(int i = 0; i < 2; i++) {
1046
                Entity *e = SK.GetEntity(i == 0 ? entityA : entityB);
1047
                a = SK.GetEntity(e->point[0])->PointGetNum();
1048
                b = SK.GetEntity(e->point[1])->PointGetNum();
1049

1050
                if(workplane != Entity::FREE_IN_3D) {
1051
                    DoProjectedPoint(canvas, hcs, &a);
1052
                    DoProjectedPoint(canvas, hcs, &b);
1053
                }
1054

1055
                if(ShouldDrawExploded()) {
1056
                    Vector offset = e->ExplodeOffset();
1057
                    a = a.Plus(offset);
1058
                    b = b.Plus(offset);
1059
                }
1060

1061
                Vector ref;
1062
                DoEqualLenTicks(canvas, hcs, a, b, gn, &ref);
1063
                if(refs) refs->push_back(ref);
1064
            }
1065
            if((type == Type::LENGTH_RATIO) || (type == Type::LENGTH_DIFFERENCE)) {
1066
                Vector ref = ((a.Plus(b)).ScaledBy(0.5)).Plus(disp.offset);
1067
                DoLabel(canvas, hcs, ref, labelPos, gr, gu);
1068
            }
1069
            return;
1070
        }
1071
        case Type::ARC_ARC_LEN_RATIO:
1072
        case Type::ARC_ARC_DIFFERENCE: {
1073
            Entity *circle = SK.GetEntity(entityA);
1074
            Vector center = SK.GetEntity(circle->point[0])->PointGetNum();
1075
            Quaternion q = SK.GetEntity(circle->normal)->NormalGetNum();
1076
            Vector n = q.RotationN().WithMagnitude(1);
1077

1078
            Vector ref2;
1079
            DoEqualRadiusTicks(canvas, hcs, entityA, &ref2);
1080
            DoEqualRadiusTicks(canvas, hcs, entityB, &ref2);
1081
            
1082
            Vector ref = center.Plus(disp.offset);
1083
            // Force the label into the same plane as the circle.
1084
            ref = ref.Minus(n.ScaledBy(n.Dot(ref) - n.Dot(center)));
1085
            if(refs) refs->push_back(ref);
1086
            Vector topLeft;
1087
            DoLabel(canvas, hcs, ref, &topLeft, gr, gu);
1088
            if(labelPos) *labelPos = topLeft;
1089
            return;
1090
        }
1091
        case Type::ARC_LINE_LEN_RATIO:
1092
        case Type::ARC_LINE_DIFFERENCE: {
1093
            Vector a, b = Vector::From(0, 0, 0);
1094
            Vector ref;
1095
            Entity *e = SK.GetEntity(entityA);
1096
            a = SK.GetEntity(e->point[0])->PointGetNum();
1097
            b = SK.GetEntity(e->point[1])->PointGetNum();
1098
            DoEqualLenTicks(canvas, hcs, a, b, gn, &ref);
1099
            if(refs) refs->push_back(ref);
1100
            DoEqualRadiusTicks(canvas, hcs, entityB, &ref);
1101
            if(refs) refs->push_back(ref);
1102
            ref = ((a.Plus(b)).ScaledBy(0.5)).Plus(disp.offset);
1103
            DoLabel(canvas, hcs, ref, labelPos, gr, gu);
1104
            return;
1105
        }
1106
        
1107
        case Type::EQ_LEN_PT_LINE_D: {
1108
            Entity *forLen = SK.GetEntity(entityA);
1109
            Vector a = SK.GetEntity(forLen->point[0])->PointGetNum(),
1110
                   b = SK.GetEntity(forLen->point[1])->PointGetNum();
1111
            if(workplane != Entity::FREE_IN_3D) {
1112
                DoProjectedPoint(canvas, hcs, &a);
1113
                DoProjectedPoint(canvas, hcs, &b);
1114
            }
1115
            if(ShouldDrawExploded()) {
1116
                Vector offset = forLen->ExplodeOffset();
1117
                a = a.Plus(offset);
1118
                b = b.Plus(offset);
1119
            }
1120
            Vector refa;
1121
            DoEqualLenTicks(canvas, hcs, a, b, gn, &refa);
1122
            if(refs) refs->push_back(refa);
1123

1124
            Entity *ln = SK.GetEntity(entityB);
1125
            Vector la = SK.GetEntity(ln->point[0])->PointGetNum(),
1126
                   lb = SK.GetEntity(ln->point[1])->PointGetNum();
1127
            Vector pt = SK.GetEntity(ptA)->PointGetNum();
1128
            if(workplane != Entity::FREE_IN_3D) {
1129
                DoProjectedPoint(canvas, hcs, &pt);
1130
                la = la.ProjectInto(workplane);
1131
                lb = lb.ProjectInto(workplane);
1132
            }
1133

1134
            Vector closest = pt.ClosestPointOnLine(la, lb.Minus(la));
1135
            if(ShouldDrawExploded()) {
1136
                Vector offset = SK.GetEntity(ptA)->ExplodeOffset();
1137
                pt = pt.Plus(offset);
1138
                closest = closest.Plus(offset);
1139
            }
1140
            DoLine(canvas, hcs, pt, closest);
1141
            Vector refb;
1142
            DoEqualLenTicks(canvas, hcs, pt, closest, gn, &refb);
1143
            if(refs) refs->push_back(refb);
1144
            return;
1145
        }
1146

1147
        case Type::EQ_PT_LN_DISTANCES: {
1148
            for(int i = 0; i < 2; i++) {
1149
                Entity *ln = SK.GetEntity(i == 0 ? entityA : entityB);
1150
                Vector la = SK.GetEntity(ln->point[0])->PointGetNum(),
1151
                       lb = SK.GetEntity(ln->point[1])->PointGetNum();
1152
                Entity *pte = SK.GetEntity(i == 0 ? ptA : ptB);
1153
                Vector pt = pte->PointGetNum();
1154

1155
                if(workplane != Entity::FREE_IN_3D) {
1156
                    DoProjectedPoint(canvas, hcs, &pt);
1157
                    la = la.ProjectInto(workplane);
1158
                    lb = lb.ProjectInto(workplane);
1159
                }
1160

1161
                Vector closest = pt.ClosestPointOnLine(la, lb.Minus(la));
1162
                if(ShouldDrawExploded()) {
1163
                    Vector offset = pte->ExplodeOffset();
1164
                    pt = pt.Plus(offset);
1165
                    closest = closest.Plus(offset);
1166
                }
1167
                DoLine(canvas, hcs, pt, closest);
1168

1169
                Vector ref;
1170
                DoEqualLenTicks(canvas, hcs, pt, closest, gn, &ref);
1171
                if(refs) refs->push_back(ref);
1172
            }
1173
            return;
1174
        }
1175

1176
        {
1177
        case Type::SYMMETRIC:
1178
            Vector n;
1179
            n = SK.GetEntity(entityA)->Normal()->NormalN(); goto s;
1180
        case Type::SYMMETRIC_HORIZ:
1181
            n = SK.GetEntity(workplane)->Normal()->NormalU(); goto s;
1182
        case Type::SYMMETRIC_VERT:
1183
            n = SK.GetEntity(workplane)->Normal()->NormalV(); goto s;
1184
        case Type::SYMMETRIC_LINE: {
1185
            Entity *ln = SK.GetEntity(entityA);
1186
            Vector la = SK.GetEntity(ln->point[0])->PointGetNum(),
1187
                   lb = SK.GetEntity(ln->point[1])->PointGetNum();
1188
            la = la.ProjectInto(workplane);
1189
            lb = lb.ProjectInto(workplane);
1190
            n = lb.Minus(la);
1191
            Vector nw = SK.GetEntity(workplane)->Normal()->NormalN();
1192
            n = n.RotatedAbout(nw, PI/2);
1193
            goto s;
1194
        }
1195
s:
1196
            Vector a = SK.GetEntity(ptA)->PointGetDrawNum();
1197
            Vector b = SK.GetEntity(ptB)->PointGetDrawNum();
1198

1199
            for(int i = 0; i < 2; i++) {
1200
                Vector tail = (i == 0) ? a : b;
1201
                Vector d = (i == 0) ? b : a;
1202
                d = d.Minus(tail);
1203
                // Project the direction in which the arrow is drawn normal
1204
                // to the symmetry plane; for projected symmetry constraints,
1205
                // they might not be in the same direction, even when the
1206
                // constraint is fully solved.
1207
                d = n.ScaledBy(d.Dot(n));
1208
                d = d.WithMagnitude(20/camera.scale);
1209
                Vector tip = tail.Plus(d);
1210

1211
                DoLine(canvas, hcs, tail, tip);
1212
                d = d.WithMagnitude(9/camera.scale);
1213
                DoLine(canvas, hcs, tip, tip.Minus(d.RotatedAbout(gn,  0.6)));
1214
                DoLine(canvas, hcs, tip, tip.Minus(d.RotatedAbout(gn, -0.6)));
1215
                if(refs) refs->push_back(tip);
1216
            }
1217
            return;
1218
        }
1219

1220
        case Type::AT_MIDPOINT:
1221
        case Type::HORIZONTAL:
1222
        case Type::VERTICAL:
1223
            if(entityA.v) {
1224
                Vector r, u, n;
1225
                if(workplane == Entity::FREE_IN_3D) {
1226
                    r = gr; u = gu; n = gn;
1227
                } else {
1228
                    r = SK.GetEntity(workplane)->Normal()->NormalU();
1229
                    u = SK.GetEntity(workplane)->Normal()->NormalV();
1230
                    n = r.Cross(u);
1231
                }
1232
                // For "at midpoint", this branch is always taken.
1233
                Entity *e = SK.GetEntity(entityA);
1234
                Vector a = SK.GetEntity(e->point[0])->PointGetDrawNum();
1235
                Vector b = SK.GetEntity(e->point[1])->PointGetDrawNum();
1236
                Vector m = (a.ScaledBy(0.5)).Plus(b.ScaledBy(0.5));
1237
                Vector offset = (a.Minus(b)).Cross(n);
1238
                offset = offset.WithMagnitude(textHeight);
1239
                // Draw midpoint constraint on other side of line, so that
1240
                // a line can be midpoint and horizontal at same time.
1241
                if(type == Type::AT_MIDPOINT) offset = offset.ScaledBy(-1);
1242

1243
                std::string s;
1244
                switch(type) {
1245
                    case Type::HORIZONTAL:  s = "H"; break;
1246
                    case Type::VERTICAL:    s = "V"; break;
1247
                    case Type::AT_MIDPOINT: s = "M"; break;
1248
                    default: ssassert(false, "Unexpected constraint type");
1249
                }
1250
                Vector o  = m.Plus(offset).Plus(u.WithMagnitude(textHeight/5)),
1251
                       ex = VectorFont::Builtin()->GetExtents(textHeight, s);
1252
                Vector shift = r.WithMagnitude(ex.x).Plus(
1253
                               u.WithMagnitude(ex.y));
1254

1255
                canvas->DrawVectorText(s, textHeight, o.Minus(shift.ScaledBy(0.5)),
1256
                                       r.WithMagnitude(1), u.WithMagnitude(1), hcs);
1257
                if(refs) refs->push_back(o);
1258
            } else {
1259
                Vector a = SK.GetEntity(ptA)->PointGetDrawNum();
1260
                Vector b = SK.GetEntity(ptB)->PointGetDrawNum();
1261

1262
                Entity *w = SK.GetEntity(workplane);
1263
                Vector cu = w->Normal()->NormalU();
1264
                Vector cv = w->Normal()->NormalV();
1265
                Vector cn = w->Normal()->NormalN();
1266

1267
                int i;
1268
                for(i = 0; i < 2; i++) {
1269
                    Vector o = (i == 0) ? a : b;
1270
                    Vector oo = (i == 0) ? a.Minus(b) : b.Minus(a);
1271
                    Vector d = (type == Type::HORIZONTAL) ? cu : cv;
1272
                    if(oo.Dot(d) < 0) d = d.ScaledBy(-1);
1273

1274
                    Vector dp = cn.Cross(d);
1275
                    d = d.WithMagnitude(14/camera.scale);
1276
                    Vector c = o.Minus(d);
1277
                    DoLine(canvas, hcs, o, c);
1278
                    d = d.WithMagnitude(3/camera.scale);
1279
                    dp = dp.WithMagnitude(2/camera.scale);
1280
                    canvas->DrawQuad((c.Plus(d)).Plus(dp),
1281
                                     (c.Minus(d)).Plus(dp),
1282
                                     (c.Minus(d)).Minus(dp),
1283
                                     (c.Plus(d)).Minus(dp),
1284
                                     hcf);
1285
                    if(refs) refs->push_back(c);
1286
                }
1287
            }
1288
            return;
1289

1290
        case Type::COMMENT: {
1291
            Vector u, v;
1292
            if(workplane == Entity::FREE_IN_3D) {
1293
                u = gr;
1294
                v = gu;
1295
            } else {
1296
                EntityBase *norm = SK.GetEntity(workplane)->Normal();
1297
                u = norm->NormalU();
1298
                v = norm->NormalV();
1299
            }
1300

1301
            if(disp.style.v != 0) {
1302
                RgbaColor color = stroke.color;
1303
                stroke = Style::Stroke(disp.style);
1304
                stroke.layer = Canvas::Layer::FRONT;
1305
                if(how != DrawAs::DEFAULT) {
1306
                    stroke.color = color;
1307
                }
1308
                hcs = canvas->GetStroke(stroke);
1309
            }
1310
            Vector ref = disp.offset;
1311
            if(ptA.v) {
1312
                Vector a = SK.GetEntity(ptA)->PointGetNum();
1313
                ref = a.Plus(disp.offset);
1314
            }
1315
            DoLabel(canvas, hcs, ref, labelPos, u, v);
1316
            if(refs) refs->push_back(ref);
1317
            return;
1318
        }
1319
    }
1320
    ssassert(false, "Unexpected constraint type");
1321
}
1322

1323
void Constraint::Draw(DrawAs how, Canvas *canvas) {
1324
    DoLayout(how, canvas, NULL, NULL);
1325
}
1326

1327
Vector Constraint::GetLabelPos(const Camera &camera) {
1328
    Vector p;
1329

1330
    ObjectPicker canvas = {};
1331
    canvas.camera = camera;
1332
    DoLayout(DrawAs::DEFAULT, &canvas, &p, NULL);
1333
    canvas.Clear();
1334

1335
    return p;
1336
}
1337

1338
void Constraint::GetReferencePoints(const Camera &camera, std::vector<Vector> *refs) {
1339
    ObjectPicker canvas = {};
1340
    canvas.camera = camera;
1341
    DoLayout(DrawAs::DEFAULT, &canvas, NULL, refs);
1342
    canvas.Clear();
1343
}
1344

1345
bool Constraint::IsStylable() const {
1346
    if(type == Type::COMMENT) return true;
1347
    return false;
1348
}
1349

1350
hStyle Constraint::GetStyle() const {
1351
    if(disp.style.v != 0) return disp.style;
1352
    return { Style::CONSTRAINT };
1353
}
1354

1355
bool Constraint::HasLabel() const {
1356
    switch(type) {
1357
        case Type::COMMENT:
1358
        case Type::PT_PT_DISTANCE:
1359
        case Type::PT_PLANE_DISTANCE:
1360
        case Type::PT_LINE_DISTANCE:
1361
        case Type::PT_FACE_DISTANCE:
1362
        case Type::PROJ_PT_DISTANCE:
1363
        case Type::LENGTH_RATIO:
1364
        case Type::ARC_ARC_LEN_RATIO:
1365
        case Type::ARC_LINE_LEN_RATIO:
1366
        case Type::LENGTH_DIFFERENCE:
1367
        case Type::ARC_ARC_DIFFERENCE:
1368
        case Type::ARC_LINE_DIFFERENCE:
1369
        case Type::DIAMETER:
1370
        case Type::ANGLE:
1371
            return true;
1372

1373
        default:
1374
            return false;
1375
    }
1376
}
1377

1378
bool Constraint::ShouldDrawExploded() const {
1379
    return SK.GetGroup(group)->ShouldDrawExploded();
1380
}
1381

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

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

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

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