03384a45e4611c91fbaa56601969419131b25c15
[xscreensaver] / hacks / glx / menger.c
1 /* menger, Copyright (c) 2001-2006 Jamie Zawinski <jwz@jwz.org>
2  *         Copyright (c) 2002 Aurelien Jacobs <aurel@gnuage.org>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  *
12  * Generates a 3D Menger Sponge gasket:
13  *
14  *                                ___+._______
15  *                           __-""   --     __"""----._____
16  *                    __.--"" -- ___--+---_____.     __  .+'|
17  *              _.-'""  __    +:"__   | ._..+"" __    .+'   F
18  *            J"--.____         __ """""+"         .+'  .J  F
19  *            J        """""---.___       --   .+'"     F'  F
20  *             L                   """""--...+'    .J       F
21  *             L   F"9      --.            |   .   F'      J
22  *             L   -_J      L_J      F"9   | ;'J    .+J .J J
23  *             |                     L_J   | F.'  .'| J F' J
24  *             |        |"""--.__          | '   |""  J    J
25  *             J   ._   J ;;; |  "L        |   . |-___J    |
26  *             J   L J  J ;-' |   L        | .'J |_  .'  . |
27  *             J    ""  J    .---_L  F"9   | F.' | .'   FJ |
28  *              L       J .-'  __ |  L_J   | '   :'     ' .+
29  *              L       '--.___   |        |       .J   .'
30  *              |  F"9         """'        |   .   F' .'
31  *              |  -_J      F"9            | .'J    .'
32  *              +__         -_J      F"9   | F.'  .'
33  *                 """--___          L_J   | '  .'
34  *                         """---___       |  .'
35  *                                  ""---._|.'
36  *
37  *  The straightforward way to generate this object creates way more polygons
38  *  than are needed, since there end up being many buried, interior faces.
39  *  So during the recursive building of the object we store which face of
40  *  each unitary cube we need to draw. Doing this reduces the polygon count
41  *  by 40% - 60%.
42  *
43  *  Another optimization we could do to reduce the polygon count would be to
44  *  merge adjascent coplanar squares together into rectangles.  This would
45  *  result in the outer faces being composed of 1xN strips.  It's tricky to
46  *  to find these adjascent faces in non-exponential time, though.
47  *
48  *  We could actually simulate large depths with a texture map -- if the
49  *  depth is such that the smallest holes are only a few pixels across,
50  *  just draw them as spots on the surface!  It would look the same.
51  */
52
53 #define DEFAULTS        "*delay:         30000          \n" \
54                         "*showFPS:       False          \n" \
55                         "*wireframe:     False          \n" \
56
57
58 # define refresh_sponge 0
59 # define release_sponge 0
60 #undef countof
61 #define countof(x) (sizeof((x))/sizeof((*x)))
62
63 #include "xlockmore.h"
64 #include "colors.h"
65 #include "rotator.h"
66 #include "gltrackball.h"
67 #include <ctype.h>
68
69 #ifdef USE_GL /* whole file */
70
71 #define DEF_SPIN        "True"
72 #define DEF_WANDER      "True"
73 #define DEF_SPEED       "150"
74 #define DEF_MAX_DEPTH   "3"
75
76 typedef struct {
77   GLXContext *glx_context;
78   rotator *rot;
79   trackball_state *trackball;
80   Bool button_down_p;
81   GLuint sponge_list0;            /* we store X, Y, and Z-facing surfaces */
82   GLuint sponge_list1;            /* in their own lists, to make it easy  */
83   GLuint sponge_list2;            /* to color them differently.           */
84
85   unsigned long squares_fp;
86
87   int current_depth;
88
89   int ncolors;
90   XColor *colors;
91   int ccolor0;
92   int ccolor1;
93   int ccolor2;
94
95   int draw_tick;
96
97 } sponge_configuration;
98
99 static sponge_configuration *sps = NULL;
100
101 static Bool do_spin;
102 static Bool do_wander;
103 static int speed;
104 static int max_depth;
105
106 static XrmOptionDescRec opts[] = {
107   { "-wander", ".wander",   XrmoptionNoArg, "True"  },
108   { "+wander", ".wander",   XrmoptionNoArg, "False" },
109   { "-spin",   ".spin",     XrmoptionSepArg, 0 },
110   { "-speed",  ".speed",    XrmoptionSepArg, 0 },
111   { "-depth",  ".maxDepth", XrmoptionSepArg, 0 },
112 };
113
114 static argtype vars[] = {
115   {&do_spin,     "spin",     "Spin",     DEF_SPIN,      t_Bool},
116   {&do_wander,   "wander",   "Wander",   DEF_WANDER,    t_Bool},
117   {&speed,       "speed",    "Speed",    DEF_SPEED,     t_Int},
118   {&max_depth,   "maxDepth", "MaxDepth", DEF_MAX_DEPTH, t_Int},
119 };
120
121 ENTRYPOINT ModeSpecOpt sponge_opts = {countof(opts), opts, countof(vars), vars, NULL};
122
123
124 /* Window management, etc
125  */
126 ENTRYPOINT void
127 reshape_sponge (ModeInfo *mi, int width, int height)
128 {
129   GLfloat h = (GLfloat) height / (GLfloat) width;
130
131   glViewport (0, 0, (GLint) width, (GLint) height);
132
133   glMatrixMode(GL_PROJECTION);
134   glLoadIdentity();
135   gluPerspective (30.0, 1/h, 1.0, 100.0);
136
137   glMatrixMode(GL_MODELVIEW);
138   glLoadIdentity();
139   gluLookAt( 0.0, 0.0, 30.0,
140              0.0, 0.0, 0.0,
141              0.0, 1.0, 0.0);
142
143   glClear(GL_COLOR_BUFFER_BIT);
144 }
145
146
147 #define X0 0x01
148 #define X1 0x02
149 #define Y0 0x04
150 #define Y1 0x08
151 #define Z0 0x10
152 #define Z1 0x20
153
154 static int
155 cube (float x0, float x1, float y0, float y1, float z0, float z1,
156       int faces, int wireframe)
157 {
158   int n = 0;
159
160   if (faces & X0)
161     {
162       glBegin (wireframe ? GL_LINE_LOOP : GL_POLYGON);
163       glNormal3f (-1.0, 0.0, 0.0);
164       glVertex3f (x0, y1, z0);
165       glVertex3f (x0, y0, z0);
166       glVertex3f (x0, y0, z1);
167       glVertex3f (x0, y1, z1);
168       glEnd ();
169       n++;
170     }
171   if (faces & X1)
172     {
173       glBegin (wireframe ? GL_LINE_LOOP : GL_POLYGON);
174       glNormal3f (1.0, 0.0, 0.0);
175       glVertex3f (x1, y1, z1);
176       glVertex3f (x1, y0, z1);
177       glVertex3f (x1, y0, z0);
178       glVertex3f (x1, y1, z0);
179       glEnd ();
180       n++;
181     }
182   if (faces & Y0)
183     {
184       glBegin (wireframe ? GL_LINE_LOOP : GL_POLYGON);
185       glNormal3f (0.0, -1.0, 0.0);
186       glVertex3f (x0, y0, z0);
187       glVertex3f (x0, y0, z1);
188       glVertex3f (x1, y0, z1);
189       glVertex3f (x1, y0, z0);
190       glEnd ();
191       n++;
192     }
193   if (faces & Y1)
194     {
195       glBegin (wireframe ? GL_LINE_LOOP : GL_POLYGON);
196       glNormal3f (0.0, 1.0, 0.0);
197       glVertex3f (x0, y1, z0);
198       glVertex3f (x0, y1, z1);
199       glVertex3f (x1, y1, z1);
200       glVertex3f (x1, y1, z0);
201       glEnd ();
202       n++;
203     }
204   if (faces & Z0)
205     {
206       glBegin (wireframe ? GL_LINE_LOOP : GL_POLYGON);
207       glNormal3f (0.0, 0.0, -1.0);
208       glVertex3f (x1, y1, z0);
209       glVertex3f (x1, y0, z0);
210       glVertex3f (x0, y0, z0);
211       glVertex3f (x0, y1, z0);
212       glEnd ();
213       n++;
214     }
215   if (faces & Z1)
216     {
217       glBegin (wireframe ? GL_LINE_LOOP : GL_POLYGON);
218       glNormal3f (0.0, 0.0, 1.0);
219       glVertex3f (x0, y1, z1);
220       glVertex3f (x0, y0, z1);
221       glVertex3f (x1, y0, z1);
222       glVertex3f (x1, y1, z1);
223       glEnd ();
224       n++;
225     }
226
227   return n;
228 }
229
230 static int
231 menger_recurs_1 (int level, float x0, float x1, float y0, float y1,
232                  float z0, float z1, int faces, Bool wireframe, 
233                  int orig, int forig)
234 {
235   float xi, yi, zi;
236   int f, x, y, z;
237   int n = 0;
238
239   if (orig)
240     {
241       if (wireframe)
242         n += cube (x0, x1, y0, y1, z0, z1,
243                    faces & (X0 | X1 | Y0 | Y1), wireframe);
244     }
245
246   if (level == 0)
247     {
248       if (!wireframe)
249         n += cube (x0, x1, y0, y1, z0, z1, faces, wireframe);
250     }
251   else
252     {
253       xi = (x1 - x0) / 3;
254       yi = (y1 - y0) / 3;
255       zi = (z1 - z0) / 3;
256
257       for (x = 0; x < 3; x++)
258         for (y = 0; y < 3; y++)
259           for (z = 0; z < 3; z++)
260             {
261               if ((x != 1 && y != 1)
262                   || (y != 1 && z != 1)
263                   || (x != 1 && z != 1))
264                 {
265                   f = faces;
266
267                   if (x == 1 || (x == 2 && (y != 1 && z != 1)))
268                     f &= ~X0;
269                   if (x == 1 || (x == 0 && (y != 1 && z != 1)))
270                     f &= ~X1;
271                   if (forig & X0 && x == 2 && (y == 1 || z == 1))
272                     f |= X0;
273                   if (forig & X1 && x == 0 && (y == 1 || z == 1))
274                     f |= X1;
275
276                   if (y == 1 || (y == 2 && (x != 1 && z != 1)))
277                     f &= ~Y0;
278                   if (y == 1 || (y == 0 && (x != 1 && z != 1)))
279                     f &= ~Y1;
280                   if (forig & Y0 && y == 2 && (x == 1 || z == 1))
281                     f |= Y0;
282                   if (forig & Y1 && y == 0 && (x == 1 || z == 1))
283                     f |= Y1;
284
285                   if (z == 1 || (z == 2 && (x != 1 && y != 1)))
286                     f &= ~Z0;
287                   if (z == 1 || (z == 0 && (x != 1 && y != 1)))
288                     f &= ~Z1;
289                   if (forig & Z0 && z == 2 && (x == 1 || y == 1))
290                     f |= Z0;
291                   if (forig & Z1 && z == 0 && (x == 1 || y == 1))
292                     f |= Z1;
293
294                   n += menger_recurs_1 (level-1,
295                                         x0+x*xi, x0+(x+1)*xi,
296                                         y0+y*yi, y0+(y+1)*yi,
297                                         z0+z*zi, z0+(z+1)*zi, f, wireframe, 0,
298                                         forig);
299                 }
300               else if (wireframe && (x != 1 || y != 1 || z != 1))
301                 n += cube (x0+x*xi, x0+(x+1)*xi,
302                            y0+y*yi, y0+(y+1)*yi,
303                            z0+z*zi, z0+(z+1)*zi,
304                            forig & (X0 | X1 | Y0 | Y1), wireframe);
305             }
306     }
307
308   return n;
309 }
310
311 static int
312 menger_recurs (int level, float x0, float x1, float y0, float y1,
313                float z0, float z1, int faces, Bool wireframe, 
314                int orig)
315 {
316   return menger_recurs_1 (level, x0, x1, y0, y1, z0, z1, faces, 
317                           wireframe, orig, faces);
318 }
319
320
321 static void
322 build_sponge (sponge_configuration *sp, Bool wireframe, int level)
323 {
324   glDeleteLists (sp->sponge_list0, 1);
325   glNewList(sp->sponge_list0, GL_COMPILE);
326   sp->squares_fp = menger_recurs (level, -1.5, 1.5, -1.5, 1.5, -1.5, 1.5,
327                                   X0 | X1, wireframe,1);
328   glEndList();
329
330   glDeleteLists (sp->sponge_list1, 1);
331   glNewList(sp->sponge_list1, GL_COMPILE);
332   sp->squares_fp += menger_recurs (level, -1.5, 1.5, -1.5, 1.5, -1.5, 1.5,
333                                    Y0 | Y1, wireframe,1);
334   glEndList();
335
336   glDeleteLists (sp->sponge_list2, 1);
337   glNewList(sp->sponge_list2, GL_COMPILE);
338   sp->squares_fp += menger_recurs (level, -1.5, 1.5, -1.5, 1.5, -1.5, 1.5,
339                                    Z0 | Z1, wireframe,1);
340   glEndList();
341 }
342
343
344 ENTRYPOINT Bool
345 sponge_handle_event (ModeInfo *mi, XEvent *event)
346 {
347   sponge_configuration *sp = &sps[MI_SCREEN(mi)];
348
349   if (event->xany.type == ButtonPress &&
350       event->xbutton.button == Button1)
351     {
352       sp->button_down_p = True;
353       gltrackball_start (sp->trackball,
354                          event->xbutton.x, event->xbutton.y,
355                          MI_WIDTH (mi), MI_HEIGHT (mi));
356       return True;
357     }
358   else if (event->xany.type == ButtonRelease &&
359            event->xbutton.button == Button1)
360     {
361       sp->button_down_p = False;
362       return True;
363     }
364   else if (event->xany.type == ButtonPress &&
365            (event->xbutton.button == Button4 ||
366             event->xbutton.button == Button5))
367     {
368       gltrackball_mousewheel (sp->trackball, event->xbutton.button, 5,
369                               !!event->xbutton.state);
370       return True;
371     }
372   else if (event->xany.type == MotionNotify &&
373            sp->button_down_p)
374     {
375       gltrackball_track (sp->trackball,
376                          event->xmotion.x, event->xmotion.y,
377                          MI_WIDTH (mi), MI_HEIGHT (mi));
378       return True;
379     }
380
381   return False;
382 }
383
384
385
386 ENTRYPOINT void 
387 init_sponge (ModeInfo *mi)
388 {
389   sponge_configuration *sp;
390   int wire = MI_IS_WIREFRAME(mi);
391
392   if (!sps) {
393     sps = (sponge_configuration *)
394       calloc (MI_NUM_SCREENS(mi), sizeof (sponge_configuration));
395     if (!sps) {
396       fprintf(stderr, "%s: out of memory\n", progname);
397       exit(1);
398     }
399
400     sp = &sps[MI_SCREEN(mi)];
401   }
402
403   sp = &sps[MI_SCREEN(mi)];
404
405   if ((sp->glx_context = init_GL(mi)) != NULL) {
406     reshape_sponge (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
407   }
408
409   if (!wire)
410     {
411       static const GLfloat pos0[4] = {-1.0, -1.0, 1.0, 0.1};
412       static const GLfloat pos1[4] = { 1.0, -0.2, 0.2, 0.1};
413       static const GLfloat dif0[4] = {1.0, 1.0, 1.0, 1.0};
414       static const GLfloat dif1[4] = {1.0, 1.0, 1.0, 1.0};
415
416       glLightfv(GL_LIGHT0, GL_POSITION, pos0);
417       glLightfv(GL_LIGHT1, GL_POSITION, pos1);
418       glLightfv(GL_LIGHT0, GL_DIFFUSE, dif0);
419       glLightfv(GL_LIGHT1, GL_DIFFUSE, dif1);
420
421       glEnable(GL_LIGHTING);
422       glEnable(GL_LIGHT0);
423       glEnable(GL_LIGHT1);
424
425       glEnable(GL_DEPTH_TEST);
426       glEnable(GL_NORMALIZE);
427
428       glShadeModel(GL_SMOOTH);
429     }
430
431   {
432     double spin_speed   = 1.0;
433     double wander_speed = 0.03;
434     sp->rot = make_rotator (do_spin ? spin_speed : 0,
435                             do_spin ? spin_speed : 0,
436                             do_spin ? spin_speed : 0,
437                             1.0,
438                             do_wander ? wander_speed : 0,
439                             True);
440     sp->trackball = gltrackball_init ();
441   }
442
443   sp->ncolors = 128;
444   sp->colors = (XColor *) calloc(sp->ncolors, sizeof(XColor));
445   make_smooth_colormap (0, 0, 0,
446                         sp->colors, &sp->ncolors,
447                         False, 0, False);
448   sp->ccolor0 = 0;
449   sp->ccolor1 = sp->ncolors / 3;
450   sp->ccolor2 = sp->ccolor1 * 2;
451
452   sp->sponge_list0 = glGenLists (1);
453   sp->sponge_list1 = glGenLists (1);
454   sp->sponge_list2 = glGenLists (1);
455
456   sp->draw_tick = 9999999;
457 }
458
459
460 ENTRYPOINT void
461 draw_sponge (ModeInfo *mi)
462 {
463   sponge_configuration *sp = &sps[MI_SCREEN(mi)];
464   Display *dpy = MI_DISPLAY(mi);
465   Window window = MI_WINDOW(mi);
466
467   GLfloat color0[4] = {0.0, 0.0, 0.0, 1.0};
468   GLfloat color1[4] = {0.0, 0.0, 0.0, 1.0};
469   GLfloat color2[4] = {0.0, 0.0, 0.0, 1.0};
470
471   if (!sp->glx_context)
472     return;
473
474   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(sp->glx_context));
475
476   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
477
478   glPushMatrix ();
479
480   glScalef(1.1, 1.1, 1.1);
481
482   {
483     double x, y, z;
484     get_position (sp->rot, &x, &y, &z, !sp->button_down_p);
485     glTranslatef((x - 0.5) * 8,
486                  (y - 0.5) * 6,
487                  (z - 0.5) * 15);
488
489     gltrackball_rotate (sp->trackball);
490
491     get_rotation (sp->rot, &x, &y, &z, !sp->button_down_p);
492     glRotatef (x * 360, 1.0, 0.0, 0.0);
493     glRotatef (y * 360, 0.0, 1.0, 0.0);
494     glRotatef (z * 360, 0.0, 0.0, 1.0);
495   }
496
497   color0[0] = sp->colors[sp->ccolor0].red   / 65536.0;
498   color0[1] = sp->colors[sp->ccolor0].green / 65536.0;
499   color0[2] = sp->colors[sp->ccolor0].blue  / 65536.0;
500
501   color1[0] = sp->colors[sp->ccolor1].red   / 65536.0;
502   color1[1] = sp->colors[sp->ccolor1].green / 65536.0;
503   color1[2] = sp->colors[sp->ccolor1].blue  / 65536.0;
504
505   color2[0] = sp->colors[sp->ccolor2].red   / 65536.0;
506   color2[1] = sp->colors[sp->ccolor2].green / 65536.0;
507   color2[2] = sp->colors[sp->ccolor2].blue  / 65536.0;
508
509
510   sp->ccolor0++;
511   sp->ccolor1++;
512   sp->ccolor2++;
513   if (sp->ccolor0 >= sp->ncolors) sp->ccolor0 = 0;
514   if (sp->ccolor1 >= sp->ncolors) sp->ccolor1 = 0;
515   if (sp->ccolor2 >= sp->ncolors) sp->ccolor2 = 0;
516
517   if (sp->draw_tick++ >= speed)
518     {
519       sp->draw_tick = 0;
520       if (sp->current_depth >= max_depth)
521         sp->current_depth = -max_depth;
522       sp->current_depth++;
523       build_sponge (sp,
524                     MI_IS_WIREFRAME(mi),
525                     (sp->current_depth < 0
526                      ? -sp->current_depth : sp->current_depth));
527
528       mi->polygon_count = sp->squares_fp;  /* for FPS display */
529     }
530
531   glScalef (2.0, 2.0, 2.0);
532
533   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color0);
534   glCallList (sp->sponge_list0);
535   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color1);
536   glCallList (sp->sponge_list1);
537   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color2);
538   glCallList (sp->sponge_list2);
539
540   glPopMatrix ();
541
542   if (mi->fps_p) do_fps (mi);
543   glFinish();
544
545   glXSwapBuffers(dpy, window);
546 }
547
548 XSCREENSAVER_MODULE_2 ("Menger", menger, sponge)
549
550 #endif /* USE_GL */