From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / hacks / glx / cityflow.c
1 /* cityflow, Copyright (c) 2014-2017 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, 0, 0);
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   MI_INIT (mi, ccs, NULL);
294
295   cc = &ccs[MI_SCREEN(mi)];
296
297   if ((cc->glx_context = init_GL(mi)) != NULL) {
298     reshape_cube (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
299   }
300
301   cc->trackball = gltrackball_init (False);
302
303   cc->ncolors = 256;
304   cc->colors = (XColor *) calloc(cc->ncolors, sizeof(XColor));
305
306   reset_colors (mi);
307   init_wave (mi);
308
309   cc->ncubes = MI_COUNT (mi);
310
311   if (cc->ncubes < 1) cc->ncubes = 1;
312
313   cc->cubes = (cube *) calloc (sizeof(cube), cc->ncubes);
314   for (i = 0; i < cc->ncubes; i++)
315     {
316       /* Set the size to roughly cover a 2x2 square on average. */
317       GLfloat scale = 1.8 / sqrt (cc->ncubes);
318       cube *cube = &cc->cubes[i];
319       double th = -(skew ? frand(skew) : 0) * M_PI / 180;
320
321       cube->x = (frand(1)-0.5);
322       cube->y = (frand(1)-0.5);
323
324       cube->z = frand(0.12);
325       cube->cth = cos(th);
326       cube->sth = sin(th);
327
328       cube->w = scale * (frand(1) + 0.2);
329       cube->d = scale * (frand(1) + 0.2);
330
331       if (cube->x < cc->min_x) cc->min_x = cube->x;
332       if (cube->y < cc->min_y) cc->min_y = cube->y;
333       if (cube->x > cc->max_x) cc->max_x = cube->x;
334       if (cube->y > cc->max_y) cc->max_y = cube->y;
335     }
336
337   /* Sorting by depth improves frame rate slightly. With 6000 polygons we get:
338      3.9 FPS unsorted;
339      3.1 FPS back to front;
340      4.3 FPS front to back.
341    */
342   qsort (cc->cubes, cc->ncubes, sizeof(*cc->cubes), cmp_cubes);
343 }
344
345
346 static void
347 animate_cubes (ModeInfo *mi)
348 {
349   cube_configuration *cc = &ccs[MI_SCREEN(mi)];
350   int i;
351   for (i = 0; i < cc->ncubes; i++)
352     {
353       cube *cube = &cc->cubes[i];
354       GLfloat fx = (cube->x - cc->min_x) / (cc->max_x - cc->min_x);
355       GLfloat fy = (cube->y - cc->min_y) / (cc->max_y - cc->min_y);
356       int x = (int) (cc->texture_width  * fx) % cc->texture_width;
357       int y = (int) (cc->texture_height * fy) % cc->texture_height;
358       unsigned char v = interference_point (cc, x, y);
359       cube->h = cube->z + (v / 256.0 / 2.5) + 0.1;
360     }
361 }
362
363
364 ENTRYPOINT void
365 draw_cube (ModeInfo *mi)
366 {
367   cube_configuration *cc = &ccs[MI_SCREEN(mi)];
368   int wire = MI_IS_WIREFRAME(mi);
369   Display *dpy = MI_DISPLAY(mi);
370   Window window = MI_WINDOW(mi);
371   int i;
372
373   if (!cc->glx_context)
374     return;
375
376   mi->polygon_count = 0;
377   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(cc->glx_context));
378
379   interference (mi);
380   animate_cubes (mi);
381
382   glShadeModel(GL_FLAT);
383
384   glEnable(GL_DEPTH_TEST);
385   glEnable(GL_NORMALIZE);
386   glEnable(GL_CULL_FACE);
387   /* glEnable (GL_POLYGON_OFFSET_FILL); */
388
389   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
390
391   glPushMatrix ();
392
393   glRotatef(current_device_rotation(), 0, 0, 1);
394   gltrackball_rotate (cc->trackball);
395   glRotatef (-180, 1, 0, 0);
396
397   {
398     GLfloat s = 15;
399     glScalef (s, s, s);
400   }
401   glRotatef (-90, 1, 0, 0);
402
403   glTranslatef (-0.18, 0, -0.18);
404   glRotatef (37, 1, 0, 0);
405   glRotatef (20, 0, 0, 1);
406
407   glScalef (2.1, 2.1, 2.1);
408
409   /* Position lights after device rotation. */
410   if (!wire)
411     {
412       static const GLfloat pos[4] = {0.0, 0.25, -1.0, 0.0};
413       static const GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0};
414       static const GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
415
416       glLightfv(GL_LIGHT0, GL_POSITION, pos);
417       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
418       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
419
420       glEnable(GL_LIGHTING);
421       glEnable(GL_LIGHT0);
422       glEnable(GL_DEPTH_TEST);
423       glEnable(GL_CULL_FACE);
424     }
425
426   glBegin (wire ? GL_LINES : GL_QUADS);
427
428   for (i = 0; i < cc->ncubes; i++)
429     {
430       cube *cube = &cc->cubes[i];
431       GLfloat cth = cube->cth;
432       GLfloat sth = cube->sth;
433       GLfloat x =  cth*cube->x + sth*cube->y;
434       GLfloat y = -sth*cube->x + cth*cube->y;
435       GLfloat w = cube->w/2;
436       GLfloat h = cube->h/2;
437       GLfloat d = cube->d/2;
438       GLfloat bottom = 5;
439
440       GLfloat xw =  cth*w, xd = sth*d;
441       GLfloat yw = -sth*w, yd = cth*d;
442
443       GLfloat color[4];
444       int c = cube->h * cc->ncolors * 0.7;
445       c %= cc->ncolors;
446
447       color[0] = cc->colors[c].red   / 65536.0;
448       color[1] = cc->colors[c].green / 65536.0;
449       color[2] = cc->colors[c].blue  / 65536.0;
450       color[3] = 1.0;
451       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
452
453       /* Putting this in a display list makes no performance difference. */
454
455       if (! wire)
456         {
457           glNormal3f (0, 0, -1);                                /* top */
458           glVertex3f (x+xw+xd, y+yw+yd, -h);
459           glVertex3f (x+xw-xd, y+yw-yd, -h);
460           glVertex3f (x-xw-xd, y-yw-yd, -h);
461           glVertex3f (x-xw+xd, y-yw+yd, -h);
462           mi->polygon_count++;
463
464           glNormal3f (sth, cth, 0);                             /* front */
465           glVertex3f (x+xw+xd, y+yw+yd, bottom);
466           glVertex3f (x+xw+xd, y+yw+yd, -h);
467           glVertex3f (x-xw+xd, y-yw+yd, -h);
468           glVertex3f (x-xw+xd, y-yw+yd, bottom);
469           mi->polygon_count++;
470
471           glNormal3f (cth, -sth, 0);                            /* right */
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           glVertex3f (x+xw-xd, y+yw-yd, bottom);
476           mi->polygon_count++;
477
478 # if 0    /* Omitting these makes no performance difference. */
479
480           glNormal3f (-cth, sth, 0);                    /* left */
481           glVertex3f (x-xw+xd, y-yw+yd, -h);
482           glVertex3f (x-xw-xd, y-yw-yd, -h);
483           glVertex3f (x-xw-xd, y-yw-yd, bottom);
484           glVertex3f (x-xw+xd, y-yw+yd, bottom);
485           mi->polygon_count++;
486
487           glNormal3f (-sth, -cth, 0);                   /* back */
488           glVertex3f (x-xw-xd, y-yw-yd, bottom);
489           glVertex3f (x-xw-xd, y-yw-yd, -h);
490           glVertex3f (x+xw-xd, y+yw-yd, -h);
491           glVertex3f (x+xw-xd, y+yw-yd, bottom);
492           mi->polygon_count++;
493 # endif
494         }
495       else
496         {
497           glNormal3f (0, 0, -1);                                /* top */
498           glVertex3f (x+xw+xd, y+yw+yd, -h);
499           glVertex3f (x+xw-xd, y+yw-yd, -h);
500
501           glVertex3f (x+xw-xd, y+yw-yd, -h);
502           glVertex3f (x-xw-xd, y-yw-yd, -h);
503
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           mi->polygon_count++;
510         }
511     }
512   glEnd();
513
514   glPolygonOffset (0, 0);
515
516 # if 0
517   glDisable(GL_DEPTH_TEST);     /* Outline the playfield */
518   glColor3f(1,1,1);
519   glBegin(GL_LINE_LOOP);
520   glVertex3f (-0.5, -0.5, 0);
521   glVertex3f (-0.5,  0.5, 0);
522   glVertex3f ( 0.5,  0.5, 0);
523   glVertex3f ( 0.5, -0.5, 0);
524   glEnd();
525 # endif
526
527   glPopMatrix();
528
529   if (mi->fps_p) do_fps (mi);
530   glFinish();
531
532   glXSwapBuffers(dpy, window);
533 }
534
535
536 XSCREENSAVER_MODULE_2 ("Cityflow", cityflow, cube)
537
538 #endif /* USE_GL */