source: git/src/gla-gl.cc @ 8a7804fb

main
Last change on this file since 8a7804fb was 8a7804fb, checked in by Olly Betts <olly@…>, 6 weeks ago

View volume now includes terrain vertical extent

Previously this could lead to the terrain not being fully visible
or even being entirely hidden in cases where the vertical difference
between the terrain and the cave was extreme.

These changes also improve the handling of scale when reloading an
updated version of the .3d file already being viewed.

Hopefully fixes #148, reported by Chris Curry.

  • Property mode set to 100644
File size: 49.6 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    diameter = max(glaCoord(1.0), diameter);
714    if (diameter != m_VolumeDiameter) {
715        // Adjust scale so on-screen scale is unchanged.
716        SetScale(m_Scale * diameter / m_VolumeDiameter);
717        m_VolumeDiameter = diameter;
718    }
719}
720
721void GLACanvas::StartDrawing()
722{
723    // Prepare for a redraw operation.
724
725    ctx.SetCurrent(*this);
726    glDepthMask(GL_TRUE);
727
728    if (!save_hints) return;
729
730    // We want to check on the second redraw.
731    static int draw_count = 2;
732    if (--draw_count != 0) return;
733
734    if (cross_method != LINES) {
735        SetColour(col_WHITE);
736        Clear();
737        SetDataTransform();
738        BeginCrosses();
739        DrawCross(-m_Translation.GetX(), -m_Translation.GetY(), -m_Translation.GetZ());
740        EndCrosses();
741        static const unsigned char expected_cross[64 * 3] = {
742#define o 0,0,0
743#define I 255,255,255
744            CROSS_TEXTURE
745#undef o
746#undef I
747        };
748        if (!CheckVisualFidelity(expected_cross)) {
749            cross_method = LINES;
750            save_hints = true;
751        }
752    }
753
754    if (blob_method != LINES) {
755        SetColour(col_WHITE);
756        Clear();
757        SetDataTransform();
758        BeginBlobs();
759        DrawBlob(-m_Translation.GetX(), -m_Translation.GetY(), -m_Translation.GetZ());
760        EndBlobs();
761        static const unsigned char expected_blob[64 * 3] = {
762#define o 0,0,0
763#define I 255,255,255
764            BLOB_TEXTURE
765#undef o
766#undef I
767        };
768        if (!CheckVisualFidelity(expected_blob)) {
769            blob_method = LINES;
770            save_hints = true;
771        }
772    }
773
774    wxConfigBase * cfg = wxConfigBase::Get();
775    cfg->Write(wxT("opengl_survex"), wxT(VERSION));
776    cfg->Write(wxT("opengl_vendor"), vendor);
777    cfg->Write(wxT("opengl_renderer"), renderer);
778    cfg->Write(wxT("blob_method"), blob_method);
779    cfg->Write(wxT("cross_method"), cross_method);
780    cfg->Flush();
781    save_hints = false;
782}
783
784void GLACanvas::EnableSmoothPolygons(bool filled)
785{
786    // Prepare for drawing smoothly-shaded polygons.
787    // Only use this when required (in particular lines in lists may not be
788    // coloured correctly when this is enabled).
789
790    glPushAttrib(GL_ENABLE_BIT|GL_LIGHTING_BIT|GL_POLYGON_BIT);
791    if (filled) {
792        glShadeModel(GL_SMOOTH);
793        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
794    } else {
795        glDisable(GL_LINE_SMOOTH);
796        glDisable(GL_TEXTURE_2D);
797        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
798    }
799    CHECK_GL_ERROR("EnableSmoothPolygons", "glPolygonMode");
800
801    if (filled && m_SmoothShading) {
802        static const GLfloat mat_specular[] = { 0.2, 0.2, 0.2, 1.0 };
803        static const GLfloat light_position[] = { -1.0, -1.0, -1.0, 0.0 };
804        static const GLfloat light_ambient[] = { 0.3, 0.3, 0.3, 1.0 };
805        static const GLfloat light_diffuse[] = { 0.7, 0.7, 0.7, 1.0 };
806        glEnable(GL_COLOR_MATERIAL);
807        glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mat_specular);
808        glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 10.0);
809        glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
810        glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
811        glLightfv(GL_LIGHT0, GL_POSITION, light_position);
812        glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
813        glEnable(GL_LIGHTING);
814        glEnable(GL_LIGHT0);
815    }
816}
817
818void GLACanvas::DisableSmoothPolygons()
819{
820    glPopAttrib();
821}
822
823void GLACanvas::PlaceNormal(const Vector3 &v)
824{
825    // Add a normal (for polygons etc.)
826
827    glNormal3d(v.GetX(), v.GetY(), v.GetZ());
828}
829
830void GLACanvas::SetDataTransform()
831{
832    // Set projection.
833    glMatrixMode(GL_PROJECTION);
834    CHECK_GL_ERROR("SetDataTransform", "glMatrixMode");
835    glLoadIdentity();
836    CHECK_GL_ERROR("SetDataTransform", "glLoadIdentity");
837
838    double aspect = double(y_size) / double(x_size);
839
840    GLdouble near_plane = 1.0;
841    if (m_Perspective) {
842        GLdouble lr = near_plane * tan(rad(25.0));
843        GLdouble far_plane = m_VolumeDiameter * 5 + near_plane; // FIXME: work out properly
844        GLdouble tb = lr * aspect;
845        glFrustum(-lr, lr, -tb, tb, near_plane, far_plane);
846        CHECK_GL_ERROR("SetViewportAndProjection", "glFrustum");
847    } else {
848        near_plane = 0.0;
849        assert(m_Scale != 0.0);
850        GLdouble lr = m_VolumeDiameter / m_Scale * 0.5;
851        GLdouble far_plane = m_VolumeDiameter + near_plane;
852        GLdouble tb = lr;
853        if (aspect >= 1.0) {
854            tb *= aspect;
855        } else {
856            lr /= aspect;
857        }
858        glOrtho(-lr, lr, -tb, tb, near_plane, far_plane);
859        CHECK_GL_ERROR("SetViewportAndProjection", "glOrtho");
860    }
861
862    // Set the modelview transform for drawing data.
863    glMatrixMode(GL_MODELVIEW);
864    CHECK_GL_ERROR("SetDataTransform", "glMatrixMode");
865    glLoadIdentity();
866    CHECK_GL_ERROR("SetDataTransform", "glLoadIdentity");
867    if (m_Perspective) {
868        glTranslated(0.0, 0.0, -near_plane);
869    } else {
870        glTranslated(0.0, 0.0, -0.5 * m_VolumeDiameter);
871    }
872    CHECK_GL_ERROR("SetDataTransform", "glTranslated");
873    // Get axes the correct way around (z upwards, y into screen)
874    glRotated(-90.0, 1.0, 0.0, 0.0);
875    CHECK_GL_ERROR("SetDataTransform", "glRotated");
876    glRotated(-m_Tilt, 1.0, 0.0, 0.0);
877    CHECK_GL_ERROR("SetDataTransform", "glRotated");
878    glRotated(m_Pan, 0.0, 0.0, 1.0);
879    CHECK_GL_ERROR("SetDataTransform", "glRotated");
880    if (m_Perspective) {
881        glTranslated(m_Translation.GetX(),
882                     m_Translation.GetY(),
883                     m_Translation.GetZ());
884        CHECK_GL_ERROR("SetDataTransform", "glTranslated");
885    }
886    if (z_stretch != 1.0) {
887        glScaled(1.0, 1.0, z_stretch);
888        CHECK_GL_ERROR("SetDataTransform", "glScaled");
889    }
890
891    // Save projection matrix.
892    glGetDoublev(GL_PROJECTION_MATRIX, projection_matrix);
893    CHECK_GL_ERROR("SetDataTransform", "glGetDoublev");
894
895    // Save viewport coordinates.
896    glGetIntegerv(GL_VIEWPORT, viewport);
897    CHECK_GL_ERROR("SetDataTransform", "glGetIntegerv");
898
899    // Save modelview matrix.
900    glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix);
901    CHECK_GL_ERROR("SetDataTransform", "glGetDoublev");
902
903    if (!m_Perspective) {
904        // Adjust the translation so we don't change the Z position of the model
905        double X, Y, Z;
906        gluProject(m_Translation.GetX(),
907                   m_Translation.GetY(),
908                   m_Translation.GetZ(),
909                   modelview_matrix, projection_matrix, viewport,
910                   &X, &Y, &Z);
911        double Tx, Ty, Tz;
912        gluUnProject(X, Y, 0.5, modelview_matrix, projection_matrix, viewport,
913                     &Tx, &Ty, &Tz);
914        glTranslated(Tx, Ty, Tz);
915        CHECK_GL_ERROR("SetDataTransform", "glTranslated");
916        glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix);
917    }
918
919    glEnable(GL_DEPTH_TEST);
920    CHECK_GL_ERROR("SetDataTransform", "glEnable GL_DEPTH_TEST");
921
922    if (m_Textured) {
923        glBindTexture(GL_TEXTURE_2D, m_Texture);
924        glEnable(GL_TEXTURE_2D);
925        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
926        CHECK_GL_ERROR("ToggleTextured", "glTexParameteri GL_TEXTURE_WRAP_S");
927        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
928        CHECK_GL_ERROR("ToggleTextured", "glTexParameteri GL_TEXTURE_WRAP_T");
929        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
930        CHECK_GL_ERROR("ToggleTextured", "glTexParameteri GL_TEXTURE_MAG_FILTER");
931        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
932                        GL_LINEAR_MIPMAP_LINEAR);
933        CHECK_GL_ERROR("ToggleTextured", "glTexParameteri GL_TEXTURE_MIN_FILTER");
934        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
935    } else {
936        glDisable(GL_TEXTURE_2D);
937    }
938    if (m_Fog) {
939        glFogf(GL_FOG_START, near_plane);
940        glFogf(GL_FOG_END, near_plane + m_VolumeDiameter);
941        glEnable(GL_FOG);
942    } else {
943        glDisable(GL_FOG);
944    }
945
946    glEnable(GL_BLEND);
947    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
948    if (m_AntiAlias) {
949        glEnable(GL_LINE_SMOOTH);
950    } else {
951        glDisable(GL_LINE_SMOOTH);
952    }
953}
954
955void GLACanvas::SetIndicatorTransform()
956{
957    list_flags |= NEVER_CACHE;
958
959    // Set the modelview transform and projection for drawing indicators.
960
961    glDisable(GL_DEPTH_TEST);
962    CHECK_GL_ERROR("SetIndicatorTransform", "glDisable GL_DEPTH_TEST");
963    glDisable(GL_FOG);
964    CHECK_GL_ERROR("SetIndicatorTransform", "glDisable GL_FOG");
965
966    // Just a simple 2D projection.
967    glMatrixMode(GL_PROJECTION);
968    CHECK_GL_ERROR("SetIndicatorTransform", "glMatrixMode");
969    glLoadIdentity();
970    CHECK_GL_ERROR("SetIndicatorTransform", "glLoadIdentity (2)");
971    gluOrtho2D(0, x_size, 0, y_size);
972    CHECK_GL_ERROR("SetIndicatorTransform", "gluOrtho2D");
973
974    // No modelview transform.
975    glMatrixMode(GL_MODELVIEW);
976    CHECK_GL_ERROR("SetIndicatorTransform", "glMatrixMode");
977    glLoadIdentity();
978    CHECK_GL_ERROR("SetIndicatorTransform", "glLoadIdentity");
979
980    glDisable(GL_TEXTURE_2D);
981    CHECK_GL_ERROR("SetIndicatorTransform", "glDisable GL_TEXTURE_2D");
982    glDisable(GL_BLEND);
983    CHECK_GL_ERROR("SetIndicatorTransform", "glDisable GL_BLEND");
984    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
985    CHECK_GL_ERROR("SetIndicatorTransform", "glTexParameteri GL_TEXTURE_WRAP_S");
986    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
987    CHECK_GL_ERROR("SetIndicatorTransform", "glTexParameteri GL_TEXTURE_WRAP_T");
988    glAlphaFunc(GL_GREATER, 0.5f);
989    CHECK_GL_ERROR("SetIndicatorTransform", "glAlphaFunc");
990    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
991    CHECK_GL_ERROR("SetIndicatorTransform", "glTexParameteri GL_TEXTURE_MAG_FILTER");
992    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
993    CHECK_GL_ERROR("SetIndicatorTransform", "glTexParameteri GL_TEXTURE_MIN_FILTER");
994    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
995    CHECK_GL_ERROR("SetIndicatorTransform", "glHint");
996}
997
998void GLACanvas::FinishDrawing()
999{
1000    // Complete a redraw operation.
1001
1002    if (double_buffered) {
1003        SwapBuffers();
1004    } else {
1005        glFlush();
1006        CHECK_GL_ERROR("FinishDrawing", "glFlush");
1007    }
1008}
1009
1010void GLACanvas::DrawList(unsigned int l)
1011{
1012    // FIXME: uncomment to disable use of lists for debugging:
1013    // GenerateList(l); return;
1014    if (l >= drawing_lists.size()) drawing_lists.resize(l + 1);
1015
1016    // We generate the OpenGL lists lazily to minimise delays on startup.
1017    // So check if we need to generate the OpenGL list now.
1018    if (drawing_lists[l].need_to_generate()) {
1019        // Clear list_flags so that we can note what conditions to invalidate
1020        // the cached OpenGL list on.
1021        list_flags = 0;
1022
1023#ifdef GLA_DEBUG
1024        printf("generating list #%u... ", l);
1025        m_Vertices = 0;
1026#endif
1027        GenerateList(l);
1028#ifdef GLA_DEBUG
1029        printf("done (%d vertices)\n", m_Vertices);
1030#endif
1031        drawing_lists[l].finalise(list_flags);
1032    }
1033
1034    if (!drawing_lists[l].DrawList()) {
1035        // That list isn't cached (which means it probably can't usefully be
1036        // cached).
1037        GenerateList(l);
1038    }
1039}
1040
1041void GLACanvas::DrawListZPrepass(unsigned int l)
1042{
1043    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
1044    DrawList(l);
1045    glDepthMask(GL_FALSE);
1046    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
1047    glDepthFunc(GL_EQUAL);
1048    DrawList(l);
1049    glDepthMask(GL_TRUE);
1050    glDepthFunc(GL_LESS);
1051}
1052
1053void GLACanvas::DrawList2D(unsigned int l, glaCoord x, glaCoord y, double rotation)
1054{
1055    glMatrixMode(GL_PROJECTION);
1056    CHECK_GL_ERROR("DrawList2D", "glMatrixMode");
1057    glPushMatrix();
1058    CHECK_GL_ERROR("DrawList2D", "glPushMatrix");
1059    glTranslated(x, y, 0);
1060    CHECK_GL_ERROR("DrawList2D", "glTranslated");
1061    if (rotation != 0.0) {
1062        glRotated(rotation, 0, 0, -1);
1063        CHECK_GL_ERROR("DrawList2D", "glRotated");
1064    }
1065    DrawList(l);
1066    glMatrixMode(GL_PROJECTION);
1067    CHECK_GL_ERROR("DrawList2D", "glMatrixMode 2");
1068    glPopMatrix();
1069    CHECK_GL_ERROR("DrawList2D", "glPopMatrix");
1070}
1071
1072void GLACanvas::SetColour(const GLAPen& pen, double rgb_scale)
1073{
1074    // Set the colour for subsequent operations.
1075    glColor4f(pen.GetRed() * rgb_scale, pen.GetGreen() * rgb_scale,
1076              pen.GetBlue() * rgb_scale, alpha);
1077}
1078
1079void GLACanvas::SetColour(const GLAPen& pen)
1080{
1081    // Set the colour for subsequent operations.
1082    glColor4d(pen.components[0], pen.components[1], pen.components[2], alpha);
1083}
1084
1085void GLACanvas::SetColour(gla_colour colour, double rgb_scale)
1086{
1087    // Set the colour for subsequent operations.
1088    rgb_scale /= 255.0;
1089    glColor4f(COLOURS[colour].r * rgb_scale,
1090              COLOURS[colour].g * rgb_scale,
1091              COLOURS[colour].b * rgb_scale,
1092              alpha);
1093}
1094
1095void GLACanvas::SetColour(gla_colour colour)
1096{
1097    // Set the colour for subsequent operations.
1098    if (alpha == 1.0) {
1099        glColor3ubv(&COLOURS[colour].r);
1100    } else {
1101        glColor4ub(COLOURS[colour].r,
1102                   COLOURS[colour].g,
1103                   COLOURS[colour].b,
1104                   (unsigned char)(255 * alpha));
1105    }
1106}
1107
1108void GLACanvas::DrawText(glaCoord x, glaCoord y, glaCoord z, const wxString& str)
1109{
1110    // Draw a text string on the current buffer in the current font.
1111    glRasterPos3d(x, y, z);
1112    CHECK_GL_ERROR("DrawText", "glRasterPos3d");
1113    m_Font.write_string(str.data(), str.size());
1114}
1115
1116void GLACanvas::DrawIndicatorText(int x, int y, const wxString& str)
1117{
1118    glRasterPos2d(x, y);
1119    CHECK_GL_ERROR("DrawIndicatorText", "glRasterPos2d");
1120    m_Font.write_string(str.data(), str.size());
1121}
1122
1123void GLACanvas::GetTextExtent(const wxString& str, int * x_ext, int * y_ext) const
1124{
1125    m_Font.get_text_extent(str.data(), str.size(), x_ext, y_ext);
1126}
1127
1128void GLACanvas::BeginQuadrilaterals()
1129{
1130    // Commence drawing of quadrilaterals.
1131
1132    glBegin(GL_QUADS);
1133}
1134
1135void GLACanvas::EndQuadrilaterals()
1136{
1137    // Finish drawing of quadrilaterals.
1138
1139    glEnd();
1140    CHECK_GL_ERROR("EndQuadrilaterals", "glEnd GL_QUADS");
1141}
1142
1143void GLACanvas::BeginLines()
1144{
1145    // Commence drawing of a set of lines.
1146
1147    glBegin(GL_LINES);
1148}
1149
1150void GLACanvas::EndLines()
1151{
1152    // Finish drawing of a set of lines.
1153
1154    glEnd();
1155    CHECK_GL_ERROR("EndLines", "glEnd GL_LINES");
1156}
1157
1158void GLACanvas::BeginTriangles()
1159{
1160    // Commence drawing of a set of triangles.
1161
1162    glBegin(GL_TRIANGLES);
1163}
1164
1165void GLACanvas::EndTriangles()
1166{
1167    // Finish drawing of a set of triangles.
1168
1169    glEnd();
1170    CHECK_GL_ERROR("EndTriangles", "glEnd GL_TRIANGLES");
1171}
1172
1173void GLACanvas::BeginTriangleStrip()
1174{
1175    // Commence drawing of a triangle strip.
1176
1177    glBegin(GL_TRIANGLE_STRIP);
1178}
1179
1180void GLACanvas::EndTriangleStrip()
1181{
1182    // Finish drawing of a triangle strip.
1183
1184    glEnd();
1185    CHECK_GL_ERROR("EndTriangleStrip", "glEnd GL_TRIANGLE_STRIP");
1186}
1187
1188void GLACanvas::BeginPolyline()
1189{
1190    // Commence drawing of a polyline.
1191
1192    glBegin(GL_LINE_STRIP);
1193}
1194
1195void GLACanvas::EndPolyline()
1196{
1197    // Finish drawing of a polyline.
1198
1199    glEnd();
1200    CHECK_GL_ERROR("EndPolyline", "glEnd GL_LINE_STRIP");
1201}
1202
1203void GLACanvas::BeginPolyloop()
1204{
1205    // Commence drawing of a polyloop.
1206
1207    glBegin(GL_LINE_LOOP);
1208}
1209
1210void GLACanvas::EndPolyloop()
1211{
1212    // Finish drawing of a polyloop.
1213
1214    glEnd();
1215    CHECK_GL_ERROR("EndPolyloop", "glEnd GL_LINE_LOOP");
1216}
1217
1218void GLACanvas::BeginPolygon()
1219{
1220    // Commence drawing of a polygon.
1221
1222    glBegin(GL_POLYGON);
1223}
1224
1225void GLACanvas::EndPolygon()
1226{
1227    // Finish drawing of a polygon.
1228
1229    glEnd();
1230    CHECK_GL_ERROR("EndPolygon", "glEnd GL_POLYGON");
1231}
1232
1233void GLACanvas::BeginPoints()
1234{
1235    // Commence drawing points.
1236
1237    glPushAttrib(GL_POINT_BIT);
1238    CHECK_GL_ERROR("BeginPoints", "glPushAttrib");
1239    glPointSize(3);
1240    CHECK_GL_ERROR("BeginPoints", "glPointSize");
1241    glBegin(GL_POINTS);
1242}
1243
1244void GLACanvas::EndPoints()
1245{
1246    // Finish drawing points.
1247
1248    glEnd();
1249    CHECK_GL_ERROR("EndPoints", "glEnd GL_POINTS");
1250    glPopAttrib();
1251    CHECK_GL_ERROR("EndPoints", "glPopAttrib");
1252}
1253
1254void GLACanvas::PlaceVertex(glaCoord x, glaCoord y, glaCoord z)
1255{
1256    // Place a vertex for the current object being drawn.
1257
1258#ifdef GLA_DEBUG
1259    m_Vertices++;
1260#endif
1261    glVertex3d(x, y, z);
1262}
1263
1264void GLACanvas::PlaceVertex(glaCoord x, glaCoord y, glaCoord z,
1265                            glaTexCoord tex_x, glaTexCoord tex_y)
1266{
1267    // Place a vertex for the current object being drawn.
1268
1269#ifdef GLA_DEBUG
1270    m_Vertices++;
1271#endif
1272    glTexCoord2f(tex_x, tex_y);
1273    glVertex3d(x, y, z);
1274}
1275
1276void GLACanvas::PlaceIndicatorVertex(glaCoord x, glaCoord y)
1277{
1278    // Place a vertex for the current indicator object being drawn.
1279
1280    PlaceVertex(x, y, 0.0);
1281}
1282
1283void GLACanvas::BeginBlobs()
1284{
1285    // Commence drawing of a set of blobs.
1286    if (blob_method == SPRITE) {
1287        glPushAttrib(GL_ENABLE_BIT|GL_POINT_BIT);
1288        CHECK_GL_ERROR("BeginBlobs", "glPushAttrib");
1289        glBindTexture(GL_TEXTURE_2D, m_BlobTexture);
1290        CHECK_GL_ERROR("BeginBlobs", "glBindTexture");
1291        glEnable(GL_ALPHA_TEST);
1292        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_ALPHA_TEST");
1293        glPointSize(8);
1294        CHECK_GL_ERROR("BeginBlobs", "glPointSize");
1295        glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE);
1296        CHECK_GL_ERROR("BeginBlobs", "glTexEnvi GL_POINT_SPRITE");
1297        glEnable(GL_TEXTURE_2D);
1298        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_TEXTURE_2D");
1299        glEnable(GL_POINT_SPRITE);
1300        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_POINT_SPRITE");
1301        glBegin(GL_POINTS);
1302    } else if (blob_method == POINT) {
1303        glPushAttrib(GL_ENABLE_BIT);
1304        CHECK_GL_ERROR("BeginBlobs", "glPushAttrib");
1305        glEnable(GL_ALPHA_TEST);
1306        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_ALPHA_TEST");
1307        glEnable(GL_POINT_SMOOTH);
1308        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_POINT_SMOOTH");
1309        glBegin(GL_POINTS);
1310    } else {
1311        glPushAttrib(GL_TRANSFORM_BIT|GL_VIEWPORT_BIT|GL_ENABLE_BIT);
1312        CHECK_GL_ERROR("BeginBlobs", "glPushAttrib");
1313        SetIndicatorTransform();
1314        glEnable(GL_DEPTH_TEST);
1315        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_DEPTH_TEST");
1316        glBegin(GL_LINES);
1317    }
1318}
1319
1320void GLACanvas::EndBlobs()
1321{
1322    // Finish drawing of a set of blobs.
1323    glEnd();
1324    if (blob_method != LINES) {
1325        CHECK_GL_ERROR("EndBlobs", "glEnd GL_POINTS");
1326    } else {
1327        CHECK_GL_ERROR("EndBlobs", "glEnd GL_LINES");
1328    }
1329    glPopAttrib();
1330    CHECK_GL_ERROR("EndBlobs", "glPopAttrib");
1331}
1332
1333void GLACanvas::DrawBlob(glaCoord x, glaCoord y, glaCoord z)
1334{
1335    if (blob_method != LINES) {
1336        // Draw a marker.
1337        PlaceVertex(x, y, z);
1338    } else {
1339        double X, Y, Z;
1340        if (!Transform(Vector3(x, y, z), &X, &Y, &Z)) {
1341            printf("bad transform\n");
1342            return;
1343        }
1344        // Stuff behind us (in perspective view) will get clipped,
1345        // but we can save effort with a cheap check here.
1346        if (Z <= 0) return;
1347
1348        X -= BLOB_DIAMETER * 0.5;
1349        Y -= BLOB_DIAMETER * 0.5;
1350
1351        PlaceVertex(X, Y + 1, Z);
1352        PlaceVertex(X, Y + (BLOB_DIAMETER - 1), Z);
1353
1354        for (int i = 1; i < (BLOB_DIAMETER - 1); ++i) {
1355            PlaceVertex(X + i, Y, Z);
1356            PlaceVertex(X + i, Y + BLOB_DIAMETER, Z);
1357        }
1358
1359        PlaceVertex(X + (BLOB_DIAMETER - 1), Y + 1, Z);
1360        PlaceVertex(X + (BLOB_DIAMETER - 1), Y + (BLOB_DIAMETER - 1), Z);
1361    }
1362#ifdef GLA_DEBUG
1363    m_Vertices++;
1364#endif
1365}
1366
1367void GLACanvas::DrawBlob(glaCoord x, glaCoord y)
1368{
1369    if (blob_method != LINES) {
1370        // Draw a marker.
1371        PlaceVertex(x, y, 0);
1372    } else {
1373        x -= BLOB_DIAMETER * 0.5;
1374        y -= BLOB_DIAMETER * 0.5;
1375
1376        PlaceVertex(x, y + 1, 0);
1377        PlaceVertex(x, y + (BLOB_DIAMETER - 1), 0);
1378
1379        for (int i = 1; i < (BLOB_DIAMETER - 1); ++i) {
1380            PlaceVertex(x + i, y, 0);
1381            PlaceVertex(x + i, y + BLOB_DIAMETER, 0);
1382        }
1383
1384        PlaceVertex(x + (BLOB_DIAMETER - 1), y + 1, 0);
1385        PlaceVertex(x + (BLOB_DIAMETER - 1), y + (BLOB_DIAMETER - 1), 0);
1386    }
1387#ifdef GLA_DEBUG
1388    m_Vertices++;
1389#endif
1390}
1391
1392void GLACanvas::BeginCrosses()
1393{
1394    // Plot crosses.
1395    if (cross_method == SPRITE) {
1396        list_flags |= NEVER_CACHE;
1397        SetDataTransform();
1398        glPushAttrib(GL_ENABLE_BIT|GL_POINT_BIT);
1399        CHECK_GL_ERROR("BeginCrosses", "glPushAttrib");
1400        glBindTexture(GL_TEXTURE_2D, m_CrossTexture);
1401        CHECK_GL_ERROR("BeginCrosses", "glBindTexture");
1402        glEnable(GL_ALPHA_TEST);
1403        CHECK_GL_ERROR("BeginCrosses", "glEnable GL_ALPHA_TEST");
1404        glPointSize(8);
1405        CHECK_GL_ERROR("BeginCrosses", "glPointSize");
1406        glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE);
1407        CHECK_GL_ERROR("BeginCrosses", "glTexEnvi GL_POINT_SPRITE");
1408        glEnable(GL_TEXTURE_2D);
1409        CHECK_GL_ERROR("BeginCrosses", "glEnable GL_TEXTURE_2D");
1410        glEnable(GL_POINT_SPRITE);
1411        CHECK_GL_ERROR("BeginCrosses", "glEnable GL_POINT_SPRITE");
1412        glBegin(GL_POINTS);
1413    } else {
1414        // To get the crosses to appear at a constant size and orientation on
1415        // screen, we plot them in the Indicator transform coordinates (which
1416        // unfortunately means they can't be usefully put in an opengl display
1417        // list).
1418        glPushAttrib(GL_TRANSFORM_BIT|GL_VIEWPORT_BIT|GL_ENABLE_BIT);
1419        CHECK_GL_ERROR("BeginCrosses", "glPushAttrib 2");
1420        SetIndicatorTransform();
1421        // Align line drawing to pixel centres to get pixel-perfect rendering
1422        // (graphics card and driver bugs aside).
1423        glTranslated(-0.5, -0.5, 0);
1424        CHECK_GL_ERROR("BeginCrosses", "glTranslated");
1425        glEnable(GL_DEPTH_TEST);
1426        CHECK_GL_ERROR("BeginCrosses", "glEnable GL_DEPTH_TEST");
1427        glBegin(GL_LINES);
1428    }
1429}
1430
1431void GLACanvas::EndCrosses()
1432{
1433    glEnd();
1434    if (cross_method == SPRITE) {
1435        CHECK_GL_ERROR("EndCrosses", "glEnd GL_POINTS");
1436    } else {
1437        CHECK_GL_ERROR("EndCrosses", "glEnd GL_LINES");
1438    }
1439    glPopAttrib();
1440    CHECK_GL_ERROR("EndCrosses", "glPopAttrib");
1441}
1442
1443void GLACanvas::DrawCross(glaCoord x, glaCoord y, glaCoord z)
1444{
1445    if (cross_method == SPRITE) {
1446        // Draw a marker.
1447        PlaceVertex(x, y, z);
1448    } else {
1449        double X, Y, Z;
1450        if (!Transform(Vector3(x, y, z), &X, &Y, &Z)) {
1451            printf("bad transform\n");
1452            return;
1453        }
1454        // Stuff behind us (in perspective view) will get clipped,
1455        // but we can save effort with a cheap check here.
1456        if (Z <= 0) return;
1457
1458        // Round to integers before adding on the offsets for the
1459        // cross arms to avoid uneven crosses.
1460        X = rint(X);
1461        Y = rint(Y);
1462        // Need to extend lines by an extra pixel (which shouldn't get drawn by
1463        // the diamond-exit rule).
1464        PlaceVertex(X - 3, Y - 3, Z);
1465        PlaceVertex(X + 4, Y + 4, Z);
1466        PlaceVertex(X - 3, Y + 3, Z);
1467        PlaceVertex(X + 4, Y - 4, Z);
1468    }
1469#ifdef GLA_DEBUG
1470    m_Vertices++;
1471#endif
1472}
1473
1474void GLACanvas::DrawRing(glaCoord x, glaCoord y)
1475{
1476    // Draw an unfilled circle of radius 4
1477
1478    // Round to integers to get an even ring.
1479    x = rint(x);
1480    y = rint(y);
1481
1482    glBegin(GL_LINE_LOOP);
1483    PlaceIndicatorVertex(x + 3.5, y - 1.5);
1484    PlaceIndicatorVertex(x + 1.5, y - 3.5);
1485    PlaceIndicatorVertex(x - 1.5, y - 3.5);
1486    PlaceIndicatorVertex(x - 3.5, y - 1.5);
1487    PlaceIndicatorVertex(x - 3.5, y + 1.5);
1488    PlaceIndicatorVertex(x - 1.5, y + 3.5);
1489    PlaceIndicatorVertex(x + 1.5, y + 3.5);
1490    PlaceIndicatorVertex(x + 3.5, y + 1.5);
1491    glEnd();
1492    CHECK_GL_ERROR("DrawRing", "glEnd GL_LINE_LOOP");
1493}
1494
1495void GLACanvas::DrawRectangle(gla_colour fill, gla_colour edge,
1496                              glaCoord x0, glaCoord y0, glaCoord w, glaCoord h)
1497{
1498    // Draw a filled rectangle with an edge in the indicator plane.
1499    // (x0, y0) specify the bottom-left corner of the rectangle and (w, h) the
1500    // size.
1501
1502    SetColour(fill);
1503    BeginQuadrilaterals();
1504    PlaceIndicatorVertex(x0, y0);
1505    PlaceIndicatorVertex(x0 + w, y0);
1506    PlaceIndicatorVertex(x0 + w, y0 + h);
1507    PlaceIndicatorVertex(x0, y0 + h);
1508    EndQuadrilaterals();
1509
1510    if (edge != fill) {
1511        SetColour(edge);
1512        BeginPolyline();
1513        PlaceIndicatorVertex(x0, y0);
1514        PlaceIndicatorVertex(x0 + w, y0);
1515        PlaceIndicatorVertex(x0 + w, y0 + h);
1516        PlaceIndicatorVertex(x0, y0 + h);
1517        PlaceIndicatorVertex(x0, y0);
1518        EndPolyline();
1519    }
1520}
1521
1522void
1523GLACanvas::DrawShadedRectangle(const GLAPen & fill_bot, const GLAPen & fill_top,
1524                               glaCoord x0, glaCoord y0,
1525                               glaCoord w, glaCoord h)
1526{
1527    // Draw a graduated filled rectangle in the indicator plane.
1528    // (x0, y0) specify the bottom-left corner of the rectangle and (w, h) the
1529    // size.
1530
1531    glShadeModel(GL_SMOOTH);
1532    CHECK_GL_ERROR("DrawShadedRectangle", "glShadeModel GL_SMOOTH");
1533    BeginQuadrilaterals();
1534    SetColour(fill_bot);
1535    PlaceIndicatorVertex(x0, y0);
1536    PlaceIndicatorVertex(x0 + w, y0);
1537    SetColour(fill_top);
1538    PlaceIndicatorVertex(x0 + w, y0 + h);
1539    PlaceIndicatorVertex(x0, y0 + h);
1540    EndQuadrilaterals();
1541    glShadeModel(GL_FLAT);
1542    CHECK_GL_ERROR("DrawShadedRectangle", "glShadeModel GL_FLAT");
1543}
1544
1545void GLACanvas::DrawCircle(gla_colour edge, gla_colour fill,
1546                           glaCoord cx, glaCoord cy, glaCoord radius)
1547{
1548    // Draw a filled circle with an edge.
1549    SetColour(fill);
1550    glMatrixMode(GL_MODELVIEW);
1551    CHECK_GL_ERROR("DrawCircle", "glMatrixMode");
1552    glPushMatrix();
1553    CHECK_GL_ERROR("DrawCircle", "glPushMatrix");
1554    glTranslated(cx, cy, 0.0);
1555    CHECK_GL_ERROR("DrawCircle", "glTranslated");
1556    assert(m_Quadric);
1557    gluDisk(m_Quadric, 0.0, radius, 36, 1);
1558    CHECK_GL_ERROR("DrawCircle", "gluDisk");
1559    SetColour(edge);
1560    gluDisk(m_Quadric, radius - 1.0, radius, 36, 1);
1561    CHECK_GL_ERROR("DrawCircle", "gluDisk (2)");
1562    glPopMatrix();
1563    CHECK_GL_ERROR("DrawCircle", "glPopMatrix");
1564}
1565
1566void GLACanvas::DrawSemicircle(gla_colour edge, gla_colour fill,
1567                               glaCoord cx, glaCoord cy,
1568                               glaCoord radius, glaCoord start)
1569{
1570    // Draw a filled semicircle with an edge.
1571    // The semicircle extends from "start" deg to "start"+180 deg (increasing
1572    // clockwise, 0 deg upwards).
1573    SetColour(fill);
1574    glMatrixMode(GL_MODELVIEW);
1575    CHECK_GL_ERROR("DrawSemicircle", "glMatrixMode");
1576    glPushMatrix();
1577    CHECK_GL_ERROR("DrawSemicircle", "glPushMatrix");
1578    glTranslated(cx, cy, 0.0);
1579    CHECK_GL_ERROR("DrawSemicircle", "glTranslated");
1580    assert(m_Quadric);
1581    gluPartialDisk(m_Quadric, 0.0, radius, 36, 1, start, 180.0);
1582    CHECK_GL_ERROR("DrawSemicircle", "gluPartialDisk");
1583    SetColour(edge);
1584    gluPartialDisk(m_Quadric, radius - 1.0, radius, 36, 1, start, 180.0);
1585    CHECK_GL_ERROR("DrawSemicircle", "gluPartialDisk (2)");
1586    glPopMatrix();
1587    CHECK_GL_ERROR("DrawSemicircle", "glPopMatrix");
1588}
1589
1590void GLACanvas::EnableDashedLines()
1591{
1592    // Enable dashed lines, and start drawing in them.
1593
1594    glLineStipple(1, 0x3333);
1595    CHECK_GL_ERROR("EnableDashedLines", "glLineStipple");
1596    glEnable(GL_LINE_STIPPLE);
1597    CHECK_GL_ERROR("EnableDashedLines", "glEnable GL_LINE_STIPPLE");
1598}
1599
1600void GLACanvas::DisableDashedLines()
1601{
1602    glDisable(GL_LINE_STIPPLE);
1603    CHECK_GL_ERROR("DisableDashedLines", "glDisable GL_LINE_STIPPLE");
1604}
1605
1606bool GLACanvas::Transform(const Vector3 & v,
1607                          glaCoord* x_out, glaCoord* y_out, glaCoord* z_out) const
1608{
1609    // Convert from data coordinates to screen coordinates.
1610
1611    // Perform the projection.
1612    return gluProject(v.GetX(), v.GetY(), v.GetZ(),
1613                      modelview_matrix, projection_matrix, viewport,
1614                      x_out, y_out, z_out);
1615}
1616
1617void GLACanvas::ReverseTransform(double x, double y,
1618                                 glaCoord* x_out, glaCoord* y_out, glaCoord* z_out) const
1619{
1620    // Convert from screen coordinates to data coordinates.
1621
1622    // Perform the projection.
1623    gluUnProject(x, y, 0.0, modelview_matrix, projection_matrix, viewport,
1624                 x_out, y_out, z_out);
1625    CHECK_GL_ERROR("ReverseTransform", "gluUnProject");
1626}
1627
1628double GLACanvas::SurveyUnitsAcrossViewport() const
1629{
1630    // Measure the current viewport in survey units, taking into account the
1631    // current display scale.
1632
1633    assert(m_Scale != 0.0);
1634    list_flags |= INVALIDATE_ON_SCALE;
1635    double result = m_VolumeDiameter / m_Scale;
1636    if (y_size < x_size) {
1637        result = result * x_size / y_size;
1638    }
1639    return result;
1640}
1641
1642void GLACanvas::ToggleSmoothShading()
1643{
1644    m_SmoothShading = !m_SmoothShading;
1645}
1646
1647void GLACanvas::ToggleTextured()
1648{
1649    m_Textured = !m_Textured;
1650    if (m_Textured && m_Texture == 0) {
1651        glGenTextures(1, &m_Texture);
1652        CHECK_GL_ERROR("ToggleTextured", "glGenTextures");
1653
1654        glBindTexture(GL_TEXTURE_2D, m_Texture);
1655        CHECK_GL_ERROR("ToggleTextured", "glBindTexture");
1656
1657        ::wxInitAllImageHandlers();
1658
1659        wxImage img;
1660        wxString texture(wmsg_cfgpth());
1661        texture += wxCONFIG_PATH_SEPARATOR;
1662        texture += wxT("images");
1663        texture += wxCONFIG_PATH_SEPARATOR;
1664        texture += wxT("texture.png");
1665        if (!img.LoadFile(texture, wxBITMAP_TYPE_PNG)) {
1666            // FIXME
1667            fprintf(stderr, "Couldn't load image.\n");
1668            exit(1);
1669        }
1670
1671        // Generate mipmaps.
1672        gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, // was GL_LUMINANCE
1673                          img.GetWidth(), img.GetHeight(),
1674                          GL_RGB, GL_UNSIGNED_BYTE, img.GetData());
1675        CHECK_GL_ERROR("ToggleTextured", "gluBuild2DMipmaps");
1676
1677        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
1678        CHECK_GL_ERROR("ToggleTextured", "glTexEnvi");
1679    }
1680}
1681
1682bool GLACanvas::SaveScreenshot(const wxString & fnm, wxBitmapType type) const
1683{
1684    const int width = x_size;
1685    const int height = y_size;
1686    unsigned char *pixels = (unsigned char *)malloc(3 * width * (height + 1));
1687    if (!pixels) return false;
1688    glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid *)pixels);
1689    CHECK_GL_ERROR("SaveScreenshot", "glReadPixels");
1690    unsigned char * tmp_row = pixels + 3 * width * height;
1691    // We need to flip the image vertically - this approach should be more
1692    // efficient than using wxImage::Mirror(false) as that creates a new
1693    // wxImage object.
1694    for (int y = height / 2 - 1; y >= 0; --y) {
1695        unsigned char * upper = pixels + 3 * width * y;
1696        unsigned char * lower = pixels + 3 * width * (height - y - 1);
1697        memcpy(tmp_row, upper, 3 * width);
1698        memcpy(upper, lower, 3 * width);
1699        memcpy(lower, tmp_row, 3 * width);
1700    }
1701    // NB wxImage constructor calls free(pixels) for us.
1702    wxImage grab(width, height, pixels);
1703    return grab.SaveFile(fnm, type);
1704}
1705
1706bool GLACanvas::CheckVisualFidelity(const unsigned char * target) const
1707{
1708    unsigned char pixels[3 * 8 * 8];
1709    if (double_buffered) {
1710        glReadBuffer(GL_BACK);
1711        CHECK_GL_ERROR("FirstShow", "glReadBuffer");
1712    }
1713    glReadPixels(x_size / 2 - 4, y_size / 2 - 5, 8, 8,
1714                 GL_RGB, GL_UNSIGNED_BYTE, (GLvoid *)pixels);
1715    CHECK_GL_ERROR("CheckVisualFidelity", "glReadPixels");
1716    if (double_buffered) {
1717        glReadBuffer(GL_FRONT);
1718        CHECK_GL_ERROR("FirstShow", "glReadBuffer");
1719    }
1720#if 0
1721    // Show what got drawn and what was expected for debugging.
1722    for (int y = 0; y < 8; ++y) {
1723        for (int x = 0; x < 8; ++x) {
1724            int o = (y * 8 + x) * 3;
1725            printf("%c", pixels[o] ? 'X' : '.');
1726        }
1727        printf(" ");
1728        for (int x = 0; x < 8; ++x) {
1729            int o = (y * 8 + x) * 3;
1730            printf("%c", target[o] ? 'X' : '.');
1731        }
1732        printf("\n");
1733    }
1734#endif
1735    return (memcmp(pixels, target, sizeof(pixels)) == 0);
1736}
1737
1738void GLACanvas::ReadPixels(int width, int height, unsigned char * buf) const
1739{
1740    CHECK_GL_ERROR("ReadPixels", "glReadPixels");
1741    glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid *)buf);
1742}
1743
1744void GLACanvas::PolygonOffset(bool on) const
1745{
1746    if (on) {
1747        glPolygonOffset(1.0, 1.0);
1748        glEnable(GL_POLYGON_OFFSET_FILL);
1749    } else {
1750        glDisable(GL_POLYGON_OFFSET_FILL);
1751    }
1752}
Note: See TracBrowser for help on using the repository browser.