source: git/src/gla-gl.cc @ 81b44f0

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

Pick initial scale based on min(width, height)

Previously we always used the window width, which can result in
parts of the cave being outside the initial view. Reported by
Wookey.

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