]> Pileus Git - aweather/blob - src/plugins/level2.c
Convert GtkBox and GtkScale to GTK 3 version
[aweather] / src / plugins / level2.c
1 /*
2  * Copyright (C) 2009-2011 Andy Spencer <andy753421@gmail.com>
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 #include <config.h>
19 #include <math.h>
20 #include <glib/gstdio.h>
21 #include <grits.h>
22 #include <rsl.h>
23
24 #include "level2.h"
25
26 #include "compat.h"
27
28 #define ISO_MIN 30
29 #define ISO_MAX 80
30
31 /**************************
32  * Data loading functions *
33  **************************/
34 /* Convert a sweep to an 2d array of data points */
35 static void _bscan_sweep(Sweep *sweep, AWeatherColormap *colormap,
36                 guint8 **data, int *width, int *height)
37 {
38         g_debug("AWeatherLevel2: _bscan_sweep - %p, %p, %p",
39                         sweep, colormap, data);
40         /* Calculate max number of bins */
41         int max_bins = 0;
42         for (int i = 0; i < sweep->h.nrays; i++)
43                 max_bins = MAX(max_bins, sweep->ray[i]->h.nbins);
44
45         /* Allocate buffer using max number of bins for each ray */
46         guint8 *buf = g_malloc0(sweep->h.nrays * max_bins * 4);
47
48         /* Fill the data */
49         for (int ri = 0; ri < sweep->h.nrays; ri++) {
50                 Ray *ray  = sweep->ray[ri];
51                 for (int bi = 0; bi < ray->h.nbins; bi++) {
52                         guint  buf_i = (ri*max_bins+bi)*4;
53                         float  value = ray->h.f(ray->range[bi]);
54
55                         /* Check for bad values */
56                         if (value == BADVAL     || value == RFVAL      || value == APFLAG ||
57                             value == NOTFOUND_H || value == NOTFOUND_V || value == NOECHO) {
58                                 buf[buf_i+3] = 0x00; // transparent
59                                 continue;
60                         }
61
62                         /* Copy color to buffer */
63                         guint8 *data = colormap_get(colormap, value);
64                         buf[buf_i+0] = data[0];
65                         buf[buf_i+1] = data[1];
66                         buf[buf_i+2] = data[2];
67                         buf[buf_i+3] = data[3]*0.75; // TESTING
68                 }
69         }
70
71         /* set output */
72         *width  = max_bins;
73         *height = sweep->h.nrays;
74         *data   = buf;
75 }
76
77 /* Load a sweep into an OpenGL texture */
78 static void _load_sweep_gl(AWeatherLevel2 *level2)
79 {
80         g_debug("AWeatherLevel2: _load_sweep_gl");
81         guint8 *data;
82         gint width, height;
83         _bscan_sweep(level2->sweep, level2->sweep_colors, &data, &width, &height);
84         gint tex_width  = pow(2, ceil(log(width )/log(2)));
85         gint tex_height = pow(2, ceil(log(height)/log(2)));
86         level2->sweep_coords[0] = (double)width  / tex_width;
87         level2->sweep_coords[1] = (double)height / tex_height;
88
89         if (!level2->sweep_tex)
90                  glGenTextures(1, &level2->sweep_tex);
91         glBindTexture(GL_TEXTURE_2D, level2->sweep_tex);
92         glPixelStorei(GL_PACK_ALIGNMENT, 1);
93         glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
94         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, tex_width, tex_height, 0,
95                         GL_RGBA, GL_UNSIGNED_BYTE, NULL);
96         glTexSubImage2D(GL_TEXTURE_2D, 0, 0,0, width,height,
97                         GL_RGBA, GL_UNSIGNED_BYTE, data);
98         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
99         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
100
101         g_free(data);
102 }
103
104 /* Decompress a radar file using wsr88dec */
105 static gboolean _decompress_radar(const gchar *file, const gchar *raw)
106 {
107         g_debug("AWeatherLevel2: _decompress_radar - \n\t%s\n\t%s", file, raw);
108         char *argv[] = {"wsr88ddec", (gchar*)file, (gchar*)raw, NULL};
109         gint status;
110         GError *error = NULL;
111         g_spawn_sync(
112                 NULL,    // const gchar *working_directory
113                 argv,    // gchar **argv
114                 NULL,    // gchar **envp
115                 G_SPAWN_SEARCH_PATH, // GSpawnFlags flags
116                 NULL,    // GSpawnChildSetupFunc child_setup
117                 NULL,    // gpointer user_data
118                 NULL,    // gchar *standard_output
119                 NULL,    // gchar *standard_output
120                 &status, // gint *exit_status
121                 &error); // GError **error
122         if (error) {
123                 g_warning("AWeatherLevel2: _decompress_radar - %s", error->message);
124                 g_error_free(error);
125                 return FALSE;
126         }
127         if (status != 0) {
128                 gchar *msg = g_strdup_printf("wsr88ddec exited with status %d", status);
129                 g_warning("AWeatherLevel2: _decompress_radar - %s", msg);
130                 g_free(msg);
131                 return FALSE;
132         }
133         return TRUE;
134 }
135
136 /* Load the radar into a Grits Volume */
137 static void _cart_to_sphere(VolCoord *out, VolCoord *in)
138 {
139         gdouble angle = in->x;
140         gdouble dist  = in->y;
141         gdouble tilt  = in->z;
142         gdouble lx    = sin(angle);
143         gdouble ly    = cos(angle);
144         gdouble lz    = sin(tilt);
145         //out->x = (ly*dist)/20000;
146         //out->y = (lz*dist)/10000-0.5;
147         //out->z = (lx*dist)/20000-1.5;
148         out->x = (lx*dist);
149         out->y = (ly*dist);
150         out->z = (lz*dist);
151 }
152
153 static VolGrid *_load_grid(Volume *vol)
154 {
155         g_debug("AWeatherLevel2: _load_grid");
156
157         Sweep *sweep   = vol->sweep[0];
158         Ray   *ray     = sweep->ray[0];
159         gint nsweeps   = vol->h.nsweeps;
160         gint nrays     = sweep->h.nrays/(1/sweep->h.beam_width)+1;
161         gint nbins     = ray->h.nbins  /(1000/ray->h.gate_size);
162         nbins = MIN(nbins, 150);
163
164         VolGrid  *grid = vol_grid_new(nrays, nbins, nsweeps);
165
166         gint rs, bs, val;
167         gint si=0, ri=0, bi=0;
168         for (si = 0; si < nsweeps; si++) {
169                 sweep = vol->sweep[si];
170                 rs    = 1.0/sweep->h.beam_width;
171         for (ri = 0; ri < nrays; ri++) {
172                 /* TODO: missing rays, pick ri based on azmith */
173                 ray   = sweep->ray[(ri*rs) % sweep->h.nrays];
174                 bs    = 1000/ray->h.gate_size;
175         for (bi = 0; bi < nbins; bi++) {
176                 if (bi*bs >= ray->h.nbins)
177                         break;
178                 val   = ray->h.f(ray->range[bi*bs]);
179                 if (val == BADVAL     || val == RFVAL      ||
180                     val == APFLAG     || val == NOECHO     ||
181                     val == NOTFOUND_H || val == NOTFOUND_V ||
182                     val > 80)
183                         val = 0;
184                 VolPoint *point = vol_grid_get(grid, ri, bi, si);
185                 point->value = val;
186                 point->c.x = deg2rad(ray->h.azimuth);
187                 point->c.y = bi*bs*ray->h.gate_size + ray->h.range_bin1;
188                 point->c.z = deg2rad(ray->h.elev);
189         } } }
190
191         for (si = 0; si < nsweeps; si++)
192         for (ri = 0; ri < nrays; ri++)
193         for (bi = 0; bi < nbins; bi++) {
194                 VolPoint *point = vol_grid_get(grid, ri, bi, si);
195                 if (point->c.y == 0)
196                         point->value = nan("");
197                 else
198                         _cart_to_sphere(&point->c, &point->c);
199         }
200         return grid;
201 }
202
203
204 /*********************
205  * Drawing functions *
206  *********************/
207 void aweather_level2_draw(GritsObject *_level2, GritsOpenGL *opengl)
208 {
209         AWeatherLevel2 *level2 = AWEATHER_LEVEL2(_level2);
210         if (!level2->sweep || !level2->sweep_tex)
211                 return;
212
213         /* Draw wsr88d */
214         Sweep *sweep = level2->sweep;
215         //glDisable(GL_ALPHA_TEST);
216         glDisable(GL_CULL_FACE);
217         glDisable(GL_LIGHTING);
218         glEnable(GL_TEXTURE_2D);
219         glEnable(GL_POLYGON_OFFSET_FILL);
220         glPolygonOffset(1.0, -2.0);
221         glColor4f(1,1,1,1);
222
223         /* Draw the rays */
224         gdouble xscale = level2->sweep_coords[0];
225         gdouble yscale = level2->sweep_coords[1];
226         glBindTexture(GL_TEXTURE_2D, level2->sweep_tex);
227         glBegin(GL_TRIANGLE_STRIP);
228         for (int ri = 0; ri <= sweep->h.nrays; ri++) {
229                 Ray  *ray = NULL;
230                 double angle = 0;
231                 if (ri < sweep->h.nrays) {
232                         ray = sweep->ray[ri];
233                         angle = deg2rad(ray->h.azimuth - ((double)ray->h.beam_width/2.));
234                 } else {
235                         /* Do the right side of the last sweep */
236                         ray = sweep->ray[ri-1];
237                         angle = deg2rad(ray->h.azimuth + ((double)ray->h.beam_width/2.));
238                 }
239
240                 double lx = sin(angle);
241                 double ly = cos(angle);
242
243                 double near_dist = ray->h.range_bin1 - ((double)ray->h.gate_size/2.);
244                 double far_dist  = near_dist + (double)ray->h.nbins*ray->h.gate_size;
245
246                 /* (find middle of bin) / scale for opengl */
247                 // near left
248                 glTexCoord2f(0.0, ((double)ri/sweep->h.nrays)*yscale);
249                 glVertex3f(lx*near_dist, ly*near_dist, 2.0);
250
251                 // far  left
252                 // todo: correct range-height function
253                 double height = sin(deg2rad(ray->h.elev)) * far_dist;
254                 glTexCoord2f(xscale, ((double)ri/sweep->h.nrays)*yscale);
255                 glVertex3f(lx*far_dist,  ly*far_dist, height);
256         }
257         glEnd();
258         //g_print("ri=%d, nr=%d, bw=%f\n", _ri, sweep->h.nrays, sweep->h.beam_width);
259
260         /* Texture debug */
261         //glBegin(GL_QUADS);
262         //glTexCoord2d( 0.,  0.); glVertex3f(-500.,   0., 0.); // bot left
263         //glTexCoord2d( 0.,  1.); glVertex3f(-500., 500., 0.); // top left
264         //glTexCoord2d( 1.,  1.); glVertex3f( 0.,   500., 3.); // top right
265         //glTexCoord2d( 1.,  0.); glVertex3f( 0.,     0., 3.); // bot right
266         //glEnd();
267 }
268
269 void aweather_level2_hide(GritsObject *_level2, gboolean hidden)
270 {
271         AWeatherLevel2 *level2 = AWEATHER_LEVEL2(_level2);
272         if (level2->volume)
273                 grits_object_hide(GRITS_OBJECT(level2->volume), hidden);
274 }
275
276
277 /***********
278  * Methods *
279  ***********/
280 static gboolean _set_sweep_cb(gpointer _level2)
281 {
282         g_debug("AWeatherLevel2: _set_sweep_cb");
283         AWeatherLevel2 *level2 = _level2;
284         _load_sweep_gl(level2);
285         grits_object_queue_draw(_level2);
286         g_object_unref(level2);
287         return FALSE;
288 }
289 void aweather_level2_set_sweep(AWeatherLevel2 *level2,
290                 int type, float elev)
291 {
292         g_debug("AWeatherLevel2: set_sweep - %d %f", type, elev);
293
294         /* Find sweep */
295         Volume *volume = RSL_get_volume(level2->radar, type);
296         if (!volume) return;
297         level2->sweep = RSL_get_closest_sweep(volume, elev, 90);
298         if (!level2->sweep) return;
299
300         /* Find colormap */
301         level2->sweep_colors = NULL;
302         for (int i = 0; level2->colormap[i].file; i++)
303                 if (level2->colormap[i].type == type)
304                         level2->sweep_colors = &level2->colormap[i];
305         if (!level2->sweep_colors) {
306                 g_warning("AWeatherLevel2: set_sweep - missing colormap[%d]", type);
307                 level2->sweep_colors = &level2->colormap[0];
308         }
309
310         /* Load data */
311         g_object_ref(level2);
312         g_idle_add(_set_sweep_cb, level2);
313 }
314
315 void aweather_level2_set_iso(AWeatherLevel2 *level2, gfloat level)
316 {
317         g_debug("AWeatherLevel2: set_iso - %f", level);
318
319         if (!level2->volume) {
320                 g_debug("AWeatherLevel2: set_iso - creating new volume");
321                 Volume      *rvol = RSL_get_volume(level2->radar, DZ_INDEX);
322                 VolGrid     *grid = _load_grid(rvol);
323                 GritsVolume *vol  = grits_volume_new(grid);
324                 vol->proj = GRITS_VOLUME_CARTESIAN;
325                 vol->disp = GRITS_VOLUME_SURFACE;
326                 GRITS_OBJECT(vol)->center = GRITS_OBJECT(level2)->center;
327                 grits_viewer_add(GRITS_OBJECT(level2)->viewer,
328                                 GRITS_OBJECT(vol), GRITS_LEVEL_WORLD+5, TRUE);
329                 level2->volume = vol;
330         }
331         if (ISO_MIN < level && level < ISO_MAX) {
332                 guint8 *data = colormap_get(&level2->colormap[0], level);
333                 level2->volume->color[0] = data[0];
334                 level2->volume->color[1] = data[1];
335                 level2->volume->color[2] = data[2];
336                 level2->volume->color[3] = data[3];
337                 grits_volume_set_level(level2->volume, level);
338                 grits_object_hide(GRITS_OBJECT(level2->volume), FALSE);
339         } else {
340                 grits_object_hide(GRITS_OBJECT(level2->volume), TRUE);
341         }
342 }
343
344 AWeatherLevel2 *aweather_level2_new(Radar *radar, AWeatherColormap *colormap)
345 {
346         g_debug("AWeatherLevel2: new - %s", radar->h.radar_name);
347         RSL_sort_radar(radar);
348         AWeatherLevel2 *level2 = g_object_new(AWEATHER_TYPE_LEVEL2, NULL);
349         level2->radar    = radar;
350         level2->colormap = colormap;
351         aweather_level2_set_sweep(level2, DZ_INDEX, 0);
352
353         GritsPoint center;
354         Radar_header *h = &radar->h;
355         center.lat  = (double)h->latd + (double)h->latm/60 + (double)h->lats/(60*60);
356         center.lon  = (double)h->lond + (double)h->lonm/60 + (double)h->lons/(60*60);
357         center.elev = h->height;
358         GRITS_OBJECT(level2)->center = center;
359         return level2;
360 }
361
362 AWeatherLevel2 *aweather_level2_new_from_file(const gchar *file, const gchar *site,
363                 AWeatherColormap *colormap)
364 {
365         g_debug("AWeatherLevel2: new_from_file %s %s", site, file);
366
367         /* Decompress radar */
368         gchar *raw = g_strconcat(file, ".raw", NULL);
369         if (g_file_test(raw, G_FILE_TEST_EXISTS)) {
370                 struct stat files, raws;
371                 g_stat(file, &files);
372                 g_stat(raw,  &raws);
373                 if (files.st_mtime > raws.st_mtime)
374                         if (!_decompress_radar(file, raw))
375                                 return NULL;
376         } else {
377                 if (!_decompress_radar(file, raw))
378                         return NULL;
379         }
380
381         /* Load the radar file */
382         RSL_read_these_sweeps("all", NULL);
383         g_debug("AWeatherLevel2: rsl read start");
384         Radar *radar = RSL_wsr88d_to_radar(raw, (gchar*)site);
385         g_debug("AWeatherLevel2: rsl read done");
386         g_free(raw);
387         if (!radar)
388                 return NULL;
389
390         return aweather_level2_new(radar, colormaps);
391 }
392
393 static void _on_sweep_clicked(GtkRadioButton *button, gpointer _level2)
394 {
395         AWeatherLevel2 *level2 = _level2;
396         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))) {
397                 gint type = (glong)g_object_get_data(G_OBJECT(button), "type");
398                 gint elev = (glong)g_object_get_data(G_OBJECT(button), "elev");
399                 aweather_level2_set_sweep(level2, type, (float)elev/100);
400                 //level2->colormap = level2->sweep_colors;
401         }
402 }
403
404 static void _on_iso_changed(GtkRange *range, gpointer _level2)
405 {
406         AWeatherLevel2 *level2 = _level2;
407         gfloat level = gtk_range_get_value(range);
408         aweather_level2_set_iso(level2, level);
409 }
410
411 GtkWidget *aweather_level2_get_config(AWeatherLevel2 *level2)
412 {
413         Radar *radar = level2->radar;
414         g_debug("AWeatherLevel2: get_config - %p, %p", level2, radar);
415         /* Clear existing items */
416         gfloat elev;
417         guint rows = 1, cols = 1, cur_cols;
418         gchar row_label_str[64], col_label_str[64], button_str[64];
419         GtkWidget *row_label, *col_label, *button = NULL, *elev_box = NULL;
420         GtkWidget *table = gtk_table_new(rows, cols, FALSE);
421
422         /* Add date */
423         gchar *date_str = g_strdup_printf("<b><i>%04d-%02d-%02d %02d:%02d</i></b>",
424                         radar->h.year, radar->h.month, radar->h.day,
425                         radar->h.hour, radar->h.minute);
426         GtkWidget *date_label = gtk_label_new(date_str);
427         gtk_label_set_use_markup(GTK_LABEL(date_label), TRUE);
428         gtk_table_attach(GTK_TABLE(table), date_label,
429                         0,1, 0,1, GTK_FILL,GTK_FILL, 5,0);
430         g_free(date_str);
431
432         /* Add sweeps */
433         for (guint vi = 0; vi < radar->h.nvolumes; vi++) {
434                 Volume *vol = radar->v[vi];
435                 if (vol == NULL) continue;
436                 rows++; cols = 1; elev = 0;
437
438                 /* Row label */
439                 g_snprintf(row_label_str, 64, "<b>%s:</b>", vol->h.type_str);
440                 row_label = gtk_label_new(row_label_str);
441                 gtk_label_set_use_markup(GTK_LABEL(row_label), TRUE);
442                 gtk_misc_set_alignment(GTK_MISC(row_label), 1, 0.5);
443                 gtk_table_attach(GTK_TABLE(table), row_label,
444                                 0,1, rows-1,rows, GTK_FILL,GTK_FILL, 5,0);
445
446                 for (guint si = 0; si < vol->h.nsweeps; si++) {
447                         Sweep *sweep = vol->sweep[si];
448                         if (sweep == NULL || sweep->h.elev == 0) continue;
449                         if (sweep->h.elev != elev) {
450                                 cols++;
451                                 elev = sweep->h.elev;
452
453                                 /* Column label */
454                                 g_object_get(table, "n-columns", &cur_cols, NULL);
455                                 if (cols >  cur_cols) {
456                                         g_snprintf(col_label_str, 64, "<b>%.2f°</b>", elev);
457                                         col_label = gtk_label_new(col_label_str);
458                                         gtk_label_set_use_markup(GTK_LABEL(col_label), TRUE);
459                                         gtk_widget_set_size_request(col_label, 50, -1);
460                                         gtk_table_attach(GTK_TABLE(table), col_label,
461                                                         cols-1,cols, 0,1, GTK_FILL,GTK_FILL, 0,0);
462                                 }
463
464                                 elev_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
465                                 gtk_box_set_homogeneous(GTK_BOX(elev_box), TRUE);
466                                 gtk_table_attach(GTK_TABLE(table), elev_box,
467                                                 cols-1,cols, rows-1,rows, GTK_FILL,GTK_FILL, 0,0);
468                         }
469
470
471                         /* Button */
472                         g_snprintf(button_str, 64, "%3.2f", elev);
473                         button = gtk_radio_button_new_with_label_from_widget(
474                                         GTK_RADIO_BUTTON(button), button_str);
475                         gtk_widget_set_size_request(button, -1, 26);
476                         //button = gtk_radio_button_new_from_widget(GTK_RADIO_BUTTON(button));
477                         //gtk_widget_set_size_request(button, -1, 22);
478                         g_object_set(button, "draw-indicator", FALSE, NULL);
479                         gtk_box_pack_end(GTK_BOX(elev_box), button, TRUE, TRUE, 0);
480
481                         g_object_set_data(G_OBJECT(button), "level2", level2);
482                         g_object_set_data(G_OBJECT(button), "type", (gpointer)(guintptr)vi);
483                         g_object_set_data(G_OBJECT(button), "elev", (gpointer)(guintptr)(elev*100));
484                         g_signal_connect(button, "clicked", G_CALLBACK(_on_sweep_clicked), level2);
485                 }
486         }
487
488         /* Add Iso-surface volume */
489         g_object_get(table, "n-columns", &cols, NULL);
490         row_label = gtk_label_new("<b>Isosurface:</b>");
491         gtk_label_set_use_markup(GTK_LABEL(row_label), TRUE);
492         gtk_misc_set_alignment(GTK_MISC(row_label), 1, 0.5);
493         gtk_table_attach(GTK_TABLE(table), row_label,
494                         0,1, rows,rows+1, GTK_FILL,GTK_FILL, 5,0);
495         GtkWidget *scale = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL, ISO_MIN, ISO_MAX, 0.5);
496         gtk_widget_set_size_request(scale, -1, 26);
497         gtk_scale_set_value_pos(GTK_SCALE(scale), GTK_POS_LEFT);
498         gtk_range_set_inverted(GTK_RANGE(scale), TRUE);
499         gtk_range_set_value(GTK_RANGE(scale), ISO_MAX);
500         g_signal_connect(scale, "value-changed", G_CALLBACK(_on_iso_changed), level2);
501         gtk_table_attach(GTK_TABLE(table), scale,
502                         1,cols+1, rows,rows+1, GTK_FILL|GTK_EXPAND,GTK_FILL, 0,0);
503         /* Shove all the buttons to the left, but keep the slider expanded */
504         gtk_table_attach(GTK_TABLE(table), gtk_label_new(""),
505                         cols,cols+1, 0,1, GTK_FILL|GTK_EXPAND,GTK_FILL, 0,0);
506         return table;
507 }
508
509 /****************
510  * GObject code *
511  ****************/
512 G_DEFINE_TYPE(AWeatherLevel2, aweather_level2, GRITS_TYPE_OBJECT);
513 static void aweather_level2_init(AWeatherLevel2 *level2)
514 {
515 }
516 static void aweather_level2_dispose(GObject *_level2)
517 {
518         AWeatherLevel2 *level2 = AWEATHER_LEVEL2(_level2);
519         g_debug("AWeatherLevel2: dispose - %p", _level2);
520         if (level2->volume) {
521                 grits_viewer_remove(GRITS_OBJECT(level2->volume)->viewer,
522                                 GRITS_OBJECT(level2->volume));
523                 level2->volume = NULL;
524         }
525         G_OBJECT_CLASS(aweather_level2_parent_class)->dispose(_level2);
526 }
527 static void aweather_level2_finalize(GObject *_level2)
528 {
529         AWeatherLevel2 *level2 = AWEATHER_LEVEL2(_level2);
530         g_debug("AWeatherLevel2: finalize - %p", _level2);
531         RSL_free_radar(level2->radar);
532         if (level2->sweep_tex)
533                 glDeleteTextures(1, &level2->sweep_tex);
534         G_OBJECT_CLASS(aweather_level2_parent_class)->finalize(_level2);
535 }
536 static void aweather_level2_class_init(AWeatherLevel2Class *klass)
537 {
538         G_OBJECT_CLASS(klass)->dispose  = aweather_level2_dispose;
539         G_OBJECT_CLASS(klass)->finalize = aweather_level2_finalize;
540         GRITS_OBJECT_CLASS(klass)->draw = aweather_level2_draw;
541         GRITS_OBJECT_CLASS(klass)->hide = aweather_level2_hide;
542 }