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 ¶llel);
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