source: git/src/gla-gl.cc @ 0b99107

main
Last change on this file since 0b99107 was 0b99107, checked in by Olly Betts <olly@…>, 7 weeks ago

Eliminate old FSF addresses

Update GPL/LGPL licence files and boilerplate to direct people who
didn't receive the licence text to the FSF website, as the current
versions of the FSF licence texts now do, rather than giving a postal
address.

  • Property mode set to 100644
File size: 49.4 KB
Line 
1//
2//  gla-gl.cc
3//
4//  OpenGL implementation for the GLA abstraction layer.
5//
6//  Copyright (C) 2002-2003,2005 Mark R. Shinwell
7//  Copyright (C) 2003-2025 Olly Betts
8//
9//  This program is free software; you can redistribute it and/or modify
10//  it under the terms of the GNU General Public License as published by
11//  the Free Software Foundation; either version 2 of the License, or
12//  (at your option) any later version.
13//
14//  This program is distributed in the hope that it will be useful,
15//  but WITHOUT ANY WARRANTY; without even the implied warranty of
16//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17//  GNU General Public License for more details.
18//
19//  You should have received a copy of the GNU General Public License
20//  along with this program; if not, see
21//  <https://www.gnu.org/licenses/>.
22//
23
24#include <config.h>
25
26#include <wx/confbase.h>
27
28#include <algorithm>
29
30#include "aven.h"
31#include "gla.h"
32#include "gllogerror.h"
33#include "message.h"
34#include "useful.h"
35
36#ifdef HAVE_GL_GL_H
37# include <GL/gl.h>
38#elif defined HAVE_OPENGL_GL_H
39# include <OpenGL/gl.h>
40#endif
41
42#ifdef HAVE_GL_GLU_H
43# include <GL/glu.h>
44#elif defined HAVE_OPENGL_GLU_H
45# include <OpenGL/glu.h>
46#endif
47
48#ifdef HAVE_GL_GLEXT_H
49# include <GL/glext.h>
50#elif defined HAVE_OPENGL_GLEXT_H
51# include <OpenGL/glext.h>
52#endif
53
54#ifndef GL_POINT_SIZE_MAX
55#define GL_POINT_SIZE_MAX 0x8127
56#endif
57#ifndef GL_POINT_SPRITE
58#define GL_POINT_SPRITE 0x8861
59#endif
60#ifndef GL_COORD_REPLACE
61#define GL_COORD_REPLACE 0x8862
62#endif
63// GL_POINT_SIZE_RANGE is deprecated in OpenGL 1.2 and later, and replaced by
64// GL_SMOOTH_POINT_SIZE_RANGE.
65#ifndef GL_SMOOTH_POINT_SIZE_RANGE
66#define GL_SMOOTH_POINT_SIZE_RANGE GL_POINT_SIZE_RANGE
67#endif
68// GL_POINT_SIZE_GRANULARITY is deprecated in OpenGL 1.2 and later, and
69// replaced by GL_SMOOTH_POINT_SIZE_GRANULARITY.
70#ifndef GL_SMOOTH_POINT_SIZE_GRANULARITY
71#define GL_SMOOTH_POINT_SIZE_GRANULARITY GL_POINT_SIZE_GRANULARITY
72#endif
73// GL_ALIASED_POINT_SIZE_RANGE was added in OpenGL 1.2.
74#ifndef GL_ALIASED_POINT_SIZE_RANGE
75#define GL_ALIASED_POINT_SIZE_RANGE 0x846D
76#endif
77
78using namespace std;
79
80const int BLOB_DIAMETER = 5;
81
82#define BLOB_TEXTURE \
83            o, o, o, o, o, o, o, o,\
84            o, o, o, o, o, o, o, o,\
85            o, o, I, I, I, o, o, o,\
86            o, I, I, I, I, I, o, o,\
87            o, I, I, I, I, I, o, o,\
88            o, I, I, I, I, I, o, o,\
89            o, o, I, I, I, o, o, o,\
90            o, o, o, o, o, o, o, o
91
92#define CROSS_TEXTURE \
93            o, o, o, o, o, o, o, o,\
94            I, o, o, o, o, o, I, o,\
95            o, I, o, o, o, I, o, o,\
96            o, o, I, o, I, o, o, o,\
97            o, o, o, I, o, o, o, o,\
98            o, o, I, o, I, o, o, o,\
99            o, I, o, o, o, I, o, o,\
100            I, o, o, o, o, o, I, o
101
102// Declared in gllogerror.h.
103bool opengl_initialised = false;
104
105static bool double_buffered = false;
106
107static const int* wx_gl_attribs = NULL;
108
109bool
110GLACanvas::check_visual()
111{
112    static const int wx_gl_attribs_full[] = {
113        WX_GL_DOUBLEBUFFER,
114        WX_GL_RGBA,
115        WX_GL_DEPTH_SIZE, 16,
116        0
117    };
118
119    // Use a double-buffered visual if available, as it will give much smoother
120    // animation.
121    double_buffered = true;
122    wx_gl_attribs = wx_gl_attribs_full;
123    if (!IsDisplaySupported(wx_gl_attribs)) {
124        ++wx_gl_attribs;
125        if (!IsDisplaySupported(wx_gl_attribs)) {
126            return false;
127        }
128        double_buffered = false;
129    }
130    return true;
131}
132
133string GetGLSystemDescription()
134{
135    // If OpenGL isn't initialised we may get a SEGV from glGetString.
136    if (!opengl_initialised)
137        return "No OpenGL information available yet - try opening a file.";
138    const char *p = (const char*)glGetString(GL_VERSION);
139    if (!p)
140        return "Couldn't read OpenGL version!";
141
142    string info;
143    info += "OpenGL ";
144    info += p;
145    info += '\n';
146    info += (const char*)glGetString(GL_VENDOR);
147    info += '\n';
148    info += (const char*)glGetString(GL_RENDERER);
149#if !(wxUSE_GLCANVAS_EGL-0) && \
150    (defined __WXGTK__ || defined __WXX11__ || defined __WXMOTIF__)
151    info += string_format("\nGLX %0.1f\n", wxGLCanvas::GetGLXVersion() * 0.1);
152#else
153    info += '\n';
154#endif
155
156    GLint red, green, blue;
157    glGetIntegerv(GL_RED_BITS, &red);
158    glGetIntegerv(GL_GREEN_BITS, &green);
159    glGetIntegerv(GL_BLUE_BITS, &blue);
160    GLint max_texture_size;
161    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
162    GLint max_viewport[2];
163    glGetIntegerv(GL_MAX_VIEWPORT_DIMS, max_viewport);
164    GLdouble point_size_range[2];
165    glGetDoublev(GL_SMOOTH_POINT_SIZE_RANGE, point_size_range);
166    GLdouble point_size_granularity;
167    glGetDoublev(GL_SMOOTH_POINT_SIZE_GRANULARITY, &point_size_granularity);
168    info += string_format("R%dG%dB%d\n"
169             "Max Texture size: %dx%d\n"
170             "Max Viewport size: %dx%d\n"
171             "Smooth Point Size %.3f-%.3f (granularity %.3f)",
172             (int)red, (int)green, (int)blue,
173             (int)max_texture_size, (int)max_texture_size,
174             (int)max_viewport[0], (int)max_viewport[1],
175             point_size_range[0], point_size_range[1],
176             point_size_granularity);
177    glGetDoublev(GL_ALIASED_POINT_SIZE_RANGE, point_size_range);
178    if (glGetError() != GL_INVALID_ENUM) {
179        info += string_format("\nAliased point size %.3f-%.3f",
180                              point_size_range[0], point_size_range[1]);
181    }
182
183    info += "\nDouble buffered: ";
184    if (double_buffered)
185        info += "true";
186    else
187        info += "false";
188
189    const GLubyte* gl_extensions = glGetString(GL_EXTENSIONS);
190    if (*gl_extensions) {
191        info += '\n';
192        info += (const char*)gl_extensions;
193    }
194    return info;
195}
196
197static bool
198glpoint_sprite_works()
199{
200    // Point sprites provide an easy, fast way for us to draw crosses by
201    // texture mapping GL points.
202    //
203    // If we have OpenGL >= 2.0 then we definitely have GL_POINT_SPRITE.
204    // Otherwise see if we have the GL_ARB_point_sprite or GL_NV_point_sprite
205    // extensions.
206    //
207    // The symbolic constants GL_POINT_SPRITE, GL_POINT_SPRITE_ARB, and
208    // GL_POINT_SPRITE_NV all give the same number so it doesn't matter
209    // which we use.
210    static bool glpoint_sprite = false;
211    static bool checked = false;
212    if (!checked) {
213        float maxSize = 0.0f;
214        glGetFloatv(GL_POINT_SIZE_MAX, &maxSize);
215        if (maxSize >= 8) {
216            glpoint_sprite = (atoi((const char *)glGetString(GL_VERSION)) >= 2);
217            if (!glpoint_sprite) {
218                const char * p = (const char *)glGetString(GL_EXTENSIONS);
219                while (true) {
220                    size_t l = 0;
221                    if (memcmp(p, "GL_ARB_point_sprite", 19) == 0) {
222                        l = 19;
223                    } else if (memcmp(p, "GL_NV_point_sprite", 18) == 0) {
224                        l = 18;
225                    }
226                    if (l) {
227                        p += l;
228                        if (*p == '\0' || *p == ' ') {
229                            glpoint_sprite = true;
230                            break;
231                        }
232                    }
233                    p = strchr(p + 1, ' ');
234                    if (!p) break;
235                    ++p;
236                }
237            }
238        }
239        checked = true;
240    }
241    return glpoint_sprite;
242}
243
244void
245log_gl_error(const wxChar * str, GLenum error_code)
246{
247    wxString msg;
248    switch (error_code) {
249        case GL_INVALID_ENUM:
250            msg = "Invalid OpenGL enumerated value";
251            break;
252        case GL_INVALID_VALUE:
253            msg = "Invalid OpenGL numeric argument value";
254            break;
255        case GL_INVALID_OPERATION:
256            msg = "Invalid OpenGL operation";
257            break;
258        case GL_INVALID_FRAMEBUFFER_OPERATION:
259            msg = "Invalid OpenGL framebuffer operation";
260            break;
261        case GL_OUT_OF_MEMORY:
262            msg = wmsg(/*Out of memory*/389);
263            break;
264        case GL_STACK_UNDERFLOW:
265            msg = "OpenGL stack underflow";
266            break;
267        case GL_STACK_OVERFLOW:
268            msg = "OpenGL stack overflow";
269            break;
270        default:
271            msg.Format("Unknown OpenGL error code: %d", int(error_code));
272            break;
273    }
274    wxLogError(str, msg);
275}
276
277//
278//  GLAPen
279//
280
281void GLAPen::SetColour(double red, double green, double blue)
282{
283    components[0] = red;
284    components[1] = green;
285    components[2] = blue;
286}
287
288double GLAPen::GetRed() const
289{
290    return components[0];
291}
292
293double GLAPen::GetGreen() const
294{
295    return components[1];
296}
297
298double GLAPen::GetBlue() const
299{
300    return components[2];
301}
302
303void GLAPen::Interpolate(const GLAPen& pen, double how_far)
304{
305    components[0] += how_far * (pen.GetRed() - components[0]);
306    components[1] += how_far * (pen.GetGreen() - components[1]);
307    components[2] += how_far * (pen.GetBlue() - components[2]);
308}
309
310struct ColourTriple {
311    // RGB triple: values are from 0-255 inclusive for each component.
312    unsigned char r, g, b;
313};
314
315// Order must match that in enum gla_colour[] in gla.h.
316static const ColourTriple COLOURS[] = {
317    { 0, 0, 0 },       // black
318    { 100, 100, 100 }, // grey
319    { 180, 180, 180 }, // light grey
320    { 140, 140, 140 }, // light grey 2
321    { 90, 90, 90 },    // dark grey
322    { 255, 255, 255 }, // white
323    { 0, 100, 255},    // turquoise
324    { 0, 255, 40 },    // green
325    { 150, 205, 224 }, // indicator 1
326    { 114, 149, 160 }, // indicator 2
327    { 255, 255, 0 },   // yellow
328    { 255, 0, 0 },     // red
329    { 40, 40, 255 },   // blue
330    { 255, 0, 255 },   // magenta
331};
332
333bool GLAList::need_to_generate() {
334    // Bail out if the list is already cached, or can't usefully be cached.
335    if (flags & (GLACanvas::CACHED|GLACanvas::NEVER_CACHE))
336        return false;
337
338    // Create a new OpenGL list to hold this sequence of drawing
339    // operations.
340    if (gl_list == 0) {
341        gl_list = glGenLists(1);
342        CHECK_GL_ERROR("GLAList::need_to_generate", "glGenLists");
343#ifdef GLA_DEBUG
344        printf("glGenLists(1) returned %u\n", (unsigned)gl_list);
345#endif
346        if (gl_list == 0) {
347            // If we can't create a list for any reason, fall back to just
348            // drawing directly, and flag the list as NEVER_CACHE as there's
349            // unlikely to be much point calling glGenLists() again.
350            flags = GLACanvas::NEVER_CACHE;
351            return false;
352        }
353
354        // We should have 256 lists for font drawing and a dozen or so for 2D
355        // and 3D lists.  So something is amiss if we've generated 1000 lists,
356        // probably a infinite loop in the lazy list mechanism.
357        assert(gl_list < 1000);
358    }
359    // https://www.opengl.org/resources/faq/technical/displaylist.htm advises:
360    //
361    // "Stay away from GL_COMPILE_AND_EXECUTE mode. Instead, create the
362    // list using GL_COMPILE mode, then execute it with glCallList()."
363    glNewList(gl_list, GL_COMPILE);
364    CHECK_GL_ERROR("GLAList::need_to_generate", "glNewList");
365    return true;
366}
367
368void GLAList::finalise(unsigned int list_flags)
369{
370    glEndList();
371    CHECK_GL_ERROR("GLAList::finalise", "glEndList");
372    if (list_flags & GLACanvas::NEVER_CACHE) {
373        glDeleteLists(gl_list, 1);
374        CHECK_GL_ERROR("GLAList::finalise", "glDeleteLists");
375        gl_list = 0;
376        flags = GLACanvas::NEVER_CACHE;
377    } else {
378        flags = list_flags | GLACanvas::CACHED;
379    }
380}
381
382bool GLAList::DrawList() const {
383    if ((flags & GLACanvas::CACHED) == 0)
384        return false;
385    glCallList(gl_list);
386    CHECK_GL_ERROR("GLAList::DrawList", "glCallList");
387    return true;
388}
389
390//
391//  GLACanvas
392//
393
394BEGIN_EVENT_TABLE(GLACanvas, wxGLCanvas)
395    EVT_SIZE(GLACanvas::OnSize)
396#ifdef wxHAS_DPI_INDEPENDENT_PIXELS
397    EVT_MOVE(GLACanvas::OnMove)
398#endif
399END_EVENT_TABLE()
400
401// Pass wxWANTS_CHARS so that the window gets cursor keys on MS Windows.
402GLACanvas::GLACanvas(wxWindow* parent, int id)
403    : wxGLCanvas(parent, id, wx_gl_attribs, wxDefaultPosition,
404                 wxDefaultSize, wxWANTS_CHARS),
405      ctx(this)
406{
407}
408
409GLACanvas::~GLACanvas()
410{
411    // Destructor.
412
413    if (m_Quadric) {
414        gluDeleteQuadric(m_Quadric);
415        CHECK_GL_ERROR("~GLACanvas", "gluDeleteQuadric");
416    }
417}
418
419void GLACanvas::FirstShow()
420{
421    UpdateSize();
422
423    ctx.SetCurrent(*this);
424    opengl_initialised = true;
425
426    // Set the background colour of the canvas to black.
427    glClearColor(0.0, 0.0, 0.0, 1.0);
428    CHECK_GL_ERROR("FirstShow", "glClearColor");
429
430    // Set viewport.
431    glViewport(0, 0, x_size, y_size);
432    CHECK_GL_ERROR("FirstShow", "glViewport");
433
434    save_hints = false;
435
436    vendor = wxString((const char *)glGetString(GL_VENDOR), wxConvUTF8);
437    renderer = wxString((const char *)glGetString(GL_RENDERER), wxConvUTF8);
438    {
439        wxConfigBase * cfg = wxConfigBase::Get();
440        wxString s;
441        if (cfg->Read(wxT("opengl_survex"), &s, wxString()) && s == wxT(VERSION) &&
442            cfg->Read(wxT("opengl_vendor"), &s, wxString()) && s == vendor &&
443            cfg->Read(wxT("opengl_renderer"), &s, wxString()) && s == renderer) {
444            // The survex version, vendor and renderer are the same as those
445            // we cached hints for, so use those hints.
446            int v;
447            if (cfg->Read(wxT("blob_method"), &v, 0) &&
448                (v == SPRITE || v == POINT || v == LINES)) {
449                // How to draw blobs.
450                blob_method = v;
451            }
452            if (cfg->Read(wxT("cross_method"), &v, 0) &&
453                (v == SPRITE || v == LINES)) {
454                // How to draw crosses.
455                cross_method = v;
456            }
457        }
458    }
459
460    if (m_Quadric) return;
461    // One time initialisation follows.
462
463    m_Quadric = gluNewQuadric();
464    CHECK_GL_ERROR("FirstShow", "gluNewQuadric");
465    if (!m_Quadric) {
466        abort(); // FIXME need to cope somehow
467    }
468
469    glShadeModel(GL_FLAT);
470    CHECK_GL_ERROR("FirstShow", "glShadeModel");
471    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // So text works.
472    CHECK_GL_ERROR("FirstShow", "glPolygonMode");
473    //glAlphaFunc(GL_GREATER, 0.5f);
474    //CHECK_GL_ERROR("FirstShow", "glAlphaFunc");
475
476    // We want glReadPixels() to read from the front buffer (which is the
477    // default for single-buffered displays).
478    if (double_buffered) {
479        glReadBuffer(GL_FRONT);
480        CHECK_GL_ERROR("FirstShow", "glReadBuffer");
481    }
482
483    // Grey fog effect.
484    GLfloat fogcolour[4] = { 0.5, 0.5, 0.5, 1.0 };
485    glFogfv(GL_FOG_COLOR, fogcolour);
486    CHECK_GL_ERROR("FirstShow", "glFogfv");
487
488    // Linear fogging.
489    glFogi(GL_FOG_MODE, GL_LINEAR);
490    CHECK_GL_ERROR("FirstShow", "glFogi");
491
492    // Optimise for speed (compute fog per vertex).
493    glHint(GL_FOG_HINT, GL_FASTEST);
494    CHECK_GL_ERROR("FirstShow", "glHint");
495
496    // No padding on pixel packing and unpacking (default is to pad each
497    // line to a multiple of 4 bytes).
498    glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // For setting texture maps.
499    CHECK_GL_ERROR("FirstShow", "glPixelStorei GL_UNPACK_ALIGNMENT");
500    glPixelStorei(GL_PACK_ALIGNMENT, 1); // For screengrabs and movies.
501    CHECK_GL_ERROR("FirstShow", "glPixelStorei GL_PACK_ALIGNMENT");
502
503    // Load font
504    wxString path = wmsg_cfgpth();
505    path += wxCONFIG_PATH_SEPARATOR;
506    path += wxT("unifont.pixelfont");
507    if (!m_Font.load(path, dpi_scale_factor >= 2)) {
508        // FIXME: do something better.
509        // We have this message available: Error in format of font file “%s”
510        fprintf(stderr, "Failed to parse compiled-in font data\n");
511        exit(1);
512    }
513
514    if (blob_method == UNKNOWN) {
515        // Check if we can use GL_POINTS to plot blobs at stations.
516        GLdouble point_size_range[2];
517        glGetDoublev(GL_SMOOTH_POINT_SIZE_RANGE, point_size_range);
518        CHECK_GL_ERROR("FirstShow", "glGetDoublev GL_SMOOTH_POINT_SIZE_RANGE");
519        if (point_size_range[0] <= BLOB_DIAMETER &&
520            point_size_range[1] >= BLOB_DIAMETER) {
521            blob_method = POINT;
522        } else {
523            blob_method = glpoint_sprite_works() ? SPRITE : LINES;
524        }
525        save_hints = true;
526    }
527
528    if (blob_method == POINT) {
529        glPointSize(BLOB_DIAMETER);
530        CHECK_GL_ERROR("FirstShow", "glPointSize");
531    }
532
533    if (cross_method == UNKNOWN) {
534        cross_method = glpoint_sprite_works() ? SPRITE : LINES;
535        save_hints = true;
536    }
537
538    if (cross_method == SPRITE) {
539        glGenTextures(1, &m_CrossTexture);
540        CHECK_GL_ERROR("FirstShow", "glGenTextures");
541        glBindTexture(GL_TEXTURE_2D, m_CrossTexture);
542        CHECK_GL_ERROR("FirstShow", "glBindTexture");
543        // Cross image for drawing crosses using texture mapped point sprites.
544        const unsigned char crossteximage[128] = {
545#define o 0,0
546#define I 255,255
547            CROSS_TEXTURE
548#undef o
549#undef I
550        };
551        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
552        CHECK_GL_ERROR("FirstShow", "glPixelStorei");
553        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
554        CHECK_GL_ERROR("FirstShow", "glTexEnvi");
555        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
556        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_WRAP_S");
557        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
558        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_WRAP_T");
559        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, 8, 8, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, (GLvoid *)crossteximage);
560        CHECK_GL_ERROR("FirstShow", "glTexImage2D");
561        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
562        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_MAG_FILTER");
563        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
564        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_MIN_FILTER");
565    }
566
567    if (blob_method == SPRITE) {
568        glGenTextures(1, &m_BlobTexture);
569        CHECK_GL_ERROR("FirstShow", "glGenTextures");
570        glBindTexture(GL_TEXTURE_2D, m_BlobTexture);
571        CHECK_GL_ERROR("FirstShow", "glBindTexture");
572        // Image for drawing blobs using texture mapped point sprites.
573        const unsigned char blobteximage[128] = {
574#define o 0,0
575#define I 255,255
576            BLOB_TEXTURE
577#undef o
578#undef I
579        };
580        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
581        CHECK_GL_ERROR("FirstShow", "glPixelStorei");
582        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
583        CHECK_GL_ERROR("FirstShow", "glTexEnvi");
584        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
585        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_WRAP_S");
586        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
587        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_WRAP_T");
588        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, 8, 8, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, (GLvoid *)blobteximage);
589        CHECK_GL_ERROR("FirstShow", "glTexImage2D");
590        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
591        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_MAG_FILTER");
592        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
593        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_MIN_FILTER");
594    }
595}
596
597void GLACanvas::Clear()
598{
599    // Clear the canvas.
600
601    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
602    CHECK_GL_ERROR("Clear", "glClear");
603}
604
605void GLACanvas::ClearNative()
606{
607    // Clear the canvas to the native background colour.
608
609    wxColour background_colour = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWFRAME);
610    glClearColor(background_colour.Red() / 255.,
611                 background_colour.Green() / 255.,
612                 background_colour.Blue() / 255.,
613                 1.0);
614    CHECK_GL_ERROR("ClearNative", "glClearColor");
615    glClear(GL_COLOR_BUFFER_BIT);
616    CHECK_GL_ERROR("ClearNative", "glClear");
617    glClearColor(0.0, 0.0, 0.0, 1.0);
618    CHECK_GL_ERROR("ClearNative", "glClearColor (2)");
619}
620
621void GLACanvas::SetScale(double scale)
622{
623    if (scale != m_Scale) {
624        for (auto & i : drawing_lists) {
625            i.invalidate_if(INVALIDATE_ON_SCALE);
626        }
627
628        m_Scale = scale;
629    }
630}
631
632void GLACanvas::OnMove(wxMoveEvent & event)
633{
634    UpdateSize();
635    event.Skip();
636}
637
638void GLACanvas::UpdateSize() {
639    // Update our record of the DPI and client area size, and invalidate any
640    // cached lists which depend on anything that has changed.
641    unsigned int mask = 0;
642
643    double new_dpi_scale_factor = wxGLCanvas::GetDPIScaleFactor();
644    if (dpi_scale_factor != new_dpi_scale_factor) {
645        dpi_scale_factor = new_dpi_scale_factor;
646        mask |= INVALIDATE_ON_HIDPI;
647    }
648
649    int new_w, new_h;
650    GetClientSize(&new_w, &new_h);
651#ifdef wxHAS_DPI_INDEPENDENT_PIXELS
652    auto content_scale_factor = GetContentScaleFactor();
653    new_w *= content_scale_factor;
654    new_h *= content_scale_factor;
655#endif
656    // The width and height go to zero when the panel is dragged right
657    // across so we clamp them to be at least 1 to avoid problems.
658    if (new_w < 1) new_w = 1;
659    if (new_h < 1) new_h = 1;
660    if (x_size != new_w) {
661        x_size = new_w;
662        mask |= INVALIDATE_ON_X_RESIZE;
663    }
664    if (y_size != new_h) {
665        y_size = new_h;
666        mask |= INVALIDATE_ON_Y_RESIZE;
667    }
668    if (mask) {
669        for (auto& i : drawing_lists) {
670            i.invalidate_if(mask);
671        }
672    }
673}
674
675void GLACanvas::OnSize(wxSizeEvent & event)
676{
677    UpdateSize();
678
679    event.Skip();
680
681    if (!opengl_initialised) return;
682
683    // Set viewport.
684    glViewport(0, 0, x_size, y_size);
685    CHECK_GL_ERROR("OnSize", "glViewport");
686}
687
688void GLACanvas::AddTranslationScreenCoordinates(int dx, int dy)
689{
690    // Translate the data by a given amount, specified in screen coordinates.
691
692    // Find out how far the translation takes us in data coordinates.
693    SetDataTransform();
694
695    double x0, y0, z0;
696    double x, y, z;
697    gluUnProject(0.0, 0.0, 0.0, modelview_matrix, projection_matrix, viewport,
698                 &x0, &y0, &z0);
699    CHECK_GL_ERROR("AddTranslationScreenCoordinates", "gluUnProject");
700    gluUnProject(dx, -dy, 0.0, modelview_matrix, projection_matrix, viewport,
701                 &x, &y, &z);
702    CHECK_GL_ERROR("AddTranslationScreenCoordinates", "gluUnProject (2)");
703
704    // Apply the translation.
705    AddTranslation(Vector3(x - x0, y - y0, z - z0));
706}
707
708void GLACanvas::SetVolumeDiameter(glaCoord diameter)
709{
710    // Set the size of the data drawing volume by giving the diameter of the
711    // smallest sphere containing it.
712
713    m_VolumeDiameter = max(glaCoord(1.0), diameter);
714}
715
716void GLACanvas::StartDrawing()
717{
718    // Prepare for a redraw operation.
719
720    ctx.SetCurrent(*this);
721    glDepthMask(GL_TRUE);
722
723    if (!save_hints) return;
724
725    // We want to check on the second redraw.
726    static int draw_count = 2;
727    if (--draw_count != 0) return;
728
729    if (cross_method != LINES) {
730        SetColour(col_WHITE);
731        Clear();
732        SetDataTransform();
733        BeginCrosses();
734        DrawCross(-m_Translation.GetX(), -m_Translation.GetY(), -m_Translation.GetZ());
735        EndCrosses();
736        static const unsigned char expected_cross[64 * 3] = {
737#define o 0,0,0
738#define I 255,255,255
739            CROSS_TEXTURE
740#undef o
741#undef I
742        };
743        if (!CheckVisualFidelity(expected_cross)) {
744            cross_method = LINES;
745            save_hints = true;
746        }
747    }
748
749    if (blob_method != LINES) {
750        SetColour(col_WHITE);
751        Clear();
752        SetDataTransform();
753        BeginBlobs();
754        DrawBlob(-m_Translation.GetX(), -m_Translation.GetY(), -m_Translation.GetZ());
755        EndBlobs();
756        static const unsigned char expected_blob[64 * 3] = {
757#define o 0,0,0
758#define I 255,255,255
759            BLOB_TEXTURE
760#undef o
761#undef I
762        };
763        if (!CheckVisualFidelity(expected_blob)) {
764            blob_method = LINES;
765            save_hints = true;
766        }
767    }
768
769    wxConfigBase * cfg = wxConfigBase::Get();
770    cfg->Write(wxT("opengl_survex"), wxT(VERSION));
771    cfg->Write(wxT("opengl_vendor"), vendor);
772    cfg->Write(wxT("opengl_renderer"), renderer);
773    cfg->Write(wxT("blob_method"), blob_method);
774    cfg->Write(wxT("cross_method"), cross_method);
775    cfg->Flush();
776    save_hints = false;
777}
778
779void GLACanvas::EnableSmoothPolygons(bool filled)
780{
781    // Prepare for drawing smoothly-shaded polygons.
782    // Only use this when required (in particular lines in lists may not be
783    // coloured correctly when this is enabled).
784
785    glPushAttrib(GL_ENABLE_BIT|GL_LIGHTING_BIT|GL_POLYGON_BIT);
786    if (filled) {
787        glShadeModel(GL_SMOOTH);
788        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
789    } else {
790        glDisable(GL_LINE_SMOOTH);
791        glDisable(GL_TEXTURE_2D);
792        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
793    }
794    CHECK_GL_ERROR("EnableSmoothPolygons", "glPolygonMode");
795
796    if (filled && m_SmoothShading) {
797        static const GLfloat mat_specular[] = { 0.2, 0.2, 0.2, 1.0 };
798        static const GLfloat light_position[] = { -1.0, -1.0, -1.0, 0.0 };
799        static const GLfloat light_ambient[] = { 0.3, 0.3, 0.3, 1.0 };
800        static const GLfloat light_diffuse[] = { 0.7, 0.7, 0.7, 1.0 };
801        glEnable(GL_COLOR_MATERIAL);
802        glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mat_specular);
803        glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 10.0);
804        glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
805        glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
806        glLightfv(GL_LIGHT0, GL_POSITION, light_position);
807        glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
808        glEnable(GL_LIGHTING);
809        glEnable(GL_LIGHT0);
810    }
811}
812
813void GLACanvas::DisableSmoothPolygons()
814{
815    glPopAttrib();
816}
817
818void GLACanvas::PlaceNormal(const Vector3 &v)
819{
820    // Add a normal (for polygons etc.)
821
822    glNormal3d(v.GetX(), v.GetY(), v.GetZ());
823}
824
825void GLACanvas::SetDataTransform()
826{
827    // Set projection.
828    glMatrixMode(GL_PROJECTION);
829    CHECK_GL_ERROR("SetDataTransform", "glMatrixMode");
830    glLoadIdentity();
831    CHECK_GL_ERROR("SetDataTransform", "glLoadIdentity");
832
833    double aspect = double(y_size) / double(x_size);
834
835    GLdouble near_plane = 1.0;
836    if (m_Perspective) {
837        GLdouble lr = near_plane * tan(rad(25.0));
838        GLdouble far_plane = m_VolumeDiameter * 5 + near_plane; // FIXME: work out properly
839        GLdouble tb = lr * aspect;
840        glFrustum(-lr, lr, -tb, tb, near_plane, far_plane);
841        CHECK_GL_ERROR("SetViewportAndProjection", "glFrustum");
842    } else {
843        near_plane = 0.0;
844        assert(m_Scale != 0.0);
845        GLdouble lr = m_VolumeDiameter / m_Scale * 0.5;
846        GLdouble far_plane = m_VolumeDiameter + near_plane;
847        GLdouble tb = lr;
848        if (aspect >= 1.0) {
849            tb *= aspect;
850        } else {
851            lr /= aspect;
852        }
853        glOrtho(-lr, lr, -tb, tb, near_plane, far_plane);
854        CHECK_GL_ERROR("SetViewportAndProjection", "glOrtho");
855    }
856
857    // Set the modelview transform for drawing data.
858    glMatrixMode(GL_MODELVIEW);
859    CHECK_GL_ERROR("SetDataTransform", "glMatrixMode");
860    glLoadIdentity();
861    CHECK_GL_ERROR("SetDataTransform", "glLoadIdentity");
862    if (m_Perspective) {
863        glTranslated(0.0, 0.0, -near_plane);
864    } else {
865        glTranslated(0.0, 0.0, -0.5 * m_VolumeDiameter);
866    }
867    CHECK_GL_ERROR("SetDataTransform", "glTranslated");
868    // Get axes the correct way around (z upwards, y into screen)
869    glRotated(-90.0, 1.0, 0.0, 0.0);
870    CHECK_GL_ERROR("SetDataTransform", "glRotated");
871    glRotated(-m_Tilt, 1.0, 0.0, 0.0);
872    CHECK_GL_ERROR("SetDataTransform", "glRotated");
873    glRotated(m_Pan, 0.0, 0.0, 1.0);
874    CHECK_GL_ERROR("SetDataTransform", "glRotated");
875    if (m_Perspective) {
876        glTranslated(m_Translation.GetX(),
877                     m_Translation.GetY(),
878                     m_Translation.GetZ());
879        CHECK_GL_ERROR("SetDataTransform", "glTranslated");
880    }
881    if (z_stretch != 1.0) {
882        glScaled(1.0, 1.0, z_stretch);
883        CHECK_GL_ERROR("SetDataTransform", "glScaled");
884    }
885
886    // Save projection matrix.
887    glGetDoublev(GL_PROJECTION_MATRIX, projection_matrix);
888    CHECK_GL_ERROR("SetDataTransform", "glGetDoublev");
889
890    // Save viewport coordinates.
891    glGetIntegerv(GL_VIEWPORT, viewport);
892    CHECK_GL_ERROR("SetDataTransform", "glGetIntegerv");
893
894    // Save modelview matrix.
895    glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix);
896    CHECK_GL_ERROR("SetDataTransform", "glGetDoublev");
897
898    if (!m_Perspective) {
899        // Adjust the translation so we don't change the Z position of the model
900        double X, Y, Z;
901        gluProject(m_Translation.GetX(),
902                   m_Translation.GetY(),
903                   m_Translation.GetZ(),
904                   modelview_matrix, projection_matrix, viewport,
905                   &X, &Y, &Z);
906        double Tx, Ty, Tz;
907        gluUnProject(X, Y, 0.5, modelview_matrix, projection_matrix, viewport,
908                     &Tx, &Ty, &Tz);
909        glTranslated(Tx, Ty, Tz);
910        CHECK_GL_ERROR("SetDataTransform", "glTranslated");
911        glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix);
912    }
913
914    glEnable(GL_DEPTH_TEST);
915    CHECK_GL_ERROR("SetDataTransform", "glEnable GL_DEPTH_TEST");
916
917    if (m_Textured) {
918        glBindTexture(GL_TEXTURE_2D, m_Texture);
919        glEnable(GL_TEXTURE_2D);
920        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
921        CHECK_GL_ERROR("ToggleTextured", "glTexParameteri GL_TEXTURE_WRAP_S");
922        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
923        CHECK_GL_ERROR("ToggleTextured", "glTexParameteri GL_TEXTURE_WRAP_T");
924        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
925        CHECK_GL_ERROR("ToggleTextured", "glTexParameteri GL_TEXTURE_MAG_FILTER");
926        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
927                        GL_LINEAR_MIPMAP_LINEAR);
928        CHECK_GL_ERROR("ToggleTextured", "glTexParameteri GL_TEXTURE_MIN_FILTER");
929        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
930    } else {
931        glDisable(GL_TEXTURE_2D);
932    }
933    if (m_Fog) {
934        glFogf(GL_FOG_START, near_plane);
935        glFogf(GL_FOG_END, near_plane + m_VolumeDiameter);
936        glEnable(GL_FOG);
937    } else {
938        glDisable(GL_FOG);
939    }
940
941    glEnable(GL_BLEND);
942    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
943    if (m_AntiAlias) {
944        glEnable(GL_LINE_SMOOTH);
945    } else {
946        glDisable(GL_LINE_SMOOTH);
947    }
948}
949
950void GLACanvas::SetIndicatorTransform()
951{
952    list_flags |= NEVER_CACHE;
953
954    // Set the modelview transform and projection for drawing indicators.
955
956    glDisable(GL_DEPTH_TEST);
957    CHECK_GL_ERROR("SetIndicatorTransform", "glDisable GL_DEPTH_TEST");
958    glDisable(GL_FOG);
959    CHECK_GL_ERROR("SetIndicatorTransform", "glDisable GL_FOG");
960
961    // Just a simple 2D projection.
962    glMatrixMode(GL_PROJECTION);
963    CHECK_GL_ERROR("SetIndicatorTransform", "glMatrixMode");
964    glLoadIdentity();
965    CHECK_GL_ERROR("SetIndicatorTransform", "glLoadIdentity (2)");
966    gluOrtho2D(0, x_size, 0, y_size);
967    CHECK_GL_ERROR("SetIndicatorTransform", "gluOrtho2D");
968
969    // No modelview transform.
970    glMatrixMode(GL_MODELVIEW);
971    CHECK_GL_ERROR("SetIndicatorTransform", "glMatrixMode");
972    glLoadIdentity();
973    CHECK_GL_ERROR("SetIndicatorTransform", "glLoadIdentity");
974
975    glDisable(GL_TEXTURE_2D);
976    CHECK_GL_ERROR("SetIndicatorTransform", "glDisable GL_TEXTURE_2D");
977    glDisable(GL_BLEND);
978    CHECK_GL_ERROR("SetIndicatorTransform", "glDisable GL_BLEND");
979    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
980    CHECK_GL_ERROR("SetIndicatorTransform", "glTexParameteri GL_TEXTURE_WRAP_S");
981    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
982    CHECK_GL_ERROR("SetIndicatorTransform", "glTexParameteri GL_TEXTURE_WRAP_T");
983    glAlphaFunc(GL_GREATER, 0.5f);
984    CHECK_GL_ERROR("SetIndicatorTransform", "glAlphaFunc");
985    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
986    CHECK_GL_ERROR("SetIndicatorTransform", "glTexParameteri GL_TEXTURE_MAG_FILTER");
987    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
988    CHECK_GL_ERROR("SetIndicatorTransform", "glTexParameteri GL_TEXTURE_MIN_FILTER");
989    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
990    CHECK_GL_ERROR("SetIndicatorTransform", "glHint");
991}
992
993void GLACanvas::FinishDrawing()
994{
995    // Complete a redraw operation.
996
997    if (double_buffered) {
998        SwapBuffers();
999    } else {
1000        glFlush();
1001        CHECK_GL_ERROR("FinishDrawing", "glFlush");
1002    }
1003}
1004
1005void GLACanvas::DrawList(unsigned int l)
1006{
1007    // FIXME: uncomment to disable use of lists for debugging:
1008    // GenerateList(l); return;
1009    if (l >= drawing_lists.size()) drawing_lists.resize(l + 1);
1010
1011    // We generate the OpenGL lists lazily to minimise delays on startup.
1012    // So check if we need to generate the OpenGL list now.
1013    if (drawing_lists[l].need_to_generate()) {
1014        // Clear list_flags so that we can note what conditions to invalidate
1015        // the cached OpenGL list on.
1016        list_flags = 0;
1017
1018#ifdef GLA_DEBUG
1019        printf("generating list #%u... ", l);
1020        m_Vertices = 0;
1021#endif
1022        GenerateList(l);
1023#ifdef GLA_DEBUG
1024        printf("done (%d vertices)\n", m_Vertices);
1025#endif
1026        drawing_lists[l].finalise(list_flags);
1027    }
1028
1029    if (!drawing_lists[l].DrawList()) {
1030        // That list isn't cached (which means it probably can't usefully be
1031        // cached).
1032        GenerateList(l);
1033    }
1034}
1035
1036void GLACanvas::DrawListZPrepass(unsigned int l)
1037{
1038    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
1039    DrawList(l);
1040    glDepthMask(GL_FALSE);
1041    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
1042    glDepthFunc(GL_EQUAL);
1043    DrawList(l);
1044    glDepthMask(GL_TRUE);
1045    glDepthFunc(GL_LESS);
1046}
1047
1048void GLACanvas::DrawList2D(unsigned int l, glaCoord x, glaCoord y, double rotation)
1049{
1050    glMatrixMode(GL_PROJECTION);
1051    CHECK_GL_ERROR("DrawList2D", "glMatrixMode");
1052    glPushMatrix();
1053    CHECK_GL_ERROR("DrawList2D", "glPushMatrix");
1054    glTranslated(x, y, 0);
1055    CHECK_GL_ERROR("DrawList2D", "glTranslated");
1056    if (rotation != 0.0) {
1057        glRotated(rotation, 0, 0, -1);
1058        CHECK_GL_ERROR("DrawList2D", "glRotated");
1059    }
1060    DrawList(l);
1061    glMatrixMode(GL_PROJECTION);
1062    CHECK_GL_ERROR("DrawList2D", "glMatrixMode 2");
1063    glPopMatrix();
1064    CHECK_GL_ERROR("DrawList2D", "glPopMatrix");
1065}
1066
1067void GLACanvas::SetColour(const GLAPen& pen, double rgb_scale)
1068{
1069    // Set the colour for subsequent operations.
1070    glColor4f(pen.GetRed() * rgb_scale, pen.GetGreen() * rgb_scale,
1071              pen.GetBlue() * rgb_scale, alpha);
1072}
1073
1074void GLACanvas::SetColour(const GLAPen& pen)
1075{
1076    // Set the colour for subsequent operations.
1077    glColor4d(pen.components[0], pen.components[1], pen.components[2], alpha);
1078}
1079
1080void GLACanvas::SetColour(gla_colour colour, double rgb_scale)
1081{
1082    // Set the colour for subsequent operations.
1083    rgb_scale /= 255.0;
1084    glColor4f(COLOURS[colour].r * rgb_scale,
1085              COLOURS[colour].g * rgb_scale,
1086              COLOURS[colour].b * rgb_scale,
1087              alpha);
1088}
1089
1090void GLACanvas::SetColour(gla_colour colour)
1091{
1092    // Set the colour for subsequent operations.
1093    if (alpha == 1.0) {
1094        glColor3ubv(&COLOURS[colour].r);
1095    } else {
1096        glColor4ub(COLOURS[colour].r,
1097                   COLOURS[colour].g,
1098                   COLOURS[colour].b,
1099                   (unsigned char)(255 * alpha));
1100    }
1101}
1102
1103void GLACanvas::DrawText(glaCoord x, glaCoord y, glaCoord z, const wxString& str)
1104{
1105    // Draw a text string on the current buffer in the current font.
1106    glRasterPos3d(x, y, z);
1107    CHECK_GL_ERROR("DrawText", "glRasterPos3d");
1108    m_Font.write_string(str.data(), str.size());
1109}
1110
1111void GLACanvas::DrawIndicatorText(int x, int y, const wxString& str)
1112{
1113    glRasterPos2d(x, y);
1114    CHECK_GL_ERROR("DrawIndicatorText", "glRasterPos2d");
1115    m_Font.write_string(str.data(), str.size());
1116}
1117
1118void GLACanvas::GetTextExtent(const wxString& str, int * x_ext, int * y_ext) const
1119{
1120    m_Font.get_text_extent(str.data(), str.size(), x_ext, y_ext);
1121}
1122
1123void GLACanvas::BeginQuadrilaterals()
1124{
1125    // Commence drawing of quadrilaterals.
1126
1127    glBegin(GL_QUADS);
1128}
1129
1130void GLACanvas::EndQuadrilaterals()
1131{
1132    // Finish drawing of quadrilaterals.
1133
1134    glEnd();
1135    CHECK_GL_ERROR("EndQuadrilaterals", "glEnd GL_QUADS");
1136}
1137
1138void GLACanvas::BeginLines()
1139{
1140    // Commence drawing of a set of lines.
1141
1142    glBegin(GL_LINES);
1143}
1144
1145void GLACanvas::EndLines()
1146{
1147    // Finish drawing of a set of lines.
1148
1149    glEnd();
1150    CHECK_GL_ERROR("EndLines", "glEnd GL_LINES");
1151}
1152
1153void GLACanvas::BeginTriangles()
1154{
1155    // Commence drawing of a set of triangles.
1156
1157    glBegin(GL_TRIANGLES);
1158}
1159
1160void GLACanvas::EndTriangles()
1161{
1162    // Finish drawing of a set of triangles.
1163
1164    glEnd();
1165    CHECK_GL_ERROR("EndTriangles", "glEnd GL_TRIANGLES");
1166}
1167
1168void GLACanvas::BeginTriangleStrip()
1169{
1170    // Commence drawing of a triangle strip.
1171
1172    glBegin(GL_TRIANGLE_STRIP);
1173}
1174
1175void GLACanvas::EndTriangleStrip()
1176{
1177    // Finish drawing of a triangle strip.
1178
1179    glEnd();
1180    CHECK_GL_ERROR("EndTriangleStrip", "glEnd GL_TRIANGLE_STRIP");
1181}
1182
1183void GLACanvas::BeginPolyline()
1184{
1185    // Commence drawing of a polyline.
1186
1187    glBegin(GL_LINE_STRIP);
1188}
1189
1190void GLACanvas::EndPolyline()
1191{
1192    // Finish drawing of a polyline.
1193
1194    glEnd();
1195    CHECK_GL_ERROR("EndPolyline", "glEnd GL_LINE_STRIP");
1196}
1197
1198void GLACanvas::BeginPolyloop()
1199{
1200    // Commence drawing of a polyloop.
1201
1202    glBegin(GL_LINE_LOOP);
1203}
1204
1205void GLACanvas::EndPolyloop()
1206{
1207    // Finish drawing of a polyloop.
1208
1209    glEnd();
1210    CHECK_GL_ERROR("EndPolyloop", "glEnd GL_LINE_LOOP");
1211}
1212
1213void GLACanvas::BeginPolygon()
1214{
1215    // Commence drawing of a polygon.
1216
1217    glBegin(GL_POLYGON);
1218}
1219
1220void GLACanvas::EndPolygon()
1221{
1222    // Finish drawing of a polygon.
1223
1224    glEnd();
1225    CHECK_GL_ERROR("EndPolygon", "glEnd GL_POLYGON");
1226}
1227
1228void GLACanvas::BeginPoints()
1229{
1230    // Commence drawing points.
1231
1232    glPushAttrib(GL_POINT_BIT);
1233    CHECK_GL_ERROR("BeginPoints", "glPushAttrib");
1234    glPointSize(3);
1235    CHECK_GL_ERROR("BeginPoints", "glPointSize");
1236    glBegin(GL_POINTS);
1237}
1238
1239void GLACanvas::EndPoints()
1240{
1241    // Finish drawing points.
1242
1243    glEnd();
1244    CHECK_GL_ERROR("EndPoints", "glEnd GL_POINTS");
1245    glPopAttrib();
1246    CHECK_GL_ERROR("EndPoints", "glPopAttrib");
1247}
1248
1249void GLACanvas::PlaceVertex(glaCoord x, glaCoord y, glaCoord z)
1250{
1251    // Place a vertex for the current object being drawn.
1252
1253#ifdef GLA_DEBUG
1254    m_Vertices++;
1255#endif
1256    glVertex3d(x, y, z);
1257}
1258
1259void GLACanvas::PlaceVertex(glaCoord x, glaCoord y, glaCoord z,
1260                            glaTexCoord tex_x, glaTexCoord tex_y)
1261{
1262    // Place a vertex for the current object being drawn.
1263
1264#ifdef GLA_DEBUG
1265    m_Vertices++;
1266#endif
1267    glTexCoord2f(tex_x, tex_y);
1268    glVertex3d(x, y, z);
1269}
1270
1271void GLACanvas::PlaceIndicatorVertex(glaCoord x, glaCoord y)
1272{
1273    // Place a vertex for the current indicator object being drawn.
1274
1275    PlaceVertex(x, y, 0.0);
1276}
1277
1278void GLACanvas::BeginBlobs()
1279{
1280    // Commence drawing of a set of blobs.
1281    if (blob_method == SPRITE) {
1282        glPushAttrib(GL_ENABLE_BIT|GL_POINT_BIT);
1283        CHECK_GL_ERROR("BeginBlobs", "glPushAttrib");
1284        glBindTexture(GL_TEXTURE_2D, m_BlobTexture);
1285        CHECK_GL_ERROR("BeginBlobs", "glBindTexture");
1286        glEnable(GL_ALPHA_TEST);
1287        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_ALPHA_TEST");
1288        glPointSize(8);
1289        CHECK_GL_ERROR("BeginBlobs", "glPointSize");
1290        glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE);
1291        CHECK_GL_ERROR("BeginBlobs", "glTexEnvi GL_POINT_SPRITE");
1292        glEnable(GL_TEXTURE_2D);
1293        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_TEXTURE_2D");
1294        glEnable(GL_POINT_SPRITE);
1295        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_POINT_SPRITE");
1296        glBegin(GL_POINTS);
1297    } else if (blob_method == POINT) {
1298        glPushAttrib(GL_ENABLE_BIT);
1299        CHECK_GL_ERROR("BeginBlobs", "glPushAttrib");
1300        glEnable(GL_ALPHA_TEST);
1301        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_ALPHA_TEST");
1302        glEnable(GL_POINT_SMOOTH);
1303        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_POINT_SMOOTH");
1304        glBegin(GL_POINTS);
1305    } else {
1306        glPushAttrib(GL_TRANSFORM_BIT|GL_VIEWPORT_BIT|GL_ENABLE_BIT);
1307        CHECK_GL_ERROR("BeginBlobs", "glPushAttrib");
1308        SetIndicatorTransform();
1309        glEnable(GL_DEPTH_TEST);
1310        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_DEPTH_TEST");
1311        glBegin(GL_LINES);
1312    }
1313}
1314
1315void GLACanvas::EndBlobs()
1316{
1317    // Finish drawing of a set of blobs.
1318    glEnd();
1319    if (blob_method != LINES) {
1320        CHECK_GL_ERROR("EndBlobs", "glEnd GL_POINTS");
1321    } else {
1322        CHECK_GL_ERROR("EndBlobs", "glEnd GL_LINES");
1323    }
1324    glPopAttrib();
1325    CHECK_GL_ERROR("EndBlobs", "glPopAttrib");
1326}
1327
1328void GLACanvas::DrawBlob(glaCoord x, glaCoord y, glaCoord z)
1329{
1330    if (blob_method != LINES) {
1331        // Draw a marker.
1332        PlaceVertex(x, y, z);
1333    } else {
1334        double X, Y, Z;
1335        if (!Transform(Vector3(x, y, z), &X, &Y, &Z)) {
1336            printf("bad transform\n");
1337            return;
1338        }
1339        // Stuff behind us (in perspective view) will get clipped,
1340        // but we can save effort with a cheap check here.
1341        if (Z <= 0) return;
1342
1343        X -= BLOB_DIAMETER * 0.5;
1344        Y -= BLOB_DIAMETER * 0.5;
1345
1346        PlaceVertex(X, Y + 1, Z);
1347        PlaceVertex(X, Y + (BLOB_DIAMETER - 1), Z);
1348
1349        for (int i = 1; i < (BLOB_DIAMETER - 1); ++i) {
1350            PlaceVertex(X + i, Y, Z);
1351            PlaceVertex(X + i, Y + BLOB_DIAMETER, Z);
1352        }
1353
1354        PlaceVertex(X + (BLOB_DIAMETER - 1), Y + 1, Z);
1355        PlaceVertex(X + (BLOB_DIAMETER - 1), Y + (BLOB_DIAMETER - 1), Z);
1356    }
1357#ifdef GLA_DEBUG
1358    m_Vertices++;
1359#endif
1360}
1361
1362void GLACanvas::DrawBlob(glaCoord x, glaCoord y)
1363{
1364    if (blob_method != LINES) {
1365        // Draw a marker.
1366        PlaceVertex(x, y, 0);
1367    } else {
1368        x -= BLOB_DIAMETER * 0.5;
1369        y -= BLOB_DIAMETER * 0.5;
1370
1371        PlaceVertex(x, y + 1, 0);
1372        PlaceVertex(x, y + (BLOB_DIAMETER - 1), 0);
1373
1374        for (int i = 1; i < (BLOB_DIAMETER - 1); ++i) {
1375            PlaceVertex(x + i, y, 0);
1376            PlaceVertex(x + i, y + BLOB_DIAMETER, 0);
1377        }
1378
1379        PlaceVertex(x + (BLOB_DIAMETER - 1), y + 1, 0);
1380        PlaceVertex(x + (BLOB_DIAMETER - 1), y + (BLOB_DIAMETER - 1), 0);
1381    }
1382#ifdef GLA_DEBUG
1383    m_Vertices++;
1384#endif
1385}
1386
1387void GLACanvas::BeginCrosses()
1388{
1389    // Plot crosses.
1390    if (cross_method == SPRITE) {
1391        list_flags |= NEVER_CACHE;
1392        SetDataTransform();
1393        glPushAttrib(GL_ENABLE_BIT|GL_POINT_BIT);
1394        CHECK_GL_ERROR("BeginCrosses", "glPushAttrib");
1395        glBindTexture(GL_TEXTURE_2D, m_CrossTexture);
1396        CHECK_GL_ERROR("BeginCrosses", "glBindTexture");
1397        glEnable(GL_ALPHA_TEST);
1398        CHECK_GL_ERROR("BeginCrosses", "glEnable GL_ALPHA_TEST");
1399        glPointSize(8);
1400        CHECK_GL_ERROR("BeginCrosses", "glPointSize");
1401        glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE);
1402        CHECK_GL_ERROR("BeginCrosses", "glTexEnvi GL_POINT_SPRITE");
1403        glEnable(GL_TEXTURE_2D);
1404        CHECK_GL_ERROR("BeginCrosses", "glEnable GL_TEXTURE_2D");
1405        glEnable(GL_POINT_SPRITE);
1406        CHECK_GL_ERROR("BeginCrosses", "glEnable GL_POINT_SPRITE");
1407        glBegin(GL_POINTS);
1408    } else {
1409        // To get the crosses to appear at a constant size and orientation on
1410        // screen, we plot them in the Indicator transform coordinates (which
1411        // unfortunately means they can't be usefully put in an opengl display
1412        // list).
1413        glPushAttrib(GL_TRANSFORM_BIT|GL_VIEWPORT_BIT|GL_ENABLE_BIT);
1414        CHECK_GL_ERROR("BeginCrosses", "glPushAttrib 2");
1415        SetIndicatorTransform();
1416        // Align line drawing to pixel centres to get pixel-perfect rendering
1417        // (graphics card and driver bugs aside).
1418        glTranslated(-0.5, -0.5, 0);
1419        CHECK_GL_ERROR("BeginCrosses", "glTranslated");
1420        glEnable(GL_DEPTH_TEST);
1421        CHECK_GL_ERROR("BeginCrosses", "glEnable GL_DEPTH_TEST");
1422        glBegin(GL_LINES);
1423    }
1424}
1425
1426void GLACanvas::EndCrosses()
1427{
1428    glEnd();
1429    if (cross_method == SPRITE) {
1430        CHECK_GL_ERROR("EndCrosses", "glEnd GL_POINTS");
1431    } else {
1432        CHECK_GL_ERROR("EndCrosses", "glEnd GL_LINES");
1433    }
1434    glPopAttrib();
1435    CHECK_GL_ERROR("EndCrosses", "glPopAttrib");
1436}
1437
1438void GLACanvas::DrawCross(glaCoord x, glaCoord y, glaCoord z)
1439{
1440    if (cross_method == SPRITE) {
1441        // Draw a marker.
1442        PlaceVertex(x, y, z);
1443    } else {
1444        double X, Y, Z;
1445        if (!Transform(Vector3(x, y, z), &X, &Y, &Z)) {
1446            printf("bad transform\n");
1447            return;
1448        }
1449        // Stuff behind us (in perspective view) will get clipped,
1450        // but we can save effort with a cheap check here.
1451        if (Z <= 0) return;
1452
1453        // Round to integers before adding on the offsets for the
1454        // cross arms to avoid uneven crosses.
1455        X = rint(X);
1456        Y = rint(Y);
1457        // Need to extend lines by an extra pixel (which shouldn't get drawn by
1458        // the diamond-exit rule).
1459        PlaceVertex(X - 3, Y - 3, Z);
1460        PlaceVertex(X + 4, Y + 4, Z);
1461        PlaceVertex(X - 3, Y + 3, Z);
1462        PlaceVertex(X + 4, Y - 4, Z);
1463    }
1464#ifdef GLA_DEBUG
1465    m_Vertices++;
1466#endif
1467}
1468
1469void GLACanvas::DrawRing(glaCoord x, glaCoord y)
1470{
1471    // Draw an unfilled circle of radius 4
1472
1473    // Round to integers to get an even ring.
1474    x = rint(x);
1475    y = rint(y);
1476
1477    glBegin(GL_LINE_LOOP);
1478    PlaceIndicatorVertex(x + 3.5, y - 1.5);
1479    PlaceIndicatorVertex(x + 1.5, y - 3.5);
1480    PlaceIndicatorVertex(x - 1.5, y - 3.5);
1481    PlaceIndicatorVertex(x - 3.5, y - 1.5);
1482    PlaceIndicatorVertex(x - 3.5, y + 1.5);
1483    PlaceIndicatorVertex(x - 1.5, y + 3.5);
1484    PlaceIndicatorVertex(x + 1.5, y + 3.5);
1485    PlaceIndicatorVertex(x + 3.5, y + 1.5);
1486    glEnd();
1487    CHECK_GL_ERROR("DrawRing", "glEnd GL_LINE_LOOP");
1488}
1489
1490void GLACanvas::DrawRectangle(gla_colour fill, gla_colour edge,
1491                              glaCoord x0, glaCoord y0, glaCoord w, glaCoord h)
1492{
1493    // Draw a filled rectangle with an edge in the indicator plane.
1494    // (x0, y0) specify the bottom-left corner of the rectangle and (w, h) the
1495    // size.
1496
1497    SetColour(fill);
1498    BeginQuadrilaterals();
1499    PlaceIndicatorVertex(x0, y0);
1500    PlaceIndicatorVertex(x0 + w, y0);
1501    PlaceIndicatorVertex(x0 + w, y0 + h);
1502    PlaceIndicatorVertex(x0, y0 + h);
1503    EndQuadrilaterals();
1504
1505    if (edge != fill) {
1506        SetColour(edge);
1507        BeginPolyline();
1508        PlaceIndicatorVertex(x0, y0);
1509        PlaceIndicatorVertex(x0 + w, y0);
1510        PlaceIndicatorVertex(x0 + w, y0 + h);
1511        PlaceIndicatorVertex(x0, y0 + h);
1512        PlaceIndicatorVertex(x0, y0);
1513        EndPolyline();
1514    }
1515}
1516
1517void
1518GLACanvas::DrawShadedRectangle(const GLAPen & fill_bot, const GLAPen & fill_top,
1519                               glaCoord x0, glaCoord y0,
1520                               glaCoord w, glaCoord h)
1521{
1522    // Draw a graduated filled rectangle in the indicator plane.
1523    // (x0, y0) specify the bottom-left corner of the rectangle and (w, h) the
1524    // size.
1525
1526    glShadeModel(GL_SMOOTH);
1527    CHECK_GL_ERROR("DrawShadedRectangle", "glShadeModel GL_SMOOTH");
1528    BeginQuadrilaterals();
1529    SetColour(fill_bot);
1530    PlaceIndicatorVertex(x0, y0);
1531    PlaceIndicatorVertex(x0 + w, y0);
1532    SetColour(fill_top);
1533    PlaceIndicatorVertex(x0 + w, y0 + h);
1534    PlaceIndicatorVertex(x0, y0 + h);
1535    EndQuadrilaterals();
1536    glShadeModel(GL_FLAT);
1537    CHECK_GL_ERROR("DrawShadedRectangle", "glShadeModel GL_FLAT");
1538}
1539
1540void GLACanvas::DrawCircle(gla_colour edge, gla_colour fill,
1541                           glaCoord cx, glaCoord cy, glaCoord radius)
1542{
1543    // Draw a filled circle with an edge.
1544    SetColour(fill);
1545    glMatrixMode(GL_MODELVIEW);
1546    CHECK_GL_ERROR("DrawCircle", "glMatrixMode");
1547    glPushMatrix();
1548    CHECK_GL_ERROR("DrawCircle", "glPushMatrix");
1549    glTranslated(cx, cy, 0.0);
1550    CHECK_GL_ERROR("DrawCircle", "glTranslated");
1551    assert(m_Quadric);
1552    gluDisk(m_Quadric, 0.0, radius, 36, 1);
1553    CHECK_GL_ERROR("DrawCircle", "gluDisk");
1554    SetColour(edge);
1555    gluDisk(m_Quadric, radius - 1.0, radius, 36, 1);
1556    CHECK_GL_ERROR("DrawCircle", "gluDisk (2)");
1557    glPopMatrix();
1558    CHECK_GL_ERROR("DrawCircle", "glPopMatrix");
1559}
1560
1561void GLACanvas::DrawSemicircle(gla_colour edge, gla_colour fill,
1562                               glaCoord cx, glaCoord cy,
1563                               glaCoord radius, glaCoord start)
1564{
1565    // Draw a filled semicircle with an edge.
1566    // The semicircle extends from "start" deg to "start"+180 deg (increasing
1567    // clockwise, 0 deg upwards).
1568    SetColour(fill);
1569    glMatrixMode(GL_MODELVIEW);
1570    CHECK_GL_ERROR("DrawSemicircle", "glMatrixMode");
1571    glPushMatrix();
1572    CHECK_GL_ERROR("DrawSemicircle", "glPushMatrix");
1573    glTranslated(cx, cy, 0.0);
1574    CHECK_GL_ERROR("DrawSemicircle", "glTranslated");
1575    assert(m_Quadric);
1576    gluPartialDisk(m_Quadric, 0.0, radius, 36, 1, start, 180.0);
1577    CHECK_GL_ERROR("DrawSemicircle", "gluPartialDisk");
1578    SetColour(edge);
1579    gluPartialDisk(m_Quadric, radius - 1.0, radius, 36, 1, start, 180.0);
1580    CHECK_GL_ERROR("DrawSemicircle", "gluPartialDisk (2)");
1581    glPopMatrix();
1582    CHECK_GL_ERROR("DrawSemicircle", "glPopMatrix");
1583}
1584
1585void GLACanvas::EnableDashedLines()
1586{
1587    // Enable dashed lines, and start drawing in them.
1588
1589    glLineStipple(1, 0x3333);
1590    CHECK_GL_ERROR("EnableDashedLines", "glLineStipple");
1591    glEnable(GL_LINE_STIPPLE);
1592    CHECK_GL_ERROR("EnableDashedLines", "glEnable GL_LINE_STIPPLE");
1593}
1594
1595void GLACanvas::DisableDashedLines()
1596{
1597    glDisable(GL_LINE_STIPPLE);
1598    CHECK_GL_ERROR("DisableDashedLines", "glDisable GL_LINE_STIPPLE");
1599}
1600
1601bool GLACanvas::Transform(const Vector3 & v,
1602                          glaCoord* x_out, glaCoord* y_out, glaCoord* z_out) const
1603{
1604    // Convert from data coordinates to screen coordinates.
1605
1606    // Perform the projection.
1607    return gluProject(v.GetX(), v.GetY(), v.GetZ(),
1608                      modelview_matrix, projection_matrix, viewport,
1609                      x_out, y_out, z_out);
1610}
1611
1612void GLACanvas::ReverseTransform(double x, double y,
1613                                 glaCoord* x_out, glaCoord* y_out, glaCoord* z_out) const
1614{
1615    // Convert from screen coordinates to data coordinates.
1616
1617    // Perform the projection.
1618    gluUnProject(x, y, 0.0, modelview_matrix, projection_matrix, viewport,
1619                 x_out, y_out, z_out);
1620    CHECK_GL_ERROR("ReverseTransform", "gluUnProject");
1621}
1622
1623double GLACanvas::SurveyUnitsAcrossViewport() const
1624{
1625    // Measure the current viewport in survey units, taking into account the
1626    // current display scale.
1627
1628    assert(m_Scale != 0.0);
1629    list_flags |= INVALIDATE_ON_SCALE;
1630    double result = m_VolumeDiameter / m_Scale;
1631    if (y_size < x_size) {
1632        result = result * x_size / y_size;
1633    }
1634    return result;
1635}
1636
1637void GLACanvas::ToggleSmoothShading()
1638{
1639    m_SmoothShading = !m_SmoothShading;
1640}
1641
1642void GLACanvas::ToggleTextured()
1643{
1644    m_Textured = !m_Textured;
1645    if (m_Textured && m_Texture == 0) {
1646        glGenTextures(1, &m_Texture);
1647        CHECK_GL_ERROR("ToggleTextured", "glGenTextures");
1648
1649        glBindTexture(GL_TEXTURE_2D, m_Texture);
1650        CHECK_GL_ERROR("ToggleTextured", "glBindTexture");
1651
1652        ::wxInitAllImageHandlers();
1653
1654        wxImage img;
1655        wxString texture(wmsg_cfgpth());
1656        texture += wxCONFIG_PATH_SEPARATOR;
1657        texture += wxT("images");
1658        texture += wxCONFIG_PATH_SEPARATOR;
1659        texture += wxT("texture.png");
1660        if (!img.LoadFile(texture, wxBITMAP_TYPE_PNG)) {
1661            // FIXME
1662            fprintf(stderr, "Couldn't load image.\n");
1663            exit(1);
1664        }
1665
1666        // Generate mipmaps.
1667        gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, // was GL_LUMINANCE
1668                          img.GetWidth(), img.GetHeight(),
1669                          GL_RGB, GL_UNSIGNED_BYTE, img.GetData());
1670        CHECK_GL_ERROR("ToggleTextured", "gluBuild2DMipmaps");
1671
1672        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
1673        CHECK_GL_ERROR("ToggleTextured", "glTexEnvi");
1674    }
1675}
1676
1677bool GLACanvas::SaveScreenshot(const wxString & fnm, wxBitmapType type) const
1678{
1679    const int width = x_size;
1680    const int height = y_size;
1681    unsigned char *pixels = (unsigned char *)malloc(3 * width * (height + 1));
1682    if (!pixels) return false;
1683    glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid *)pixels);
1684    CHECK_GL_ERROR("SaveScreenshot", "glReadPixels");
1685    unsigned char * tmp_row = pixels + 3 * width * height;
1686    // We need to flip the image vertically - this approach should be more
1687    // efficient than using wxImage::Mirror(false) as that creates a new
1688    // wxImage object.
1689    for (int y = height / 2 - 1; y >= 0; --y) {
1690        unsigned char * upper = pixels + 3 * width * y;
1691        unsigned char * lower = pixels + 3 * width * (height - y - 1);
1692        memcpy(tmp_row, upper, 3 * width);
1693        memcpy(upper, lower, 3 * width);
1694        memcpy(lower, tmp_row, 3 * width);
1695    }
1696    // NB wxImage constructor calls free(pixels) for us.
1697    wxImage grab(width, height, pixels);
1698    return grab.SaveFile(fnm, type);
1699}
1700
1701bool GLACanvas::CheckVisualFidelity(const unsigned char * target) const
1702{
1703    unsigned char pixels[3 * 8 * 8];
1704    if (double_buffered) {
1705        glReadBuffer(GL_BACK);
1706        CHECK_GL_ERROR("FirstShow", "glReadBuffer");
1707    }
1708    glReadPixels(x_size / 2 - 4, y_size / 2 - 5, 8, 8,
1709                 GL_RGB, GL_UNSIGNED_BYTE, (GLvoid *)pixels);
1710    CHECK_GL_ERROR("CheckVisualFidelity", "glReadPixels");
1711    if (double_buffered) {
1712        glReadBuffer(GL_FRONT);
1713        CHECK_GL_ERROR("FirstShow", "glReadBuffer");
1714    }
1715#if 0
1716    // Show what got drawn and what was expected for debugging.
1717    for (int y = 0; y < 8; ++y) {
1718        for (int x = 0; x < 8; ++x) {
1719            int o = (y * 8 + x) * 3;
1720            printf("%c", pixels[o] ? 'X' : '.');
1721        }
1722        printf(" ");
1723        for (int x = 0; x < 8; ++x) {
1724            int o = (y * 8 + x) * 3;
1725            printf("%c", target[o] ? 'X' : '.');
1726        }
1727        printf("\n");
1728    }
1729#endif
1730    return (memcmp(pixels, target, sizeof(pixels)) == 0);
1731}
1732
1733void GLACanvas::ReadPixels(int width, int height, unsigned char * buf) const
1734{
1735    CHECK_GL_ERROR("ReadPixels", "glReadPixels");
1736    glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid *)buf);
1737}
1738
1739void GLACanvas::PolygonOffset(bool on) const
1740{
1741    if (on) {
1742        glPolygonOffset(1.0, 1.0);
1743        glEnable(GL_POLYGON_OFFSET_FILL);
1744    } else {
1745        glDisable(GL_POLYGON_OFFSET_FILL);
1746    }
1747}
Note: See TracBrowser for help on using the repository browser.