source: git/src/gla-gl.cc

Last change on this file was 4c83f84, checked in by Olly Betts <olly@…>, 36 hours ago

Don't check HAVE_CONFIG_H in most cases

This check is only useful for img.c, which is intended to be usable
outside of Survex (and had fallbacks for functions which may not be
available which will get used if built in a non-autotools project).
For all the other source files it's just useless boilerplate.

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