From http://www.jwz.org/xscreensaver/xscreensaver-5.32.tar.gz
[xscreensaver] / hacks / glx / cityflow.c
1 /* cityflow, Copyright (c) 2014 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 #define DEFAULTS        "*delay:        20000       \n" \
13                         "*count:        800         \n" \
14                         "*showFPS:      False       \n" \
15                         "*wireframe:    False       \n" \
16
17 # define refresh_cube 0
18 # define release_cube 0
19 #undef countof
20 #define countof(x) (sizeof((x))/sizeof((*x)))
21
22 #include "xlockmore.h"
23 #include "colors.h"
24 #include "gltrackball.h"
25 #include <ctype.h>
26
27 #ifdef USE_GL /* whole file */
28
29
30 #define DEF_SKEW        "12"
31
32 #define DEF_WAVES        "6"
33 #define DEF_WAVE_SPEED   "25"
34 #define DEF_WAVE_RADIUS  "256"
35 static int texture_size = 512;
36
37 typedef struct {
38   GLfloat x, y, z;
39   GLfloat w, h, d;
40   GLfloat cth, sth;
41 } cube;
42
43 typedef struct {
44   int x, y;
45   double xth, yth;
46 } wave_src;
47
48 typedef struct {
49   int nwaves;
50   int radius;
51   int speed;
52   wave_src *srcs;
53   int *heights;
54 } waves;
55
56
57 typedef struct {
58   GLXContext *glx_context;
59   trackball_state *trackball;
60   Bool button_down_p;
61   GLuint cube_list;
62   int cube_polys;
63   int ncubes;
64   cube *cubes;
65   waves *waves;
66   GLfloat min_x, max_x, min_y, max_y;
67   int texture_width, texture_height;
68   int ncolors;
69   XColor *colors;
70
71 } cube_configuration;
72
73 static cube_configuration *ccs = NULL;
74
75 static int wave_count;
76 static int wave_speed;
77 static int wave_radius;
78 static int skew;
79
80 static XrmOptionDescRec opts[] = {
81   {"-waves",       ".waves",      XrmoptionSepArg, 0 },
82   {"-wave-speed",  ".waveSpeed",  XrmoptionSepArg, 0 },
83   {"-wave-radius", ".waveRadius", XrmoptionSepArg, 0 },
84   {"-skew",        ".skew",       XrmoptionSepArg, 0 },
85 };
86
87 static argtype vars[] = {
88   {&wave_count, "waves",     "Waves",      DEF_WAVES, t_Int},
89   {&wave_speed, "waveSpeed", "WaveSpeed",  DEF_WAVE_SPEED, t_Int},
90   {&wave_radius,"waveRadius","WaveRadius", DEF_WAVE_RADIUS,t_Int},
91   {&skew,       "skew",      "Skew",       DEF_SKEW,t_Int},
92 };
93
94 ENTRYPOINT ModeSpecOpt cube_opts = {
95   countof(opts), opts, countof(vars), vars, NULL};
96
97
98 ENTRYPOINT void
99 reshape_cube (ModeInfo *mi, int width, int height)
100 {
101   GLfloat h = (GLfloat) height / (GLfloat) width;
102
103   glViewport (0, 0, (GLint) width, (GLint) height);
104
105   glMatrixMode(GL_PROJECTION);
106   glLoadIdentity();
107
108   /* For this one it's really important to minimize the distance between
109      near and far. */
110   gluPerspective (30, 1/h, 10, 50);
111   glMatrixMode(GL_MODELVIEW);
112   glLoadIdentity();
113   gluLookAt( 0.0, 0.0, 30.0,
114              0.0, 0.0, 0.0,
115              0.0, 1.0, 0.0);
116
117   glClear(GL_COLOR_BUFFER_BIT);
118 }
119
120
121 static void
122 reset_colors (ModeInfo *mi)
123 {
124   cube_configuration *cc = &ccs[MI_SCREEN(mi)];
125   make_smooth_colormap (0, 0, 0,
126                         cc->colors, &cc->ncolors, 
127                         False, 0, False);
128   if (! MI_IS_WIREFRAME(mi))
129     glClearColor (cc->colors[0].red   / 65536.0,
130                   cc->colors[0].green / 65536.0,
131                   cc->colors[0].blue  / 65536.0,
132                   1);
133 }
134
135
136 static void
137 tweak_cubes (ModeInfo *mi)
138 {
139   cube_configuration *cc = &ccs[MI_SCREEN(mi)];
140   int i;
141   for (i = 0; i < cc->ncubes; i++)
142     {
143       cube *cube = &cc->cubes[i];
144       cube->x += (frand(2)-1)*0.01;
145       cube->y += (frand(2)-1)*0.01;
146       cube->z += (frand(2)-1)*0.01;
147     }
148 }
149
150
151 ENTRYPOINT Bool
152 cube_handle_event (ModeInfo *mi, XEvent *event)
153 {
154   cube_configuration *cc = &ccs[MI_SCREEN(mi)];
155
156   /* Neutralize any vertical motion */
157   GLfloat rot = current_device_rotation();
158   Bool rotp = ((rot >  45 && rot <  135) ||
159                (rot < -45 && rot > -135));
160                
161   if (event->xany.type == ButtonPress ||
162       event->xany.type == ButtonRelease)
163     {
164       if (rotp)
165         event->xbutton.x = MI_WIDTH(mi) / 2;
166       else
167         event->xbutton.y = MI_HEIGHT(mi) / 2;
168     }
169   else if (event->xany.type == MotionNotify)
170     {
171       if (rotp)
172         event->xmotion.x = MI_WIDTH(mi) / 2;
173       else
174         event->xmotion.y = MI_HEIGHT(mi) / 2;
175     }
176
177   if (gltrackball_event_handler (event, cc->trackball,
178                                  MI_WIDTH (mi), MI_HEIGHT (mi),
179                                  &cc->button_down_p))
180     return True;
181   else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
182     {
183       reset_colors (mi);
184       tweak_cubes (mi);
185       gltrackball_reset (cc->trackball);
186       return True;
187     }
188
189   return False;
190 }
191
192
193 /* Waves.
194    Adapted from ../hacks/interference.c by Hannu Mallat.
195  */
196
197 static void
198 init_wave (ModeInfo *mi)
199 {
200   cube_configuration *cc = &ccs[MI_SCREEN(mi)];
201   waves *ww;
202   int i;
203   cc->waves = ww = (waves *) calloc (sizeof(*cc->waves), 1);
204   ww->nwaves = wave_count;
205   ww->radius = wave_radius;
206   ww->speed  = wave_speed;
207   ww->heights = (int *) calloc (sizeof(*ww->heights), ww->radius);
208   ww->srcs = (wave_src *) calloc (sizeof(*ww->srcs), ww->nwaves);
209
210   for (i = 0; i < ww->radius; i++)
211     {
212       float max = (cc->ncolors * (ww->radius - i) / (float) ww->radius);
213       ww->heights[i] = ((max + max * cos(i / 50.0)) / 2.0);
214     }
215
216   for (i = 0; i < ww->nwaves; i++)
217     {
218       ww->srcs[i].xth = frand(2.0) * M_PI;
219       ww->srcs[i].yth = frand(2.0) * M_PI;
220     }
221
222   cc->texture_width  = texture_size;
223   cc->texture_height = texture_size;
224 }
225
226
227 static int
228 interference_point (cube_configuration *cc, int x, int y)
229 {
230   /* Compute the effect of the waves on a pixel. */
231
232   waves *ww = cc->waves;
233   int result = 0;
234   int i;
235   for (i = 0; i < ww->nwaves; i++)
236     {
237       int dx = x - ww->srcs[i].x;
238       int dy = y - ww->srcs[i].y;
239       int dist = sqrt (dx*dx + dy*dy);
240       result += (dist >= ww->radius ? 0 : ww->heights[dist]);
241     }
242   result *= 0.4;
243   if (result > 255) result = 255;
244   return result;
245 }
246
247
248 static void
249 interference (ModeInfo *mi)
250 {
251   cube_configuration *cc = &ccs[MI_SCREEN(mi)];
252   waves *ww = cc->waves;
253   int i;
254
255   /* Move the wave origins around
256    */
257   for (i = 0; i < ww->nwaves; i++)
258     {
259       ww->srcs[i].xth += (ww->speed / 1000.0);
260       if (ww->srcs[i].xth > 2*M_PI)
261         ww->srcs[i].xth -= 2*M_PI;
262       ww->srcs[i].yth += (ww->speed / 1000.0);
263       if (ww->srcs[i].yth > 2*M_PI)
264         ww->srcs[i].yth -= 2*M_PI;
265
266       ww->srcs[i].x = (cc->texture_width/2 +
267                        (cos (ww->srcs[i].xth) *
268                         cc->texture_width / 2));
269       ww->srcs[i].y = (cc->texture_height/2 +
270                        (cos (ww->srcs[i].yth) *
271                         cc->texture_height / 2));
272     }
273 }
274
275
276 /* qsort comparator for sorting cubes by y position */
277 static int
278 cmp_cubes (const void *aa, const void *bb)
279 {
280   const cube *a = (cube *) aa;
281   const cube *b = (cube *) bb;
282   return ((int) (b->y * 10000) -
283           (int) (a->y * 10000));
284 }
285
286
287 ENTRYPOINT void 
288 init_cube (ModeInfo *mi)
289 {
290   int i;
291   cube_configuration *cc;
292
293   if (!ccs) {
294     ccs = (cube_configuration *)
295       calloc (MI_NUM_SCREENS(mi), sizeof (cube_configuration));
296     if (!ccs) {
297       fprintf(stderr, "%s: out of memory\n", progname);
298       exit(1);
299     }
300   }
301
302   cc = &ccs[MI_SCREEN(mi)];
303
304   if ((cc->glx_context = init_GL(mi)) != NULL) {
305     reshape_cube (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
306   }
307
308   cc->trackball = gltrackball_init (True);
309
310   cc->ncolors = 256;
311   cc->colors = (XColor *) calloc(cc->ncolors, sizeof(XColor));
312
313   reset_colors (mi);
314   init_wave (mi);
315
316   cc->ncubes = MI_COUNT (mi);
317
318   if (cc->ncubes < 1) cc->ncubes = 1;
319
320   cc->cubes = (cube *) calloc (sizeof(cube), cc->ncubes);
321   for (i = 0; i < cc->ncubes; i++)
322     {
323       /* Set the size to roughly cover a 2x2 square on average. */
324       GLfloat scale = 1.8 / sqrt (cc->ncubes);
325       cube *cube = &cc->cubes[i];
326       cube->x = (frand(1)-0.5);
327       cube->y = (frand(1)-0.5);
328
329       cube->z = frand(0.12);
330       double th = -(skew ? frand(skew) : 0) * M_PI / 180;
331       cube->cth = cos(th);
332       cube->sth = sin(th);
333
334       cube->w = scale * (frand(1) + 0.2);
335       cube->d = scale * (frand(1) + 0.2);
336
337       if (cube->x < cc->min_x) cc->min_x = cube->x;
338       if (cube->y < cc->min_y) cc->min_y = cube->y;
339       if (cube->x > cc->max_x) cc->max_x = cube->x;
340       if (cube->y > cc->max_y) cc->max_y = cube->y;
341     }
342
343   /* Sorting by depth improves frame rate slightly. With 6000 polygons we get:
344      3.9 FPS unsorted;
345      3.1 FPS back to front;
346      4.3 FPS front to back.
347    */
348   qsort (cc->cubes, cc->ncubes, sizeof(*cc->cubes), cmp_cubes);
349 }
350
351
352 static void
353 animate_cubes (ModeInfo *mi)
354 {
355   cube_configuration *cc = &ccs[MI_SCREEN(mi)];
356   int i;
357   for (i = 0; i < cc->ncubes; i++)
358     {
359       cube *cube = &cc->cubes[i];
360       GLfloat fx = (cube->x - cc->min_x) / (cc->max_x - cc->min_x);
361       GLfloat fy = (cube->y - cc->min_y) / (cc->max_y - cc->min_y);
362       int x = (int) (cc->texture_width  * fx) % cc->texture_width;
363       int y = (int) (cc->texture_height * fy) % cc->texture_height;
364       unsigned char v = interference_point (cc, x, y);
365       cube->h = cube->z + (v / 256.0 / 2.5) + 0.1;
366     }
367 }
368
369
370 ENTRYPOINT void
371 draw_cube (ModeInfo *mi)
372 {
373   cube_configuration *cc = &ccs[MI_SCREEN(mi)];
374   int wire = MI_IS_WIREFRAME(mi);
375   Display *dpy = MI_DISPLAY(mi);
376   Window window = MI_WINDOW(mi);
377   int i;
378
379   if (!cc->glx_context)
380     return;
381
382   mi->polygon_count = 0;
383   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(cc->glx_context));
384
385   interference (mi);
386   animate_cubes (mi);
387
388   glShadeModel(GL_FLAT);
389
390   glEnable(GL_DEPTH_TEST);
391   glEnable(GL_NORMALIZE);
392   glEnable(GL_CULL_FACE);
393   /* glEnable (GL_POLYGON_OFFSET_FILL); */
394
395   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
396
397   glPushMatrix ();
398
399   gltrackball_rotate (cc->trackball);
400   glRotatef (-180, 1, 0, 0);
401
402   {
403     GLfloat s = 15;
404     glScalef (s, s, s);
405   }
406   glRotatef (-90, 1, 0, 0);
407   glRotatef(current_device_rotation(), 0, 1, 0);
408
409   glTranslatef (-0.18, 0, -0.18);
410   glRotatef (37, 1, 0, 0);
411   glRotatef (20, 0, 0, 1);
412
413   glScalef (2.1, 2.1, 2.1);
414
415   /* Position lights after device rotation. */
416   if (!wire)
417     {
418       static const GLfloat pos[4] = {0.0, 0.25, -1.0, 0.0};
419       static const GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0};
420       static const GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
421
422       glLightfv(GL_LIGHT0, GL_POSITION, pos);
423       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
424       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
425
426       glEnable(GL_LIGHTING);
427       glEnable(GL_LIGHT0);
428       glEnable(GL_DEPTH_TEST);
429       glEnable(GL_CULL_FACE);
430     }
431
432   glBegin (wire ? GL_LINES : GL_QUADS);
433
434   for (i = 0; i < cc->ncubes; i++)
435     {
436       cube *cube = &cc->cubes[i];
437       GLfloat cth = cube->cth;
438       GLfloat sth = cube->sth;
439       GLfloat x =  cth*cube->x + sth*cube->y;
440       GLfloat y = -sth*cube->x + cth*cube->y;
441       GLfloat w = cube->w/2;
442       GLfloat h = cube->h/2;
443       GLfloat d = cube->d/2;
444       GLfloat bottom = 5;
445
446       GLfloat xw =  cth*w, xd = sth*d;
447       GLfloat yw = -sth*w, yd = cth*d;
448
449       GLfloat color[4];
450       int c = cube->h * cc->ncolors * 0.7;
451       c %= cc->ncolors;
452
453       color[0] = cc->colors[c].red   / 65536.0;
454       color[1] = cc->colors[c].green / 65536.0;
455       color[2] = cc->colors[c].blue  / 65536.0;
456       color[3] = 1.0;
457       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
458
459       /* Putting this in a display list makes no performance difference. */
460
461       if (! wire)
462         {
463           glNormal3f (0, 0, -1);                                /* top */
464           glVertex3f (x+xw+xd, y+yw+yd, -h);
465           glVertex3f (x+xw-xd, y+yw-yd, -h);
466           glVertex3f (x-xw-xd, y-yw-yd, -h);
467           glVertex3f (x-xw+xd, y-yw+yd, -h);
468           mi->polygon_count++;
469
470           glNormal3f (sth, cth, 0);                             /* front */
471           glVertex3f (x+xw+xd, y+yw+yd, bottom);
472           glVertex3f (x+xw+xd, y+yw+yd, -h);
473           glVertex3f (x-xw+xd, y-yw+yd, -h);
474           glVertex3f (x-xw+xd, y-yw+yd, bottom);
475           mi->polygon_count++;
476
477           glNormal3f (cth, -sth, 0);                            /* right */
478           glVertex3f (x+xw-xd, y+yw-yd, -h);
479           glVertex3f (x+xw+xd, y+yw+yd, -h);
480           glVertex3f (x+xw+xd, y+yw+yd, bottom);
481           glVertex3f (x+xw-xd, y+yw-yd, bottom);
482           mi->polygon_count++;
483
484 # if 0    /* Omitting these makes no performance difference. */
485
486           glNormal3f (-cth, sth, 0);                    /* left */
487           glVertex3f (x-xw+xd, y-yw+yd, -h);
488           glVertex3f (x-xw-xd, y-yw-yd, -h);
489           glVertex3f (x-xw-xd, y-yw-yd, bottom);
490           glVertex3f (x-xw+xd, y-yw+yd, bottom);
491           mi->polygon_count++;
492
493           glNormal3f (-sth, -cth, 0);                   /* back */
494           glVertex3f (x-xw-xd, y-yw-yd, bottom);
495           glVertex3f (x-xw-xd, y-yw-yd, -h);
496           glVertex3f (x+xw-xd, y+yw-yd, -h);
497           glVertex3f (x+xw-xd, y+yw-yd, bottom);
498           mi->polygon_count++;
499 # endif
500         }
501       else
502         {
503           glNormal3f (0, 0, -1);                                /* top */
504           glVertex3f (x+xw+xd, y+yw+yd, -h);
505           glVertex3f (x+xw-xd, y+yw-yd, -h);
506
507           glVertex3f (x+xw-xd, y+yw-yd, -h);
508           glVertex3f (x-xw-xd, y-yw-yd, -h);
509
510           glVertex3f (x-xw-xd, y-yw-yd, -h);
511           glVertex3f (x-xw+xd, y-yw+yd, -h);
512
513           glVertex3f (x-xw+xd, y-yw+yd, -h);
514           glVertex3f (x+xw+xd, y+yw+yd, -h);
515           mi->polygon_count++;
516         }
517     }
518   glEnd();
519
520   glPolygonOffset (0, 0);
521
522 # if 0
523   glDisable(GL_DEPTH_TEST);     /* Outline the playfield */
524   glColor3f(1,1,1);
525   glBegin(GL_LINE_LOOP);
526   glVertex3f (-0.5, -0.5, 0);
527   glVertex3f (-0.5,  0.5, 0);
528   glVertex3f ( 0.5,  0.5, 0);
529   glVertex3f ( 0.5, -0.5, 0);
530   glEnd();
531 # endif
532
533   glPopMatrix();
534
535   if (mi->fps_p) do_fps (mi);
536   glFinish();
537
538   glXSwapBuffers(dpy, window);
539 }
540
541
542 XSCREENSAVER_MODULE_2 ("Cityflow", cityflow, cube)
543
544 #endif /* USE_GL */