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

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

Use OpenGL alpha to draw faded splay legs

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