source: git/src/gla-gl.cc @ 17c483d

RELEASE/1.2debug-cidebug-ci-sanitisersstereowalls-data
Last change on this file since 17c483d was 17c483d, checked in by Olly Betts <olly@…>, 11 years ago

src/gla-gl.cc,src/glbitmapfont.cc: Add checks for errors when reading
the font file.

  • Property mode set to 100644
File size: 44.7 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,2013 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        // We have this message available: Error in format of font file “%s”
402        fprintf(stderr, "Couldn't load font.\n");
403        exit(1);
404    }
405
406    if (blob_method == UNKNOWN) {
407        // Check if we can use GL_POINTS to plot blobs at stations.
408        GLdouble point_size_range[2];
409        glGetDoublev(GL_SMOOTH_POINT_SIZE_RANGE, point_size_range);
410        CHECK_GL_ERROR("FirstShow", "glGetDoublev GL_SMOOTH_POINT_SIZE_RANGE");
411        if (point_size_range[0] <= BLOB_DIAMETER &&
412            point_size_range[1] >= BLOB_DIAMETER) {
413            blob_method = POINT;
414        } else {
415            blob_method = LINES;
416        }
417        save_hints = true;
418    }
419
420    if (blob_method == POINT) {
421        glPointSize(BLOB_DIAMETER);
422        CHECK_GL_ERROR("FirstShow", "glPointSize");
423    }
424
425    // Point sprites provide an easy, fast way for us to draw crosses by
426    // texture mapping GL points.
427    //
428    // If we have OpenGL >= 2.0 then we definitely have GL_POINT_SPRITE.
429    // Otherwise see if we have the GL_ARB_point_sprite or GL_NV_point_sprite
430    // extensions.
431    //
432    // The symbolic constants GL_POINT_SPRITE, GL_POINT_SPRITE_ARB, and
433    // GL_POINT_SPRITE_NV all give the same number so it doesn't matter
434    // which we use.
435    if (cross_method == UNKNOWN) {
436        bool glpoint_sprite = false;
437        float maxSize = 0.0f;
438        glGetFloatv(GL_POINT_SIZE_MAX, &maxSize);
439        if (maxSize >= 8) {
440            glpoint_sprite = (atoi((const char *)glGetString(GL_VERSION)) >= 2);
441            if (!glpoint_sprite) {
442                const char * p = (const char *)glGetString(GL_EXTENSIONS);
443                while (true) {
444                    size_t l = 0;
445                    if (memcmp(p, "GL_ARB_point_sprite", 19) == 0) {
446                        l = 19;
447                    } else if (memcmp(p, "GL_NV_point_sprite", 18) == 0) {
448                        l = 18;
449                    }
450                    if (l) {
451                        p += l;
452                        if (*p == '\0' || *p == ' ') {
453                            glpoint_sprite = true;
454                            break;
455                        }
456                    }
457                    p = strchr(p + 1, ' ');
458                    if (!p) break;
459                    ++p;
460                }
461            }
462        }
463        cross_method = glpoint_sprite ? SPRITE : LINES;
464        save_hints = true;
465    }
466
467    if (cross_method == SPRITE) {
468        glGenTextures(1, &m_CrossTexture);
469        CHECK_GL_ERROR("FirstShow", "glGenTextures");
470        glBindTexture(GL_TEXTURE_2D, m_CrossTexture);
471        CHECK_GL_ERROR("FirstShow", "glBindTexture");
472        // Cross image for drawing crosses using texture mapped point sprites.
473        const unsigned char crossteximage[128] = {
474              0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
475            255,255,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,255,255,  0,  0,
476              0,  0,255,255,  0,  0,  0,  0,  0,  0,255,255,  0,  0,  0,  0,
477              0,  0,  0,  0,255,255,  0,  0,255,255,  0,  0,  0,  0,  0,  0,
478              0,  0,  0,  0,  0,  0,255,255,  0,  0,  0,  0,  0,  0,  0,  0,
479              0,  0,  0,  0,255,255,  0,  0,255,255,  0,  0,  0,  0,  0,  0,
480              0,  0,255,255,  0,  0,  0,  0,  0,  0,255,255,  0,  0,  0,  0,
481            255,255,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,255,255,  0,  0
482        };
483        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
484        CHECK_GL_ERROR("FirstShow", "glPixelStorei");
485        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
486        CHECK_GL_ERROR("FirstShow", "glTexEnvi");
487        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
488        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_WRAP_S");
489        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
490        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_WRAP_T");
491        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, 8, 8, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, (GLvoid *)crossteximage);
492        CHECK_GL_ERROR("FirstShow", "glTexImage2D");
493        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
494        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_MAG_FILTER");
495        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
496        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_MIN_FILTER");
497    }
498}
499
500void GLACanvas::Clear()
501{
502    // Clear the canvas.
503
504    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
505    CHECK_GL_ERROR("Clear", "glClear");
506}
507
508void GLACanvas::SetScale(Double scale)
509{
510    if (scale != m_Scale) {
511        vector<GLAList>::iterator i;
512        for (i = drawing_lists.begin(); i != drawing_lists.end(); ++i) {
513            i->invalidate_if(INVALIDATE_ON_SCALE);
514        }
515
516        m_Scale = scale;
517    }
518}
519
520void GLACanvas::OnSize(wxSizeEvent & event)
521{
522    wxSize size = event.GetSize();
523
524    unsigned int mask = 0;
525    if (size.GetWidth() != x_size) mask |= INVALIDATE_ON_X_RESIZE;
526    if (size.GetHeight() != y_size) mask |= INVALIDATE_ON_Y_RESIZE;
527    if (mask) {
528        vector<GLAList>::iterator i;
529        for (i = drawing_lists.begin(); i != drawing_lists.end(); ++i) {
530            i->invalidate_if(mask);
531        }
532
533        // The width and height go to zero when the panel is dragged right
534        // across so we clamp them to be at least 1 to avoid problems.
535        x_size = size.GetWidth();
536        y_size = size.GetHeight();
537        if (x_size < 1) x_size = 1;
538        if (y_size < 1) y_size = 1;
539    }
540
541    event.Skip();
542
543    if (!opengl_initialised) return;
544
545    // Set viewport.
546    glViewport(0, 0, x_size, y_size);
547    CHECK_GL_ERROR("OnSize", "glViewport");
548}
549
550void GLACanvas::AddTranslationScreenCoordinates(int dx, int dy)
551{
552    // Translate the data by a given amount, specified in screen coordinates.
553
554    // Find out how far the translation takes us in data coordinates.
555    SetDataTransform();
556
557    double x0, y0, z0;
558    double x, y, z;
559    gluUnProject(0.0, 0.0, 0.0, modelview_matrix, projection_matrix, viewport,
560                 &x0, &y0, &z0);
561    CHECK_GL_ERROR("AddTranslationScreenCoordinates", "gluUnProject");
562    gluUnProject(dx, -dy, 0.0, modelview_matrix, projection_matrix, viewport,
563                 &x, &y, &z);
564    CHECK_GL_ERROR("AddTranslationScreenCoordinates", "gluUnProject (2)");
565
566    // Apply the translation.
567    AddTranslation(Vector3(x - x0, y - y0, z - z0));
568}
569
570void GLACanvas::SetVolumeDiameter(glaCoord diameter)
571{
572    // Set the size of the data drawing volume by giving the diameter of the
573    // smallest sphere containing it.
574
575    m_VolumeDiameter = max(glaCoord(1.0), diameter);
576}
577
578void GLACanvas::StartDrawing()
579{
580    // Prepare for a redraw operation.
581
582    ctx.SetCurrent(*this);
583    glDepthMask(GL_TRUE);
584
585    if (!save_hints) return;
586
587    // We want to check on the second redraw.
588    static int draw_count = 2;
589    if (--draw_count != 0) return;
590
591    if (cross_method != LINES) {
592        SetColour(col_WHITE);
593        Clear();
594        SetDataTransform();
595        BeginCrosses();
596        DrawCross(-m_Translation.GetX(), -m_Translation.GetY(), -m_Translation.GetZ());
597        EndCrosses();
598        static const unsigned char expected_cross[64 * 3] = {
599#define o 0,0,0
600#define I 255,255,255
601            o, o, o, o, o, o, o, o,
602            I, o, o, o, o, o, I, o,
603            o, I, o, o, o, I, o, o,
604            o, o, I, o, I, o, o, o,
605            o, o, o, I, o, o, o, o,
606            o, o, I, o, I, o, o, o,
607            o, I, o, o, o, I, o, o,
608            I, o, o, o, o, o, I, o
609#undef o
610#undef I
611        };
612        if (!CheckVisualFidelity(expected_cross)) {
613            cross_method = LINES;
614            save_hints = true;
615        }
616    }
617
618    if (blob_method != LINES) {
619        SetColour(col_WHITE);
620        Clear();
621        SetDataTransform();
622        BeginBlobs();
623        DrawBlob(-m_Translation.GetX(), -m_Translation.GetY(), -m_Translation.GetZ());
624        EndBlobs();
625        static const unsigned char expected_blob[64 * 3] = {
626#define o 0,0,0
627#define I 255,255,255
628            o, o, o, o, o, o, o, o,
629            o, o, o, o, o, o, o, o,
630            o, o, I, I, I, o, o, o,
631            o, I, I, I, I, I, o, o,
632            o, I, I, I, I, I, o, o,
633            o, I, I, I, I, I, o, o,
634            o, o, I, I, I, o, o, o,
635            o, o, o, o, o, o, o, o
636#undef o
637#undef I
638        };
639        if (!CheckVisualFidelity(expected_blob)) {
640            blob_method = LINES;
641            save_hints = true;
642        }
643    }
644
645    wxConfigBase * cfg = wxConfigBase::Get();
646    cfg->Write(wxT("opengl_vendor"), vendor);
647    cfg->Write(wxT("opengl_renderer"), renderer);
648    cfg->Write(wxT("blob_method"), blob_method);
649    cfg->Write(wxT("cross_method"), cross_method);
650    cfg->Flush();
651    save_hints = false;
652}
653
654void GLACanvas::EnableSmoothPolygons(bool filled)
655{
656    // Prepare for drawing smoothly-shaded polygons.
657    // Only use this when required (in particular lines in lists may not be
658    // coloured correctly when this is enabled).
659
660    glPushAttrib(GL_ENABLE_BIT|GL_LIGHTING_BIT|GL_POLYGON_BIT);
661    if (filled) {
662        glShadeModel(GL_SMOOTH);
663        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
664    } else {
665        glDisable(GL_LINE_SMOOTH);
666        glDisable(GL_TEXTURE_2D);
667        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
668    }
669    CHECK_GL_ERROR("EnableSmoothPolygons", "glPolygonMode");
670
671    if (filled && m_SmoothShading) {
672        static const GLfloat mat_specular[] = { 0.2, 0.2, 0.2, 1.0 };
673        static const GLfloat light_position[] = { -1.0, -1.0, -1.0, 0.0 };
674        static const GLfloat light_ambient[] = { 0.3, 0.3, 0.3, 1.0 };
675        static const GLfloat light_diffuse[] = { 0.7, 0.7, 0.7, 1.0 };
676        glEnable(GL_COLOR_MATERIAL);
677        glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mat_specular);
678        glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 10.0);
679        glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
680        glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
681        glLightfv(GL_LIGHT0, GL_POSITION, light_position);
682        glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
683        glEnable(GL_LIGHTING);
684        glEnable(GL_LIGHT0);
685    }
686}
687
688void GLACanvas::DisableSmoothPolygons()
689{
690    glPopAttrib();
691}
692
693void GLACanvas::PlaceNormal(const Vector3 &v)
694{
695    // Add a normal (for polygons etc.)
696
697    glNormal3d(v.GetX(), v.GetY(), v.GetZ());
698}
699
700void GLACanvas::SetDataTransform()
701{
702    // Set projection.
703    glMatrixMode(GL_PROJECTION);
704    CHECK_GL_ERROR("SetDataTransform", "glMatrixMode");
705    glLoadIdentity();
706    CHECK_GL_ERROR("SetDataTransform", "glLoadIdentity");
707
708    double aspect = double(y_size) / double(x_size);
709
710    Double near_plane = 1.0;
711    if (m_Perspective) {
712        Double lr = near_plane * tan(rad(25.0));
713        Double far_plane = m_VolumeDiameter * 5 + near_plane; // FIXME: work out properly
714        Double tb = lr * aspect;
715        glFrustum(-lr, lr, -tb, tb, near_plane, far_plane);
716        CHECK_GL_ERROR("SetViewportAndProjection", "glFrustum");
717    } else {
718        near_plane = 0.0;
719        assert(m_Scale != 0.0);
720        Double lr = m_VolumeDiameter / m_Scale * 0.5;
721        Double far_plane = m_VolumeDiameter + near_plane;
722        Double tb = lr * aspect;
723        glOrtho(-lr, lr, -tb, tb, near_plane, far_plane);
724        CHECK_GL_ERROR("SetViewportAndProjection", "glOrtho");
725    }
726
727    // Set the modelview transform for drawing data.
728    glMatrixMode(GL_MODELVIEW);
729    CHECK_GL_ERROR("SetDataTransform", "glMatrixMode");
730    glLoadIdentity();
731    CHECK_GL_ERROR("SetDataTransform", "glLoadIdentity");
732    if (m_Perspective) {
733        glTranslated(0.0, 0.0, -near_plane);
734    } else {
735        glTranslated(0.0, 0.0, -0.5 * m_VolumeDiameter);
736    }
737    CHECK_GL_ERROR("SetDataTransform", "glTranslated");
738    // Get axes the correct way around (z upwards, y into screen)
739    glRotated(-90.0, 1.0, 0.0, 0.0);
740    CHECK_GL_ERROR("SetDataTransform", "glRotated");
741    glRotated(-m_Tilt, 1.0, 0.0, 0.0);
742    CHECK_GL_ERROR("SetDataTransform", "glRotated");
743    glRotated(m_Pan, 0.0, 0.0, 1.0);
744    CHECK_GL_ERROR("SetDataTransform", "CopyToOpenGL");
745    if (m_Perspective) {
746        glTranslated(m_Translation.GetX(),
747                     m_Translation.GetY(),
748                     m_Translation.GetZ());
749        CHECK_GL_ERROR("SetDataTransform", "glTranslated");
750    }
751
752    // Save projection matrix.
753    glGetDoublev(GL_PROJECTION_MATRIX, projection_matrix);
754    CHECK_GL_ERROR("SetDataTransform", "glGetDoublev");
755
756    // Save viewport coordinates.
757    glGetIntegerv(GL_VIEWPORT, viewport);
758    CHECK_GL_ERROR("SetDataTransform", "glGetIntegerv");
759
760    // Save modelview matrix.
761    glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix);
762    CHECK_GL_ERROR("SetDataTransform", "glGetDoublev");
763
764    if (!m_Perspective) {
765        // Adjust the translation so we don't change the Z position of the model
766        double X, Y, Z;
767        gluProject(m_Translation.GetX(),
768                   m_Translation.GetY(),
769                   m_Translation.GetZ(),
770                   modelview_matrix, projection_matrix, viewport,
771                   &X, &Y, &Z);
772        double Tx, Ty, Tz;
773        gluUnProject(X, Y, 0.5, modelview_matrix, projection_matrix, viewport,
774                     &Tx, &Ty, &Tz);
775        glTranslated(Tx, Ty, Tz);
776        CHECK_GL_ERROR("SetDataTransform", "glTranslated");
777        glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix);
778    }
779
780    glEnable(GL_DEPTH_TEST);
781    CHECK_GL_ERROR("SetDataTransform", "glEnable GL_DEPTH_TEST");
782
783    if (m_Textured) {
784        glBindTexture(GL_TEXTURE_2D, m_Texture);
785        glEnable(GL_TEXTURE_2D);
786        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
787        CHECK_GL_ERROR("ToggleTextured", "glTexParameteri GL_TEXTURE_WRAP_S");
788        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
789        CHECK_GL_ERROR("ToggleTextured", "glTexParameteri GL_TEXTURE_WRAP_T");
790        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
791        CHECK_GL_ERROR("ToggleTextured", "glTexParameteri GL_TEXTURE_MAG_FILTER");
792        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
793                        GL_LINEAR_MIPMAP_LINEAR);
794        CHECK_GL_ERROR("ToggleTextured", "glTexParameteri GL_TEXTURE_MIN_FILTER");
795        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
796    } else {
797        glDisable(GL_TEXTURE_2D);
798    }
799    if (m_Fog) {
800        glFogf(GL_FOG_START, near_plane);
801        glFogf(GL_FOG_END, near_plane + m_VolumeDiameter);
802        glEnable(GL_FOG);
803    } else {
804        glDisable(GL_FOG);
805    }
806
807    if (m_AntiAlias) {
808        glEnable(GL_LINE_SMOOTH);
809        glEnable(GL_BLEND);
810        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
811    } else {
812        glDisable(GL_LINE_SMOOTH);
813    }
814}
815
816void GLACanvas::SetIndicatorTransform()
817{
818    list_flags |= NEVER_CACHE;
819
820    // Set the modelview transform and projection for drawing indicators.
821
822    glDisable(GL_DEPTH_TEST);
823    CHECK_GL_ERROR("SetIndicatorTransform", "glDisable GL_DEPTH_TEST");
824    glDisable(GL_FOG);
825    CHECK_GL_ERROR("SetIndicatorTransform", "glDisable GL_FOG");
826
827    // Just a simple 2D projection.
828    glMatrixMode(GL_PROJECTION);
829    CHECK_GL_ERROR("SetIndicatorTransform", "glMatrixMode");
830    glLoadIdentity();
831    CHECK_GL_ERROR("SetIndicatorTransform", "glLoadIdentity (2)");
832    gluOrtho2D(0, x_size, 0, y_size);
833    CHECK_GL_ERROR("SetIndicatorTransform", "gluOrtho2D");
834
835    // No modelview transform.
836    glMatrixMode(GL_MODELVIEW);
837    CHECK_GL_ERROR("SetIndicatorTransform", "glMatrixMode");
838    glLoadIdentity();
839    CHECK_GL_ERROR("SetIndicatorTransform", "glLoadIdentity");
840
841    glDisable(GL_TEXTURE_2D);
842    CHECK_GL_ERROR("SetIndicatorTransform", "glDisable GL_TEXTURE_2D");
843    glDisable(GL_BLEND);
844    CHECK_GL_ERROR("SetIndicatorTransform", "glDisable GL_BLEND");
845    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
846    CHECK_GL_ERROR("SetIndicatorTransform", "glTexParameteri GL_TEXTURE_WRAP_S");
847    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
848    CHECK_GL_ERROR("SetIndicatorTransform", "glTexParameteri GL_TEXTURE_WRAP_T");
849    glAlphaFunc(GL_GREATER, 0.5f);
850    CHECK_GL_ERROR("SetIndicatorTransform", "glAlphaFunc");
851    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
852    CHECK_GL_ERROR("SetIndicatorTransform", "glTexParameteri GL_TEXTURE_MAG_FILTER");
853    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
854    CHECK_GL_ERROR("SetIndicatorTransform", "glTexParameteri GL_TEXTURE_MIN_FILTER");
855    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
856    CHECK_GL_ERROR("SetIndicatorTransform", "glHint");
857}
858
859void GLACanvas::FinishDrawing()
860{
861    // Complete a redraw operation.
862
863    if (double_buffered) {
864        SwapBuffers();
865    } else {
866        glFlush();
867        CHECK_GL_ERROR("FinishDrawing", "glFlush");
868    }
869}
870
871void GLACanvas::DrawList(unsigned int l)
872{
873    // FIXME: uncomment to disable use of lists for debugging:
874    // GenerateList(l); return;
875    if (l >= drawing_lists.size()) drawing_lists.resize(l + 1);
876
877    // We generate the OpenGL lists lazily to minimise delays on startup.
878    // So check if we need to generate the OpenGL list now.
879    if (drawing_lists[l].need_to_generate()) {
880        // Clear list_flags so that we can note what conditions to invalidate
881        // the cached OpenGL list on.
882        list_flags = 0;
883
884#ifdef GLA_DEBUG
885        printf("generating list #%u... ", l);
886        m_Vertices = 0;
887#endif
888        GenerateList(l);
889#ifdef GLA_DEBUG
890        printf("done (%d vertices)\n", m_Vertices);
891#endif
892        drawing_lists[l].finalise(list_flags);
893    }
894
895    if (!drawing_lists[l].DrawList()) {
896        // That list isn't cached (which means it probably can't usefully be
897        // cached).
898        GenerateList(l);
899    }
900}
901
902void GLACanvas::DrawList2D(unsigned int l, glaCoord x, glaCoord y, Double rotation)
903{
904    glMatrixMode(GL_PROJECTION);
905    CHECK_GL_ERROR("DrawList2D", "glMatrixMode");
906    glPushMatrix();
907    CHECK_GL_ERROR("DrawList2D", "glPushMatrix");
908    glTranslated(x, y, 0);
909    CHECK_GL_ERROR("DrawList2D", "glTranslated");
910    if (rotation != 0.0) {
911        glRotated(rotation, 0, 0, -1);
912        CHECK_GL_ERROR("DrawList2D", "glRotated");
913    }
914    DrawList(l);
915    glMatrixMode(GL_PROJECTION);
916    CHECK_GL_ERROR("DrawList2D", "glMatrixMode 2");
917    glPopMatrix();
918    CHECK_GL_ERROR("DrawList2D", "glPopMatrix");
919}
920
921void GLACanvas::SetColour(const GLAPen& pen, double rgb_scale)
922{
923    // Set the colour for subsequent operations.
924    glColor3f(pen.GetRed() * rgb_scale, pen.GetGreen() * rgb_scale,
925              pen.GetBlue() * rgb_scale);
926}
927
928void GLACanvas::SetColour(const GLAPen& pen)
929{
930    // Set the colour for subsequent operations.
931    glColor3dv(pen.components);
932}
933
934void GLACanvas::SetColour(gla_colour colour)
935{
936    // Set the colour for subsequent operations.
937    glColor3ubv(&COLOURS[colour].r);
938}
939
940void GLACanvas::DrawText(glaCoord x, glaCoord y, glaCoord z, const wxString& str)
941{
942    // Draw a text string on the current buffer in the current font.
943    glRasterPos3d(x, y, z);
944    CHECK_GL_ERROR("DrawText", "glRasterPos3d");
945    m_Font.write_string(str.data(), str.size());
946}
947
948void GLACanvas::DrawIndicatorText(int x, int y, const wxString& str)
949{
950    glRasterPos2d(x, y);
951    CHECK_GL_ERROR("DrawIndicatorText", "glRasterPos2d");
952    m_Font.write_string(str.data(), str.size());
953}
954
955void GLACanvas::GetTextExtent(const wxString& str, int * x_ext, int * y_ext)
956{
957    m_Font.get_text_extent(str.data(), str.size(), x_ext, y_ext);
958}
959
960void GLACanvas::BeginQuadrilaterals()
961{
962    // Commence drawing of quadrilaterals.
963
964    glBegin(GL_QUADS);
965}
966
967void GLACanvas::EndQuadrilaterals()
968{
969    // Finish drawing of quadrilaterals.
970
971    glEnd();
972    CHECK_GL_ERROR("EndQuadrilaterals", "glEnd GL_QUADS");
973}
974
975void GLACanvas::BeginLines()
976{
977    // Commence drawing of a set of lines.
978
979    glBegin(GL_LINES);
980}
981
982void GLACanvas::EndLines()
983{
984    // Finish drawing of a set of lines.
985
986    glEnd();
987    CHECK_GL_ERROR("EndLines", "glEnd GL_LINES");
988}
989
990void GLACanvas::BeginTriangles()
991{
992    // Commence drawing of a set of triangles.
993
994    glBegin(GL_TRIANGLES);
995}
996
997void GLACanvas::EndTriangles()
998{
999    // Finish drawing of a set of triangles.
1000
1001    glEnd();
1002    CHECK_GL_ERROR("EndTriangles", "glEnd GL_TRIANGLES");
1003}
1004
1005void GLACanvas::BeginTriangleStrip()
1006{
1007    // Commence drawing of a triangle strip.
1008
1009    glBegin(GL_TRIANGLE_STRIP);
1010}
1011
1012void GLACanvas::EndTriangleStrip()
1013{
1014    // Finish drawing of a triangle strip.
1015
1016    glEnd();
1017    CHECK_GL_ERROR("EndTriangleStrip", "glEnd GL_TRIANGLE_STRIP");
1018}
1019
1020void GLACanvas::BeginPolyline()
1021{
1022    // Commence drawing of a polyline.
1023
1024    glBegin(GL_LINE_STRIP);
1025}
1026
1027void GLACanvas::EndPolyline()
1028{
1029    // Finish drawing of a polyline.
1030
1031    glEnd();
1032    CHECK_GL_ERROR("EndPolyline", "glEnd GL_LINE_STRIP");
1033}
1034
1035void GLACanvas::BeginPolygon()
1036{
1037    // Commence drawing of a polygon.
1038
1039    glBegin(GL_POLYGON);
1040}
1041
1042void GLACanvas::EndPolygon()
1043{
1044    // Finish drawing of a polygon.
1045
1046    glEnd();
1047    CHECK_GL_ERROR("EndPolygon", "glEnd GL_POLYGON");
1048}
1049
1050void GLACanvas::PlaceVertex(glaCoord x, glaCoord y, glaCoord z)
1051{
1052    // Place a vertex for the current object being drawn.
1053
1054#ifdef GLA_DEBUG
1055    m_Vertices++;
1056#endif
1057    glVertex3d(x, y, z);
1058}
1059
1060void GLACanvas::PlaceVertex(glaCoord x, glaCoord y, glaCoord z,
1061                            glaTexCoord tex_x, glaTexCoord tex_y)
1062{
1063    // Place a vertex for the current object being drawn.
1064
1065#ifdef GLA_DEBUG
1066    m_Vertices++;
1067#endif
1068    glTexCoord2i(tex_x, tex_y);
1069    glVertex3d(x, y, z);
1070}
1071
1072void GLACanvas::PlaceIndicatorVertex(glaCoord x, glaCoord y)
1073{
1074    // Place a vertex for the current indicator object being drawn.
1075
1076    PlaceVertex(x, y, 0.0);
1077}
1078
1079void GLACanvas::BeginBlobs()
1080{
1081    // Commence drawing of a set of blobs.
1082    if (blob_method == POINT) {
1083        glPushAttrib(GL_ENABLE_BIT);
1084        CHECK_GL_ERROR("BeginBlobs", "glPushAttrib");
1085        glEnable(GL_ALPHA_TEST);
1086        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_ALPHA_TEST");
1087        glEnable(GL_POINT_SMOOTH);
1088        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_POINT_SMOOTH");
1089        glBegin(GL_POINTS);
1090    } else {
1091        glPushAttrib(GL_TRANSFORM_BIT|GL_VIEWPORT_BIT|GL_ENABLE_BIT);
1092        CHECK_GL_ERROR("BeginBlobs", "glPushAttrib");
1093        SetIndicatorTransform();
1094        glEnable(GL_DEPTH_TEST);
1095        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_DEPTH_TEST");
1096        glBegin(GL_LINES);
1097    }
1098}
1099
1100void GLACanvas::EndBlobs()
1101{
1102    // Finish drawing of a set of blobs.
1103    glEnd();
1104    if (blob_method == POINT) {
1105        CHECK_GL_ERROR("EndBlobs", "glEnd GL_POINTS");
1106    } else {
1107        CHECK_GL_ERROR("EndBlobs", "glEnd GL_LINES");
1108    }
1109    glPopAttrib();
1110    CHECK_GL_ERROR("EndBlobs", "glPopAttrib");
1111}
1112
1113void GLACanvas::DrawBlob(glaCoord x, glaCoord y, glaCoord z)
1114{
1115    if (blob_method == POINT) {
1116        // Draw a marker.
1117        PlaceVertex(x, y, z);
1118    } else {
1119        double X, Y, Z;
1120        if (!Transform(Vector3(x, y, z), &X, &Y, &Z)) {
1121            printf("bad transform\n");
1122            return;
1123        }
1124        // Stuff behind us (in perspective view) will get clipped,
1125        // but we can save effort with a cheap check here.
1126        if (Z <= 0) return;
1127
1128        X -= BLOB_DIAMETER * 0.5;
1129        Y -= BLOB_DIAMETER * 0.5;
1130
1131        PlaceVertex(X, Y + 1, Z);
1132        PlaceVertex(X, Y + (BLOB_DIAMETER - 1), Z);
1133
1134        for (int i = 1; i < (BLOB_DIAMETER - 1); ++i) {
1135            PlaceVertex(X + i, Y, Z);
1136            PlaceVertex(X + i, Y + BLOB_DIAMETER, Z);
1137        }
1138
1139        PlaceVertex(X + (BLOB_DIAMETER - 1), Y + 1, Z);
1140        PlaceVertex(X + (BLOB_DIAMETER - 1), Y + (BLOB_DIAMETER - 1), Z);
1141    }
1142#ifdef GLA_DEBUG
1143    m_Vertices++;
1144#endif
1145}
1146
1147void GLACanvas::DrawBlob(glaCoord x, glaCoord y)
1148{
1149    if (blob_method == POINT) {
1150        // Draw a marker.
1151        PlaceVertex(x, y, 0);
1152    } else {
1153        x -= BLOB_DIAMETER * 0.5;
1154        y -= BLOB_DIAMETER * 0.5;
1155
1156        PlaceVertex(x, y + 1, 0);
1157        PlaceVertex(x, y + (BLOB_DIAMETER - 1), 0);
1158
1159        for (int i = 1; i < (BLOB_DIAMETER - 1); ++i) {
1160            PlaceVertex(x + i, y, 0);
1161            PlaceVertex(x + i, y + BLOB_DIAMETER, 0);
1162        }
1163
1164        PlaceVertex(x + (BLOB_DIAMETER - 1), y + 1, 0);
1165        PlaceVertex(x + (BLOB_DIAMETER - 1), y + (BLOB_DIAMETER - 1), 0);
1166    }
1167#ifdef GLA_DEBUG
1168    m_Vertices++;
1169#endif
1170}
1171
1172void GLACanvas::BeginCrosses()
1173{
1174    // Plot crosses.
1175    if (cross_method == SPRITE) {
1176        glPushAttrib(GL_ENABLE_BIT|GL_POINT_BIT);
1177        CHECK_GL_ERROR("BeginCrosses", "glPushAttrib");
1178        glBindTexture(GL_TEXTURE_2D, m_CrossTexture);
1179        CHECK_GL_ERROR("BeginCrosses", "glBindTexture");
1180        glEnable(GL_ALPHA_TEST);
1181        CHECK_GL_ERROR("BeginCrosses", "glEnable GL_ALPHA_TEST");
1182        glPointSize(8);
1183        CHECK_GL_ERROR("BeginCrosses", "glPointSize");
1184        glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE);
1185        CHECK_GL_ERROR("BeginCrosses", "glTexEnvi GL_POINT_SPRITE");
1186        glEnable(GL_TEXTURE_2D);
1187        CHECK_GL_ERROR("BeginCrosses", "glEnable GL_TEXTURE_2D");
1188        glEnable(GL_POINT_SPRITE);
1189        CHECK_GL_ERROR("BeginCrosses", "glEnable GL_POINT_SPRITE");
1190        glBegin(GL_POINTS);
1191    } else {
1192        // To get the crosses to appear at a constant size and orientation on
1193        // screen, we plot them in the Indicator transform coordinates (which
1194        // unfortunately means they can't be usefully put in an opengl display
1195        // list).
1196        glPushAttrib(GL_TRANSFORM_BIT|GL_VIEWPORT_BIT|GL_ENABLE_BIT);
1197        CHECK_GL_ERROR("BeginCrosses", "glPushAttrib 2");
1198        SetIndicatorTransform();
1199        glEnable(GL_DEPTH_TEST);
1200        CHECK_GL_ERROR("BeginCrosses", "glEnable GL_DEPTH_TEST");
1201        glBegin(GL_LINES);
1202    }
1203}
1204
1205void GLACanvas::EndCrosses()
1206{
1207    glEnd();
1208    if (cross_method == SPRITE) {
1209        CHECK_GL_ERROR("EndCrosses", "glEnd GL_POINTS");
1210    } else {
1211        CHECK_GL_ERROR("EndCrosses", "glEnd GL_LINES");
1212    }
1213    glPopAttrib();
1214    CHECK_GL_ERROR("EndCrosses", "glPopAttrib");
1215}
1216
1217void GLACanvas::DrawCross(glaCoord x, glaCoord y, glaCoord z)
1218{
1219    if (cross_method == SPRITE) {
1220        PlaceVertex(x, y, z);
1221    } else {
1222        double X, Y, Z;
1223        if (!Transform(Vector3(x, y, z), &X, &Y, &Z)) {
1224            printf("bad transform\n");
1225            return;
1226        }
1227        // Stuff behind us (in perspective view) will get clipped,
1228        // but we can save effort with a cheap check here.
1229        if (Z > 0) {
1230            // Round to integers before adding on the offsets for the
1231            // cross arms to avoid uneven crosses.
1232            X = rint(X);
1233            Y = rint(Y);
1234            PlaceVertex(X - 3, Y - 3, Z);
1235            PlaceVertex(X + 3, Y + 3, Z);
1236            PlaceVertex(X - 3, Y + 3, Z);
1237            PlaceVertex(X + 3, Y - 3, Z);
1238        }
1239    }
1240#ifdef GLA_DEBUG
1241    m_Vertices++;
1242#endif
1243}
1244
1245void GLACanvas::DrawRing(glaCoord x, glaCoord y)
1246{
1247    // Draw an unfilled circle
1248    const Double radius = 4;
1249    assert(m_Quadric);
1250    glMatrixMode(GL_MODELVIEW);
1251    CHECK_GL_ERROR("DrawRing", "glMatrixMode");
1252    glPushMatrix();
1253    CHECK_GL_ERROR("DrawRing", "glPushMatrix");
1254    glTranslated(x, y, 0.0);
1255    CHECK_GL_ERROR("DrawRing", "glTranslated");
1256    gluDisk(m_Quadric, radius - 1.0, radius, 12, 1);
1257    CHECK_GL_ERROR("DrawRing", "gluDisk");
1258    glPopMatrix();
1259    CHECK_GL_ERROR("DrawRing", "glPopMatrix");
1260}
1261
1262void GLACanvas::DrawRectangle(gla_colour edge, gla_colour fill,
1263                              glaCoord x0, glaCoord y0, glaCoord w, glaCoord h)
1264{
1265    // Draw a filled rectangle with an edge in the indicator plane.
1266    // (x0, y0) specify the bottom-left corner of the rectangle and (w, h) the
1267    // size.
1268
1269    SetColour(fill);
1270    BeginQuadrilaterals();
1271    PlaceIndicatorVertex(x0, y0);
1272    PlaceIndicatorVertex(x0 + w, y0);
1273    PlaceIndicatorVertex(x0 + w, y0 + h);
1274    PlaceIndicatorVertex(x0, y0 + h);
1275    EndQuadrilaterals();
1276
1277    if (edge != fill) {
1278        SetColour(edge);
1279        BeginLines();
1280        PlaceIndicatorVertex(x0, y0);
1281        PlaceIndicatorVertex(x0 + w, y0);
1282        PlaceIndicatorVertex(x0 + w, y0 + h);
1283        PlaceIndicatorVertex(x0, y0 + h);
1284        EndLines();
1285    }
1286}
1287
1288void
1289GLACanvas::DrawShadedRectangle(const GLAPen & fill_bot, const GLAPen & fill_top,
1290                               glaCoord x0, glaCoord y0,
1291                               glaCoord w, glaCoord h)
1292{
1293    // Draw a graduated filled rectangle in the indicator plane.
1294    // (x0, y0) specify the bottom-left corner of the rectangle and (w, h) the
1295    // size.
1296
1297    glShadeModel(GL_SMOOTH);
1298    CHECK_GL_ERROR("DrawShadedRectangle", "glShadeModel GL_SMOOTH");
1299    BeginQuadrilaterals();
1300    SetColour(fill_bot);
1301    PlaceIndicatorVertex(x0, y0);
1302    PlaceIndicatorVertex(x0 + w, y0);
1303    SetColour(fill_top);
1304    PlaceIndicatorVertex(x0 + w, y0 + h);
1305    PlaceIndicatorVertex(x0, y0 + h);
1306    EndQuadrilaterals();
1307    glShadeModel(GL_FLAT);
1308    CHECK_GL_ERROR("DrawShadedRectangle", "glShadeModel GL_FLAT");
1309}
1310
1311void GLACanvas::DrawCircle(gla_colour edge, gla_colour fill,
1312                           glaCoord cx, glaCoord cy, glaCoord radius)
1313{
1314    // Draw a filled circle with an edge.
1315    SetColour(fill);
1316    glMatrixMode(GL_MODELVIEW);
1317    CHECK_GL_ERROR("DrawCircle", "glMatrixMode");
1318    glPushMatrix();
1319    CHECK_GL_ERROR("DrawCircle", "glPushMatrix");
1320    glTranslated(cx, cy, 0.0);
1321    CHECK_GL_ERROR("DrawCircle", "glTranslated");
1322    assert(m_Quadric);
1323    gluDisk(m_Quadric, 0.0, radius, 36, 1);
1324    CHECK_GL_ERROR("DrawCircle", "gluDisk");
1325    SetColour(edge);
1326    gluDisk(m_Quadric, radius - 1.0, radius, 36, 1);
1327    CHECK_GL_ERROR("DrawCircle", "gluDisk (2)");
1328    glPopMatrix();
1329    CHECK_GL_ERROR("DrawCircle", "glPopMatrix");
1330}
1331
1332void GLACanvas::DrawSemicircle(gla_colour edge, gla_colour fill,
1333                               glaCoord cx, glaCoord cy,
1334                               glaCoord radius, glaCoord start)
1335{
1336    // Draw a filled semicircle with an edge.
1337    // The semicircle extends from "start" deg to "start"+180 deg (increasing
1338    // clockwise, 0 deg upwards).
1339    SetColour(fill);
1340    glMatrixMode(GL_MODELVIEW);
1341    CHECK_GL_ERROR("DrawSemicircle", "glMatrixMode");
1342    glPushMatrix();
1343    CHECK_GL_ERROR("DrawSemicircle", "glPushMatrix");
1344    glTranslated(cx, cy, 0.0);
1345    CHECK_GL_ERROR("DrawSemicircle", "glTranslated");
1346    assert(m_Quadric);
1347    gluPartialDisk(m_Quadric, 0.0, radius, 36, 1, start, 180.0);
1348    CHECK_GL_ERROR("DrawSemicircle", "gluPartialDisk");
1349    SetColour(edge);
1350    gluPartialDisk(m_Quadric, radius - 1.0, radius, 36, 1, start, 180.0);
1351    CHECK_GL_ERROR("DrawSemicircle", "gluPartialDisk (2)");
1352    glPopMatrix();
1353    CHECK_GL_ERROR("DrawSemicircle", "glPopMatrix");
1354}
1355
1356void
1357GLACanvas::DrawTriangle(gla_colour edge, gla_colour fill,
1358                        const Vector3 &p0, const Vector3 &p1, const Vector3 &p2)
1359{
1360    // Draw a filled triangle with an edge.
1361
1362    SetColour(fill);
1363    BeginTriangles();
1364    PlaceIndicatorVertex(p0.GetX(), p0.GetY());
1365    PlaceIndicatorVertex(p1.GetX(), p1.GetY());
1366    PlaceIndicatorVertex(p2.GetX(), p2.GetY());
1367    EndTriangles();
1368
1369    SetColour(edge);
1370    glBegin(GL_LINE_STRIP);
1371    PlaceIndicatorVertex(p0.GetX(), p0.GetY());
1372    PlaceIndicatorVertex(p1.GetX(), p1.GetY());
1373    PlaceIndicatorVertex(p2.GetX(), p2.GetY());
1374    glEnd();
1375    CHECK_GL_ERROR("DrawTriangle", "glEnd GL_LINE_STRIP");
1376}
1377
1378void GLACanvas::EnableDashedLines()
1379{
1380    // Enable dashed lines, and start drawing in them.
1381
1382    glLineStipple(1, 0x3333);
1383    CHECK_GL_ERROR("EnableDashedLines", "glLineStipple");
1384    glEnable(GL_LINE_STIPPLE);
1385    CHECK_GL_ERROR("EnableDashedLines", "glEnable GL_LINE_STIPPLE");
1386}
1387
1388void GLACanvas::DisableDashedLines()
1389{
1390    glDisable(GL_LINE_STIPPLE);
1391    CHECK_GL_ERROR("DisableDashedLines", "glDisable GL_LINE_STIPPLE");
1392}
1393
1394bool GLACanvas::Transform(const Vector3 & v,
1395                          double* x_out, double* y_out, double* z_out) const
1396{
1397    // Convert from data coordinates to screen coordinates.
1398
1399    // Perform the projection.
1400    return gluProject(v.GetX(), v.GetY(), v.GetZ(),
1401                      modelview_matrix, projection_matrix, viewport,
1402                      x_out, y_out, z_out);
1403}
1404
1405void GLACanvas::ReverseTransform(Double x, Double y,
1406                                 double* x_out, double* y_out, double* z_out) const
1407{
1408    // Convert from screen coordinates to data coordinates.
1409
1410    // Perform the projection.
1411    gluUnProject(x, y, 0.0, modelview_matrix, projection_matrix, viewport,
1412                 x_out, y_out, z_out);
1413    CHECK_GL_ERROR("ReverseTransform", "gluUnProject");
1414}
1415
1416Double GLACanvas::SurveyUnitsAcrossViewport() const
1417{
1418    // Measure the current viewport in survey units, taking into account the
1419    // current display scale.
1420
1421    assert(m_Scale != 0.0);
1422    list_flags |= INVALIDATE_ON_SCALE;
1423    return m_VolumeDiameter / m_Scale;
1424}
1425
1426void GLACanvas::ToggleSmoothShading()
1427{
1428    m_SmoothShading = !m_SmoothShading;
1429}
1430
1431void GLACanvas::ToggleTextured()
1432{
1433    m_Textured = !m_Textured;
1434    if (m_Textured && m_Texture == 0) {
1435        glGenTextures(1, &m_Texture);
1436        CHECK_GL_ERROR("ToggleTextured", "glGenTextures");
1437
1438        glBindTexture(GL_TEXTURE_2D, m_Texture);
1439        CHECK_GL_ERROR("ToggleTextured", "glBindTexture");
1440
1441        ::wxInitAllImageHandlers();
1442
1443        wxImage img;
1444        wxString texture(wmsg_cfgpth());
1445        texture += wxCONFIG_PATH_SEPARATOR;
1446        texture += wxT("icons");
1447        texture += wxCONFIG_PATH_SEPARATOR;
1448        texture += wxT("texture.png");
1449        if (!img.LoadFile(texture, wxBITMAP_TYPE_PNG)) {
1450            // FIXME
1451            fprintf(stderr, "Couldn't load image.\n");
1452            exit(1);
1453        }
1454
1455        // Generate mipmaps.
1456        gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, // was GL_LUMINANCE
1457                          img.GetWidth(), img.GetHeight(),
1458                          GL_RGB, GL_UNSIGNED_BYTE, img.GetData());
1459        CHECK_GL_ERROR("ToggleTextured", "gluBuild2DMipmaps");
1460
1461        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
1462        CHECK_GL_ERROR("ToggleTextured", "glTexEnvi");
1463    }
1464}
1465
1466bool GLACanvas::SaveScreenshot(const wxString & fnm, wxBitmapType type) const
1467{
1468    const int width = x_size;
1469    const int height = y_size;
1470    unsigned char *pixels = (unsigned char *)malloc(3 * width * (height + 1));
1471    if (!pixels) return false;
1472    glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid *)pixels);
1473    CHECK_GL_ERROR("SaveScreenshot", "glReadPixels");
1474    unsigned char * tmp_row = pixels + 3 * width * height;
1475    // We need to flip the image vertically - this approach should be more
1476    // efficient than using wxImage::Mirror(false) as that creates a new
1477    // wxImage object.
1478    for (int y = height / 2 - 1; y >= 0; --y) {
1479        unsigned char * upper = pixels + 3 * width * y;
1480        unsigned char * lower = pixels + 3 * width * (height - y - 1);
1481        memcpy(tmp_row, upper, 3 * width);
1482        memcpy(upper, lower, 3 * width);
1483        memcpy(lower, tmp_row, 3 * width);
1484    }
1485    // NB wxImage constructor calls free(pixels) for us.
1486    wxImage grab(width, height, pixels);
1487    return grab.SaveFile(fnm, type);
1488}
1489
1490bool GLACanvas::CheckVisualFidelity(const unsigned char * target) const
1491{
1492    unsigned char pixels[3 * 8 * 8];
1493    if (double_buffered) {
1494        glReadBuffer(GL_BACK);
1495        CHECK_GL_ERROR("FirstShow", "glReadBuffer");
1496    }
1497    glReadPixels(x_size / 2 - 3, y_size / 2 - 4, 8, 8,
1498                 GL_RGB, GL_UNSIGNED_BYTE, (GLvoid *)pixels);
1499    CHECK_GL_ERROR("CheckVisualFidelity", "glReadPixels");
1500    if (double_buffered) {
1501        glReadBuffer(GL_FRONT);
1502        CHECK_GL_ERROR("FirstShow", "glReadBuffer");
1503    }
1504#if 0
1505    // Show what got drawn and what was expected for debugging.
1506    for (int y = 0; y < 8; ++y) {
1507        for (int x = 0; x < 8; ++x) {
1508            int o = (y * 8 + x) * 3;
1509            printf("%c", pixels[o] ? 'X' : '.');
1510        }
1511        printf(" ");
1512        for (int x = 0; x < 8; ++x) {
1513            int o = (y * 8 + x) * 3;
1514            printf("%c", target[o] ? 'X' : '.');
1515        }
1516        printf("\n");
1517    }
1518#endif
1519    return (memcmp(pixels, target, sizeof(pixels)) == 0);
1520}
1521
1522void GLACanvas::ReadPixels(int width, int height, unsigned char * buf) const
1523{
1524    CHECK_GL_ERROR("ReadPixels", "glReadPixels");
1525    glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid *)buf);
1526}
1527
1528void GLACanvas::PolygonOffset(bool on) const
1529{
1530    if (on) {
1531        glPolygonOffset(1.0, 1.0);
1532        glEnable(GL_POLYGON_OFFSET_FILL);
1533    } else {
1534        glDisable(GL_POLYGON_OFFSET_FILL);
1535    }
1536}
Note: See TracBrowser for help on using the repository browser.