source: git/src/gla-gl.cc @ 739f15f

Last change on this file since 739f15f was 739f15f, checked in by Olly Betts <olly@…>, 11 years ago

Use OpenGL alpha to draw faded splay legs

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