solvespace/src/draw.cpp

812 lines
28 KiB
C++

//-----------------------------------------------------------------------------
// The root function to paint our graphics window, after setting up all the
// views and such appropriately. Also contains all the stuff to manage the
// selection.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
bool GraphicsWindow::Selection::Equals(Selection *b) {
if(entity.v != b->entity.v) return false;
if(constraint.v != b->constraint.v) return false;
return true;
}
bool GraphicsWindow::Selection::IsEmpty() {
if(entity.v) return false;
if(constraint.v) return false;
return true;
}
bool GraphicsWindow::Selection::HasEndpoints() {
if(!entity.v) return false;
Entity *e = SK.GetEntity(entity);
return e->HasEndpoints();
}
void GraphicsWindow::Selection::Clear() {
entity.v = constraint.v = 0;
emphasized = false;
}
void GraphicsWindow::Selection::Draw() {
Vector refp[2];
refp[0] = refp[1] = Vector::From(0, 0, 0);
if(entity.v) {
Entity *e = SK.GetEntity(entity);
e->Draw(/*drawAsHidden=*/false);
if(emphasized) {
refp[0] = refp[1] = e->GetReferencePos();
}
}
if(constraint.v) {
Constraint *c = SK.GetConstraint(constraint);
c->Draw();
if(emphasized) c->GetReferencePos(refp);
}
if(emphasized && (constraint.v || entity.v)) {
// We want to emphasize this constraint or entity, by drawing a thick
// line from the top left corner of the screen to the reference point(s)
// of that entity or constraint.
double s = 0.501/SS.GW.scale;
Vector topLeft = SS.GW.projRight.ScaledBy(-SS.GW.width*s);
topLeft = topLeft.Plus(SS.GW.projUp.ScaledBy(SS.GW.height*s));
topLeft = topLeft.Minus(SS.GW.offset);
ssglLineWidth(40);
RgbaColor rgb = Style::Color(Style::HOVERED);
glColor4d(rgb.redF(), rgb.greenF(), rgb.blueF(), 0.2);
for(int i = 0; i < (refp[0].Equals(refp[1]) ? 1 : 2); i++) {
glBegin(GL_LINES);
ssglVertex3v(topLeft);
ssglVertex3v(refp[i]);
glEnd();
}
ssglLineWidth(1);
}
}
void GraphicsWindow::ClearSelection() {
selection.Clear();
SS.ScheduleShowTW();
InvalidateGraphics();
}
void GraphicsWindow::ClearNonexistentSelectionItems() {
bool change = false;
Selection *s;
selection.ClearTags();
for(s = selection.First(); s; s = selection.NextAfter(s)) {
if(s->constraint.v && !(SK.constraint.FindByIdNoOops(s->constraint))) {
s->tag = 1;
change = true;
}
if(s->entity.v && !(SK.entity.FindByIdNoOops(s->entity))) {
s->tag = 1;
change = true;
}
}
selection.RemoveTagged();
if(change) InvalidateGraphics();
}
//-----------------------------------------------------------------------------
// Is this entity/constraint selected?
//-----------------------------------------------------------------------------
bool GraphicsWindow::IsSelected(hEntity he) {
Selection s = {};
s.entity = he;
return IsSelected(&s);
}
bool GraphicsWindow::IsSelected(Selection *st) {
Selection *s;
for(s = selection.First(); s; s = selection.NextAfter(s)) {
if(s->Equals(st)) {
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------
// Unselect an item, if it is selected. We can either unselect just that item,
// or also unselect any coincident points. The latter is useful if the user
// somehow selects two coincident points (like with select all), because it
// would otherwise be impossible to de-select the lower of the two.
//-----------------------------------------------------------------------------
void GraphicsWindow::MakeUnselected(hEntity he, bool coincidentPointTrick) {
Selection stog = {};
stog.entity = he;
MakeUnselected(&stog, coincidentPointTrick);
}
void GraphicsWindow::MakeUnselected(Selection *stog, bool coincidentPointTrick){
if(stog->IsEmpty()) return;
Selection *s;
// If an item was selected, then we just un-select it.
selection.ClearTags();
for(s = selection.First(); s; s = selection.NextAfter(s)) {
if(s->Equals(stog)) {
s->tag = 1;
}
}
// If two points are coincident, then it's impossible to hover one of
// them. But make sure to deselect both, to avoid mysterious seeming
// inability to deselect if the bottom one did somehow get selected.
if(stog->entity.v && coincidentPointTrick) {
Entity *e = SK.GetEntity(stog->entity);
if(e->IsPoint()) {
Vector ep = e->PointGetNum();
for(s = selection.First(); s; s = selection.NextAfter(s)) {
if(!s->entity.v) continue;
if(s->entity.v == stog->entity.v) continue;
Entity *se = SK.GetEntity(s->entity);
if(!se->IsPoint()) continue;
if(ep.Equals(se->PointGetNum())) {
s->tag = 1;
}
}
}
}
selection.RemoveTagged();
}
//-----------------------------------------------------------------------------
// Select an item, if it isn't selected already.
//-----------------------------------------------------------------------------
void GraphicsWindow::MakeSelected(hEntity he) {
Selection stog = {};
stog.entity = he;
MakeSelected(&stog);
}
void GraphicsWindow::MakeSelected(hConstraint hc) {
Selection stog = {};
stog.constraint = hc;
MakeSelected(&stog);
}
void GraphicsWindow::MakeSelected(Selection *stog) {
if(stog->IsEmpty()) return;
if(IsSelected(stog)) return;
if(stog->entity.v != 0 && SK.GetEntity(stog->entity)->IsFace()) {
// In the interest of speed for the triangle drawing code,
// only two faces may be selected at a time.
int c = 0;
Selection *s;
selection.ClearTags();
for(s = selection.First(); s; s = selection.NextAfter(s)) {
hEntity he = s->entity;
if(he.v != 0 && SK.GetEntity(he)->IsFace()) {
c++;
if(c >= 2) s->tag = 1;
}
}
selection.RemoveTagged();
}
selection.Add(stog);
}
//-----------------------------------------------------------------------------
// Select everything that lies within the marquee view-aligned rectangle.
//-----------------------------------------------------------------------------
void GraphicsWindow::SelectByMarquee() {
Point2d marqueePoint = ProjectPoint(orig.marqueePoint);
BBox marqueeBBox = BBox::From(Vector::From(marqueePoint.x, marqueePoint.y, -1),
Vector::From(orig.mouse.x, orig.mouse.y, 1));
Entity *e;
for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) {
if(e->group.v != SS.GW.activeGroup.v) continue;
if(e->IsFace() || e->IsDistance()) continue;
if(!e->IsVisible()) continue;
bool entityHasBBox;
BBox entityBBox = e->GetOrGenerateScreenBBox(&entityHasBBox);
if(entityHasBBox && entityBBox.Overlaps(marqueeBBox)) {
MakeSelected(e->h);
}
}
}
//-----------------------------------------------------------------------------
// Sort the selection according to various critieria: the entities and
// constraints separately, counts of certain types of entities (circles,
// lines, etc.), and so on.
//-----------------------------------------------------------------------------
void GraphicsWindow::GroupSelection() {
gs = {};
int i;
for(i = 0; i < selection.n && i < MAX_SELECTED; i++) {
Selection *s = &(selection.elem[i]);
if(s->entity.v) {
(gs.n)++;
Entity *e = SK.entity.FindById(s->entity);
if(e->IsStylable()) gs.stylables++;
// A list of points, and a list of all entities that aren't points.
if(e->IsPoint()) {
gs.point[(gs.points)++] = s->entity;
} else {
gs.entity[(gs.entities)++] = s->entity;
}
// And an auxiliary list of normals, including normals from
// workplanes.
if(e->IsNormal()) {
gs.anyNormal[(gs.anyNormals)++] = s->entity;
} else if(e->IsWorkplane()) {
gs.anyNormal[(gs.anyNormals)++] = e->Normal()->h;
}
// And of vectors (i.e., stuff with a direction to constrain)
if(e->HasVector()) {
gs.vector[(gs.vectors)++] = s->entity;
}
// Faces (which are special, associated/drawn with triangles)
if(e->IsFace()) {
gs.face[(gs.faces)++] = s->entity;
}
if(e->HasEndpoints()) {
(gs.withEndpoints)++;
}
// And some aux counts too
switch(e->type) {
case Entity::Type::WORKPLANE: (gs.workplanes)++; break;
case Entity::Type::LINE_SEGMENT: (gs.lineSegments)++; break;
case Entity::Type::CUBIC: (gs.cubics)++; break;
case Entity::Type::CUBIC_PERIODIC: (gs.periodicCubics)++; break;
case Entity::Type::ARC_OF_CIRCLE:
(gs.circlesOrArcs)++;
(gs.arcs)++;
break;
case Entity::Type::CIRCLE: (gs.circlesOrArcs)++; break;
default: break;
}
}
if(s->constraint.v) {
gs.constraint[(gs.constraints)++] = s->constraint;
Constraint *c = SK.GetConstraint(s->constraint);
if(c->IsStylable()) gs.stylables++;
if(c->HasLabel()) gs.constraintLabels++;
}
}
}
void GraphicsWindow::HitTestMakeSelection(Point2d mp) {
int i;
double d, dmin = 1e12;
Selection s = {};
// Did the view projection change? If so, invalidate bounding boxes.
if(!offset.EqualsExactly(cached.offset) ||
!projRight.EqualsExactly(cached.projRight) ||
!projUp.EqualsExactly(cached.projUp) ||
EXACT(scale != cached.scale)) {
cached.offset = offset;
cached.projRight = projRight;
cached.projUp = projUp;
cached.scale = scale;
for(Entity *e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) {
e->screenBBoxValid = false;
}
}
// Always do the entities; we might be dragging something that should
// be auto-constrained, and we need the hover for that.
for(i = 0; i < SK.entity.n; i++) {
Entity *e = &(SK.entity.elem[i]);
// Don't hover whatever's being dragged.
if(e->h.request().v == pending.point.request().v) {
// The one exception is when we're creating a new cubic; we
// want to be able to hover the first point, because that's
// how we turn it into a periodic spline.
if(!e->IsPoint()) continue;
if(!e->h.isFromRequest()) continue;
Request *r = SK.GetRequest(e->h.request());
if(r->type != Request::Type::CUBIC) continue;
if(r->extraPoints < 2) continue;
if(e->h.v != r->h.entity(1).v) continue;
}
d = e->GetDistance(mp);
if(d < SELECTION_RADIUS && d < dmin) {
s = {};
s.entity = e->h;
dmin = d;
}
}
// The constraints and faces happen only when nothing's in progress.
if(pending.operation == Pending::NONE) {
// Constraints
for(i = 0; i < SK.constraint.n; i++) {
d = SK.constraint.elem[i].GetDistance(mp);
if(d < SELECTION_RADIUS && d < dmin) {
s = {};
s.constraint = SK.constraint.elem[i].h;
dmin = d;
}
}
// Faces, from the triangle mesh; these are lowest priority
if(s.constraint.v == 0 && s.entity.v == 0 && showShaded && showFaces) {
Group *g = SK.GetGroup(activeGroup);
SMesh *m = &(g->displayMesh);
uint32_t v = m->FirstIntersectionWith(mp);
if(v) {
s.entity.v = v;
}
}
}
if(!s.Equals(&hover)) {
hover = s;
PaintGraphics();
}
}
//-----------------------------------------------------------------------------
// Project a point in model space to screen space, exactly as gl would; return
// units are pixels.
//-----------------------------------------------------------------------------
Point2d GraphicsWindow::ProjectPoint(Vector p) {
Vector p3 = ProjectPoint3(p);
Point2d p2 = { p3.x, p3.y };
return p2;
}
//-----------------------------------------------------------------------------
// Project a point in model space to screen space, exactly as gl would; return
// units are pixels. The z coordinate is also returned, also in pixels.
//-----------------------------------------------------------------------------
Vector GraphicsWindow::ProjectPoint3(Vector p) {
double w;
Vector r = ProjectPoint4(p, &w);
return r.ScaledBy(scale/w);
}
//-----------------------------------------------------------------------------
// Project a point in model space halfway into screen space. The scale is
// not applied, and the perspective divide isn't applied; instead the w
// coordinate is returned separately.
//-----------------------------------------------------------------------------
Vector GraphicsWindow::ProjectPoint4(Vector p, double *w) {
p = p.Plus(offset);
Vector r;
r.x = p.Dot(projRight);
r.y = p.Dot(projUp);
r.z = p.Dot(projUp.Cross(projRight));
*w = 1 + r.z*SS.CameraTangent()*scale;
return r;
}
//-----------------------------------------------------------------------------
// Return a point in the plane parallel to the screen and through the offset,
// that projects onto the specified (x, y) coordinates.
//-----------------------------------------------------------------------------
Vector GraphicsWindow::UnProjectPoint(Point2d p) {
Vector orig = offset.ScaledBy(-1);
// Note that we ignoring the effects of perspective. Since our returned
// point has the same component normal to the screen as the offset, it
// will have z = 0 after the rotation is applied, thus w = 1. So this is
// correct.
orig = orig.Plus(projRight.ScaledBy(p.x / scale)).Plus(
projUp. ScaledBy(p.y / scale));
return orig;
}
Vector GraphicsWindow::UnProjectPoint3(Vector p) {
p.z = p.z / (scale - p.z * SS.CameraTangent() * scale);
double w = 1 + p.z * SS.CameraTangent() * scale;
p.x *= w / scale;
p.y *= w / scale;
Vector orig = offset.ScaledBy(-1);
orig = orig.Plus(projRight.ScaledBy(p.x)).Plus(
projUp. ScaledBy(p.y).Plus(
projRight.Cross(projUp). ScaledBy(p.z)));
return orig;
}
void GraphicsWindow::NormalizeProjectionVectors() {
if(projRight.Magnitude() < LENGTH_EPS) {
projRight = Vector::From(1, 0, 0);
}
Vector norm = projRight.Cross(projUp);
// If projRight and projUp somehow ended up parallel, then pick an
// arbitrary projUp normal to projRight.
if(norm.Magnitude() < LENGTH_EPS) {
norm = projRight.Normal(0);
}
projUp = norm.Cross(projRight);
projUp = projUp.WithMagnitude(1);
projRight = projRight.WithMagnitude(1);
}
Vector GraphicsWindow::VectorFromProjs(Vector rightUpForward) {
Vector n = projRight.Cross(projUp);
Vector r = (projRight.ScaledBy(rightUpForward.x));
r = r.Plus(projUp.ScaledBy(rightUpForward.y));
r = r.Plus(n.ScaledBy(rightUpForward.z));
return r;
}
void GraphicsWindow::Paint() {
int i;
havePainted = true;
int w, h;
GetGraphicsWindowSize(&w, &h);
width = w; height = h;
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glScaled(scale*2.0/w, scale*2.0/h, scale*1.0/30000);
double mat[16];
// Last thing before display is to apply the perspective
double clp = SS.CameraTangent()*scale;
MakeMatrix(mat, 1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, clp, 1);
glMultMatrixd(mat);
// Before that, we apply the rotation
Vector n = projUp.Cross(projRight);
MakeMatrix(mat, projRight.x, projRight.y, projRight.z, 0,
projUp.x, projUp.y, projUp.z, 0,
n.x, n.y, n.z, 0,
0, 0, 0, 1);
glMultMatrixd(mat);
// And before that, the translation
MakeMatrix(mat, 1, 0, 0, offset.x,
0, 1, 0, offset.y,
0, 0, 1, offset.z,
0, 0, 0, 1);
glMultMatrixd(mat);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glShadeModel(GL_SMOOTH);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
glEnable(GL_LINE_SMOOTH);
// don't enable GL_POLYGON_SMOOTH; that looks ugly on some graphics cards,
// drawn with leaks in the mesh
glEnable(GL_POLYGON_OFFSET_LINE);
glEnable(GL_POLYGON_OFFSET_FILL);
glEnable(GL_DEPTH_TEST);
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
glEnable(GL_NORMALIZE);
// At the same depth, we want later lines drawn over earlier.
glDepthFunc(GL_LEQUAL);
if(SS.ActiveGroupsOkay()) {
glClearColor(SS.backgroundColor.redF(),
SS.backgroundColor.greenF(),
SS.backgroundColor.blueF(), 1.0f);
} else {
// Draw a different background whenever we're having solve problems.
RgbaColor rgb = Style::Color(Style::DRAW_ERROR);
glClearColor(0.4f*rgb.redF(), 0.4f*rgb.greenF(), 0.4f*rgb.blueF(), 1.0f);
// And show the text window, which has info to debug it
ForceTextWindowShown();
}
glClear(GL_COLOR_BUFFER_BIT);
glClearDepth(1.0);
glClear(GL_DEPTH_BUFFER_BIT);
if(!SS.bgImage.pixmap.IsEmpty()) {
double mmw = SS.bgImage.pixmap.width / SS.bgImage.scale,
mmh = SS.bgImage.pixmap.height / SS.bgImage.scale;
Vector origin = SS.bgImage.origin;
origin = origin.DotInToCsys(projRight, projUp, n);
// Place the depth of our origin at the point that corresponds to
// w = 1, so that it's unaffected by perspective.
origin.z = (offset.ScaledBy(-1)).Dot(n);
origin = origin.ScaleOutOfCsys(projRight, projUp, n);
// Place the background at the very back of the Z order, though, by
// mucking with the depth range.
glDepthRange(1, 1);
ssglDrawPixmap(SS.bgImage.pixmap,
origin,
origin.Plus(projUp.ScaledBy(mmh)),
origin.Plus(projRight.ScaledBy(mmw).Plus(
projUp. ScaledBy(mmh))),
origin.Plus(projRight.ScaledBy(mmw)));
}
ssglDepthRangeOffset(0);
// Nasty case when we're reloading the linked files; could be that
// we get an error, so a dialog pops up, and a message loop starts, and
// we have to get called to paint ourselves. If the sketch is screwed
// up, then we could trigger an oops trying to draw.
if(!SS.allConsistent) return;
// Let's use two lights, at the user-specified locations
GLfloat f;
glEnable(GL_LIGHT0);
f = (GLfloat)SS.lightIntensity[0];
GLfloat li0[] = { f, f, f, 1.0f };
glLightfv(GL_LIGHT0, GL_DIFFUSE, li0);
glLightfv(GL_LIGHT0, GL_SPECULAR, li0);
glEnable(GL_LIGHT1);
f = (GLfloat)SS.lightIntensity[1];
GLfloat li1[] = { f, f, f, 1.0f };
glLightfv(GL_LIGHT1, GL_DIFFUSE, li1);
glLightfv(GL_LIGHT1, GL_SPECULAR, li1);
Vector ld;
ld = VectorFromProjs(SS.lightDir[0]);
GLfloat ld0[4] = { (GLfloat)ld.x, (GLfloat)ld.y, (GLfloat)ld.z, 0 };
glLightfv(GL_LIGHT0, GL_POSITION, ld0);
ld = VectorFromProjs(SS.lightDir[1]);
GLfloat ld1[4] = { (GLfloat)ld.x, (GLfloat)ld.y, (GLfloat)ld.z, 0 };
glLightfv(GL_LIGHT1, GL_POSITION, ld1);
GLfloat ambient[4] = { (float)SS.ambientIntensity,
(float)SS.ambientIntensity,
(float)SS.ambientIntensity, 1 };
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);
ssglUnlockColor();
if(showSnapGrid && LockedInWorkplane()) {
hEntity he = ActiveWorkplane();
EntityBase *wrkpl = SK.GetEntity(he),
*norm = wrkpl->Normal();
Vector wu, wv, wn, wp;
wp = SK.GetEntity(wrkpl->point[0])->PointGetNum();
wu = norm->NormalU();
wv = norm->NormalV();
wn = norm->NormalN();
double g = SS.gridSpacing;
double umin = VERY_POSITIVE, umax = VERY_NEGATIVE,
vmin = VERY_POSITIVE, vmax = VERY_NEGATIVE;
int a;
for(a = 0; a < 4; a++) {
// Ideally, we would just do +/- half the width and height; but
// allow some extra slop for rounding.
Vector horiz = projRight.ScaledBy((0.6*width)/scale + 2*g),
vert = projUp. ScaledBy((0.6*height)/scale + 2*g);
if(a == 2 || a == 3) horiz = horiz.ScaledBy(-1);
if(a == 1 || a == 3) vert = vert. ScaledBy(-1);
Vector tp = horiz.Plus(vert).Minus(offset);
// Project the point into our grid plane, normal to the screen
// (not to the grid plane). If the plane is on edge then this is
// impossible so don't try to draw the grid.
bool parallel;
Vector tpp = Vector::AtIntersectionOfPlaneAndLine(
wn, wn.Dot(wp),
tp, tp.Plus(n),
&parallel);
if(parallel) goto nogrid;
tpp = tpp.Minus(wp);
double uu = tpp.Dot(wu),
vv = tpp.Dot(wv);
umin = min(uu, umin);
umax = max(uu, umax);
vmin = min(vv, vmin);
vmax = max(vv, vmax);
}
int i, j, i0, i1, j0, j1;
i0 = (int)(umin / g);
i1 = (int)(umax / g);
j0 = (int)(vmin / g);
j1 = (int)(vmax / g);
if(i0 > i1 || i1 - i0 > 400) goto nogrid;
if(j0 > j1 || j1 - j0 > 400) goto nogrid;
ssglLineWidth(1);
ssglColorRGBa(Style::Color(Style::DATUM), 0.3);
glBegin(GL_LINES);
for(i = i0 + 1; i < i1; i++) {
ssglVertex3v(wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j0*g)));
ssglVertex3v(wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j1*g)));
}
for(j = j0 + 1; j < j1; j++) {
ssglVertex3v(wp.Plus(wu.ScaledBy(i0*g)).Plus(wv.ScaledBy(j*g)));
ssglVertex3v(wp.Plus(wu.ScaledBy(i1*g)).Plus(wv.ScaledBy(j*g)));
}
glEnd();
// Clear the depth buffer, so that the grid is at the very back of
// the Z order.
glClear(GL_DEPTH_BUFFER_BIT);
nogrid:;
}
// Draw the active group; this does stuff like the mesh and edges.
(SK.GetGroup(activeGroup))->Draw();
// Now draw the entities.
if(SS.GW.showHdnLines) {
ssglDepthRangeOffset(2);
glDepthFunc(GL_GREATER);
Entity::DrawAll(/*drawAsHidden=*/true);
glDepthFunc(GL_LEQUAL);
}
ssglDepthRangeOffset(0);
Entity::DrawAll(/*drawAsHidden=*/false);
// Draw filled paths in all groups, when those filled paths were requested
// specially by assigning a style with a fill color, or when the filled
// paths are just being filled by default. This should go last, to make
// the transparency work.
for(i = 0; i < SK.groupOrder.n; i++) {
Group *g = SK.GetGroup(SK.groupOrder.elem[i]);
if(!(g->IsVisible())) continue;
g->DrawFilledPaths();
}
glDisable(GL_DEPTH_TEST);
// Draw the constraints
for(i = 0; i < SK.constraint.n; i++) {
SK.constraint.elem[i].Draw();
}
// Draw the "pending" constraint, i.e. a constraint that would be
// placed on a line that is almost horizontal or vertical
if(SS.GW.pending.operation == Pending::DRAGGING_NEW_LINE_POINT &&
SS.GW.pending.hasSuggestion) {
Constraint c = {};
c.group = SS.GW.activeGroup;
c.workplane = SS.GW.ActiveWorkplane();
c.type = SS.GW.pending.suggestion;
c.ptA = Entity::NO_ENTITY;
c.ptB = Entity::NO_ENTITY;
c.entityA = SS.GW.pending.request.entity(0);
c.entityB = Entity::NO_ENTITY;
c.other = false;
c.other2 = false;
// Only draw.
c.Draw();
}
// Draw the traced path, if one exists
ssglLineWidth(Style::Width(Style::ANALYZE));
ssglColorRGB(Style::Color(Style::ANALYZE));
SContour *sc = &(SS.traced.path);
glBegin(GL_LINE_STRIP);
for(i = 0; i < sc->l.n; i++) {
ssglVertex3v(sc->l.elem[i].p);
}
glEnd();
// And the naked edges, if the user did Analyze -> Show Naked Edges.
ssglDrawEdges(&(SS.nakedEdges), /*endpointsToo=*/true, { Style::DRAW_ERROR });
// Then redraw whatever the mouse is hovering over, highlighted.
glDisable(GL_DEPTH_TEST);
ssglLockColorTo(Style::Color(Style::HOVERED));
hover.Draw();
// And finally draw the selection, same mechanism.
ssglLockColorTo(Style::Color(Style::SELECTED));
for(Selection *s = selection.First(); s; s = selection.NextAfter(s)) {
s->Draw();
}
ssglUnlockColor();
// If a marquee selection is in progress, then draw the selection
// rectangle, as an outline and a transparent fill.
if(pending.operation == Pending::DRAGGING_MARQUEE) {
Point2d begin = ProjectPoint(orig.marqueePoint);
double xmin = min(orig.mouse.x, begin.x),
xmax = max(orig.mouse.x, begin.x),
ymin = min(orig.mouse.y, begin.y),
ymax = max(orig.mouse.y, begin.y);
Vector tl = UnProjectPoint(Point2d::From(xmin, ymin)),
tr = UnProjectPoint(Point2d::From(xmax, ymin)),
br = UnProjectPoint(Point2d::From(xmax, ymax)),
bl = UnProjectPoint(Point2d::From(xmin, ymax));
ssglLineWidth((GLfloat)1.3);
ssglColorRGB(Style::Color(Style::HOVERED));
glBegin(GL_LINE_LOOP);
ssglVertex3v(tl);
ssglVertex3v(tr);
ssglVertex3v(br);
ssglVertex3v(bl);
glEnd();
ssglColorRGBa(Style::Color(Style::HOVERED), 0.10);
glBegin(GL_QUADS);
ssglVertex3v(tl);
ssglVertex3v(tr);
ssglVertex3v(br);
ssglVertex3v(bl);
glEnd();
}
// An extra line, used to indicate the origin when rotating within the
// plane of the monitor.
if(SS.extraLine.draw) {
ssglLineWidth(1);
ssglLockColorTo(Style::Color(Style::DATUM));
glBegin(GL_LINES);
ssglVertex3v(SS.extraLine.ptA);
ssglVertex3v(SS.extraLine.ptB);
glEnd();
}
// A note to indicate the origin in the just-exported file.
if(SS.justExportedInfo.draw) {
Vector p, u, v;
if(SS.justExportedInfo.showOrigin) {
p = SS.justExportedInfo.pt,
u = SS.justExportedInfo.u,
v = SS.justExportedInfo.v;
} else {
p = SS.GW.offset.ScaledBy(-1);
u = SS.GW.projRight;
v = SS.GW.projUp;
}
ssglColorRGB(Style::Color(Style::DATUM));
ssglWriteText("previewing exported geometry; press Esc to return",
Style::DefaultTextHeight(),
p.Plus(u.ScaledBy(10/scale)).Plus(v.ScaledBy(10/scale)),
u, v, NULL, NULL);
if(SS.justExportedInfo.showOrigin) {
ssglLineWidth(1.5);
glBegin(GL_LINES);
ssglVertex3v(p.Plus(u.WithMagnitude(-15/scale)));
ssglVertex3v(p.Plus(u.WithMagnitude(30/scale)));
ssglVertex3v(p.Plus(v.WithMagnitude(-15/scale)));
ssglVertex3v(p.Plus(v.WithMagnitude(30/scale)));
glEnd();
ssglWriteText("(x, y) = (0, 0) for file just exported",
Style::DefaultTextHeight(),
p.Plus(u.ScaledBy(40/scale)).Plus(
v.ScaledBy(-(Style::DefaultTextHeight())/scale)),
u, v, NULL, NULL);
}
}
// And finally the toolbar.
if(SS.showToolbar) {
ToolbarDraw();
}
}