996d7c75b610b94327c20a889a87632e4a528f8a
[xscreensaver] / hacks / glx / flipscreen3d.c
1 /*
2  * flipscreen3d - takes snapshots of the screen and flips it around
3  *
4  * version 1.0 - Oct 24, 2001
5  *
6  * Copyright (C) 2001 Ben Buxton (bb@cactii.net)
7  *
8  * Permission to use, copy, modify, distribute, and sell this software and its
9  * documentation for any purpose is hereby granted without fee, provided that
10  * the above copyright notice appear in all copies and that both that
11  * copyright notice and this permission notice appear in supporting
12  * documentation.  No representations are made about the suitability of this
13  * software for any purpose.  It is provided "as is" without express or
14  * implied warranty.
15  */
16
17 #ifdef STANDALONE
18 #define DEFAULTS "*delay:     20000 \n" \
19                  "*showFPS:   False \n" \
20                  "*wireframe: False \n" \
21                  "*useSHM:    True  \n"
22
23 # define refresh_screenflip 0
24 # include "xlockmore.h"                         /* from the xscreensaver distribution */
25 # include "gltrackball.h"
26 #else  /* !STANDALONE */
27 # include "xlock.h"                                     /* from the xlockmore distribution */
28 #endif /* !STANDALONE */
29
30 /* lifted from lament.c */
31 #define RAND(n) ((long) ((random() & 0x7fffffff) % ((long) (n))))
32 #define RANDSIGN() ((random() & 1) ? 1 : -1)
33
34
35 #ifdef USE_GL
36
37 /* Should be in <GL/glext.h> */
38 # ifndef  GL_TEXTURE_MAX_ANISOTROPY_EXT
39 #  define GL_TEXTURE_MAX_ANISOTROPY_EXT     0x84FE
40 # endif
41 # ifndef  GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT
42 #  define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF
43 # endif
44
45 static int rotate;
46
47 #define QW 12
48 #define QH 12
49
50 #undef countof
51 #define countof(x) (sizeof((x))/sizeof((*x)))
52
53
54 static XrmOptionDescRec opts[] = {
55   {"+rotate", ".screenflip.rotate", XrmoptionNoArg, "false" },
56   {"-rotate", ".screenflip.rotate", XrmoptionNoArg, "true" },
57 };
58
59
60 static argtype vars[] = {
61   {&rotate, "rotate", "Rotate", "True", t_Bool},
62 };
63
64
65
66 ENTRYPOINT ModeSpecOpt screenflip_opts = {countof(opts), opts, countof(vars), vars, NULL};
67
68
69 #ifdef USE_MODULES
70 ModStruct   screenflip_description =
71 {"screenflip", "init_screenflip", "draw_screenflip", "release_screenflip",
72  "draw_screenflip", "init_screenflip", NULL, &screenflip_opts,
73  1000, 1, 2, 1, 4, 1.0, "",
74  "Screenflips", 0, NULL};
75
76 #endif
77
78
79 typedef struct {
80   GLXContext *glx_context;
81   Window window;
82
83   int winw, winh;
84   int tw, th; /* texture width, height */
85   GLfloat min_tx, min_ty;
86   GLfloat max_tx, max_ty;
87   GLfloat qx, qy, qw, qh; /* the quad we'll draw */
88
89   int regrab;
90   int fadetime; /* fade before regrab */
91
92   trackball_state *trackball;
93   Bool button_down_p;
94
95   GLfloat show_colors[4];
96   GLfloat stretch_val_x, stretch_val_y;
97   GLfloat stretch_val_dx, stretch_val_dy;
98
99   GLfloat curx, cury, curz;
100
101   GLfloat rx, ry, rz;
102   GLfloat rot, drot, odrot, ddrot, orot;
103   float theta, rho, dtheta, drho, gamma, dgamma;
104
105   GLuint texid;
106   Bool mipmap_p;
107   Bool waiting_for_image_p;
108   Bool first_image_p;
109
110   GLfloat anisotropic;
111
112 } Screenflip;
113
114 static Screenflip *screenflip = NULL;
115
116 #include "grab-ximage.h"
117
118 static const GLfloat viewer[] = {0.0, 0.0, 15.0};
119
120
121 ENTRYPOINT Bool
122 screenflip_handle_event (ModeInfo *mi, XEvent *event)
123 {
124   Screenflip *c = &screenflip[MI_SCREEN(mi)];
125
126   if (event->xany.type == ButtonPress &&
127       event->xbutton.button == Button1)
128     {
129       c->button_down_p = True;
130       gltrackball_start (c->trackball,
131                          event->xbutton.x, event->xbutton.y,
132                          MI_WIDTH (mi), MI_HEIGHT (mi));
133       return True;
134     }
135   else if (event->xany.type == ButtonRelease &&
136            event->xbutton.button == Button1)
137     {
138       c->button_down_p = False;
139       return True;
140     }
141   else if (event->xany.type == ButtonPress &&
142            (event->xbutton.button == Button4 ||
143             event->xbutton.button == Button5))
144     {
145       gltrackball_mousewheel (c->trackball, event->xbutton.button, 10,
146                               !!event->xbutton.state);
147       return True;
148     }
149   else if (event->xany.type == MotionNotify &&
150            c->button_down_p)
151     {
152       gltrackball_track (c->trackball,
153                          event->xmotion.x, event->xmotion.y,
154                          MI_WIDTH (mi), MI_HEIGHT (mi));
155       return True;
156     }
157
158   return False;
159 }
160
161
162 /* draw the texture mapped quad (actually two back to back)*/
163 static void showscreen(Screenflip *c, int frozen, int wire)
164 {
165   GLfloat x, y, w, h;
166
167   if (c->fadetime) {
168 /*    r -= 0.02; g -= 0.02; b -= 0.02; */
169     c->show_colors[3] -= 0.02;
170     if (c->show_colors[3] < 0) {
171       c->regrab = 1;
172       c->fadetime = 0;
173     }
174   } else if (c->show_colors[3] < 0) {
175     c->show_colors[0] = c->show_colors[1] = 
176       c->show_colors[2] = c->show_colors[3] = 1;
177     c->stretch_val_x = c->stretch_val_y = 
178       c->stretch_val_dx = c->stretch_val_dy = 0;
179   }
180   if (c->stretch_val_dx == 0 && !frozen && !(random() % 25))
181     c->stretch_val_dx = (float)(random() % 100) / 5000;
182   if (c->stretch_val_dy == 0 && !frozen && !(random() % 25))
183     c->stretch_val_dy = (float)(random() % 100) / 5000;
184     
185   x = c->qx;
186   y = c->qy;
187   w = c->qx+c->qw;
188   h = c->qy-c->qh;
189
190   if (!frozen) {
191      w *= sin (c->stretch_val_x) + 1;
192      x *= sin (c->stretch_val_x) + 1;
193      if (!c->button_down_p) {
194      if (!c->fadetime) c->stretch_val_x += c->stretch_val_dx;
195      if (c->stretch_val_x > 2*M_PI && !(random() % 5))
196        c->stretch_val_dx = (float)(random() % 100) / 5000;
197      else
198        c->stretch_val_x -= 2*M_PI;
199      }
200
201      if (!c->button_down_p && !c->fadetime) c->stretch_val_y += c->stretch_val_dy;
202      h *= sin (c->stretch_val_y) / 2 + 1;
203      y *= sin (c->stretch_val_y) / 2 + 1;
204      if (!c->button_down_p) {
205      if (c->stretch_val_y > 2*M_PI && !(random() % 5))
206        c->stretch_val_dy = (float)(random() % 100) / 5000;
207      else
208        c->stretch_val_y -= 2*M_PI;
209      }
210   }
211
212   glColor4f(c->show_colors[0], c->show_colors[1], 
213             c->show_colors[2], c->show_colors[3]);
214
215   if (!wire)
216     {
217       glEnable(GL_TEXTURE_2D);
218       glEnable(GL_BLEND);
219       glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
220       glDepthMask(GL_FALSE);
221     }
222
223   glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
224
225   glNormal3f(0, 0, 1);
226   glTexCoord2f(c->max_tx, c->max_ty); glVertex3f(w, h, 0);
227   glTexCoord2f(c->max_tx, c->min_ty); glVertex3f(w, y, 0);
228   glTexCoord2f(c->min_tx, c->min_ty); glVertex3f(x, y, 0);
229   glTexCoord2f(c->min_tx, c->max_ty); glVertex3f(x, h, 0);
230
231   glNormal3f(0, 0, -1);
232   glTexCoord2f(c->min_tx, c->min_ty); glVertex3f(x, y, -0.05);
233   glTexCoord2f(c->max_tx, c->min_ty); glVertex3f(w, y, -0.05);
234   glTexCoord2f(c->max_tx, c->max_ty); glVertex3f(w, h, -0.05);
235   glTexCoord2f(c->min_tx, c->max_ty); glVertex3f(x, h, -0.05);
236   glEnd();
237
238
239   glDisable(GL_TEXTURE_2D);
240   glDepthMask(GL_TRUE);
241
242   glBegin(GL_LINE_LOOP);
243    glVertex3f(x, y, 0);
244    glVertex3f(x, h, 0);
245    glVertex3f(w, h, 0);
246    glVertex3f(w, y, 0);
247  glEnd();
248   glDisable(GL_BLEND);
249
250 }
251
252 /* This function is responsible for 'zooming back' the square after
253  * a new chunk has been grabbed with getSnapshot(), and positioning
254  * it suitably on the screen. Once positioned (where we begin to rotate),
255  * it just does a glTranslatef() and returns 1
256  */
257
258 static int inposition(Screenflip *c)
259 {
260   GLfloat wx;
261   GLfloat wy;
262   wx = 0 - (c->qw/2);
263   wy = (c->qh/2);
264
265   if (c->curx == 0) c->curx = c->qx;
266   if (c->cury == 0) c->cury = c->qy;
267   if (c->regrab) {
268      c->curz = 0;
269      c->curx = c->qx;
270      c->cury = c->qy;
271      c->regrab = 0;
272   }
273   if (c->curz > -10 || c->curx > wx + 0.1 || c->curx < wx - 0.1 ||
274          c->cury > wy + 0.1 || c->cury < wy - 0.1) {
275     if (c->curz > -10)
276       c->curz -= 0.05;
277     if (c->curx > wx) {
278        c->qx -= 0.02;
279        c->curx -= 0.02;
280     }
281     if (c->curx < wx) {
282        c->qx += 0.02;
283        c->curx += 0.02;
284     }
285     if (c->cury > wy) {
286        c->qy -= 0.02;
287        c->cury -= 0.02;
288     }
289     if (c->cury < wy) {
290        c->qy += 0.02;
291        c->cury += 0.02;
292     }
293     glTranslatef(0, 0, c->curz);
294     return 0;
295   }
296   glTranslatef(0, 0, c->curz);
297   return 1;
298
299 }
300
301 #if 0
302 static void drawgrid(void)
303 {
304   int i;
305
306   glColor3f(0, 0.7, 0);
307   glBegin(GL_LINES);
308   for (i = 0 ; i <= 50; i+=2) {
309       glVertex3f( -25, -15, i-70);
310       glVertex3f( 25, -15, i-70);
311       glVertex3f( i-25, -15, -70);
312       glVertex3f( i-25, -15, -20);
313   }
314   glEnd();
315 }
316 #endif
317
318
319 static void display(Screenflip *c, int wire)
320 {
321   int frozen;
322
323   glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
324   glLoadIdentity();
325   gluLookAt(viewer[0], viewer[1], viewer[2], 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
326   glPushMatrix();
327
328   if (inposition(c)) {
329     frozen = 0;
330     glTranslatef(5 * sin(c->theta), 5 * sin(c->rho), 10 * cos(c->gamma) - 10);
331 /* randomly change the speed */
332     if (!c->button_down_p && !(random() % 300)) {
333       if (random() % 2)
334         c->drho = 1/60 - (float)(random() % 100)/3000;
335       if (random() % 2)
336         c->dtheta = 1/60 - (float)(random() % 100)/3000;
337       if (random() % 2)
338         c->dgamma = 1/60 - (float)(random() % 100)/3000;
339     }
340     gltrackball_rotate (c->trackball);
341     if (rotate) glRotatef(c->rot, c->rx, c->ry, c->rz);
342 /* update variables with each frame */
343     if(!c->button_down_p && !c->fadetime) {
344       c->theta += c->dtheta;
345       c->rho += c->drho;
346       c->gamma += c->dgamma;
347       c->rot += c->drot;
348       c->drot += c->ddrot;
349     }
350 /* dont let our rotation speed get too high */
351     if (c->drot > 5 && c->ddrot > 0)
352         c->ddrot = 0 - (GLfloat)(random() % 100) / 1000;
353     else if (c->drot < -5 && c->ddrot < 0)
354         c->ddrot = (GLfloat)(random() % 100) / 1000;
355   } else { /* reset some paramaters */
356     c->ddrot = 0.05 - (GLfloat)(random() % 100) / 1000;
357     c->theta = c->rho = c->gamma = 0;
358     c->rot = 0;
359     frozen = 1;
360   }
361   if (!c->button_down_p && !c->fadetime && (c->rot >= 360 || c->rot <= -360) && !(random() % 7)) { /* rotate  change */
362     c->rx = (GLfloat)(random() % 100) / 100;
363     c->ry = (GLfloat)(random() % 100) / 100;
364     c->rz = (GLfloat)(random() % 100) / 100;
365   }
366   if (c->odrot * c->drot < 0 && c->tw < c->winw && !(random() % 10)) {
367     c->fadetime = 1;                /* randomly fade and get new snapshot */
368   }
369   c->orot = c->rot;
370   c->odrot = c->drot;
371   if (c->rot > 360 || c->rot < -360) /* dont overflow rotation! */
372     c->rot -= c->rot;
373   showscreen(c, frozen, wire);
374   glPopMatrix();
375   glFlush();
376 }
377
378 ENTRYPOINT void reshape_screenflip(ModeInfo *mi, int width, int height)
379 {
380  Screenflip *c = &screenflip[MI_SCREEN(mi)];
381  glViewport(0,0,(GLint)width, (GLint) height);
382  glMatrixMode(GL_PROJECTION);
383  glLoadIdentity();
384  gluPerspective(45, 1, 2.0, 85);
385  glMatrixMode(GL_MODELVIEW);
386  c->winw = width;
387  c->winh = height;
388 }
389
390 static void
391 image_loaded_cb (const char *filename, XRectangle *geometry,
392                  int image_width, int image_height, 
393                  int texture_width, int texture_height,
394                  void *closure)
395 {
396   Screenflip *c = (Screenflip *) closure;
397
398   c->tw = texture_width;
399   c->th = texture_height;
400   c->min_tx = (GLfloat) geometry->x / c->tw;
401   c->min_ty = (GLfloat) geometry->y / c->th;
402   c->max_tx = (GLfloat) (geometry->x + geometry->width)  / c->tw;
403   c->max_ty = (GLfloat) (geometry->y + geometry->height) / c->th;
404
405   c->qx = -QW/2 + ((GLfloat) geometry->x * QW / image_width);
406   c->qy =  QH/2 - ((GLfloat) geometry->y * QH / image_height);
407   c->qw =  QW   * ((GLfloat) geometry->width  / image_width);
408   c->qh =  QH   * ((GLfloat) geometry->height / image_height);
409
410   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
411   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
412                    (c->mipmap_p ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR));
413
414   if (c->anisotropic >= 1.0)
415     glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 
416                      c->anisotropic);
417
418   c->waiting_for_image_p = False;
419   c->first_image_p = False;
420 }
421
422
423 static void getSnapshot (ModeInfo *modeinfo)
424 {
425   Screenflip *c = &screenflip[MI_SCREEN(modeinfo)];
426
427   if (MI_IS_WIREFRAME(modeinfo))
428     return;
429
430   c->waiting_for_image_p = True;
431   c->mipmap_p = True;
432   load_texture_async (modeinfo->xgwa.screen, modeinfo->window,
433                       *c->glx_context, 0, 0, c->mipmap_p, c->texid,
434                       image_loaded_cb, c);
435 }
436
437 ENTRYPOINT void init_screenflip(ModeInfo *mi)
438 {
439   int screen = MI_SCREEN(mi);
440   Screenflip *c;
441
442  if (screenflip == NULL) {
443    if ((screenflip = (Screenflip *) calloc(MI_NUM_SCREENS(mi),
444                                         sizeof(Screenflip))) == NULL)
445           return;
446  }
447  c = &screenflip[screen];
448  c->window = MI_WINDOW(mi);
449
450  c->trackball = gltrackball_init ();
451
452  if ((c->glx_context = init_GL(mi)) != NULL) {
453       reshape_screenflip(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
454  } else {
455      MI_CLEARWINDOW(mi);
456  }
457  c->winh = MI_WIN_HEIGHT(mi);
458  c->winw = MI_WIN_WIDTH(mi);
459  c->qw = QW;
460  c->qh = QH;
461  c->qx = -6;
462  c->qy = 6;
463
464  c->rx = c->ry = 1;
465  c->odrot = 1;
466
467  c->show_colors[0] = c->show_colors[1] = 
468    c->show_colors[2] = c->show_colors[3] = 1;
469
470  glClearColor(0.0,0.0,0.0,0.0);
471
472  if (! MI_IS_WIREFRAME(mi))
473    {
474      glShadeModel(GL_SMOOTH);
475      glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
476      glEnable(GL_DEPTH_TEST);
477      glEnable(GL_CULL_FACE);
478      glCullFace(GL_BACK);
479      glDisable(GL_LIGHTING);
480    }
481
482  if (strstr ((char *) glGetString(GL_EXTENSIONS),
483              "GL_EXT_texture_filter_anisotropic"))
484    glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &c->anisotropic);
485  else
486    c->anisotropic = 0.0;
487
488  glGenTextures(1, &c->texid);
489
490  c->first_image_p = True;
491  getSnapshot(mi);
492 }
493
494 ENTRYPOINT void draw_screenflip(ModeInfo *mi)
495 {
496   Screenflip *c = &screenflip[MI_SCREEN(mi)];
497   Window w = MI_WINDOW(mi);
498   Display *disp = MI_DISPLAY(mi);
499
500   if (!c->glx_context)
501       return;
502
503   /* Wait for the first image; for subsequent images, load them in the
504      background while animating. */
505   if (c->waiting_for_image_p && c->first_image_p)
506     return;
507
508   glXMakeCurrent(disp, w, *(c->glx_context));
509
510   glBindTexture(GL_TEXTURE_2D, c->texid);
511
512   if (c->regrab)
513     getSnapshot(mi);
514
515   display(c, MI_IS_WIREFRAME(mi));
516
517   if(mi->fps_p) do_fps(mi);
518   glFinish(); 
519   glXSwapBuffers(disp, w);
520 }
521
522 ENTRYPOINT void release_screenflip(ModeInfo *mi)
523 {
524   if (screenflip != NULL) {
525    (void) free((void *) screenflip);
526    screenflip = NULL;
527   }
528   FreeAllGL(mi);
529 }
530
531 XSCREENSAVER_MODULE_2 ("FlipScreen3D", flipscreen3d, screenflip)
532
533 #endif