source: git/src/gla-gl.cc @ 53496ab3

RELEASE/1.2debug-cidebug-ci-sanitisersstereowalls-data
Last change on this file since 53496ab3 was 963e611, checked in by Olly Betts <olly@…>, 10 years ago

src/gla-gl.cc: Split log_fl_error helper function out of
CHECK_GL_ERROR, which will reduce code size and also the number of
deprecation warnings about gluErrorString on Mac OS X 10.9.

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