source: git/src/gla-gl.cc @ 6b536b3

RELEASE/1.2debug-cidebug-ci-sanitisersstereowalls-datawalls-data-hanging-as-warning
Last change on this file since 6b536b3 was 8c048fa, checked in by Olly Betts <olly@…>, 11 years ago

src/gla-gl.cc,src/gla.h: Update to use wxGLContext to avoid
deprecation warnings from wx 2.9.

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