Exemple #1
0
void GraphicsWindow::Selection::Draw(void) {
    Vector refp;
    if(entity.v) {
        Entity *e = SK.GetEntity(entity);
        e->Draw();
        if(emphasized) refp = e->GetReferencePos();
    }
    if(constraint.v) {
        Constraint *c = SK.GetConstraint(constraint);
        c->Draw();
        if(emphasized) refp = c->GetReferencePos();
    }
    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
        // 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);

        glLineWidth(40);
        DWORD rgb = Style::Color(Style::HOVERED);
        glColor4d(REDf(rgb), GREENf(rgb), BLUEf(rgb), 0.2);
        glBegin(GL_LINES);
            glxVertex3v(topLeft);
            glxVertex3v(refp);
        glEnd();
        glLineWidth(1);
    }
}
void TextWindow::MakeColorTable(const Color *in, float *out) {
    int i;
    for(i = 0; in[i].c != 0; i++) {
        int c = in[i].c;
        if(c < 0 || c > 255) oops();
        out[c*3 + 0] = REDf(in[i].color);
        out[c*3 + 1] = GREENf(in[i].color);
        out[c*3 + 2] = BLUEf(in[i].color);
    }
}
void TextWindow::ShowEditControlWithColorPicker(int halfRow, int col, DWORD rgb)
{
    char str[1024];
    sprintf(str, "%.2f, %.2f, %.2f", REDf(rgb), GREENf(rgb), BLUEf(rgb));

    SS.later.showTW = true;

    editControl.colorPicker.show = true;
    editControl.colorPicker.rgb = rgb;
    editControl.colorPicker.h = 0;
    editControl.colorPicker.s = 0;
    editControl.colorPicker.v = 1;
    ShowEditControl(halfRow, col, str);
}
Exemple #4
0
void TextWindow::ShowGroupInfo(void) {
    Group *pg, *g = SK.group.FindById(shown.group);
    const char *s = "???";

    if(shown.group.v == Group::HGROUP_REFERENCES.v) {
        Printf(true, "%FtGROUP  %E%s", g->DescriptionString());
        goto list_items;
    } else {
        Printf(true, "%FtGROUP  %E%s [%Fl%Ll%D%frename%E/%Fl%Ll%D%fdel%E]",
            g->DescriptionString(),
            g->h.v, &TextWindow::ScreenChangeGroupName,
            g->h.v, &TextWindow::ScreenDeleteGroup);
    }

    if(g->type == Group::LATHE) {
        Printf(true, " %Ftlathe plane sketch");
    } else if(g->type == Group::EXTRUDE || g->type == Group::ROTATE ||
              g->type == Group::TRANSLATE)
    {
        if(g->type == Group::EXTRUDE) {
            s = "extrude plane sketch";
        } else if(g->type == Group::TRANSLATE) {
            s = "translate original sketch";
        } else if(g->type == Group::ROTATE) {
            s = "rotate original sketch";
        }
        Printf(true, " %Ft%s%E", s);

        bool one = (g->subtype == Group::ONE_SIDED);
        Printf(false,
            "%Ba   %f%Ls%Fd%c one-sided%E  "
                  "%f%LS%Fd%c two-sided%E",
            &TextWindow::ScreenChangeGroupOption,
            one ? RADIO_TRUE : RADIO_FALSE,
            &TextWindow::ScreenChangeGroupOption,
            !one ? RADIO_TRUE : RADIO_FALSE);

        if(g->type == Group::ROTATE || g->type == Group::TRANSLATE) {
            if(g->subtype == Group::ONE_SIDED) {
                bool skip = g->skipFirst;
                Printf(false, 
                   "%Bd   %Ftstart  %f%LK%Fd%c with original%E  "
                         "%f%Lk%Fd%c with copy #1%E",
                    &ScreenChangeGroupOption,
                    !skip ? RADIO_TRUE : RADIO_FALSE,
                    &ScreenChangeGroupOption,
                    skip ? RADIO_TRUE : RADIO_FALSE);
            }

            int times = (int)(g->valA);
            Printf(false, "%Bp   %Ftrepeat%E %d time%s %Fl%Ll%D%f[change]%E",
                (g->subtype == Group::ONE_SIDED) ? 'a' : 'd',
                times, times == 1 ? "" : "s",
                g->h.v, &TextWindow::ScreenChangeExprA);
        }
    } else if(g->type == Group::IMPORTED) {
        Printf(true, " %Ftimport geometry from file%E");
        Printf(false, "%Ba   '%s'", g->impFileRel.c_str());
        Printf(false, "%Bd   %Ftscaled by%E %# %Fl%Ll%f%D[change]%E",
            g->scale,
            &TextWindow::ScreenChangeGroupScale, g->h.v);
    } else if(g->type == Group::DRAWING_3D) {
        Printf(true, " %Ftsketch in 3d%E");
    } else if(g->type == Group::DRAWING_WORKPLANE) {
        Printf(true, " %Ftsketch in new workplane%E");
    } else {
        Printf(true, "???");
    }
    Printf(false, "");

    if(g->type == Group::EXTRUDE ||
       g->type == Group::LATHE ||
       g->type == Group::IMPORTED)
    {
        bool un   = (g->meshCombine == Group::COMBINE_AS_UNION);
        bool diff = (g->meshCombine == Group::COMBINE_AS_DIFFERENCE);
        bool asy  = (g->meshCombine == Group::COMBINE_AS_ASSEMBLE);
        bool asa  = (g->type == Group::IMPORTED);

        Printf(false, " %Ftsolid model as");
        Printf(false, "%Ba   %f%D%Lc%Fd%c union%E  "
                             "%f%D%Lc%Fd%c difference%E  "
                             "%f%D%Lc%Fd%c%s%E  ",
            &TextWindow::ScreenChangeGroupOption,
            Group::COMBINE_AS_UNION,
            un ? RADIO_TRUE : RADIO_FALSE,
            &TextWindow::ScreenChangeGroupOption,
            Group::COMBINE_AS_DIFFERENCE,
            diff ? RADIO_TRUE : RADIO_FALSE,
            &TextWindow::ScreenChangeGroupOption,
            Group::COMBINE_AS_ASSEMBLE,
            asa ? (asy ? RADIO_TRUE : RADIO_FALSE) : 0,
            asa ? " assemble" : "");

        if(g->type == Group::EXTRUDE ||
           g->type == Group::LATHE)
        {
            Printf(false,
                "%Bd   %Ftcolor %E%Bp  %Bd (%@, %@, %@) %f%D%Lf%Fl[change]%E",
                0x80000000 | g->color,
                REDf(g->color), GREENf(g->color), BLUEf(g->color),
                ScreenColor, top[rows-1] + 2);
        } else if(g->type == Group::IMPORTED) {
            Printf(false, "   %Fd%f%LP%c  suppress this group's solid model",
                &TextWindow::ScreenChangeGroupOption,
                g->suppress ? CHECK_TRUE : CHECK_FALSE);
        }

        Printf(false, "");
    }

    Printf(false, " %f%Lv%Fd%c  show entities from this group",
        &TextWindow::ScreenChangeGroupOption,
        g->visible ? CHECK_TRUE : CHECK_FALSE);

    pg = g->PreviousGroup();
    if(pg && pg->runningMesh.IsEmpty() && g->thisMesh.IsEmpty()) {
        Printf(false, " %f%Lf%Fd%c  force NURBS surfaces to triangle mesh",
            &TextWindow::ScreenChangeGroupOption,
            g->forceToMesh ? CHECK_TRUE : CHECK_FALSE);
    } else {
        Printf(false, " (model already forced to triangle mesh)");
    }

    Printf(true, " %f%Lr%Fd%c  relax constraints and dimensions",
        &TextWindow::ScreenChangeGroupOption,
        g->relaxConstraints ? CHECK_TRUE : CHECK_FALSE);

    Printf(false, " %f%Ld%Fd%c  treat all dimensions as reference",
        &TextWindow::ScreenChangeGroupOption,
        g->allDimsReference ? CHECK_TRUE : CHECK_FALSE);

    if(g->booleanFailed) {
        Printf(false, "");
        Printf(false, "The Boolean operation failed. It may be ");
        Printf(false, "possible to fix the problem by choosing ");
        Printf(false, "'force NURBS surfaces to triangle mesh'.");
    }

list_items:
    Printf(false, "");
    Printf(false, "%Ft requests in group");

    int i, a = 0;
    for(i = 0; i < SK.request.n; i++) {
        Request *r = &(SK.request.elem[i]);

        if(r->group.v == shown.group.v) {
            char *s = r->DescriptionString();
            Printf(false, "%Bp   %Fl%Ll%D%f%h%s%E",
                (a & 1) ? 'd' : 'a',
                r->h.v, (&TextWindow::ScreenSelectRequest),
                &(TextWindow::ScreenHoverRequest), s);
            a++;
        }
    }
    if(a == 0) Printf(false, "%Ba   (none)");

    a = 0;
    Printf(false, "");
    Printf(false, "%Ft constraints in group (%d DOF)", g->solved.dof);
    for(i = 0; i < SK.constraint.n; i++) {
        Constraint *c = &(SK.constraint.elem[i]);

        if(c->group.v == shown.group.v) {
            char *s = c->DescriptionString();
            Printf(false, "%Bp   %Fl%Ll%D%f%h%s%E %s",
                (a & 1) ? 'd' : 'a',
                c->h.v, (&TextWindow::ScreenSelectConstraint),
                (&TextWindow::ScreenHoverConstraint), s,
                c->reference ? "(ref)" : "");
            a++;
        }
    }
    if(a == 0) Printf(false, "%Ba   (none)");
}
void SolveSpace::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *sm,
                                    Vector u, Vector v, Vector n,
                                        Vector origin, double cameraTan,
                                    VectorFileWriter *out)
{
    double s = 1.0 / SS.exportScale;

    // Project into the export plane; so when we're done, z doesn't matter,
    // and x and y are what goes in the DXF.
    SEdge *e;
    for(e = sel->l.First(); e; e = sel->l.NextAfter(e)) {
        // project into the specified csys, and apply export scale
        (e->a) = e->a.InPerspective(u, v, n, origin, cameraTan).ScaledBy(s);
        (e->b) = e->b.InPerspective(u, v, n, origin, cameraTan).ScaledBy(s);
    }

    SBezier *b;
    if(sbl) {
        for(b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) {
            *b = b->InPerspective(u, v, n, origin, cameraTan);
            int i;
            for(i = 0; i <= b->deg; i++) {
                b->ctrl[i] = (b->ctrl[i]).ScaledBy(s);
            }
        }
    }

    // If cutter radius compensation is requested, then perform it now
    if(fabs(SS.exportOffset) > LENGTH_EPS) {
        // assemble those edges into a polygon, and clear the edge list
        SPolygon sp;
        ZERO(&sp);
        sel->AssemblePolygon(&sp, NULL);
        sel->Clear();

        SPolygon compd;
        ZERO(&compd);
        sp.normal = Vector::From(0, 0, -1);
        sp.FixContourDirections();
        sp.OffsetInto(&compd, SS.exportOffset*s);
        sp.Clear();

        compd.MakeEdgesInto(sel);
        compd.Clear();
    }

    // Now the triangle mesh; project, then build a BSP to perform
    // occlusion testing and generated the shaded surfaces.
    SMesh smp;
    ZERO(&smp);
    if(sm) {
        Vector l0 = (SS.lightDir[0]).WithMagnitude(1),
               l1 = (SS.lightDir[1]).WithMagnitude(1);
        STriangle *tr;
        for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) {
            STriangle tt = *tr;
            tt.a = (tt.a).InPerspective(u, v, n, origin, cameraTan).ScaledBy(s);
            tt.b = (tt.b).InPerspective(u, v, n, origin, cameraTan).ScaledBy(s);
            tt.c = (tt.c).InPerspective(u, v, n, origin, cameraTan).ScaledBy(s);

            // And calculate lighting for the triangle
            Vector n = tt.Normal().WithMagnitude(1);
            double lighting = SS.ambientIntensity +
                                  max(0, (SS.lightIntensity[0])*(n.Dot(l0))) +
                                  max(0, (SS.lightIntensity[1])*(n.Dot(l1)));
            double r = min(1, REDf  (tt.meta.color)*lighting),
                   g = min(1, GREENf(tt.meta.color)*lighting),
                   b = min(1, BLUEf (tt.meta.color)*lighting);
            tt.meta.color = RGBf(r, g, b);
            smp.AddTriangle(&tt);
        }
    }

    // Use the BSP routines to generate the split triangles in paint order.
    SBsp3 *bsp = SBsp3::FromMesh(&smp);
    SMesh sms;
    ZERO(&sms);
    bsp->GenerateInPaintOrder(&sms);
    // And cull the back-facing triangles
    STriangle *tr;
    sms.l.ClearTags();
    for(tr = sms.l.First(); tr; tr = sms.l.NextAfter(tr)) {
        Vector n = tr->Normal();
        if(n.z < 0) {
            tr->tag = 1;
        }
    }
    sms.l.RemoveTagged();

    // And now we perform hidden line removal if requested
    SEdgeList hlrd;
    ZERO(&hlrd);
    if(sm && !SS.GW.showHdnLines) {
        SKdNode *root = SKdNode::From(&smp);

        // Generate the edges where a curved surface turns from front-facing
        // to back-facing.
        if(SS.GW.showEdges) {
            root->MakeCertainEdgesInto(sel, SKdNode::TURNING_EDGES,
                        false, NULL, NULL);
        }

        root->ClearTags();
        int cnt = 1234;

        SEdge *se;
        for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) {
            if(se->auxA == Style::CONSTRAINT) {
                // Constraints should not get hidden line removed; they're
                // always on top.
                hlrd.AddEdge(se->a, se->b, se->auxA);
                continue;
            }

            SEdgeList out;
            ZERO(&out);
            // Split the original edge against the mesh
            out.AddEdge(se->a, se->b, se->auxA);
            root->OcclusionTestLine(*se, &out, cnt);
            // the occlusion test splits unnecessarily; so fix those
            out.MergeCollinearSegments(se->a, se->b);
            cnt++;
            // And add the results to our output
            SEdge *sen;
            for(sen = out.l.First(); sen; sen = out.l.NextAfter(sen)) {
                hlrd.AddEdge(sen->a, sen->b, sen->auxA);
            }
            out.Clear();
        }

        sel = &hlrd;
    }

    // We kept the line segments and Beziers separate until now; but put them
    // all together, and also project everything into the xy plane, since not
    // all export targets ignore the z component of the points.
    for(e = sel->l.First(); e; e = sel->l.NextAfter(e)) {
        SBezier sb = SBezier::From(e->a, e->b);
        sb.auxA = e->auxA;
        sbl->l.Add(&sb);
    }
    for(b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) {
        for(int i = 0; i <= b->deg; i++) {
            b->ctrl[i].z = 0;
        }
    }

    // If possible, then we will assemble these output curves into loops. They
    // will then get exported as closed paths.
    SBezierLoopSetSet sblss;
    ZERO(&sblss);
    SBezierList leftovers;
    ZERO(&leftovers);
    SSurface srf = SSurface::FromPlane(Vector::From(0, 0, 0),
                                       Vector::From(1, 0, 0),
                                       Vector::From(0, 1, 0));
    SPolygon spxyz;
    ZERO(&spxyz);
    bool allClosed;
    SEdge notClosedAt;
    sbl->l.ClearTags();
    sblss.FindOuterFacesFrom(sbl, &spxyz, &srf,
                             SS.ChordTolMm()*s,
                             &allClosed, &notClosedAt,
                             NULL, NULL,
                             &leftovers);
    for(b = leftovers.l.First(); b; b = leftovers.l.NextAfter(b)) {
        sblss.AddOpenPath(b);
    }

    // Now write the lines and triangles to the output file
    out->Output(&sblss, &sms);

    leftovers.Clear();
    spxyz.Clear();
    sblss.Clear();
    smp.Clear();
    sms.Clear();
    hlrd.Clear();
}
Exemple #6
0
void GraphicsWindow::Paint(void) {
    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.AllGroupsOkay()) {
        glClearColor(REDf(SS.backgroundColor),
                     GREENf(SS.backgroundColor),
                     BLUEf(SS.backgroundColor), 1.0f);
    } else {
        // Draw a different background whenever we're having solve problems.
        DWORD rgb = Style::Color(Style::DRAW_ERROR);
        glClearColor(0.4f*REDf(rgb), 0.4f*GREENf(rgb), 0.4f*BLUEf(rgb), 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.fromFile) {
        // If a background image is loaded, then we draw it now as a texture.
        // This handles the resizing for us nicely.
        glBindTexture(GL_TEXTURE_2D, TEXTURE_BACKGROUND_IMG);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,     GL_CLAMP);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,     GL_CLAMP);
        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
                     SS.bgImage.rw, SS.bgImage.rh,
                     0,
                     GL_RGB, GL_UNSIGNED_BYTE,
                     SS.bgImage.fromFile);

        double tw = ((double)SS.bgImage.w) / SS.bgImage.rw,
               th = ((double)SS.bgImage.h) / SS.bgImage.rh;

        double mmw = SS.bgImage.w / SS.bgImage.scale,
               mmh = SS.bgImage.h / 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);
        glEnable(GL_TEXTURE_2D);
        glBegin(GL_QUADS);
            glTexCoord2d(0, 0);
            glxVertex3v(origin);

            glTexCoord2d(0, th);
            glxVertex3v(origin.Plus(projUp.ScaledBy(mmh)));

            glTexCoord2d(tw, th);
            glxVertex3v(origin.Plus(projRight.ScaledBy(mmw).Plus(
                                    projUp.   ScaledBy(mmh))));

            glTexCoord2d(tw, 0);
            glxVertex3v(origin.Plus(projRight.ScaledBy(mmw)));
        glEnd();
        glDisable(GL_TEXTURE_2D);
    }
    glxDepthRangeOffset(0);

    // Nasty case when we're reloading the imported 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);

    if(SS.drawBackFaces) {
        // For debugging, draw the backs of the triangles in red, so that we
        // notice when a shell is open
        glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, 1);
    } else {
        glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, 0);
    }

    GLfloat ambient[4] = { (float)SS.ambientIntensity,
                           (float)SS.ambientIntensity,
                           (float)SS.ambientIntensity, 1 };
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);

    glxUnlockColor();

    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;

        glLineWidth(1);
        glxColorRGBa(Style::Color(Style::DATUM), 0.3);
        glBegin(GL_LINES);
        for(i = i0 + 1; i < i1; i++) {
            glxVertex3v(wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j0*g)));
            glxVertex3v(wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j1*g)));
        }
        for(j = j0 + 1; j < j1; j++) {
            glxVertex3v(wp.Plus(wu.ScaledBy(i0*g)).Plus(wv.ScaledBy(j*g)));
            glxVertex3v(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(showHdnLines) glDisable(GL_DEPTH_TEST);
    Entity::DrawAll();

    // 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.
    Group *g;
    for(g = SK.group.First(); g; g = SK.group.NextAfter(g)) {
        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 traced path, if one exists
    glLineWidth(Style::Width(Style::ANALYZE));
    glxColorRGB(Style::Color(Style::ANALYZE));
    SContour *sc = &(SS.traced.path);
    glBegin(GL_LINE_STRIP);
    for(i = 0; i < sc->l.n; i++) {
        glxVertex3v(sc->l.elem[i].p);
    }
    glEnd();

    // And the naked edges, if the user did Analyze -> Show Naked Edges.
    glLineWidth(Style::Width(Style::DRAW_ERROR));
    glxColorRGB(Style::Color(Style::DRAW_ERROR));
    glxDrawEdges(&(SS.nakedEdges), true);

    // Then redraw whatever the mouse is hovering over, highlighted.
    glDisable(GL_DEPTH_TEST); 
    glxLockColorTo(Style::Color(Style::HOVERED));
    hover.Draw();

    // And finally draw the selection, same mechanism.
    glxLockColorTo(Style::Color(Style::SELECTED));
    for(Selection *s = selection.First(); s; s = selection.NextAfter(s)) {
        s->Draw();
    }

    glxUnlockColor();

    // If a marquee selection is in progress, then draw the selection
    // rectangle, as an outline and a transparent fill.
    if(pending.operation == 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));

        glLineWidth((GLfloat)1.3);
        glxColorRGB(Style::Color(Style::HOVERED));
        glBegin(GL_LINE_LOOP);
            glxVertex3v(tl);
            glxVertex3v(tr);
            glxVertex3v(br);
            glxVertex3v(bl);
        glEnd();
        glxColorRGBa(Style::Color(Style::HOVERED), 0.10);
        glBegin(GL_QUADS);
            glxVertex3v(tl);
            glxVertex3v(tr);
            glxVertex3v(br);
            glxVertex3v(bl);
        glEnd();
    }

    // An extra line, used to indicate the origin when rotating within the
    // plane of the monitor.
    if(SS.extraLine.draw) {
        glLineWidth(1);
        glxLockColorTo(Style::Color(Style::DATUM));
        glBegin(GL_LINES);
            glxVertex3v(SS.extraLine.ptA);
            glxVertex3v(SS.extraLine.ptB);
        glEnd();
    }

    // A note to indicate the origin in the just-exported file.
    if(SS.justExportedInfo.draw) {
        glxColorRGB(Style::Color(Style::DATUM));
        Vector p = SS.justExportedInfo.pt,
               u = SS.justExportedInfo.u,
               v = SS.justExportedInfo.v;

        glLineWidth(1.5);
        glBegin(GL_LINES);
            glxVertex3v(p.Plus(u.WithMagnitude(-15/scale)));
            glxVertex3v(p.Plus(u.WithMagnitude(30/scale)));
            glxVertex3v(p.Plus(v.WithMagnitude(-15/scale)));
            glxVertex3v(p.Plus(v.WithMagnitude(30/scale)));
        glEnd();

        glxWriteText("(x, y) = (0, 0) for file just exported",
            DEFAULT_TEXT_HEIGHT,
            p.Plus(u.ScaledBy(10/scale)).Plus(v.ScaledBy(10/scale)), 
            u, v, NULL, NULL);
        glxWriteText("press Esc to clear this message",
            DEFAULT_TEXT_HEIGHT,
            p.Plus(u.ScaledBy(40/scale)).Plus(
                   v.ScaledBy(-(DEFAULT_TEXT_HEIGHT)/scale)), 
            u, v, NULL, NULL);
    }

    // And finally the toolbar.
    if(SS.showToolbar) {
        ToolbarDraw();
    }
}