From http://www.jwz.org/xscreensaver/xscreensaver-5.38.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 free_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   int y = 0;
103
104   if (width > height * 2) {   /* tiny window: show middle */
105     height = width;
106     y = -height/2;
107     h = height / (GLfloat) width;
108   }
109
110   glViewport (0, y, (GLint) width, (GLint) height);
111
112   glMatrixMode(GL_PROJECTION);
113   glLoadIdentity();
114
115   /* For this one it's really important to minimize the distance between
116      near and far. */
117   gluPerspective (30, 1/h, 10, 50);
118   glMatrixMode(GL_MODELVIEW);
119   glLoadIdentity();
120   gluLookAt( 0.0, 0.0, 30.0,
121              0.0, 0.0, 0.0,
122              0.0, 1.0, 0.0);
123
124   glClear(GL_COLOR_BUFFER_BIT);
125 }
126
127
128 static void
129 reset_colors (ModeInfo *mi)
130 {
131   cube_configuration *cc = &ccs[MI_SCREEN(mi)];
132   make_smooth_colormap (0, 0, 0,
133                         cc->colors, &cc->ncolors, 
134                         False, 0, False);
135   if (! MI_IS_WIREFRAME(mi))
136     glClearColor (cc->colors[0].red   / 65536.0,
137                   cc->colors[0].green / 65536.0,
138                   cc->colors[0].blue  / 65536.0,
139                   1);
140 }
141
142
143 static void
144 tweak_cubes (ModeInfo *mi)
145 {
146   cube_configuration *cc = &ccs[MI_SCREEN(mi)];
147   int i;
148   for (i = 0; i < cc->ncubes; i++)
149     {
150       cube *cube = &cc->cubes[i];
151       cube->x += (frand(2)-1)*0.01;
152       cube->y += (frand(2)-1)*0.01;
153       cube->z += (frand(2)-1)*0.01;
154     }
155 }
156
157
158 ENTRYPOINT Bool
159 cube_handle_event (ModeInfo *mi, XEvent *event)
160 {
161   cube_configuration *cc = &ccs[MI_SCREEN(mi)];
162
163   /* Neutralize any vertical motion */
164   GLfloat rot = current_device_rotation();
165   Bool rotp = ((rot >  45 && rot <  135) ||
166                (rot < -45 && rot > -135));
167                
168   if (event->xany.type == ButtonPress ||
169       event->xany.type == ButtonRelease)
170     {
171       if (rotp)
172         event->xbutton.x = MI_WIDTH(mi) / 2;
173       else
174         event->xbutton.y = MI_HEIGHT(mi) / 2;
175     }
176   else if (event->xany.type == MotionNotify)
177     {
178       if (rotp)
179         event->xmotion.x = MI_WIDTH(mi) / 2;
180       else
181         event->xmotion.y = MI_HEIGHT(mi) / 2;
182     }
183
184   if (gltrackball_event_handler (event, cc->trackball,
185                                  MI_WIDTH (mi), MI_HEIGHT (mi),
186                                  &cc->button_down_p))
187     return True;
188   else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
189     {
190       reset_colors (mi);
191       tweak_cubes (mi);
192       gltrackball_reset (cc->trackball, 0, 0);
193       return True;
194     }
195
196   return False;
197 }
198
199
200 /* Waves.
201    Adapted from ../hacks/interference.c by Hannu Mallat.
202  */
203
204 static void
205 init_wave (ModeInfo *mi)
206 {
207   cube_configuration *cc = &ccs[MI_SCREEN(mi)];
208   waves *ww;
209   int i;
210   cc->waves = ww = (waves *) calloc (sizeof(*cc->waves), 1);
211   ww->nwaves = wave_count;
212   ww->radius = wave_radius;
213   ww->speed  = wave_speed;
214   ww->heights = (int *) calloc (sizeof(*ww->heights), ww->radius);
215   ww->srcs = (wave_src *) calloc (sizeof(*ww->srcs), ww->nwaves);
216
217   for (i = 0; i < ww->radius; i++)
218     {
219       float max = (cc->ncolors * (ww->radius - i) / (float) ww->radius);
220       ww->heights[i] = ((max + max * cos(i / 50.0)) / 2.0);
221     }
222
223   for (i = 0; i < ww->nwaves; i++)
224     {
225       ww->srcs[i].xth = frand(2.0) * M_PI;
226       ww->srcs[i].yth = frand(2.0) * M_PI;
227     }
228
229   cc->texture_width  = texture_size;
230   cc->texture_height = texture_size;
231 }
232
233
234 static int
235 interference_point (cube_configuration *cc, int x, int y)
236 {
237   /* Compute the effect of the waves on a pixel. */
238
239   waves *ww = cc->waves;
240   int result = 0;
241   int i;
242   for (i = 0; i < ww->nwaves; i++)
243     {
244       int dx = x - ww->srcs[i].x;
245       int dy = y - ww->srcs[i].y;
246       int dist = sqrt (dx*dx + dy*dy);
247       result += (dist >= ww->radius ? 0 : ww->heights[dist]);
248     }
249   result *= 0.4;
250   if (result > 255) result = 255;
251   return result;
252 }
253
254
255 static void
256 interference (ModeInfo *mi)
257 {
258   cube_configuration *cc = &ccs[MI_SCREEN(mi)];
259   waves *ww = cc->waves;
260   int i;
261
262   /* Move the wave origins around
263    */
264   for (i = 0; i < ww->nwaves; i++)
265     {
266       ww->srcs[i].xth += (ww->speed / 1000.0);
267       if (ww->srcs[i].xth > 2*M_PI)
268         ww->srcs[i].xth -= 2*M_PI;
269       ww->srcs[i].yth += (ww->speed / 1000.0);
270       if (ww->srcs[i].yth > 2*M_PI)
271         ww->srcs[i].yth -= 2*M_PI;
272
273       ww->srcs[i].x = (cc->texture_width/2 +
274                        (cos (ww->srcs[i].xth) *
275                         cc->texture_width / 2));
276       ww->srcs[i].y = (cc->texture_height/2 +
277                        (cos (ww->srcs[i].yth) *
278                         cc->texture_height / 2));
279     }
280 }
281
282
283 /* qsort comparator for sorting cubes by y position */
284 static int
285 cmp_cubes (const void *aa, const void *bb)
286 {
287   const cube *a = (cube *) aa;
288   const cube *b = (cube *) bb;
289   return ((int) (b->y * 10000) -
290           (int) (a->y * 10000));
291 }
292
293
294 ENTRYPOINT void 
295 init_cube (ModeInfo *mi)
296 {
297   int i;
298   cube_configuration *cc;
299
300   MI_INIT (mi, ccs);
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 (False);
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       double th = -(skew ? frand(skew) : 0) * M_PI / 180;
327
328       cube->x = (frand(1)-0.5);
329       cube->y = (frand(1)-0.5);
330
331       cube->z = frand(0.12);
332       cube->cth = cos(th);
333       cube->sth = sin(th);
334
335       cube->w = scale * (frand(1) + 0.2);
336       cube->d = scale * (frand(1) + 0.2);
337
338       if (cube->x < cc->min_x) cc->min_x = cube->x;
339       if (cube->y < cc->min_y) cc->min_y = cube->y;
340       if (cube->x > cc->max_x) cc->max_x = cube->x;
341       if (cube->y > cc->max_y) cc->max_y = cube->y;
342     }
343
344   /* Sorting by depth improves frame rate slightly. With 6000 polygons we get:
345      3.9 FPS unsorted;
346      3.1 FPS back to front;
347      4.3 FPS front to back.
348    */
349   qsort (cc->cubes, cc->ncubes, sizeof(*cc->cubes), cmp_cubes);
350 }
351
352
353 static void
354 animate_cubes (ModeInfo *mi)
355 {
356   cube_configuration *cc = &ccs[MI_SCREEN(mi)];
357   int i;
358   for (i = 0; i < cc->ncubes; i++)
359     {
360       cube *cube = &cc->cubes[i];
361       GLfloat fx = (cube->x - cc->min_x) / (cc->max_x - cc->min_x);
362       GLfloat fy = (cube->y - cc->min_y) / (cc->max_y - cc->min_y);
363       int x = (int) (cc->texture_width  * fx) % cc->texture_width;
364       int y = (int) (cc->texture_height * fy) % cc->texture_height;
365       unsigned char v = interference_point (cc, x, y);
366       cube->h = cube->z + (v / 256.0 / 2.5) + 0.1;
367     }
368 }
369
370
371 ENTRYPOINT void
372 draw_cube (ModeInfo *mi)
373 {
374   cube_configuration *cc = &ccs[MI_SCREEN(mi)];
375   int wire = MI_IS_WIREFRAME(mi);
376   Display *dpy = MI_DISPLAY(mi);
377   Window window = MI_WINDOW(mi);
378   int i;
379
380   if (!cc->glx_context)
381     return;
382
383   mi->polygon_count = 0;
384   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(cc->glx_context));
385
386   interference (mi);
387   animate_cubes (mi);
388
389   glShadeModel(GL_FLAT);
390
391   glEnable(GL_DEPTH_TEST);
392   glEnable(GL_NORMALIZE);
393   glEnable(GL_CULL_FACE);
394   /* glEnable (GL_POLYGON_OFFSET_FILL); */
395
396   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
397
398   glPushMatrix ();
399
400   glRotatef(current_device_rotation(), 0, 0, 1);
401   gltrackball_rotate (cc->trackball);
402   glRotatef (-180, 1, 0, 0);
403
404   {
405     GLfloat s = 15;
406     glScalef (s, s, s);
407   }
408   glRotatef (-90, 1, 0, 0);
409
410   glTranslatef (-0.18, 0, -0.18);
411   glRotatef (37, 1, 0, 0);
412   glRotatef (20, 0, 0, 1);
413
414   glScalef (2.1, 2.1, 2.1);
415
416   /* Position lights after device rotation. */
417   if (!wire)
418     {
419       static const GLfloat pos[4] = {0.0, 0.25, -1.0, 0.0};
420       static const GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0};
421       static const GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
422
423       glLightfv(GL_LIGHT0, GL_POSITION, pos);
424       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
425       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
426
427       glEnable(GL_LIGHTING);
428       glEnable(GL_LIGHT0);
429       glEnable(GL_DEPTH_TEST);
430       glEnable(GL_CULL_FACE);
431     }
432
433   glBegin (wire ? GL_LINES : GL_QUADS);
434
435   for (i = 0; i < cc->ncubes; i++)
436     {
437       cube *cube = &cc->cubes[i];
438       GLfloat cth = cube->cth;
439       GLfloat sth = cube->sth;
440       GLfloat x =  cth*cube->x + sth*cube->y;
441       GLfloat y = -sth*cube->x + cth*cube->y;
442       GLfloat w = cube->w/2;
443       GLfloat h = cube->h/2;
444       GLfloat d = cube->d/2;
445       GLfloat bottom = 5;
446
447       GLfloat xw =  cth*w, xd = sth*d;
448       GLfloat yw = -sth*w, yd = cth*d;
449
450       GLfloat color[4];
451       int c = cube->h * cc->ncolors * 0.7;
452       c %= cc->ncolors;
453
454       color[0] = cc->colors[c].red   / 65536.0;
455       color[1] = cc->colors[c].green / 65536.0;
456       color[2] = cc->colors[c].blue  / 65536.0;
457       color[3] = 1.0;
458       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
459
460       /* Putting this in a display list makes no performance difference. */
461
462       if (! wire)
463         {
464           glNormal3f (0, 0, -1);                                /* top */
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           glVertex3f (x-xw+xd, y-yw+yd, -h);
469           mi->polygon_count++;
470
471           glNormal3f (sth, cth, 0);                             /* front */
472           glVertex3f (x+xw+xd, y+yw+yd, bottom);
473           glVertex3f (x+xw+xd, y+yw+yd, -h);
474           glVertex3f (x-xw+xd, y-yw+yd, -h);
475           glVertex3f (x-xw+xd, y-yw+yd, bottom);
476           mi->polygon_count++;
477
478           glNormal3f (cth, -sth, 0);                            /* right */
479           glVertex3f (x+xw-xd, y+yw-yd, -h);
480           glVertex3f (x+xw+xd, y+yw+yd, -h);
481           glVertex3f (x+xw+xd, y+yw+yd, bottom);
482           glVertex3f (x+xw-xd, y+yw-yd, bottom);
483           mi->polygon_count++;
484
485 # if 0    /* Omitting these makes no performance difference. */
486
487           glNormal3f (-cth, sth, 0);                    /* left */
488           glVertex3f (x-xw+xd, y-yw+yd, -h);
489           glVertex3f (x-xw-xd, y-yw-yd, -h);
490           glVertex3f (x-xw-xd, y-yw-yd, bottom);
491           glVertex3f (x-xw+xd, y-yw+yd, bottom);
492           mi->polygon_count++;
493
494           glNormal3f (-sth, -cth, 0);                   /* back */
495           glVertex3f (x-xw-xd, y-yw-yd, bottom);
496           glVertex3f (x-xw-xd, y-yw-yd, -h);
497           glVertex3f (x+xw-xd, y+yw-yd, -h);
498           glVertex3f (x+xw-xd, y+yw-yd, bottom);
499           mi->polygon_count++;
500 # endif
501         }
502       else
503         {
504           glNormal3f (0, 0, -1);                                /* top */
505           glVertex3f (x+xw+xd, y+yw+yd, -h);
506           glVertex3f (x+xw-xd, y+yw-yd, -h);
507
508           glVertex3f (x+xw-xd, y+yw-yd, -h);
509           glVertex3f (x-xw-xd, y-yw-yd, -h);
510
511           glVertex3f (x-xw-xd, y-yw-yd, -h);
512           glVertex3f (x-xw+xd, y-yw+yd, -h);
513
514           glVertex3f (x-xw+xd, y-yw+yd, -h);
515           glVertex3f (x+xw+xd, y+yw+yd, -h);
516           mi->polygon_count++;
517         }
518     }
519   glEnd();
520
521   glPolygonOffset (0, 0);
522
523 # if 0
524   glDisable(GL_DEPTH_TEST);     /* Outline the playfield */
525   glColor3f(1,1,1);
526   glBegin(GL_LINE_LOOP);
527   glVertex3f (-0.5, -0.5, 0);
528   glVertex3f (-0.5,  0.5, 0);
529   glVertex3f ( 0.5,  0.5, 0);
530   glVertex3f ( 0.5, -0.5, 0);
531   glEnd();
532 # endif
533
534   glPopMatrix();
535
536   if (mi->fps_p) do_fps (mi);
537   glFinish();
538
539   glXSwapBuffers(dpy, window);
540 }
541
542
543 XSCREENSAVER_MODULE_2 ("Cityflow", cityflow, cube)
544
545 #endif /* USE_GL */