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

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

Fix rendering of crosses using point sprites

The texture being used was misaligned relative to the image used
for the visual fidelity check, so the check always failed and
textures would never be used.

Where point sprites are supported, they're probably the fastest
option - on my netbook this change improves FPS by ~6 fold with
crosses on for a large survey.

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