f7166493d3c366904560a7e9ec587c52ec09e08a
[xscreensaver] / hacks / glx / menger.c
1 /* menger, Copyright (c) 2001, 2002, 2004 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 #include <X11/Intrinsic.h>
54
55 extern XtAppContext app;
56
57 #define PROGCLASS       "Menger"
58 #define HACK_INIT       init_sponge
59 #define HACK_DRAW       draw_sponge
60 #define HACK_RESHAPE    reshape_sponge
61 #define HACK_HANDLE_EVENT sponge_handle_event
62 #define EVENT_MASK      PointerMotionMask
63 #define sws_opts        xlockmore_opts
64
65 #define DEF_SPIN        "True"
66 #define DEF_WANDER      "True"
67 #define DEF_SPEED       "150"
68 #define DEF_MAX_DEPTH   "3"
69 #define DEF_OPTIMIZE    "True"
70
71 #define DEFAULTS        "*delay:         30000          \n" \
72                         "*showFPS:       False          \n" \
73                         "*wireframe:     False          \n" \
74                         "*maxDepth:    " DEF_MAX_DEPTH "\n" \
75                         "*speed:"        DEF_SPEED     "\n" \
76                         "*optimize:"     DEF_OPTIMIZE  "\n" \
77                         "*spin:        " DEF_SPIN      "\n" \
78                         "*wander:      " DEF_WANDER    "\n" \
79
80
81 #undef countof
82 #define countof(x) (sizeof((x))/sizeof((*x)))
83
84 #include "xlockmore.h"
85 #include "colors.h"
86 #include "rotator.h"
87 #include "gltrackball.h"
88 #include <ctype.h>
89
90 #ifdef USE_GL /* whole file */
91
92 #include <GL/glu.h>
93
94
95 typedef struct {
96   GLXContext *glx_context;
97   rotator *rot;
98   trackball_state *trackball;
99   Bool button_down_p;
100   GLuint sponge_list0;            /* we store X, Y, and Z-facing surfaces */
101   GLuint sponge_list1;            /* in their own lists, to make it easy  */
102   GLuint sponge_list2;            /* to color them differently.           */
103
104   unsigned long squares_fp;
105
106   int current_depth;
107
108   int ncolors;
109   XColor *colors;
110   int ccolor0;
111   int ccolor1;
112   int ccolor2;
113
114 } sponge_configuration;
115
116 static sponge_configuration *sps = NULL;
117
118 static Bool do_spin;
119 static Bool do_wander;
120 static int speed;
121 static Bool do_optimize;
122 static int max_depth;
123
124 static XrmOptionDescRec opts[] = {
125   { "-spin",   ".spin",   XrmoptionNoArg, "True" },
126   { "+spin",   ".spin",   XrmoptionNoArg, "False" },
127   { "-wander", ".wander", XrmoptionNoArg, "True" },
128   { "+wander", ".wander", XrmoptionNoArg, "False" },
129   { "-speed",  ".speed",  XrmoptionSepArg, 0 },
130   { "-optimize", ".optimize", XrmoptionNoArg, "True" },
131   { "+optimize", ".optimize", XrmoptionNoArg, "False" },
132   {"-depth",   ".maxDepth", XrmoptionSepArg, 0 },
133 };
134
135 static argtype vars[] = {
136   {&do_spin,     "spin",     "Spin",     DEF_SPIN,      t_Bool},
137   {&do_wander,   "wander",   "Wander",   DEF_WANDER,    t_Bool},
138   {&speed,       "speed",    "Speed",    DEF_SPEED,     t_Int},
139   {&do_optimize, "optimize", "Optimize", DEF_OPTIMIZE,  t_Bool},
140   {&max_depth,   "maxDepth", "MaxDepth", DEF_MAX_DEPTH, t_Int},
141 };
142
143 ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
144
145
146 /* Window management, etc
147  */
148 void
149 reshape_sponge (ModeInfo *mi, int width, int height)
150 {
151   GLfloat h = (GLfloat) height / (GLfloat) width;
152
153   glViewport (0, 0, (GLint) width, (GLint) height);
154
155   glMatrixMode(GL_PROJECTION);
156   glLoadIdentity();
157   gluPerspective (30.0, 1/h, 1.0, 100.0);
158
159   glMatrixMode(GL_MODELVIEW);
160   glLoadIdentity();
161   gluLookAt( 0.0, 0.0, 30.0,
162              0.0, 0.0, 0.0,
163              0.0, 1.0, 0.0);
164
165   glClear(GL_COLOR_BUFFER_BIT);
166 }
167
168
169 #define X0 0x01
170 #define X1 0x02
171 #define Y0 0x04
172 #define Y1 0x08
173 #define Z0 0x10
174 #define Z1 0x20
175
176 static int
177 cube (float x0, float x1, float y0, float y1, float z0, float z1,
178       int faces, int wireframe)
179 {
180   int n = 0;
181
182   if (faces & X0)
183     {
184       glBegin (wireframe ? GL_LINE_LOOP : GL_POLYGON);
185       glNormal3f (-1.0, 0.0, 0.0);
186       glVertex3f (x0, y1, z0);
187       glVertex3f (x0, y0, z0);
188       glVertex3f (x0, y0, z1);
189       glVertex3f (x0, y1, z1);
190       glEnd ();
191       n++;
192     }
193   if (faces & X1)
194     {
195       glBegin (wireframe ? GL_LINE_LOOP : GL_POLYGON);
196       glNormal3f (1.0, 0.0, 0.0);
197       glVertex3f (x1, y1, z1);
198       glVertex3f (x1, y0, z1);
199       glVertex3f (x1, y0, z0);
200       glVertex3f (x1, y1, z0);
201       glEnd ();
202       n++;
203     }
204   if (faces & Y0)
205     {
206       glBegin (wireframe ? GL_LINE_LOOP : GL_POLYGON);
207       glNormal3f (0.0, -1.0, 0.0);
208       glVertex3f (x0, y0, z0);
209       glVertex3f (x0, y0, z1);
210       glVertex3f (x1, y0, z1);
211       glVertex3f (x1, y0, z0);
212       glEnd ();
213       n++;
214     }
215   if (faces & Y1)
216     {
217       glBegin (wireframe ? GL_LINE_LOOP : GL_POLYGON);
218       glNormal3f (0.0, 1.0, 0.0);
219       glVertex3f (x0, y1, z0);
220       glVertex3f (x0, y1, z1);
221       glVertex3f (x1, y1, z1);
222       glVertex3f (x1, y1, z0);
223       glEnd ();
224       n++;
225     }
226   if (faces & Z0)
227     {
228       glBegin (wireframe ? GL_LINE_LOOP : GL_POLYGON);
229       glNormal3f (0.0, 0.0, -1.0);
230       glVertex3f (x1, y1, z0);
231       glVertex3f (x1, y0, z0);
232       glVertex3f (x0, y0, z0);
233       glVertex3f (x0, y1, z0);
234       glEnd ();
235       n++;
236     }
237   if (faces & Z1)
238     {
239       glBegin (wireframe ? GL_LINE_LOOP : GL_POLYGON);
240       glNormal3f (0.0, 0.0, 1.0);
241       glVertex3f (x0, y1, z1);
242       glVertex3f (x0, y0, z1);
243       glVertex3f (x1, y0, z1);
244       glVertex3f (x1, y1, z1);
245       glEnd ();
246       n++;
247     }
248
249   return n;
250 }
251
252 static int
253 menger_recurs (int level, float x0, float x1, float y0, float y1,
254                float z0, float z1, int faces, Bool wireframe, int orig)
255 {
256   float xi, yi, zi;
257   int f, x, y, z;
258   static int forig;
259   int n = 0;
260
261   if (orig)
262     {
263       forig = faces;
264       if (wireframe)
265         n += cube (x0, x1, y0, y1, z0, z1,
266                    faces & (X0 | X1 | Y0 | Y1), wireframe);
267     }
268
269   if (level == 0)
270     {
271       if (!wireframe)
272         n += cube (x0, x1, y0, y1, z0, z1, faces, wireframe);
273     }
274   else
275     {
276       xi = (x1 - x0) / 3;
277       yi = (y1 - y0) / 3;
278       zi = (z1 - z0) / 3;
279
280       for (x = 0; x < 3; x++)
281         for (y = 0; y < 3; y++)
282           for (z = 0; z < 3; z++)
283             {
284               if ((x != 1 && y != 1)
285                   || (y != 1 && z != 1)
286                   || (x != 1 && z != 1))
287                 {
288                   f = faces;
289
290                   if (x == 1 || (x == 2 && (y != 1 && z != 1)))
291                     f &= ~X0;
292                   if (x == 1 || (x == 0 && (y != 1 && z != 1)))
293                     f &= ~X1;
294                   if (forig & X0 && x == 2 && (y == 1 || z == 1))
295                     f |= X0;
296                   if (forig & X1 && x == 0 && (y == 1 || z == 1))
297                     f |= X1;
298
299                   if (y == 1 || (y == 2 && (x != 1 && z != 1)))
300                     f &= ~Y0;
301                   if (y == 1 || (y == 0 && (x != 1 && z != 1)))
302                     f &= ~Y1;
303                   if (forig & Y0 && y == 2 && (x == 1 || z == 1))
304                     f |= Y0;
305                   if (forig & Y1 && y == 0 && (x == 1 || z == 1))
306                     f |= Y1;
307
308                   if (z == 1 || (z == 2 && (x != 1 && y != 1)))
309                     f &= ~Z0;
310                   if (z == 1 || (z == 0 && (x != 1 && y != 1)))
311                     f &= ~Z1;
312                   if (forig & Z0 && z == 2 && (x == 1 || y == 1))
313                     f |= Z0;
314                   if (forig & Z1 && z == 0 && (x == 1 || y == 1))
315                     f |= Z1;
316
317                   n += menger_recurs (level-1,
318                                       x0+x*xi, x0+(x+1)*xi,
319                                       y0+y*yi, y0+(y+1)*yi,
320                                       z0+z*zi, z0+(z+1)*zi, f, wireframe, 0);
321                 }
322               else if (wireframe && (x != 1 || y != 1 || z != 1))
323                 n += cube (x0+x*xi, x0+(x+1)*xi,
324                            y0+y*yi, y0+(y+1)*yi,
325                            z0+z*zi, z0+(z+1)*zi,
326                            forig & (X0 | X1 | Y0 | Y1), wireframe);
327             }
328     }
329
330   return n;
331 }
332
333 static void
334 build_sponge (sponge_configuration *sp, Bool wireframe, int level)
335 {
336   glDeleteLists (sp->sponge_list0, 1);
337   glNewList(sp->sponge_list0, GL_COMPILE);
338   sp->squares_fp = menger_recurs (level, -1.5, 1.5, -1.5, 1.5, -1.5, 1.5,
339                                   X0 | X1, wireframe,1);
340   glEndList();
341
342   glDeleteLists (sp->sponge_list1, 1);
343   glNewList(sp->sponge_list1, GL_COMPILE);
344   sp->squares_fp += menger_recurs (level, -1.5, 1.5, -1.5, 1.5, -1.5, 1.5,
345                                    Y0 | Y1, wireframe,1);
346   glEndList();
347
348   glDeleteLists (sp->sponge_list2, 1);
349   glNewList(sp->sponge_list2, GL_COMPILE);
350   sp->squares_fp += menger_recurs (level, -1.5, 1.5, -1.5, 1.5, -1.5, 1.5,
351                                    Z0 | Z1, wireframe,1);
352   glEndList();
353 }
354
355
356 Bool
357 sponge_handle_event (ModeInfo *mi, XEvent *event)
358 {
359   sponge_configuration *sp = &sps[MI_SCREEN(mi)];
360
361   if (event->xany.type == ButtonPress &&
362       event->xbutton.button & Button1)
363     {
364       sp->button_down_p = True;
365       gltrackball_start (sp->trackball,
366                          event->xbutton.x, event->xbutton.y,
367                          MI_WIDTH (mi), MI_HEIGHT (mi));
368       return True;
369     }
370   else if (event->xany.type == ButtonRelease &&
371            event->xbutton.button & Button1)
372     {
373       sp->button_down_p = False;
374       return True;
375     }
376   else if (event->xany.type == MotionNotify &&
377            sp->button_down_p)
378     {
379       gltrackball_track (sp->trackball,
380                          event->xmotion.x, event->xmotion.y,
381                          MI_WIDTH (mi), MI_HEIGHT (mi));
382       return True;
383     }
384
385   return False;
386 }
387
388
389
390 void 
391 init_sponge (ModeInfo *mi)
392 {
393   sponge_configuration *sp;
394   int wire = MI_IS_WIREFRAME(mi);
395
396   if (!sps) {
397     sps = (sponge_configuration *)
398       calloc (MI_NUM_SCREENS(mi), sizeof (sponge_configuration));
399     if (!sps) {
400       fprintf(stderr, "%s: out of memory\n", progname);
401       exit(1);
402     }
403
404     sp = &sps[MI_SCREEN(mi)];
405   }
406
407   sp = &sps[MI_SCREEN(mi)];
408
409   if ((sp->glx_context = init_GL(mi)) != NULL) {
410     reshape_sponge (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
411   }
412
413   if (!wire)
414     {
415       static GLfloat pos0[4] = {-1.0, -1.0, 1.0, 0.1};
416       static GLfloat pos1[4] = { 1.0, -0.2, 0.2, 0.1};
417       static GLfloat dif0[4] = {1.0, 1.0, 1.0, 1.0};
418       static GLfloat dif1[4] = {1.0, 1.0, 1.0, 1.0};
419
420       glLightfv(GL_LIGHT0, GL_POSITION, pos0);
421       glLightfv(GL_LIGHT1, GL_POSITION, pos1);
422       glLightfv(GL_LIGHT0, GL_DIFFUSE, dif0);
423       glLightfv(GL_LIGHT1, GL_DIFFUSE, dif1);
424
425       glEnable(GL_LIGHTING);
426       glEnable(GL_LIGHT0);
427       glEnable(GL_LIGHT1);
428
429       glEnable(GL_DEPTH_TEST);
430       glEnable(GL_NORMALIZE);
431
432       glShadeModel(GL_SMOOTH);
433     }
434
435   {
436     double spin_speed   = 1.0;
437     double wander_speed = 0.03;
438     sp->rot = make_rotator (do_spin ? spin_speed : 0,
439                             do_spin ? spin_speed : 0,
440                             do_spin ? spin_speed : 0,
441                             1.0,
442                             do_wander ? wander_speed : 0,
443                             True);
444     sp->trackball = gltrackball_init ();
445   }
446
447   sp->ncolors = 128;
448   sp->colors = (XColor *) calloc(sp->ncolors, sizeof(XColor));
449   make_smooth_colormap (0, 0, 0,
450                         sp->colors, &sp->ncolors,
451                         False, 0, False);
452   sp->ccolor0 = 0;
453   sp->ccolor1 = sp->ncolors / 3;
454   sp->ccolor2 = sp->ccolor1 * 2;
455
456   sp->sponge_list0 = glGenLists (1);
457   sp->sponge_list1 = glGenLists (1);
458   sp->sponge_list2 = glGenLists (1);
459 }
460
461
462 void
463 draw_sponge (ModeInfo *mi)
464 {
465   sponge_configuration *sp = &sps[MI_SCREEN(mi)];
466   Display *dpy = MI_DISPLAY(mi);
467   Window window = MI_WINDOW(mi);
468
469   static GLfloat color0[4] = {0.0, 0.0, 0.0, 1.0};
470   static GLfloat color1[4] = {0.0, 0.0, 0.0, 1.0};
471   static GLfloat color2[4] = {0.0, 0.0, 0.0, 1.0};
472
473   static int tick = 99999;
474
475   if (!sp->glx_context)
476     return;
477
478   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
479
480   glPushMatrix ();
481
482   glScalef(1.1, 1.1, 1.1);
483
484   {
485     double x, y, z;
486     get_position (sp->rot, &x, &y, &z, !sp->button_down_p);
487     glTranslatef((x - 0.5) * 8,
488                  (y - 0.5) * 6,
489                  (z - 0.5) * 15);
490
491     gltrackball_rotate (sp->trackball);
492
493     get_rotation (sp->rot, &x, &y, &z, !sp->button_down_p);
494     glRotatef (x * 360, 1.0, 0.0, 0.0);
495     glRotatef (y * 360, 0.0, 1.0, 0.0);
496     glRotatef (z * 360, 0.0, 0.0, 1.0);
497   }
498
499   color0[0] = sp->colors[sp->ccolor0].red   / 65536.0;
500   color0[1] = sp->colors[sp->ccolor0].green / 65536.0;
501   color0[2] = sp->colors[sp->ccolor0].blue  / 65536.0;
502
503   color1[0] = sp->colors[sp->ccolor1].red   / 65536.0;
504   color1[1] = sp->colors[sp->ccolor1].green / 65536.0;
505   color1[2] = sp->colors[sp->ccolor1].blue  / 65536.0;
506
507   color2[0] = sp->colors[sp->ccolor2].red   / 65536.0;
508   color2[1] = sp->colors[sp->ccolor2].green / 65536.0;
509   color2[2] = sp->colors[sp->ccolor2].blue  / 65536.0;
510
511
512   sp->ccolor0++;
513   sp->ccolor1++;
514   sp->ccolor2++;
515   if (sp->ccolor0 >= sp->ncolors) sp->ccolor0 = 0;
516   if (sp->ccolor1 >= sp->ncolors) sp->ccolor1 = 0;
517   if (sp->ccolor2 >= sp->ncolors) sp->ccolor2 = 0;
518
519   if (tick++ >= speed)
520     {
521       tick = 0;
522       if (sp->current_depth >= max_depth)
523         sp->current_depth = -max_depth;
524       sp->current_depth++;
525       build_sponge (sp,
526                     MI_IS_WIREFRAME(mi),
527                     (sp->current_depth < 0
528                      ? -sp->current_depth : sp->current_depth));
529
530       mi->polygon_count = sp->squares_fp;  /* for FPS display */
531     }
532
533   glScalef (2.0, 2.0, 2.0);
534
535   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color0);
536   glCallList (sp->sponge_list0);
537   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color1);
538   glCallList (sp->sponge_list1);
539   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color2);
540   glCallList (sp->sponge_list2);
541
542   glPopMatrix ();
543
544   if (mi->fps_p) do_fps (mi);
545   glFinish();
546
547   glXSwapBuffers(dpy, window);
548 }
549
550 #endif /* USE_GL */