source: git/src/gla-gl.cc

main
Last change on this file was 8a7804fb, checked in by Olly Betts <olly@…>, 3 months ago

View volume now includes terrain vertical extent

Previously this could lead to the terrain not being fully visible
or even being entirely hidden in cases where the vertical difference
between the terrain and the cave was extreme.

These changes also improve the handling of scale when reloading an
updated version of the .3d file already being viewed.

Hopefully fixes #148, reported by Chris Curry.

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