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

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

src/aven.cc,src/aven.h,src/gla-gl.cc: Explicitly request
double-buffering, which seems to be needed for systems with GLX >=
1.3. Report GLX version and whether double-buffering is in use in
the about dialog.

git-svn-id: file:///home/survex-svn/survex/trunk@3623 4b37db11-9a0c-4f06-9ece-9ab7cdaee568

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