source: git/src/gla-gl.cc @ 4a1cede

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

Use OpenGL alpha to draw faded splay legs

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