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 
Equals(Selection * b)10 bool GraphicsWindow::Selection::Equals(Selection *b) {
11     if(entity.v     != b->entity.v)     return false;
12     if(constraint.v != b->constraint.v) return false;
13     return true;
14 }
15 
IsEmpty(void)16 bool GraphicsWindow::Selection::IsEmpty(void) {
17     if(entity.v)        return false;
18     if(constraint.v)    return false;
19     return true;
20 }
21 
HasEndpoints(void)22 bool GraphicsWindow::Selection::HasEndpoints(void) {
23     if(!entity.v) return false;
24     Entity *e = SK.GetEntity(entity);
25     return e->HasEndpoints();
26 }
27 
Clear(void)28 void GraphicsWindow::Selection::Clear(void) {
29     entity.v = constraint.v = 0;
30     emphasized = false;
31 }
32 
Draw(void)33 void GraphicsWindow::Selection::Draw(void) {
34     Vector refp = Vector::From(0, 0, 0);
35     if(entity.v) {
36         Entity *e = SK.GetEntity(entity);
37         e->Draw(/*drawAsHidden=*/false);
38         if(emphasized) refp = e->GetReferencePos();
39     }
40     if(constraint.v) {
41         Constraint *c = SK.GetConstraint(constraint);
42         c->Draw();
43         if(emphasized) refp = c->GetReferencePos();
44     }
45     if(emphasized && (constraint.v || entity.v)) {
46         // We want to emphasize this constraint or entity, by drawing a thick
47         // line from the top left corner of the screen to the reference point
48         // of that entity or constraint.
49         double s = 0.501/SS.GW.scale;
50         Vector topLeft =       SS.GW.projRight.ScaledBy(-SS.GW.width*s);
51         topLeft = topLeft.Plus(SS.GW.projUp.ScaledBy(SS.GW.height*s));
52         topLeft = topLeft.Minus(SS.GW.offset);
53 
54         ssglLineWidth(40);
55         RgbaColor rgb = Style::Color(Style::HOVERED);
56         glColor4d(rgb.redF(), rgb.greenF(), rgb.blueF(), 0.2);
57         glBegin(GL_LINES);
58             ssglVertex3v(topLeft);
59             ssglVertex3v(refp);
60         glEnd();
61         ssglLineWidth(1);
62     }
63 }
64 
ClearSelection(void)65 void GraphicsWindow::ClearSelection(void) {
66     selection.Clear();
67     SS.ScheduleShowTW();
68     InvalidateGraphics();
69 }
70 
ClearNonexistentSelectionItems(void)71 void GraphicsWindow::ClearNonexistentSelectionItems(void) {
72     bool change = false;
73     Selection *s;
74     selection.ClearTags();
75     for(s = selection.First(); s; s = selection.NextAfter(s)) {
76         if(s->constraint.v && !(SK.constraint.FindByIdNoOops(s->constraint))) {
77             s->tag = 1;
78             change = true;
79         }
80         if(s->entity.v && !(SK.entity.FindByIdNoOops(s->entity))) {
81             s->tag = 1;
82             change = true;
83         }
84     }
85     selection.RemoveTagged();
86     if(change) InvalidateGraphics();
87 }
88 
89 //-----------------------------------------------------------------------------
90 // Is this entity/constraint selected?
91 //-----------------------------------------------------------------------------
IsSelected(hEntity he)92 bool GraphicsWindow::IsSelected(hEntity he) {
93     Selection s = {};
94     s.entity = he;
95     return IsSelected(&s);
96 }
IsSelected(Selection * st)97 bool GraphicsWindow::IsSelected(Selection *st) {
98     Selection *s;
99     for(s = selection.First(); s; s = selection.NextAfter(s)) {
100         if(s->Equals(st)) {
101             return true;
102         }
103     }
104     return false;
105 }
106 
107 //-----------------------------------------------------------------------------
108 // Unselect an item, if it is selected. We can either unselect just that item,
109 // or also unselect any coincident points. The latter is useful if the user
110 // somehow selects two coincident points (like with select all), because it
111 // would otherwise be impossible to de-select the lower of the two.
112 //-----------------------------------------------------------------------------
MakeUnselected(hEntity he,bool coincidentPointTrick)113 void GraphicsWindow::MakeUnselected(hEntity he, bool coincidentPointTrick) {
114     Selection stog = {};
115     stog.entity = he;
116     MakeUnselected(&stog, coincidentPointTrick);
117 }
MakeUnselected(Selection * stog,bool coincidentPointTrick)118 void GraphicsWindow::MakeUnselected(Selection *stog, bool coincidentPointTrick){
119     if(stog->IsEmpty()) return;
120 
121     Selection *s;
122 
123     // If an item was selected, then we just un-select it.
124     selection.ClearTags();
125     for(s = selection.First(); s; s = selection.NextAfter(s)) {
126         if(s->Equals(stog)) {
127             s->tag = 1;
128         }
129     }
130     // If two points are coincident, then it's impossible to hover one of
131     // them. But make sure to deselect both, to avoid mysterious seeming
132     // inability to deselect if the bottom one did somehow get selected.
133     if(stog->entity.v && coincidentPointTrick) {
134         Entity *e = SK.GetEntity(stog->entity);
135         if(e->IsPoint()) {
136             Vector ep = e->PointGetNum();
137             for(s = selection.First(); s; s = selection.NextAfter(s)) {
138                 if(!s->entity.v) continue;
139                 if(s->entity.v == stog->entity.v) continue;
140                 Entity *se = SK.GetEntity(s->entity);
141                 if(!se->IsPoint()) continue;
142                 if(ep.Equals(se->PointGetNum())) {
143                     s->tag = 1;
144                 }
145             }
146         }
147     }
148     selection.RemoveTagged();
149 }
150 
151 //-----------------------------------------------------------------------------
152 // Select an item, if it isn't selected already.
153 //-----------------------------------------------------------------------------
MakeSelected(hEntity he)154 void GraphicsWindow::MakeSelected(hEntity he) {
155     Selection stog = {};
156     stog.entity = he;
157     MakeSelected(&stog);
158 }
159 
MakeSelected(hConstraint hc)160 void GraphicsWindow::MakeSelected(hConstraint hc) {
161     Selection stog = {};
162     stog.constraint = hc;
163     MakeSelected(&stog);
164 }
165 
MakeSelected(Selection * stog)166 void GraphicsWindow::MakeSelected(Selection *stog) {
167     if(stog->IsEmpty()) return;
168     if(IsSelected(stog)) return;
169 
170     if(stog->entity.v != 0 && SK.GetEntity(stog->entity)->IsFace()) {
171         // In the interest of speed for the triangle drawing code,
172         // only two faces may be selected at a time.
173         int c = 0;
174         Selection *s;
175         selection.ClearTags();
176         for(s = selection.First(); s; s = selection.NextAfter(s)) {
177             hEntity he = s->entity;
178             if(he.v != 0 && SK.GetEntity(he)->IsFace()) {
179                 c++;
180                 if(c >= 2) s->tag = 1;
181             }
182         }
183         selection.RemoveTagged();
184     }
185 
186     selection.Add(stog);
187 }
188 
189 //-----------------------------------------------------------------------------
190 // Select everything that lies within the marquee view-aligned rectangle. For
191 // points, we test by the point location. For normals, we test by the normal's
192 // associated point. For anything else, we test by any piecewise linear edge.
193 //-----------------------------------------------------------------------------
SelectByMarquee(void)194 void GraphicsWindow::SelectByMarquee(void) {
195     Point2d begin = ProjectPoint(orig.marqueePoint);
196     double xmin = min(orig.mouse.x, begin.x),
197            xmax = max(orig.mouse.x, begin.x),
198            ymin = min(orig.mouse.y, begin.y),
199            ymax = max(orig.mouse.y, begin.y);
200 
201     Entity *e;
202     for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) {
203         if(e->group.v != SS.GW.activeGroup.v) continue;
204         if(e->IsFace() || e->IsDistance()) continue;
205         if(!e->IsVisible()) continue;
206 
207         if(e->IsPoint() || e->IsNormal()) {
208             Vector p = e->IsPoint() ? e->PointGetNum() :
209                                       SK.GetEntity(e->point[0])->PointGetNum();
210             Point2d pp = ProjectPoint(p);
211             if(pp.x >= xmin && pp.x <= xmax &&
212                pp.y >= ymin && pp.y <= ymax)
213             {
214                 MakeSelected(e->h);
215             }
216         } else {
217             // Use the 3d bounding box test routines, to avoid duplication;
218             // so let our bounding square become a bounding box that certainly
219             // includes the z = 0 plane.
220             Vector ptMin = Vector::From(xmin, ymin, -1),
221                    ptMax = Vector::From(xmax, ymax, 1);
222             SEdgeList sel = {};
223             e->GenerateEdges(&sel, true);
224             SEdge *se;
225             for(se = sel.l.First(); se; se = sel.l.NextAfter(se)) {
226                 Point2d ppa = ProjectPoint(se->a),
227                         ppb = ProjectPoint(se->b);
228                 Vector  ptA = Vector::From(ppa.x, ppa.y, 0),
229                         ptB = Vector::From(ppb.x, ppb.y, 0);
230                 if(Vector::BoundingBoxIntersectsLine(ptMax, ptMin,
231                                                      ptA, ptB, true) ||
232                    !ptA.OutsideAndNotOn(ptMax, ptMin) ||
233                    !ptB.OutsideAndNotOn(ptMax, ptMin))
234                 {
235                     MakeSelected(e->h);
236                     break;
237                 }
238             }
239             sel.Clear();
240         }
241     }
242 }
243 
244 //-----------------------------------------------------------------------------
245 // Sort the selection according to various critieria: the entities and
246 // constraints separately, counts of certain types of entities (circles,
247 // lines, etc.), and so on.
248 //-----------------------------------------------------------------------------
GroupSelection(void)249 void GraphicsWindow::GroupSelection(void) {
250     gs = {};
251     int i;
252     for(i = 0; i < selection.n; i++) {
253         Selection *s = &(selection.elem[i]);
254         if(s->entity.v) {
255             (gs.n)++;
256 
257             Entity *e = SK.entity.FindById(s->entity);
258 
259             if(e->IsStylable()) gs.stylables++;
260 
261             // A list of points, and a list of all entities that aren't points.
262             if(e->IsPoint()) {
263                 gs.points++;
264                 gs.point.push_back(s->entity);
265             } else {
266                 gs.entities++;
267                 gs.entity.push_back(s->entity);
268             }
269 
270             // And an auxiliary list of normals, including normals from
271             // workplanes.
272             if(e->IsNormal()) {
273                 gs.anyNormals++;
274                 gs.anyNormal.push_back(s->entity);
275             } else if(e->IsWorkplane()) {
276                 gs.anyNormals++;
277                 gs.anyNormal.push_back(e->Normal()->h);
278             }
279 
280             // And of vectors (i.e., stuff with a direction to constrain)
281             if(e->HasVector()) {
282                 gs.vectors++;
283                 gs.vector.push_back(s->entity);
284             }
285 
286             // Faces (which are special, associated/drawn with triangles)
287             if(e->IsFace()) {
288                 gs.faces++;
289                 gs.face.push_back(s->entity);
290             }
291 
292             if(e->HasEndpoints()) {
293                 (gs.withEndpoints)++;
294             }
295 
296             // And some aux counts too
297             switch(e->type) {
298                 case Entity::WORKPLANE:      (gs.workplanes)++; break;
299                 case Entity::LINE_SEGMENT:   (gs.lineSegments)++; break;
300                 case Entity::CUBIC:          (gs.cubics)++; break;
301                 case Entity::CUBIC_PERIODIC: (gs.periodicCubics)++; break;
302 
303                 case Entity::ARC_OF_CIRCLE:
304                     (gs.circlesOrArcs)++;
305                     (gs.arcs)++;
306                     break;
307 
308                 case Entity::CIRCLE:        (gs.circlesOrArcs)++; break;
309             }
310         }
311         if(s->constraint.v) {
312             gs.constraints++;
313             gs.constraint.push_back(s->constraint);
314             Constraint *c = SK.GetConstraint(s->constraint);
315             if(c->IsStylable()) gs.stylables++;
316             if(c->HasLabel()) gs.constraintLabels++;
317         }
318     }
319 }
320 
HitTestMakeSelection(Point2d mp)321 void GraphicsWindow::HitTestMakeSelection(Point2d mp) {
322     int i;
323     double d, dmin = 1e12;
324     Selection s = {};
325 
326     // Did the view projection change? If so, invalidate bounding boxes.
327     if(!offset.EqualsExactly(cached.offset) ||
328            !projRight.EqualsExactly(cached.projRight) ||
329            !projUp.EqualsExactly(cached.projUp) ||
330            EXACT(scale != cached.scale)) {
331         cached.offset = offset;
332         cached.projRight = projRight;
333         cached.projUp = projUp;
334         cached.scale = scale;
335         for(Entity *e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) {
336             e->screenBBoxValid = false;
337         }
338     }
339 
340     // Always do the entities; we might be dragging something that should
341     // be auto-constrained, and we need the hover for that.
342     for(i = 0; i < SK.entity.n; i++) {
343         Entity *e = &(SK.entity.elem[i]);
344         // Don't hover whatever's being dragged.
345         if(e->h.request().v == pending.point.request().v) {
346             // The one exception is when we're creating a new cubic; we
347             // want to be able to hover the first point, because that's
348             // how we turn it into a periodic spline.
349             if(!e->IsPoint()) continue;
350             if(!e->h.isFromRequest()) continue;
351             Request *r = SK.GetRequest(e->h.request());
352             if(r->type != Request::CUBIC) continue;
353             if(r->extraPoints < 2) continue;
354             if(e->h.v != r->h.entity(1).v) continue;
355         }
356 
357         d = e->GetDistance(mp);
358         if(d < SELECTION_RADIUS && d < dmin) {
359             s = {};
360             s.entity = e->h;
361             dmin = d;
362         }
363     }
364 
365     // The constraints and faces happen only when nothing's in progress.
366     if(pending.operation == 0) {
367         // Constraints
368         for(i = 0; i < SK.constraint.n; i++) {
369             d = SK.constraint.elem[i].GetDistance(mp);
370             if(d < SELECTION_RADIUS && d < dmin) {
371                 s = {};
372                 s.constraint = SK.constraint.elem[i].h;
373                 dmin = d;
374             }
375         }
376 
377         // Faces, from the triangle mesh; these are lowest priority
378         if(s.constraint.v == 0 && s.entity.v == 0 && showShaded && showFaces) {
379             Group *g = SK.GetGroup(activeGroup);
380             SMesh *m = &(g->displayMesh);
381 
382             uint32_t v = m->FirstIntersectionWith(mp);
383             if(v) {
384                 s.entity.v = v;
385             }
386         }
387     }
388 
389     if(!s.Equals(&hover)) {
390         hover = s;
391         PaintGraphics();
392     }
393 }
394 
395 //-----------------------------------------------------------------------------
396 // Project a point in model space to screen space, exactly as gl would; return
397 // units are pixels.
398 //-----------------------------------------------------------------------------
ProjectPoint(Vector p)399 Point2d GraphicsWindow::ProjectPoint(Vector p) {
400     Vector p3 = ProjectPoint3(p);
401     Point2d p2 = { p3.x, p3.y };
402     return p2;
403 }
404 //-----------------------------------------------------------------------------
405 // Project a point in model space to screen space, exactly as gl would; return
406 // units are pixels. The z coordinate is also returned, also in pixels.
407 //-----------------------------------------------------------------------------
ProjectPoint3(Vector p)408 Vector GraphicsWindow::ProjectPoint3(Vector p) {
409     double w;
410     Vector r = ProjectPoint4(p, &w);
411     return r.ScaledBy(scale/w);
412 }
413 //-----------------------------------------------------------------------------
414 // Project a point in model space halfway into screen space. The scale is
415 // not applied, and the perspective divide isn't applied; instead the w
416 // coordinate is returned separately.
417 //-----------------------------------------------------------------------------
ProjectPoint4(Vector p,double * w)418 Vector GraphicsWindow::ProjectPoint4(Vector p, double *w) {
419     p = p.Plus(offset);
420 
421     Vector r;
422     r.x = p.Dot(projRight);
423     r.y = p.Dot(projUp);
424     r.z = p.Dot(projUp.Cross(projRight));
425 
426     *w = 1 + r.z*SS.CameraTangent()*scale;
427     return r;
428 }
429 
430 //-----------------------------------------------------------------------------
431 // Return a point in the plane parallel to the screen and through the offset,
432 // that projects onto the specified (x, y) coordinates.
433 //-----------------------------------------------------------------------------
UnProjectPoint(Point2d p)434 Vector GraphicsWindow::UnProjectPoint(Point2d p) {
435     Vector orig = offset.ScaledBy(-1);
436 
437     // Note that we ignoring the effects of perspective. Since our returned
438     // point has the same component normal to the screen as the offset, it
439     // will have z = 0 after the rotation is applied, thus w = 1. So this is
440     // correct.
441     orig = orig.Plus(projRight.ScaledBy(p.x / scale)).Plus(
442                      projUp.   ScaledBy(p.y / scale));
443     return orig;
444 }
445 
UnProjectPoint3(Vector p)446 Vector GraphicsWindow::UnProjectPoint3(Vector p) {
447     p.z = p.z / (scale - p.z * SS.CameraTangent() * scale);
448     double w = 1 + p.z * SS.CameraTangent() * scale;
449     p.x *= w / scale;
450     p.y *= w / scale;
451 
452     Vector orig = offset.ScaledBy(-1);
453     orig = orig.Plus(projRight.ScaledBy(p.x)).Plus(
454                      projUp.   ScaledBy(p.y).Plus(
455                      projRight.Cross(projUp). ScaledBy(p.z)));
456     return orig;
457 }
458 
NormalizeProjectionVectors(void)459 void GraphicsWindow::NormalizeProjectionVectors(void) {
460     if(projRight.Magnitude() < LENGTH_EPS) {
461         projRight = Vector::From(1, 0, 0);
462     }
463 
464     Vector norm = projRight.Cross(projUp);
465     // If projRight and projUp somehow ended up parallel, then pick an
466     // arbitrary projUp normal to projRight.
467     if(norm.Magnitude() < LENGTH_EPS) {
468         norm = projRight.Normal(0);
469     }
470     projUp = norm.Cross(projRight);
471 
472     projUp = projUp.WithMagnitude(1);
473     projRight = projRight.WithMagnitude(1);
474 }
475 
VectorFromProjs(Vector rightUpForward)476 Vector GraphicsWindow::VectorFromProjs(Vector rightUpForward) {
477     Vector n = projRight.Cross(projUp);
478 
479     Vector r = (projRight.ScaledBy(rightUpForward.x));
480     r =  r.Plus(projUp.ScaledBy(rightUpForward.y));
481     r =  r.Plus(n.ScaledBy(rightUpForward.z));
482     return r;
483 }
484 
Paint(void)485 void GraphicsWindow::Paint(void) {
486     int i;
487     havePainted = true;
488 
489     int w, h;
490     GetGraphicsWindowSize(&w, &h);
491     width = w; height = h;
492     glViewport(0, 0, w, h);
493 
494     glMatrixMode(GL_PROJECTION);
495     glLoadIdentity();
496 
497     glScaled(scale*2.0/w, scale*2.0/h, scale*1.0/30000);
498 
499     double mat[16];
500     // Last thing before display is to apply the perspective
501     double clp = SS.CameraTangent()*scale;
502     MakeMatrix(mat, 1,              0,              0,              0,
503                     0,              1,              0,              0,
504                     0,              0,              1,              0,
505                     0,              0,              clp,            1);
506     glMultMatrixd(mat);
507     // Before that, we apply the rotation
508     Vector n = projUp.Cross(projRight);
509     MakeMatrix(mat, projRight.x,    projRight.y,    projRight.z,    0,
510                     projUp.x,       projUp.y,       projUp.z,       0,
511                     n.x,            n.y,            n.z,            0,
512                     0,              0,              0,              1);
513     glMultMatrixd(mat);
514     // And before that, the translation
515     MakeMatrix(mat, 1,              0,              0,              offset.x,
516                     0,              1,              0,              offset.y,
517                     0,              0,              1,              offset.z,
518                     0,              0,              0,              1);
519     glMultMatrixd(mat);
520 
521     glMatrixMode(GL_MODELVIEW);
522     glLoadIdentity();
523 
524     glShadeModel(GL_SMOOTH);
525 
526     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
527     glEnable(GL_BLEND);
528     glEnable(GL_LINE_SMOOTH);
529     // don't enable GL_POLYGON_SMOOTH; that looks ugly on some graphics cards,
530     // drawn with leaks in the mesh
531     glEnable(GL_POLYGON_OFFSET_LINE);
532     glEnable(GL_POLYGON_OFFSET_FILL);
533     glEnable(GL_DEPTH_TEST);
534     glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
535     glEnable(GL_NORMALIZE);
536 
537     // At the same depth, we want later lines drawn over earlier.
538     glDepthFunc(GL_LEQUAL);
539 
540     if(SS.ActiveGroupsOkay()) {
541         glClearColor(SS.backgroundColor.redF(),
542                      SS.backgroundColor.greenF(),
543                      SS.backgroundColor.blueF(), 1.0f);
544     } else {
545         // Draw a different background whenever we're having solve problems.
546         RgbaColor rgb = Style::Color(Style::DRAW_ERROR);
547         glClearColor(0.4f*rgb.redF(), 0.4f*rgb.greenF(), 0.4f*rgb.blueF(), 1.0f);
548         // And show the text window, which has info to debug it
549         ForceTextWindowShown();
550     }
551     glClear(GL_COLOR_BUFFER_BIT);
552     glClearDepth(1.0);
553     glClear(GL_DEPTH_BUFFER_BIT);
554 
555     if(SS.bgImage.fromFile) {
556         // If a background image is loaded, then we draw it now as a texture.
557         // This handles the resizing for us nicely.
558         glBindTexture(GL_TEXTURE_2D, TEXTURE_BACKGROUND_IMG);
559         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
560         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
561         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,     GL_CLAMP);
562         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,     GL_CLAMP);
563         glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
564         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
565                      SS.bgImage.rw, SS.bgImage.rh,
566                      0,
567                      GL_RGB, GL_UNSIGNED_BYTE,
568                      SS.bgImage.fromFile);
569 
570         double tw = ((double)SS.bgImage.w) / SS.bgImage.rw,
571                th = ((double)SS.bgImage.h) / SS.bgImage.rh;
572 
573         double mmw = SS.bgImage.w / SS.bgImage.scale,
574                mmh = SS.bgImage.h / SS.bgImage.scale;
575 
576         Vector origin = SS.bgImage.origin;
577         origin = origin.DotInToCsys(projRight, projUp, n);
578         // Place the depth of our origin at the point that corresponds to
579         // w = 1, so that it's unaffected by perspective.
580         origin.z = (offset.ScaledBy(-1)).Dot(n);
581         origin = origin.ScaleOutOfCsys(projRight, projUp, n);
582 
583         // Place the background at the very back of the Z order, though, by
584         // mucking with the depth range.
585         glDepthRange(1, 1);
586         glEnable(GL_TEXTURE_2D);
587         glBegin(GL_QUADS);
588             glTexCoord2d(0, 0);
589             ssglVertex3v(origin);
590 
591             glTexCoord2d(0, th);
592             ssglVertex3v(origin.Plus(projUp.ScaledBy(mmh)));
593 
594             glTexCoord2d(tw, th);
595             ssglVertex3v(origin.Plus(projRight.ScaledBy(mmw).Plus(
596                                      projUp.   ScaledBy(mmh))));
597 
598             glTexCoord2d(tw, 0);
599             ssglVertex3v(origin.Plus(projRight.ScaledBy(mmw)));
600         glEnd();
601         glDisable(GL_TEXTURE_2D);
602     }
603     ssglDepthRangeOffset(0);
604 
605     // Nasty case when we're reloading the linked files; could be that
606     // we get an error, so a dialog pops up, and a message loop starts, and
607     // we have to get called to paint ourselves. If the sketch is screwed
608     // up, then we could trigger an oops trying to draw.
609     if(!SS.allConsistent) return;
610 
611     // Let's use two lights, at the user-specified locations
612     GLfloat f;
613     glEnable(GL_LIGHT0);
614     f = (GLfloat)SS.lightIntensity[0];
615     GLfloat li0[] = { f, f, f, 1.0f };
616     glLightfv(GL_LIGHT0, GL_DIFFUSE, li0);
617     glLightfv(GL_LIGHT0, GL_SPECULAR, li0);
618 
619     glEnable(GL_LIGHT1);
620     f = (GLfloat)SS.lightIntensity[1];
621     GLfloat li1[] = { f, f, f, 1.0f };
622     glLightfv(GL_LIGHT1, GL_DIFFUSE, li1);
623     glLightfv(GL_LIGHT1, GL_SPECULAR, li1);
624 
625     Vector ld;
626     ld = VectorFromProjs(SS.lightDir[0]);
627     GLfloat ld0[4] = { (GLfloat)ld.x, (GLfloat)ld.y, (GLfloat)ld.z, 0 };
628     glLightfv(GL_LIGHT0, GL_POSITION, ld0);
629     ld = VectorFromProjs(SS.lightDir[1]);
630     GLfloat ld1[4] = { (GLfloat)ld.x, (GLfloat)ld.y, (GLfloat)ld.z, 0 };
631     glLightfv(GL_LIGHT1, GL_POSITION, ld1);
632 
633     GLfloat ambient[4] = { (float)SS.ambientIntensity,
634                            (float)SS.ambientIntensity,
635                            (float)SS.ambientIntensity, 1 };
636     glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);
637 
638     ssglUnlockColor();
639 
640     if(showSnapGrid && LockedInWorkplane()) {
641         hEntity he = ActiveWorkplane();
642         EntityBase *wrkpl = SK.GetEntity(he),
643                    *norm  = wrkpl->Normal();
644         Vector wu, wv, wn, wp;
645         wp = SK.GetEntity(wrkpl->point[0])->PointGetNum();
646         wu = norm->NormalU();
647         wv = norm->NormalV();
648         wn = norm->NormalN();
649 
650         double g = SS.gridSpacing;
651 
652         double umin = VERY_POSITIVE, umax = VERY_NEGATIVE,
653                vmin = VERY_POSITIVE, vmax = VERY_NEGATIVE;
654         int a;
655         for(a = 0; a < 4; a++) {
656             // Ideally, we would just do +/- half the width and height; but
657             // allow some extra slop for rounding.
658             Vector horiz = projRight.ScaledBy((0.6*width)/scale  + 2*g),
659                    vert  = projUp.   ScaledBy((0.6*height)/scale + 2*g);
660             if(a == 2 || a == 3) horiz = horiz.ScaledBy(-1);
661             if(a == 1 || a == 3) vert  = vert. ScaledBy(-1);
662             Vector tp = horiz.Plus(vert).Minus(offset);
663 
664             // Project the point into our grid plane, normal to the screen
665             // (not to the grid plane). If the plane is on edge then this is
666             // impossible so don't try to draw the grid.
667             bool parallel;
668             Vector tpp = Vector::AtIntersectionOfPlaneAndLine(
669                                             wn, wn.Dot(wp),
670                                             tp, tp.Plus(n),
671                                             &parallel);
672             if(parallel) goto nogrid;
673 
674             tpp = tpp.Minus(wp);
675             double uu = tpp.Dot(wu),
676                    vv = tpp.Dot(wv);
677 
678             umin = min(uu, umin);
679             umax = max(uu, umax);
680             vmin = min(vv, vmin);
681             vmax = max(vv, vmax);
682         }
683 
684         int i, j, i0, i1, j0, j1;
685 
686         i0 = (int)(umin / g);
687         i1 = (int)(umax / g);
688         j0 = (int)(vmin / g);
689         j1 = (int)(vmax / g);
690 
691         if(i0 > i1 || i1 - i0 > 400) goto nogrid;
692         if(j0 > j1 || j1 - j0 > 400) goto nogrid;
693 
694         ssglLineWidth(1);
695         ssglColorRGBa(Style::Color(Style::DATUM), 0.3);
696         glBegin(GL_LINES);
697         for(i = i0 + 1; i < i1; i++) {
698             ssglVertex3v(wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j0*g)));
699             ssglVertex3v(wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j1*g)));
700         }
701         for(j = j0 + 1; j < j1; j++) {
702             ssglVertex3v(wp.Plus(wu.ScaledBy(i0*g)).Plus(wv.ScaledBy(j*g)));
703             ssglVertex3v(wp.Plus(wu.ScaledBy(i1*g)).Plus(wv.ScaledBy(j*g)));
704         }
705         glEnd();
706 
707         // Clear the depth buffer, so that the grid is at the very back of
708         // the Z order.
709         glClear(GL_DEPTH_BUFFER_BIT);
710 nogrid:;
711     }
712 
713     // Draw the active group; this does stuff like the mesh and edges.
714     (SK.GetGroup(activeGroup))->Draw();
715 
716     // Now draw the entities.
717     if(SS.GW.showHdnLines) {
718         ssglDepthRangeOffset(2);
719         glDepthFunc(GL_GREATER);
720         Entity::DrawAll(/*drawAsHidden=*/true);
721         glDepthFunc(GL_LEQUAL);
722     }
723     ssglDepthRangeOffset(0);
724     Entity::DrawAll(/*drawAsHidden=*/false);
725 
726     // Draw filled paths in all groups, when those filled paths were requested
727     // specially by assigning a style with a fill color, or when the filled
728     // paths are just being filled by default. This should go last, to make
729     // the transparency work.
730     for(i = 0; i < SK.groupOrder.n; i++) {
731         Group *g = SK.GetGroup(SK.groupOrder.elem[i]);
732         if(!(g->IsVisible())) continue;
733         g->DrawFilledPaths();
734     }
735 
736 
737     glDisable(GL_DEPTH_TEST);
738     // Draw the constraints
739     for(i = 0; i < SK.constraint.n; i++) {
740         SK.constraint.elem[i].Draw();
741     }
742 
743     // Draw the "pending" constraint, i.e. a constraint that would be
744     // placed on a line that is almost horizontal or vertical
745     if(SS.GW.pending.operation == DRAGGING_NEW_LINE_POINT) {
746         if(SS.GW.pending.suggestion != GraphicsWindow::SUGGESTED_NONE) {
747             Constraint c = {};
748             c.group = SS.GW.activeGroup;
749             c.workplane = SS.GW.ActiveWorkplane();
750             c.type = SS.GW.pending.suggestion;
751             c.ptA = Entity::NO_ENTITY;
752             c.ptB = Entity::NO_ENTITY;
753             c.entityA = SS.GW.pending.request.entity(0);
754             c.entityB = Entity::NO_ENTITY;
755             c.other = false;
756             c.other2 = false;
757             // Only draw.
758             c.Draw();
759         }
760     }
761 
762     // Draw the traced path, if one exists
763     ssglLineWidth(Style::Width(Style::ANALYZE));
764     ssglColorRGB(Style::Color(Style::ANALYZE));
765     SContour *sc = &(SS.traced.path);
766     glBegin(GL_LINE_STRIP);
767     for(i = 0; i < sc->l.n; i++) {
768         ssglVertex3v(sc->l.elem[i].p);
769     }
770     glEnd();
771 
772     // And the naked edges, if the user did Analyze -> Show Naked Edges.
773     ssglDrawEdges(&(SS.nakedEdges), true, { Style::DRAW_ERROR });
774 
775     // Then redraw whatever the mouse is hovering over, highlighted.
776     glDisable(GL_DEPTH_TEST);
777     ssglLockColorTo(Style::Color(Style::HOVERED));
778     hover.Draw();
779 
780     // And finally draw the selection, same mechanism.
781     ssglLockColorTo(Style::Color(Style::SELECTED));
782     for(Selection *s = selection.First(); s; s = selection.NextAfter(s)) {
783         s->Draw();
784     }
785 
786     ssglUnlockColor();
787 
788     // If a marquee selection is in progress, then draw the selection
789     // rectangle, as an outline and a transparent fill.
790     if(pending.operation == DRAGGING_MARQUEE) {
791         Point2d begin = ProjectPoint(orig.marqueePoint);
792         double xmin = min(orig.mouse.x, begin.x),
793                xmax = max(orig.mouse.x, begin.x),
794                ymin = min(orig.mouse.y, begin.y),
795                ymax = max(orig.mouse.y, begin.y);
796 
797         Vector tl = UnProjectPoint(Point2d::From(xmin, ymin)),
798                tr = UnProjectPoint(Point2d::From(xmax, ymin)),
799                br = UnProjectPoint(Point2d::From(xmax, ymax)),
800                bl = UnProjectPoint(Point2d::From(xmin, ymax));
801 
802         ssglLineWidth((GLfloat)1.3);
803         ssglColorRGB(Style::Color(Style::HOVERED));
804         glBegin(GL_LINE_LOOP);
805             ssglVertex3v(tl);
806             ssglVertex3v(tr);
807             ssglVertex3v(br);
808             ssglVertex3v(bl);
809         glEnd();
810         ssglColorRGBa(Style::Color(Style::HOVERED), 0.10);
811         glBegin(GL_QUADS);
812             ssglVertex3v(tl);
813             ssglVertex3v(tr);
814             ssglVertex3v(br);
815             ssglVertex3v(bl);
816         glEnd();
817     }
818 
819     // An extra line, used to indicate the origin when rotating within the
820     // plane of the monitor.
821     if(SS.extraLine.draw) {
822         ssglLineWidth(1);
823         ssglLockColorTo(Style::Color(Style::DATUM));
824         glBegin(GL_LINES);
825             ssglVertex3v(SS.extraLine.ptA);
826             ssglVertex3v(SS.extraLine.ptB);
827         glEnd();
828     }
829 
830     // A note to indicate the origin in the just-exported file.
831     if(SS.justExportedInfo.draw) {
832         Vector p, u, v;
833         if(SS.justExportedInfo.showOrigin) {
834             p = SS.justExportedInfo.pt,
835             u = SS.justExportedInfo.u,
836             v = SS.justExportedInfo.v;
837         } else {
838             p = SS.GW.offset.ScaledBy(-1);
839             u = SS.GW.projRight;
840             v = SS.GW.projUp;
841         }
842 
843         ssglColorRGB(Style::Color(Style::DATUM));
844 
845         ssglWriteText("previewing exported geometry; press Esc to return",
846             Style::DefaultTextHeight(),
847             p.Plus(u.ScaledBy(10/scale)).Plus(v.ScaledBy(10/scale)),
848             u, v, NULL, NULL);
849 
850         if(SS.justExportedInfo.showOrigin) {
851             ssglLineWidth(1.5);
852             glBegin(GL_LINES);
853                 ssglVertex3v(p.Plus(u.WithMagnitude(-15/scale)));
854                 ssglVertex3v(p.Plus(u.WithMagnitude(30/scale)));
855                 ssglVertex3v(p.Plus(v.WithMagnitude(-15/scale)));
856                 ssglVertex3v(p.Plus(v.WithMagnitude(30/scale)));
857             glEnd();
858 
859             ssglWriteText("(x, y) = (0, 0) for file just exported",
860                 Style::DefaultTextHeight(),
861                 p.Plus(u.ScaledBy(40/scale)).Plus(
862                        v.ScaledBy(-(Style::DefaultTextHeight())/scale)),
863                 u, v, NULL, NULL);
864         }
865     }
866 
867     // And finally the toolbar.
868     if(SS.showToolbar) {
869         ToolbarDraw();
870     }
871 }
872 
873