From http://www.jwz.org/xscreensaver/xscreensaver-5.18.tar.gz
[xscreensaver] / hacks / glx / surfaces.c
1 /* Surface --- Parametric 3d surfaces visualization */
2
3 /*
4  * Revision History:
5  * 2000: written by Andrey Mirtchovski <mirtchov@cpsc.ucalgary.ca>
6  *       
7  * 01-Mar-2003  mirtchov    Modified as a xscreensaver hack.
8  * 01-jan-2009  steger      Renamed from klein.c to surfaces.c.
9  *                          Removed the Klein bottle.
10  *                          Added many new surfaces.
11  *                          Added many command line options.
12  *
13  */
14
15 /* surfaces to draw */
16 #define SURFACE_RANDOM            -1
17 #define SURFACE_DINI              0
18 #define SURFACE_ENNEPER           1
19 #define SURFACE_KUEN              2
20 #define SURFACE_MOEBIUS           3
21 #define SURFACE_SEASHELL          4
22 #define SURFACE_SWALLOWTAIL       5
23 #define SURFACE_BOHEMIAN          6
24 #define SURFACE_WHITNEY           7
25 #define SURFACE_PLUECKER          8
26 #define SURFACE_HENNEBERG         9
27 #define SURFACE_CATALAN           10
28 #define SURFACE_CORKSCREW         11
29 #define NUM_SURFACES              12
30
31 /* primitives to draw with 
32  * note that we skip the polygons and
33  * triangle fans -- too slow
34  *
35  * also removed triangle_strip and quads -- 
36  * just doesn't look good enough
37  */
38 #define RENDER_RANDOM             -1
39 #define RENDER_POINTS             0
40 #define RENDER_LINES              1
41 #define RENDER_LINE_LOOP          2
42 #define NUM_RENDER                3
43
44 #ifdef STANDALONE
45 # define DEFAULTS                   "*delay:        20000   \n" \
46                                     "*showFPS:      False   \n"
47
48 # define refresh_surface 0
49 # include "xlockmore.h"     /* from the xscreensaver distribution */
50 #else  /* !STANDALONE */
51 # include "xlock.h"         /* from the xlockmore distribution */
52 #endif /* !STANDALONE */
53
54 #ifdef USE_GL
55
56 #define DEF_SURFACE      "random"
57 #define DEF_MODE         "random"
58 #define DEF_SPIN         "True"
59 #define DEF_WANDER       "False"
60 #define DEF_SPEED        "300"
61
62 #include "rotator.h"
63 #include "gltrackball.h"
64
65 #undef countof
66 #define countof(x) (sizeof((x))/sizeof((*x)))
67
68
69 static char *surface_type;
70 static char *render_mode;
71 static int render;
72 static int speed;
73 static Bool do_spin;
74 static Bool do_wander;
75
76 static XrmOptionDescRec opts[] = {
77   { "-surface",        ".surface", XrmoptionSepArg, 0 },
78   { "-random-surface", ".surface", XrmoptionNoArg,  "random" },
79   { "-dini",           ".surface", XrmoptionNoArg,  "dini" },
80   { "-enneper",        ".surface", XrmoptionNoArg,  "enneper" },
81   { "-kuen",           ".surface", XrmoptionNoArg,  "kuen" },
82   { "-moebius",        ".surface", XrmoptionNoArg,  "moebius" },
83   { "-seashell",       ".surface", XrmoptionNoArg,  "seashell" },
84   { "-swallowtail",    ".surface", XrmoptionNoArg,  "swallowtail" },
85   { "-bohemian",       ".surface", XrmoptionNoArg,  "bohemian" },
86   { "-whitney",        ".surface", XrmoptionNoArg,  "whitney" },
87   { "-pluecker",       ".surface", XrmoptionNoArg,  "pluecker" },
88   { "-henneberg",      ".surface", XrmoptionNoArg,  "henneberg" },
89   { "-catalan",        ".surface", XrmoptionNoArg,  "catalan" },
90   { "-corkscrew",      ".surface", XrmoptionNoArg,  "corkscrew" },
91   { "-mode",           ".mode",    XrmoptionSepArg, 0 },
92   { "-random-mode",    ".mode",    XrmoptionNoArg,  "random" },
93   { "-points",         ".mode",    XrmoptionNoArg,  "points" },
94   { "-lines",          ".mode",    XrmoptionNoArg,  "lines" },
95   { "-line-loops",     ".mode",    XrmoptionNoArg,  "line-loops" },
96   { "-speed",          ".speed",   XrmoptionSepArg, 0 },
97   { "-spin",           ".spin",    XrmoptionNoArg, "True" },
98   { "+spin",           ".spin",    XrmoptionNoArg, "False" },
99   { "-wander",         ".wander",  XrmoptionNoArg, "True" },
100   { "+wander",         ".wander",  XrmoptionNoArg, "False" },
101 };
102
103 static argtype vars[] = {
104   {&surface_type, "surface", "Surface", DEF_SURFACE, t_String },
105   {&render_mode,  "mode",    "Mode",    DEF_MODE,    t_String },
106   {&do_spin,      "spin",    "Spin",    DEF_SPIN,    t_Bool },
107   {&do_wander,    "wander",  "Wander",  DEF_WANDER,  t_Bool },
108   {&speed,        "speed",   "Speed",   DEF_SPEED,   t_Int },
109 };
110
111
112 ENTRYPOINT ModeSpecOpt surface_opts =
113 {countof(opts), opts, countof(vars), vars, NULL};
114
115
116
117 typedef struct {
118   GLfloat x;
119   GLfloat y;
120   GLfloat z;
121 } GL_VECTOR;
122
123 typedef struct {
124   GLXContext *glx_context;
125   Window      window;
126   rotator    *rot;
127   trackball_state *trackball;
128   Bool        button_down_p;
129
130   int  render;
131   Bool random_render;
132   int  surface;
133   Bool random_surface;
134   int  frame;
135
136   float du, dv;
137   float a, b, c;
138
139   float draw_step;
140 } surfacestruct;
141
142 static surfacestruct *surface = NULL;
143
144
145 static void draw(ModeInfo *mi)
146 {
147   surfacestruct *sp = &surface[MI_SCREEN(mi)];
148   double u, v;
149   float coord[3];
150   int render;
151
152   mi->polygon_count = 0;
153   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
154
155   glEnable(GL_DEPTH_TEST);
156   glEnable(GL_NORMALIZE);
157   glEnable(GL_CULL_FACE);
158
159   glPushMatrix();
160
161   {
162     double x, y, z;
163     get_position(sp->rot, &x, &y, &z, !sp->button_down_p);
164     glTranslatef((x-0.5)*10, (y-0.5)*10, (z-0.5)*20);
165
166     /* Do it twice because we don't track the device's orientation. */
167     glRotatef( current_device_rotation(), 0, 0, 1);
168     gltrackball_rotate(sp->trackball);
169     glRotatef(-current_device_rotation(), 0, 0, 1);
170
171     get_rotation(sp->rot, &x, &y, &z, !sp->button_down_p);
172     glRotatef(x*360, 1.0, 0.0, 0.0);
173     glRotatef(y*360, 0.0, 1.0, 0.0);
174     glRotatef(z*360, 0.0, 0.0, 1.0);
175   }
176
177   glScalef(4.0, 4.0, 4.0);
178
179   switch(sp->surface)
180   {
181     case SURFACE_DINI:
182       for (v=0.11; v<=2.0; v+=sp->dv)
183       {
184         glBegin(sp->render);
185         for (u=0; u<=6.0*M_PI; u+=sp->du)
186         {
187           coord[0] = sp->a*cos(u)*sin(v);
188           coord[1] = sp->a*sin(u)*sin(v);
189           coord[2] = sp->a*(cos(v)+log(tan(0.5*v)))+0.2*sp->b*u;
190           glColor3f(coord[0]+0.7, coord[1]+0.7, coord[2]+0.7);
191           glVertex3fv(coord);
192           mi->polygon_count++;
193         }
194         glEnd();
195       }
196       break;
197     case SURFACE_ENNEPER:
198       for (u=-M_PI; u<=M_PI; u+=sp->du)
199       {
200         glBegin(sp->render);
201         for (v=-M_PI; v<M_PI; v+=sp->dv)
202         {
203           coord[0] = sp->a*(u-(1.0/3.0*u*u*u)+u*v*v);
204           coord[1] = sp->b*(v-(1.0/3.0*v*v*v)+u*u*v);
205           coord[2] = u*u-v*v;
206           glColor3f(coord[0]+0.7, coord[1]+0.7, coord[2]+0.7);
207           glVertex3fv(coord);
208           mi->polygon_count++;
209         }
210         glEnd();
211       }
212       break;
213     case SURFACE_KUEN:
214       for (u=-4.48; u<=4.48; u+=sp->du)
215       {
216         glBegin(sp->render);
217         for (v=M_PI/51; v<M_PI; v+=sp->dv)
218         {
219           coord[0] = 2*(cos(u)+u*sin(u))*sin(v)/(1+u*u*sin(v)*sin(v));
220           coord[1] = 2*(sin(u)-u*cos(u))*sin(v)/(1+u*u*sin(v)*sin(v));
221           coord[2] = log(tan(0.5*v))+2*cos(v)/(1+u*u*sin(v)*sin(v));
222           glColor3f(coord[0]+0.7, coord[1]+0.7, coord[2]+0.7);
223           glVertex3fv(coord);
224           mi->polygon_count++;
225         }
226         glEnd();
227       }
228       break;
229     case SURFACE_MOEBIUS:
230       for (u=-M_PI; u<M_PI; u+=sp->du)
231       {
232         glBegin(sp->render);
233         for (v=-0.735; v<0.74; v+=sp->dv)
234         {
235           coord[0] = cos(u)+v*cos(u/2)*cos(u);
236           coord[1] = sin(u)+v*cos(u/2)*sin(u);
237           coord[2] = v*sin(u/2);
238           glColor3f(coord[0]+0.7, coord[1]+0.7, coord[2]+0.7);
239           glVertex3fv(coord);
240           mi->polygon_count++;
241         }
242         glEnd();
243       }
244       break;
245     case SURFACE_SEASHELL:
246       for (u=0; u<2*M_PI; u+=sp->du)
247       {
248         glBegin(sp->render);
249         for (v=0; v<2*M_PI; v+=sp->dv)
250         {
251           coord[0] = sp->a*(1-v/(2*M_PI))*cos(2*v)*(1+cos(u))+sp->c*cos(2*v);
252           coord[1] = sp->a*(1-v/(2*M_PI))*sin(2*v)*(1+cos(u))+sp->c*sin(2*v);
253           coord[2] = 2*sp->b*v/(2*M_PI)+sp->a*(1-v/(2*M_PI))*sin(u);
254           glColor3f(coord[0]+0.7, coord[1]+0.7, coord[2]+0.7);
255           glVertex3fv(coord);
256           mi->polygon_count++;
257         }
258         glEnd();
259       }
260       break;
261     case SURFACE_SWALLOWTAIL:
262       for (u=-2.5; u<2.0; u+=sp->du)
263       {
264         glBegin(sp->render);
265         for (v=-1.085; v<1.09; v+=sp->dv)
266         {
267           coord[0] = u*v*v+3*v*v*v*v;
268           coord[1] = -2*u*v-4*v*v*v;
269           coord[2] = u;
270           glColor3f(coord[0]+0.7, coord[1]+0.7, coord[2]+0.7);
271           glVertex3fv(coord);
272           mi->polygon_count++;
273         }
274         glEnd();
275       }
276       break;
277     case SURFACE_BOHEMIAN:
278       for (u=-M_PI; u<M_PI; u+=sp->du)
279       {
280         glBegin(sp->render);
281         for (v=-M_PI; v<M_PI; v+=sp->dv)
282         {
283           coord[0] = sp->a*cos(u);
284           coord[1] = sp->b*cos(v)+sp->a*sin(u);
285           coord[2] = sin(v);
286           glColor3f(coord[0]+0.7, coord[1]+0.7, coord[2]+0.7);
287           glVertex3fv(coord);
288           mi->polygon_count++;
289         }
290         glEnd();
291       }
292       break;
293     case SURFACE_WHITNEY:
294       for (v=-1.995; v<2.0; v+=sp->dv)
295       {
296         glBegin(sp->render);
297         for (u=-1.995; u<2.0; u+=sp->du)
298         {
299           coord[0] = u*v;
300           coord[1] = u;
301           coord[2] = v*v-2;
302           glColor3f(coord[0]+0.7, coord[1]+0.7, coord[2]+0.7);
303           glVertex3fv(coord);
304           mi->polygon_count++;
305         }
306         glEnd();
307       }
308       break;
309     case SURFACE_PLUECKER:
310       for (u=0; u<2.5; u+=sp->dv)
311       {
312         glBegin(sp->render);
313         for (v=-M_PI; v<M_PI; v+=sp->du)
314         {
315           coord[0] = u*cos(v);
316           coord[1] = u*sin(v);
317           coord[2] = 2*cos(v)*sin(v);
318           glColor3f(coord[0]+0.7, coord[1]+0.7, coord[2]+0.7);
319           glVertex3fv(coord);
320           mi->polygon_count++;
321         }
322         glEnd();
323       }
324       break;
325     case SURFACE_HENNEBERG:
326       for (u=0.9; u<2.55; u+=sp->dv)
327       {
328         glBegin(sp->render);
329         for (v=-M_PI; v<M_PI; v+=sp->du)
330         {
331           coord[0] = sinh(1.0/3.0*u)*cos(v)-1.0/3.0*sinh(u)*cos(3.0*v);
332           coord[1] = sinh(1.0/3.0*u)*sin(v)+1.0/3.0*sinh(u)*sin(3.0*v);
333           coord[2] = cosh(2.0/3.0*u)*cos(2.0*v);
334           glColor3f(coord[0]+0.7, coord[1]+0.7, coord[2]+0.7);
335           glVertex3fv(coord);
336           mi->polygon_count++;
337         }
338         glEnd();
339       }
340       break;
341     case SURFACE_CATALAN:
342       for (v=-2; v<2; v+=sp->du)
343       {
344         glBegin(sp->render);
345         for (u=-2*M_PI; u<2*M_PI+0.05; u+=sp->dv)
346         {
347           coord[0] = 0.33*(u-sin(u)*cosh(v));
348           coord[1] = 0.33*(1.0-cos(u)*cosh(v));
349           coord[2] = 0.33*4.0*sin(0.5*u)*sinh(0.5*v);
350           glColor3f(coord[0]+0.7, coord[1]+0.7, coord[2]+0.7);
351           glVertex3fv(coord);
352           mi->polygon_count++;
353         }
354         glEnd();
355       }
356       break;
357     case SURFACE_CORKSCREW:
358       for (v=-M_PI; v<M_PI; v+=sp->du)
359       {
360         glBegin(sp->render);
361         for (u=-M_PI; u<M_PI; u+=sp->dv)
362         {
363           coord[0] = 0.5*(sp->a+2.0)*cos(u)*cos(v);
364           coord[1] = 0.5*(sp->a+2.0)*sin(u)*cos(v);
365           coord[2] = 0.5*(sp->a+2.0)*sin(v)+u;
366           glColor3f(coord[0]+0.7, coord[1]+0.7, coord[2]+0.7);
367           glVertex3fv(coord);
368           mi->polygon_count++;
369         }
370         glEnd();
371       }
372       break;
373   }
374   glPopMatrix();
375
376   if (sp->render == GL_LINES)
377     mi->polygon_count /= 2;
378
379   sp->a = sin(sp->draw_step+=0.01);
380   sp->b = cos(sp->draw_step+=0.01);
381   sp->c = sin(sp->draw_step+0.25*M_PI);
382
383   if (sp->random_surface || sp->random_render)
384   {
385     sp->frame++;
386     if (sp->frame >= speed)
387     {
388       sp->frame = 0;
389       if (sp->random_surface)
390         sp->surface = random() % NUM_SURFACES;
391       if (sp->random_render)
392       {
393         render = random() % NUM_RENDER;
394         switch (render)
395         {
396           case RENDER_POINTS:
397             sp->render = GL_POINTS;
398             break;
399           case RENDER_LINES:
400             sp->render = GL_LINES;
401             break;
402           case RENDER_LINE_LOOP:
403             if (sp->surface == SURFACE_BOHEMIAN ||
404                 sp->surface == SURFACE_PLUECKER ||
405                 sp->surface == SURFACE_HENNEBERG)
406               sp->render = GL_LINE_LOOP;
407             else
408               sp->render = GL_LINE_STRIP;
409             break;
410           default:
411             sp->render = GL_LINE_LOOP;
412             break;
413         }
414       }
415     }
416   }
417 }
418
419
420 /* new window size or exposure */
421 ENTRYPOINT void reshape_surface(ModeInfo *mi, int width, int height)
422 {
423   GLfloat h = (GLfloat) height / (GLfloat) width;
424
425   glViewport(0, 0, (GLint) width, (GLint) height);
426   glMatrixMode(GL_PROJECTION);
427   glLoadIdentity();
428   gluPerspective (30.0, 1/h, 1.0, 100.0);
429
430   glMatrixMode(GL_MODELVIEW);
431   glLoadIdentity();
432   gluLookAt(0.0, 0.0, 30.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
433     
434   glClear(GL_COLOR_BUFFER_BIT);
435 }
436
437
438 ENTRYPOINT Bool surface_handle_event(ModeInfo *mi, XEvent *event)
439 {
440   surfacestruct *sp = &surface[MI_SCREEN(mi)];
441
442   if (event->xany.type == ButtonPress && event->xbutton.button == Button1)
443   {
444     sp->button_down_p = True;
445     gltrackball_start(sp->trackball, event->xbutton.x, event->xbutton.y,
446                       MI_WIDTH (mi), MI_HEIGHT (mi));
447     return True;
448   }
449   else if (event->xany.type == ButtonRelease &&
450            event->xbutton.button == Button1)
451   {
452     sp->button_down_p = False;
453     return True;
454   }
455   else if (event->xany.type == ButtonPress &&
456            (event->xbutton.button == Button4 ||
457             event->xbutton.button == Button5 ||
458             event->xbutton.button == Button6 ||
459             event->xbutton.button == Button7)) {
460     gltrackball_mousewheel(sp->trackball, event->xbutton.button, 10,
461                            !!event->xbutton.state);
462     return True;
463   }
464   else if (event->xany.type == MotionNotify && sp->button_down_p)
465   {
466     gltrackball_track (sp->trackball, event->xmotion.x, event->xmotion.y,
467                        MI_WIDTH (mi), MI_HEIGHT (mi));
468     return True;
469   }
470
471   return False;
472 }
473
474
475 ENTRYPOINT void init_surface(ModeInfo *mi)
476 {
477   int    screen = MI_SCREEN(mi);
478   surfacestruct *sp;
479
480   if (surface == NULL)
481   {
482     if ((surface = (surfacestruct *) calloc(MI_NUM_SCREENS(mi),
483                                             sizeof(surfacestruct))) == NULL)
484       return;
485   }
486   sp = &surface[screen];
487
488   sp->window = MI_WINDOW(mi);
489
490   {
491     double spin_speed    = 1.0;
492     double wander_speed = 0.03;
493     sp->rot = make_rotator(do_spin ? spin_speed : 0,
494                            do_spin ? spin_speed : 0,
495                            do_spin ? spin_speed : 0,
496                            1.0,
497                            do_wander ? wander_speed : 0,
498                            True);
499     sp->trackball = gltrackball_init ();
500   }
501
502   if (!strcasecmp(surface_type,"random"))
503   {
504     sp->random_surface = True;
505     sp->surface = random() % NUM_SURFACES;
506   }
507   else if (!strcasecmp(surface_type,"dini"))
508   {
509     sp->random_surface = False;
510     sp->surface = SURFACE_DINI;
511   }
512   else if (!strcasecmp(surface_type,"enneper"))
513   {
514     sp->random_surface = False;
515     sp->surface = SURFACE_ENNEPER;
516   }
517   else if (!strcasecmp(surface_type,"kuen"))
518   {
519     sp->random_surface = False;
520     sp->surface = SURFACE_KUEN;
521   }
522   else if (!strcasecmp(surface_type,"moebius"))
523   {
524     sp->random_surface = False;
525     sp->surface = SURFACE_MOEBIUS;
526   }
527   else if (!strcasecmp(surface_type,"seashell"))
528   {
529     sp->random_surface = False;
530     sp->surface = SURFACE_SEASHELL;
531   }
532   else if (!strcasecmp(surface_type,"swallowtail"))
533   {
534     sp->random_surface = False;
535     sp->surface = SURFACE_SWALLOWTAIL;
536   }
537   else if (!strcasecmp(surface_type,"bohemian"))
538   {
539     sp->random_surface = False;
540     sp->surface = SURFACE_BOHEMIAN;
541   }
542   else if (!strcasecmp(surface_type,"whitney"))
543   {
544     sp->random_surface = False;
545     sp->surface = SURFACE_WHITNEY;
546   }
547   else if (!strcasecmp(surface_type,"pluecker"))
548   {
549     sp->random_surface = False;
550     sp->surface = SURFACE_PLUECKER;
551   }
552   else if (!strcasecmp(surface_type,"henneberg"))
553   {
554     sp->random_surface = False;
555     sp->surface = SURFACE_HENNEBERG;
556   }
557   else if (!strcasecmp(surface_type,"catalan"))
558   {
559     sp->random_surface = False;
560     sp->surface = SURFACE_CATALAN;
561   }
562   else if (!strcasecmp(surface_type,"corkscrew"))
563   {
564     sp->random_surface = False;
565     sp->surface = SURFACE_CORKSCREW;
566   }
567   else
568   {
569     sp->random_surface = True;
570     sp->surface = random() % NUM_SURFACES;
571   }
572
573   if (!strcasecmp(render_mode,"random"))
574   {
575     sp->random_render = True;
576     render = random() % NUM_RENDER;
577   }
578   else if (!strcasecmp(render_mode,"points"))
579   {
580     sp->random_render = False;
581     render = RENDER_POINTS;
582   }
583   else if (!strcasecmp(render_mode,"lines"))
584   {
585     sp->random_render = False;
586     render = RENDER_LINES;
587   }
588   else if (!strcasecmp(render_mode,"line-loops"))
589   {
590     sp->random_render = False;
591     render = RENDER_LINE_LOOP;
592   }
593   else
594   {
595     sp->random_render = True;
596     render = random() % NUM_RENDER;
597   }
598
599   switch (render)
600   {
601     case RENDER_POINTS:
602       sp->render = GL_POINTS;
603       break;
604     case RENDER_LINES:
605       sp->render = GL_LINES;
606       break;
607     case RENDER_LINE_LOOP:
608       if (sp->surface == SURFACE_BOHEMIAN ||
609           sp->surface == SURFACE_PLUECKER ||
610           sp->surface == SURFACE_HENNEBERG)
611         sp->render = GL_LINE_LOOP;
612       else
613         sp->render = GL_LINE_STRIP;
614       break;
615     default:
616       sp->render = GL_LINE_LOOP;
617       break;
618   }
619
620   sp->frame = 0;
621
622   sp->du = 0.07;
623   sp->dv = 0.07;
624   sp->a = sp->b = 1;
625   sp->c = 0.1;
626
627   if ((sp->glx_context = init_GL(mi)) != NULL)
628   {
629     reshape_surface(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
630   }
631   else
632   {
633     MI_CLEARWINDOW(mi);
634   }
635 }
636
637
638 ENTRYPOINT void draw_surface(ModeInfo * mi)
639 {
640   surfacestruct *sp = &surface[MI_SCREEN(mi)];
641   Display *display = MI_DISPLAY(mi);
642   Window window = MI_WINDOW(mi);
643
644   if (!sp->glx_context)
645     return;
646
647   glDrawBuffer(GL_BACK);
648
649   glXMakeCurrent(display, window, *(sp->glx_context));
650   draw(mi);
651   if (mi->fps_p)
652     do_fps(mi);
653   glFinish();
654   glXSwapBuffers(display, window);
655 }
656
657
658 ENTRYPOINT void release_surface(ModeInfo * mi)
659 {
660   if (surface != NULL)
661   {
662     int  screen;
663
664     for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
665     {
666       surfacestruct *sp = &surface[screen];
667
668       if (sp->glx_context)
669       {
670         /* Display lists MUST be freed while their glXContext is current. */
671         glXMakeCurrent(MI_DISPLAY(mi), sp->window, *(sp->glx_context));
672       }
673     }
674     (void) free((void *)surface);
675     surface = NULL;
676   }
677   FreeAllGL(mi);
678 }
679
680
681 XSCREENSAVER_MODULE_2("Surfaces", surfaces, surface)
682
683 #endif