]> Pileus Git - grits/blob - src/gis/gis-opengl.c
Lots of work on libGIS
[grits] / src / gis / gis-opengl.c
1 /*
2  * Copyright (C) 2009 Andy Spencer <spenceal@rose-hulman.edu>
3  * 
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  * 
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 /* Tessellation, "finding intersecting triangles" */
19 /* http://research.microsoft.com/pubs/70307/tr-2006-81.pdf */
20 /* http://www.opengl.org/wiki/Alpha_Blending */
21
22 #include <config.h>
23 #include <math.h>
24 #include <gdk/gdkkeysyms.h>
25 #include <gtk/gtk.h>
26 #include <gtk/gtkgl.h>
27 #include <GL/gl.h>
28 #include <GL/glu.h>
29
30 #include "gis-opengl.h"
31 #include "roam.h"
32 #include "wms.h"
33
34 #define FOV_DIST   2000.0
35 #define MPPX(dist) (4*dist/FOV_DIST)
36
37
38 /*************
39  * ROAM Code *
40  *************/
41 void roam_queue_draw(WmsCacheNode *node, gpointer _self)
42 {
43         gtk_widget_queue_draw(GTK_WIDGET(_self));
44 }
45
46 void roam_height_func(RoamPoint *point, gpointer _self)
47 {
48         GisOpenGL *self = _self;
49
50         gdouble cam_lle[3], cam_xyz[3];
51         gis_view_get_location(self->view, &cam_lle[0], &cam_lle[1], &cam_lle[2]);
52         lle2xyz(cam_lle[0], cam_lle[1], cam_lle[2], &cam_xyz[0], &cam_xyz[1], &cam_xyz[2]);
53
54         gdouble lat, lon, elev;
55         xyz2lle(point->x, point->y, point->z, &lat, &lon, &elev);
56
57         gdouble res = MPPX(distd(cam_xyz, (double*)point));
58         //g_message("lat=%f, lon=%f, res=%f", lat, lon, res);
59
60         WmsCacheNode *node = wms_info_fetch_cache(self->srtm, res, lat, lon, NULL, roam_queue_draw, self);
61
62         if (node) {
63                 WmsBil *bil = node->data;
64
65                 gint w = bil->width;
66                 gint h = bil->height;
67
68                 gdouble xmin  = node->latlon[0];
69                 gdouble ymin  = node->latlon[1];
70                 gdouble xmax  = node->latlon[2];
71                 gdouble ymax  = node->latlon[3];
72
73                 gdouble xdist = xmax - xmin;
74                 gdouble ydist = ymax - ymin;
75
76                 gdouble x =    (lon-xmin)/xdist  * w;
77                 gdouble y = (1-(lat-ymin)/ydist) * h;
78
79                 gdouble x_rem = x - (int)x;
80                 gdouble y_rem = y - (int)y;
81                 guint x_flr = (int)x;
82                 guint y_flr = (int)y;
83
84                 /* TODO: Fix interpolation at edges:
85                  *   - Pad these at the edges instead of wrapping/truncating
86                  *   - Figure out which pixels to index (is 0,0 edge, center, etc) */
87                 gint16 px00 = bil->data[MIN((y_flr  ),h-1)*w + MIN((x_flr  ),w-1)];
88                 gint16 px10 = bil->data[MIN((y_flr  ),h-1)*w + MIN((x_flr+1),w-1)];
89                 gint16 px01 = bil->data[MIN((y_flr+1),h-1)*w + MIN((x_flr  ),w-1)];
90                 gint16 px11 = bil->data[MIN((y_flr+1),h-1)*w + MIN((x_flr+1),w-1)];
91
92                 elev =  px00 * (1-x_rem) * (1-y_rem) +
93                         px10 * (  x_rem) * (1-y_rem) +
94                         px01 * (1-x_rem) * (  y_rem) +
95                         px11 * (  x_rem) * (  y_rem);
96                 //g_message("elev=%f -- %hd %hd %hd %hd",
97                 //      elev, px00, px10, px01, px11);
98         } else {
99                 elev = 0;
100         }
101
102         lle2xyz(lat, lon, elev, &point->x, &point->y, &point->z);
103 }
104
105 void roam_tri_func(RoamTriangle *tri, gpointer _self)
106 {
107         GisOpenGL *self = _self;
108         if (tri->error < 0) return;
109
110         /* Get lat-lon min and maxes for the triangle */
111         gdouble lat[3], lon[3], elev[3];
112         xyz2lle(tri->p.r->x, tri->p.r->y, tri->p.r->z, &lat[0], &lon[0], &elev[0]);
113         xyz2lle(tri->p.m->x, tri->p.m->y, tri->p.m->z, &lat[1], &lon[1], &elev[1]);
114         xyz2lle(tri->p.l->x, tri->p.l->y, tri->p.l->z, &lat[2], &lon[2], &elev[2]);
115         gdouble lat_max = MAX(MAX(lat[0], lat[1]), lat[2]);
116         gdouble lat_min = MIN(MIN(lat[0], lat[1]), lat[2]);
117         gdouble lon_max = MAX(MAX(lon[0], lon[1]), lon[2]);
118         gdouble lon_min = MIN(MIN(lon[0], lon[1]), lon[2]);
119
120         /* Get target resolution */
121         gdouble cam_lle[3], cam_xyz[3];
122         gis_view_get_location(self->view, &cam_lle[0], &cam_lle[1], &cam_lle[2]);
123         lle2xyz(cam_lle[0], cam_lle[1], cam_lle[2], &cam_xyz[0], &cam_xyz[1], &cam_xyz[2]);
124         gdouble distr = distd(cam_xyz, (double*)tri->p.r);
125         gdouble distm = distd(cam_xyz, (double*)tri->p.m);
126         gdouble distl = distd(cam_xyz, (double*)tri->p.l);
127         double res = MPPX(MIN(MIN(distr, distm), distl));
128
129         /* TODO: 
130          *   - Fetch needed textures, not all corners
131          *   - Also fetch center textures that aren't touched by a corner
132          *   - Idea: send {lat,lon}{min,max} to fetch_cache and handle it in the recursion */
133         /* Fetch textures */
134         WmsCacheNode *textures[4] = {
135                 wms_info_fetch_cache(self->bmng, res, lat_min, lon_min, NULL, roam_queue_draw, self),
136                 wms_info_fetch_cache(self->bmng, res, lat_max, lon_min, NULL, roam_queue_draw, self),
137                 wms_info_fetch_cache(self->bmng, res, lat_min, lon_max, NULL, roam_queue_draw, self),
138                 wms_info_fetch_cache(self->bmng, res, lat_max, lon_max, NULL, roam_queue_draw, self),
139         };
140
141         /* Vertex color for hieght map viewing, 8848m == Everest */
142         gfloat colors[] = {
143                 (elev[0]-EARTH_R)/8848,
144                 (elev[1]-EARTH_R)/8848,
145                 (elev[2]-EARTH_R)/8848,
146         };
147
148         /* Draw each texture */
149         /* TODO: Prevent double exposure when of hi-res textures on top of
150          * low-res textures when some high-res textures are not yet loaded. */
151         glBlendFunc(GL_ONE, GL_ZERO);
152         for (int i = 0; i < 4; i++) {
153                 /* Skip missing textures */
154                 if (textures[i] == NULL)
155                         continue;
156                 /* Skip already drawn textures */
157                 switch (i) {
158                 case 3: if (textures[i] == textures[2]) continue;
159                 case 2: if (textures[i] == textures[1]) continue;
160                 case 1: if (textures[i] == textures[0]) continue;
161                 }
162
163                 WmsCacheNode *node = textures[i];
164
165                 if (node->latlon[0] == -180) {
166                         if (lon[0] < -90 || lon[1] < -90 || lon[2] < -90) {
167                                 if (lon[0] > 90) lon[0] -= 360;
168                                 if (lon[1] > 90) lon[1] -= 360;
169                                 if (lon[2] > 90) lon[2] -= 360;
170                         }
171                 } else if (node->latlon[2] == 180.0) {
172                         if (lon[0] < -90) lon[0] += 360;
173                         if (lon[1] < -90) lon[1] += 360;
174                         if (lon[2] < -90) lon[2] += 360;
175                 }
176
177                 gdouble xmin  = node->latlon[0];
178                 gdouble ymin  = node->latlon[1];
179                 gdouble xmax  = node->latlon[2];
180                 gdouble ymax  = node->latlon[3];
181
182                 gdouble xdist = xmax - xmin;
183                 gdouble ydist = ymax - ymin;
184
185                 gdouble xy[][3] = {
186                         {(lon[0]-xmin)/xdist, 1-(lat[0]-ymin)/ydist},
187                         {(lon[1]-xmin)/xdist, 1-(lat[1]-ymin)/ydist},
188                         {(lon[2]-xmin)/xdist, 1-(lat[2]-ymin)/ydist},
189                 };
190
191                 glBindTexture(GL_TEXTURE_2D, *(guint*)node->data);
192
193                 glBegin(GL_TRIANGLES);
194                 glColor3fv(colors); glNormal3dv(tri->p.r->norm); glTexCoord2dv(xy[0]); glVertex3dv((double*)tri->p.r); 
195                 glColor3fv(colors); glNormal3dv(tri->p.m->norm); glTexCoord2dv(xy[1]); glVertex3dv((double*)tri->p.m); 
196                 glColor3fv(colors); glNormal3dv(tri->p.l->norm); glTexCoord2dv(xy[2]); glVertex3dv((double*)tri->p.l); 
197                 glEnd();
198                 glBlendFunc(GL_ONE, GL_ONE);
199         }
200 }
201
202 static void set_camera(GisOpenGL *self)
203 {
204         glMatrixMode(GL_MODELVIEW);
205         glLoadIdentity();
206         double lat, lon, elev, rx, ry, rz;
207         gis_view_get_location(self->view, &lat, &lon, &elev);
208         gis_view_get_rotation(self->view, &rx, &ry, &rz);
209         glRotatef(rx, 1, 0, 0);
210         glRotatef(rz, 0, 0, 1);
211         glTranslatef(0, 0, -elev2rad(elev));
212         glRotatef(lat, 1, 0, 0);
213         glRotatef(-lon, 0, 1, 0);
214 }
215
216 static void set_visuals(GisOpenGL *self)
217 {
218         /* Lighting */
219         glMatrixMode(GL_MODELVIEW);
220         glLoadIdentity();
221         float light_ambient[]  = {0.2f, 0.2f, 0.2f, 1.0f};
222         float light_diffuse[]  = {5.0f, 5.0f, 5.0f, 1.0f};
223         float light_position[] = {-13*EARTH_R, 1*EARTH_R, 3*EARTH_R, 1.0f};
224         glLightfv(GL_LIGHT0, GL_AMBIENT,  light_ambient);
225         glLightfv(GL_LIGHT0, GL_DIFFUSE,  light_diffuse);
226         glLightfv(GL_LIGHT0, GL_POSITION, light_position);
227         glEnable(GL_LIGHT0);
228         glEnable(GL_LIGHTING);
229
230         float material_ambient[]  = {0.2, 0.2, 0.2, 1.0};
231         float material_diffuse[]  = {0.8, 0.8, 0.8, 1.0};
232         float material_specular[] = {0.0, 0.0, 0.0, 1.0};
233         float material_emission[] = {0.0, 0.0, 0.0, 1.0};
234         glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT,  material_ambient);
235         glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE,  material_diffuse);
236         glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, material_specular);
237         glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, material_emission);
238         glDisable(GL_TEXTURE_2D);
239         glDisable(GL_COLOR_MATERIAL);
240
241         /* Camera */
242         set_camera(self);
243
244         /* Misc */
245         gdouble lat, lon, elev;
246         gis_view_get_location(self->view, &lat, &lon, &elev);
247         gdouble rg   = MAX(0, 1-(elev/20000));
248         gdouble blue = MAX(0, 1-(elev/50000));
249         glClearColor(MIN(0.8,rg), MIN(0.8,rg), MIN(1,blue), 1.0f);
250
251         glDisable(GL_ALPHA_TEST);
252
253         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
254         glEnable(GL_BLEND);
255
256         glCullFace(GL_BACK);
257         glEnable(GL_CULL_FACE);
258
259         glClearDepth(1.0);
260         glDepthFunc(GL_LEQUAL);
261         glEnable(GL_DEPTH_TEST);
262
263         glEnable(GL_LINE_SMOOTH);
264
265         glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
266         //glShadeModel(GL_FLAT);
267 }
268
269
270 /*************
271  * Callbacks *
272  *************/
273 static void on_realize(GisOpenGL *self, gpointer _)
274 {
275         set_visuals(self);
276 }
277 static gboolean on_configure(GisOpenGL *self, GdkEventConfigure *event, gpointer _)
278 {
279         g_debug("GisOpenGL: on_confiure");
280         gis_opengl_begin(self);
281
282         double width  = GTK_WIDGET(self)->allocation.width;
283         double height = GTK_WIDGET(self)->allocation.height;
284         glViewport(0, 0, width, height);
285
286         glMatrixMode(GL_PROJECTION);
287         glLoadIdentity();
288         double ang = atan(height/FOV_DIST);
289         gluPerspective(rad2deg(ang)*2, width/height, 1, 20*EARTH_R);
290
291         if (self->sphere)
292                 roam_sphere_update_errors(self->sphere);
293
294         gis_opengl_end(self);
295         return FALSE;
296 }
297
298 static void on_expose_plugin(GisPlugin *plugin, gchar *name, GisOpenGL *self)
299 {
300         set_visuals(self);
301         glMatrixMode(GL_PROJECTION); glPushMatrix();
302         glMatrixMode(GL_MODELVIEW);  glPushMatrix();
303         gis_plugin_expose(plugin);
304         glMatrixMode(GL_PROJECTION); glPopMatrix();
305         glMatrixMode(GL_MODELVIEW);  glPopMatrix();
306 }
307 static gboolean on_expose(GisOpenGL *self, GdkEventExpose *event, gpointer _)
308 {
309         g_debug("GisOpenGL: on_expose - begin");
310         gis_opengl_begin(self);
311
312         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
313
314         set_visuals(self);
315         glEnable(GL_TEXTURE_2D);
316         roam_sphere_draw(self->sphere);
317
318         //glDisable(GL_TEXTURE_2D);
319         //glEnable(GL_COLOR_MATERIAL);
320         //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
321         //roam_sphere_draw(self->sphere);
322
323         //glColor4f(0.0, 0.0, 9.0, 0.6);
324         //glDisable(GL_TEXTURE_2D);
325         //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
326         //roam_sphere_draw(self->sphere);
327
328         gis_plugins_foreach(self->plugins, G_CALLBACK(on_expose_plugin), self);
329
330         set_visuals(self);
331         gis_opengl_end(self);
332         gis_opengl_flush(self);
333         g_debug("GisOpenGL: on_expose - end\n");
334         return FALSE;
335 }
336
337 static gboolean on_button_press(GisOpenGL *self, GdkEventButton *event, gpointer _)
338 {
339         g_debug("GisOpenGL: on_button_press - Grabbing focus");
340         gtk_widget_grab_focus(GTK_WIDGET(self));
341         return TRUE;
342 }
343
344 static gboolean on_key_press(GisOpenGL *self, GdkEventKey *event, gpointer _)
345 {
346         g_debug("GisOpenGL: on_key_press - key=%x, state=%x, plus=%x",
347                         event->keyval, event->state, GDK_plus);
348
349         double lat, lon, elev, pan;
350         gis_view_get_location(self->view, &lat, &lon, &elev);
351         pan = MIN(elev/(EARTH_R/2), 30);
352         guint kv = event->keyval;
353         if      (kv == GDK_Left  || kv == GDK_h) gis_view_pan(self->view,  0,  -pan, 0);
354         else if (kv == GDK_Down  || kv == GDK_j) gis_view_pan(self->view, -pan, 0,   0);
355         else if (kv == GDK_Up    || kv == GDK_k) gis_view_pan(self->view,  pan, 0,   0);
356         else if (kv == GDK_Right || kv == GDK_l) gis_view_pan(self->view,  0,   pan, 0);
357         else if (kv == GDK_minus || kv == GDK_o) gis_view_zoom(self->view, 10./9);
358         else if (kv == GDK_plus  || kv == GDK_i) gis_view_zoom(self->view, 9./10);
359         else if (kv == GDK_H                   ) gis_view_rotate(self->view,  0,  0, -10);
360         else if (kv == GDK_J                   ) gis_view_rotate(self->view,  10, 0,  0);
361         else if (kv == GDK_K                   ) gis_view_rotate(self->view, -10, 0,  0);
362         else if (kv == GDK_L                   ) gis_view_rotate(self->view,  0,  0,  10);
363         return TRUE;
364 }
365
366 static void on_view_changed(GisView *view,
367                 gdouble _1, gdouble _2, gdouble _3, GisOpenGL *self)
368 {
369         gis_opengl_begin(self);
370         set_visuals(self);
371         roam_sphere_update_errors(self->sphere);
372         gis_opengl_redraw(self);
373         gis_opengl_end(self);
374 }
375
376 static gboolean on_idle(GisOpenGL *self)
377 {
378         gis_opengl_begin(self);
379         if (roam_sphere_split_merge(self->sphere))
380                 gis_opengl_redraw(self);
381         gis_opengl_end(self);
382         return TRUE;
383 }
384
385
386 /***********
387  * Methods *
388  ***********/
389 GisOpenGL *gis_opengl_new(GisWorld *world, GisView *view, GisPlugins *plugins)
390 {
391         g_debug("GisOpenGL: new");
392         GisOpenGL *self = g_object_new(GIS_TYPE_OPENGL, NULL);
393         self->world   = world;
394         self->view    = view;
395         self->plugins = plugins;
396         g_object_ref(world);
397         g_object_ref(view);
398
399         g_signal_connect(self->view, "location-changed", G_CALLBACK(on_view_changed), self);
400         g_signal_connect(self->view, "rotation-changed", G_CALLBACK(on_view_changed), self);
401
402         /* TODO: update point eights sometime later so we have heigh-res heights for them */
403         self->sphere = roam_sphere_new(roam_tri_func, roam_height_func, self);
404
405         return g_object_ref(self);
406 }
407
408 void gis_opengl_center_position(GisOpenGL *self, gdouble lat, gdouble lon, gdouble elev)
409 {
410         set_camera(self);
411         glRotatef(lon, 0, 1, 0);
412         glRotatef(-lat, 1, 0, 0);
413         glTranslatef(0, 0, elev2rad(elev));
414 }
415
416 void gis_opengl_redraw(GisOpenGL *self)
417 {
418         g_debug("GisOpenGL: gl_redraw");
419         gtk_widget_queue_draw(GTK_WIDGET(self));
420 }
421 void gis_opengl_begin(GisOpenGL *self)
422 {
423         g_assert(GIS_IS_OPENGL(self));
424
425         GdkGLContext   *glcontext  = gtk_widget_get_gl_context(GTK_WIDGET(self));
426         GdkGLDrawable  *gldrawable = gtk_widget_get_gl_drawable(GTK_WIDGET(self));
427
428         if (!gdk_gl_drawable_gl_begin(gldrawable, glcontext))
429                 g_assert_not_reached();
430 }
431 void gis_opengl_end(GisOpenGL *self)
432 {
433         g_assert(GIS_IS_OPENGL(self));
434         GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable(GTK_WIDGET(self));
435         gdk_gl_drawable_gl_end(gldrawable);
436 }
437 void gis_opengl_flush(GisOpenGL *self)
438 {
439         g_assert(GIS_IS_OPENGL(self));
440         GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable(GTK_WIDGET(self));
441         if (gdk_gl_drawable_is_double_buffered(gldrawable))
442                 gdk_gl_drawable_swap_buffers(gldrawable);
443         else
444                 glFlush();
445         gdk_gl_drawable_gl_end(gldrawable);
446 }
447
448
449 /****************
450  * GObject code *
451  ****************/
452 G_DEFINE_TYPE(GisOpenGL, gis_opengl, GTK_TYPE_DRAWING_AREA);
453 static void gis_opengl_init(GisOpenGL *self)
454 {
455         g_debug("GisOpenGL: init");
456         self->bmng = wms_info_new_for_bmng(NULL, NULL);
457         self->srtm = wms_info_new_for_srtm(NULL, NULL);
458
459         /* OpenGL setup */
460         GdkGLConfig *glconfig = gdk_gl_config_new_by_mode(
461                         GDK_GL_MODE_RGBA   | GDK_GL_MODE_DEPTH |
462                         GDK_GL_MODE_DOUBLE | GDK_GL_MODE_ALPHA);
463         if (!glconfig)
464                 g_error("Failed to create glconfig");
465         if (!gtk_widget_set_gl_capability(GTK_WIDGET(self),
466                                 glconfig, NULL, TRUE, GDK_GL_RGBA_TYPE))
467                 g_error("GL lacks required capabilities");
468         g_object_unref(glconfig);
469
470         gtk_widget_set_size_request(GTK_WIDGET(self), 600, 550);
471         gtk_widget_set_events(GTK_WIDGET(self),
472                         GDK_BUTTON_PRESS_MASK |
473                         GDK_ENTER_NOTIFY_MASK |
474                         GDK_KEY_PRESS_MASK);
475         g_object_set(self, "can-focus", TRUE, NULL);
476
477         self->sm_source = g_timeout_add(10, (GSourceFunc)on_idle, self);
478
479         g_signal_connect(self, "realize",            G_CALLBACK(on_realize),      NULL);
480         g_signal_connect(self, "configure-event",    G_CALLBACK(on_configure),    NULL);
481         g_signal_connect(self, "expose-event",       G_CALLBACK(on_expose),       NULL);
482
483         g_signal_connect(self, "button-press-event", G_CALLBACK(on_button_press), NULL);
484         g_signal_connect(self, "enter-notify-event", G_CALLBACK(on_button_press), NULL);
485         g_signal_connect(self, "key-press-event",    G_CALLBACK(on_key_press),    NULL);
486 }
487 static GObject *gis_opengl_constructor(GType gtype, guint n_properties,
488                 GObjectConstructParam *properties)
489 {
490         g_debug("GisOpengl: constructor");
491         GObjectClass *parent_class = G_OBJECT_CLASS(gis_opengl_parent_class);
492         return parent_class->constructor(gtype, n_properties, properties);
493 }
494 static void gis_opengl_dispose(GObject *_self)
495 {
496         g_debug("GisOpenGL: dispose");
497         GisOpenGL *self = GIS_OPENGL(_self);
498         if (self->sm_source) {
499                 g_source_remove(self->sm_source);
500                 self->sm_source = 0;
501         }
502         if (self->sphere) {
503                 roam_sphere_free(self->sphere);
504                 self->sphere = NULL;
505         }
506         if (self->world) {
507                 g_object_unref(self->world);
508                 self->world = NULL;
509         }
510         if (self->view) {
511                 g_object_unref(self->view);
512                 self->view = NULL;
513         }
514         G_OBJECT_CLASS(gis_opengl_parent_class)->dispose(_self);
515 }
516 static void gis_opengl_finalize(GObject *_self)
517 {
518         g_debug("GisOpenGL: finalize");
519         GisOpenGL *self = GIS_OPENGL(_self);
520         wms_info_free(self->bmng);
521         wms_info_free(self->srtm);
522         G_OBJECT_CLASS(gis_opengl_parent_class)->finalize(_self);
523 }
524 static void gis_opengl_class_init(GisOpenGLClass *klass)
525 {
526         g_debug("GisOpenGL: class_init");
527         GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
528         gobject_class->constructor  = gis_opengl_constructor;
529         gobject_class->dispose      = gis_opengl_dispose;
530         gobject_class->finalize     = gis_opengl_finalize;
531 }