source: git/src/gla-gl.cc @ b8f74af

RELEASE/1.2debug-cidebug-ci-sanitisersfaster-cavernloglog-selectstereostereo-2025walls-datawalls-data-hanging-as-warningwarn-only-for-hanging-survey
Last change on this file since b8f74af was caa5fda, checked in by Olly Betts <olly@…>, 7 years ago

Refactor start-up check for OpenGL

Rather than using the apparently totally undocumented wxGLApp class,
use the documented wxGLCanvas::IsDisplaySupported?() static method
which allows moving this OpenGL-specific code into the same source
file as the other OpenGL-specific code, means we can merge the two
divergent versions of the list of attributes we ask for, and also
likely fixes handling of the case when double buffering isn't available.

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