From http://www.jwz.org/xscreensaver/xscreensaver-5.27.tar.gz
[xscreensaver] / hacks / fiberlamp.c
1 /* -*- Mode: C; tab-width: 4 -*- */
2 /* fiberlamp --- A Fiber Optic Lamp */
3
4 #if 0
5 static const char sccsid[] = "@(#)fiberlamp.c   5.00 2000/11/01 xlockmore";
6
7 #endif
8
9 /*-
10  * Copyright (c) 2005 by Tim Auckland <tda10.geo@yahoo.com>
11  *
12  * Permission to use, copy, modify, and distribute this software and its
13  * documentation for any purpose and without fee is hereby granted,
14  * provided that the above copyright notice appear in all copies and that
15  * both that copyright notice and this permission notice appear in
16  * supporting documentation.
17  *
18  * This file is provided AS IS with no warranties of any kind.  The author
19  * shall have no liability with respect to the infringement of copyrights,
20  * trade secrets or any patents by this file or any part thereof.  In no
21  * event will the author be liable for any lost revenue or profits or
22  * other special, indirect and consequential damages.
23  *
24  * "fiberlamp" shows Fiber Optic Lamp.  Since there is no closed-form
25  * solution to the large-amplitude cantilever equation, the flexible
26  * fiber is modeled as a set of descrete nodes.
27  *
28  * Revision History:
29  * 13-Jan-2005: Initial development.
30  */
31
32 #ifdef STANDALONE
33 #define MODE_fiberlamp
34 #define DEFAULTS        "*delay: 10000  \n" \
35                                         "*count: 500    \n" \
36                                         "*cycles: 10000 \n" \
37                                         "*ncolors: 64   \n" \
38                                         "*fpsTop: true  \n" \
39
40 # define UNIFORM_COLORS
41 # define fiberlamp_handle_event 0
42 # include "xlockmore.h"         /* in xscreensaver distribution */
43 #else /* STANDALONE */
44 # include "xlock.h"             /* in xlockmore distribution */
45 #endif /* STANDALONE */
46
47 #ifdef MODE_fiberlamp
48
49 ENTRYPOINT ModeSpecOpt fiberlamp_opts =
50 {0, (XrmOptionDescRec *) NULL, 0, (argtype *) NULL, (OptionStruct *) NULL};
51
52 #ifdef USE_MODULES
53 ModStruct   fiberlamp_description =
54 {"fiberlamp", "init_fiberlamp", "draw_fiberlamp", "release_fiberlamp",
55  "draw_fiberlamp", "change_fiberlamp", (char *) NULL, &fiberlamp_opts,
56  1000, 500, 10000, 0, 64, 1.0, "", "Shows a Fiber Optic Lamp", 0, NULL};
57
58 #endif
59
60 #define SPREAD (30.0) /* Angular spread at the base */
61 #define SCALE (MI_WIDTH(mi)/2) /* Screen size */
62 #define NODES (20L) /* Number of nodes in a fiber.  Variable with range
63                                           10 .. 30, if desired.  High values have
64                                           stability problems unless you use small DT */
65
66 /* Physics parameters.  Tune carefully to keep realism and avoid instability*/
67 #define DT (0.5) /* Time increment: Low is slow, High is less stable. */
68 #define PY (0.12) /* Rigidity: Low droops, High is stiff. */
69 #define DAMPING (0.055) /* Damping: Low allows oscillations, High is boring. */
70
71 #undef PLAN /* Plan view (for debugging) */
72 #undef CHECKCOLORWHEEL /* Plan view with no spread */
73
74 #define DRAND(v)        (LRAND()/MAXRAND*(v))   /* double random 0 - v */
75
76 /* Length of nodes.  Uniform except for shorter notes at the tips for
77    colour highlights.  Sum from 0..NODES-1 should exactly 1.0 */
78 #define LEN(A) ((A<NODES-3) ? 1.0/(NODES-2.5) : 0.25/(NODES-2.5))
79
80
81 typedef struct {
82   double phi, phidash;
83   double eta, etadash;
84   double x;
85   double y;
86   double z;
87 } nodestruct;
88
89 typedef struct {
90   nodestruct *node;
91   XPoint *draw;
92 } fiberstruct;
93
94 typedef struct {
95   int     init;
96   double  psi;
97   double  dpsi;
98   long    count, nfibers;
99   double  cx;
100   double  rx, ry; /* Coordinates relative to root */
101   fiberstruct *fiber;
102   Bool dbufp;
103   Pixmap      buffer; /* Double Buffer */
104   long    bright, medium, dim; /* "White" colors */
105 } fiberlampstruct;
106
107 static fiberlampstruct *fiberlamps = (fiberlampstruct *) NULL;
108
109 static void
110 change_fiberlamp(ModeInfo * mi)
111 {
112   fiberlampstruct *fl;
113   if (fiberlamps == NULL)
114         return;
115   fl = &fiberlamps[MI_SCREEN(mi)];
116   fl->cx = (DRAND(SCALE/4)-SCALE/8)/SCALE; /* Knock the lamp */
117   fl->count = 0; /* Reset counter */
118   if (fl->dbufp) {
119     XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
120     XFillRectangle(MI_DISPLAY(mi), fl->buffer, MI_GC(mi), 0, 0,
121                    MI_WIDTH(mi), MI_HEIGHT(mi));
122   }
123 }
124
125 static void
126 free_fiber(fiberlampstruct *fl)
127 {
128         if (fl->fiber) {
129                 int f;
130
131                 for (f = 0; f < fl->nfibers; f++) {
132                         fiberstruct *fs = fl->fiber + f;
133
134                         if (fs->node)
135                                 free(fs->node);
136                         if (fs->draw)
137                                 free(fs->draw);
138                 }
139                 free(fl->fiber);
140                 fl->fiber = NULL;
141         }
142 }
143
144 static void
145 free_fiberlamp(ModeInfo *mi, fiberlampstruct *fl)
146 {
147         if (fl->buffer != None && fl->dbufp) {
148                 XFreePixmap(MI_DISPLAY(mi), fl->buffer);
149                 fl->buffer = None;
150         }
151         free_fiber(fl);
152 }
153
154 ENTRYPOINT void
155 init_fiberlamp(ModeInfo * mi)
156 {
157   fiberlampstruct *fl;
158
159   if (fiberlamps == NULL) {
160         if ((fiberlamps =
161                  (fiberlampstruct *) calloc(MI_NUM_SCREENS(mi),
162                         sizeof (fiberlampstruct))) == NULL)
163           return;
164   }
165   fl = &fiberlamps[MI_SCREEN(mi)];
166
167   /* Create or Resize double buffer */
168 #ifdef HAVE_COCOA       /* Don't second-guess Quartz's double-buffering */
169   fl->dbufp = False;
170 #else
171   fl->dbufp = True;
172 #endif
173
174   if(fl->buffer != None && fl->buffer != MI_WINDOW(mi) && fl->dbufp)
175     XFreePixmap(MI_DISPLAY(mi), fl->buffer);
176
177   if(fl->dbufp) {
178     fl->buffer = XCreatePixmap(MI_DISPLAY(mi), MI_WINDOW(mi),
179                                MI_WIDTH(mi), MI_HEIGHT(mi), MI_DEPTH(mi));
180     if (fl->buffer == None) {
181       free_fiberlamp(mi, fl);
182       return;
183     }
184   } else {
185     fl->buffer = MI_WINDOW(mi);
186   }
187
188   XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
189   XFillRectangle(MI_DISPLAY(mi), fl->buffer, MI_GC(mi), 0, 0,
190                                  MI_WIDTH(mi), MI_HEIGHT(mi));
191
192   if(fl->init) /* Nothing else to do (probably a resize) */
193         return;
194
195   fl->init = True;
196   fl->nfibers = MI_COUNT(mi);
197   /* Allocate fibers */
198   if((fl->fiber =
199           (fiberstruct*) calloc(fl->nfibers, sizeof (fiberstruct))) == NULL) {
200         free_fiberlamp(mi, fl);
201         return;
202   } else {
203         int f;
204         for(f = 0; f < fl->nfibers; f++) {
205           fiberstruct *fs = fl->fiber + f;
206           if((fs->node =
207                   (nodestruct*) calloc(NODES, sizeof (nodestruct))) == NULL
208                  ||(fs->draw =
209                         (XPoint*) calloc(NODES, sizeof (XPoint))) == NULL) {
210                 free_fiberlamp(mi, fl);
211                 return;
212           }
213         }
214   }
215
216   {
217         int f, i;
218         for(f = 0; f < fl->nfibers; f++) {
219           double phi = M_PI/180 * DRAND(SPREAD);
220           double eta = DRAND(2*M_PI) - M_PI;
221           for(i = 0; i < NODES; i++) {
222                 nodestruct *n = &fl->fiber[f].node[i];
223                 n->phi = phi;
224                 n->phidash = 0;
225                 n->eta = eta;
226                 n->etadash = 0;
227           }
228           fl->fiber[f].node[0].etadash = 0.002/DT;
229           fl->fiber[f].node[0].y = 0;
230           fl->fiber[f].node[0].z = 0;
231         }
232
233   }
234
235   /* Set up rotation */
236   fl->psi = DRAND(2*M_PI);
237   fl->dpsi = 0.01;
238
239   /* no "NoExpose" events from XCopyArea wanted */
240   XSetGraphicsExposures(MI_DISPLAY(mi), MI_GC(mi), False);
241
242   /* Make sure we're using 'thin' lines */
243   XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 0, LineSolid, CapNotLast,
244                                          JoinMiter);
245 #ifdef CHECKCOLORWHEEL
246   /* Only interested in tips, leave the rest black */
247   fl->bright = fl->medium = fl->dim = MI_BLACK_PIXEL(mi);
248 #else
249   if(MI_NPIXELS(mi) > 2) {
250         /* Set up colours for the fiber bodies.  Tips handled seperately */
251         XColor c, t;
252         if(XAllocNamedColor(MI_DISPLAY(mi), MI_COLORMAP(mi), "#E0E0C0", &c, &t)){
253           fl->bright = c.pixel;
254         } else {
255           fl->bright = MI_WHITE_PIXEL(mi);
256         }
257         if(XAllocNamedColor(MI_DISPLAY(mi), MI_COLORMAP(mi), "#808070", &c, &t)){
258           fl->medium = c.pixel;
259         } else {
260           fl->medium = MI_WHITE_PIXEL(mi);
261         }
262         if(XAllocNamedColor(MI_DISPLAY(mi), MI_COLORMAP(mi), "#404020", &c, &t)){
263           fl->dim = c.pixel;
264         } else {
265           fl->dim = MI_BLACK_PIXEL(mi);
266         }
267   } else {
268         fl->bright = MI_WHITE_PIXEL(mi);
269         fl->medium = MI_WHITE_PIXEL(mi);
270         fl->dim = MI_BLACK_PIXEL(mi);
271   }
272 #endif
273
274   /* Clear the background. */
275   MI_CLEARWINDOW(mi);
276   change_fiberlamp(mi);
277 }
278
279 /* Used by xscreensaver.  xlock just uses init_fiberlamp */
280 ENTRYPOINT void
281 reshape_fiberlamp(ModeInfo * mi, int width, int height)
282 {
283   init_fiberlamp(mi);
284 }
285
286 /* sort fibers so they get drawn back-to-front, one bubble pass is
287    enough as the order only changes slowly */
288 static void
289 sort_fibers(fiberlampstruct *fl)
290 {
291         int i;
292
293         for(i = 1; i < fl->nfibers; i++) {
294                 if (fl->fiber[i - 1].node[NODES - 1].z > fl->fiber[i].node[NODES - 1].z) {
295                         fiberstruct tmp = fl->fiber[i - 1];
296                         fl->fiber[i - 1] = fl->fiber[i];
297                         fl->fiber[i] = tmp;
298                 }
299         }
300 }
301
302 ENTRYPOINT void
303 draw_fiberlamp (ModeInfo * mi)
304 {
305   fiberlampstruct *fl;
306   int f, i;
307   int x, y;
308   Window unused;
309
310   short cx = MI_WIDTH(mi)/2;
311 #if defined PLAN || defined CHECKCOLORWHEEL
312   short cy = MI_HEIGHT(mi)/2;
313 #else
314   short cy = MI_HEIGHT(mi);
315 #endif
316
317   if (fiberlamps == NULL)
318         return;
319   fl = &fiberlamps[MI_SCREEN(mi)];
320
321   fl->psi += fl->dpsi;      /* turn colorwheel */
322
323   XTranslateCoordinates(MI_DISPLAY(mi), MI_WINDOW(mi),
324                                                 RootWindow(MI_DISPLAY(mi),0/*#### MI_SCREEN(mi)*/),
325                                                 cx, cy, &x, &y, &unused);
326   sort_fibers(fl);
327
328   for(f = 0; f < fl->nfibers; f++) {
329         fiberstruct *fs = fl->fiber + f;
330
331         fs->node[0].eta += DT*fs->node[0].etadash;
332         fs->node[0].x = fl->cx; /* Handle center movement */
333         /* Handle window move.  NOTE, only x is deflected, since y doesn't
334          directly affect the physics */
335         fs->node[NODES-2].x *= 0.1*(fl->ry - y);
336         fs->node[NODES-2].x += 0.05*(fl->rx - x);
337
338         /* 2nd order diff equation */
339         for(i = 1; i < NODES; i++) {
340           nodestruct *n = fs->node+i;
341           nodestruct *p = fs->node+i-1;
342           double pload = 0;
343           double eload = 0;
344           double pstress = (n->phi - p->phi)*PY;
345           double estress = (n->eta - p->eta)*PY;
346           double dxi = n->x - p->x;
347           double dzi = n->z - p->z;
348           double li = sqrt(dxi*dxi + dzi*dzi)/LEN(i);
349           double drag = DAMPING*LEN(i)*LEN(i)*NODES*NODES;
350
351           if(li > 0) {
352                 int j;
353                 for(j = i+1; j < NODES; j++) {
354                   nodestruct *nn = fs->node+j;
355                   double dxj = nn->x - n->x;
356                   double dzj = nn->z - n->z;
357
358                   pload += LEN(j)*(dxi*dxj + dzi*dzj)/li; /* Radial load */
359                   eload += LEN(j)*(dxi*dzj - dzi*dxj)/li; /* Transverse load */
360                   /* Not a perfect simulation: in reality the transverse load
361                          is only indirectly coupled to the eta deflection, but of
362                          all the approaches I've tried this produces the most
363                          stable model and looks the most realistic. */
364                 }
365           }
366
367 #ifndef CHECKCOLORWHEEL
368           n->phidash += DT*(pload - pstress - drag*n->phidash)/LEN(i);
369           n->phi += DT*n->phidash;
370 #endif
371
372           n->etadash += DT*(eload - estress - drag*n->etadash)/LEN(i);
373           n->eta += DT*n->etadash;
374
375           {
376                 double sp = sin(p->phi);
377                 double cp = cos(p->phi);
378                 double se = sin(p->eta);
379                 double ce = cos(p->eta);
380
381                 n->x = p->x + LEN(i-1) * ce * sp;
382                 n->y = p->y - LEN(i-1) * cp;
383                 n->z = p->z + LEN(i-1) * se * sp;
384           }
385
386           fs->draw[i-1].x = cx + MI_WIDTH(mi)/2*n->x;
387 #if defined PLAN || defined CHECKCOLORWHEEL /* Plan */
388           fs->draw[i-1].y = cy + MI_WIDTH(mi)/2*n->z;
389 #else /* Elevation */
390           fs->draw[i-1].y = cy + MI_WIDTH(mi)/2*n->y;
391 #endif
392         }
393         MI_IS_DRAWN(mi) = True;
394
395         /* Erase: this may only be erasing an off-screen buffer, but on a
396            slow system it may still be faster than XFillRectangle() */
397     /* That's unpossible. -jwz */
398   }
399
400   XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
401   XFillRectangle(MI_DISPLAY(mi), fl->buffer, MI_GC(mi),
402                  0, 0, MI_WIDTH(mi), MI_HEIGHT(mi));
403
404   for(f = 0; f < fl->nfibers; f++) {
405         fiberstruct *fs = fl->fiber + f;
406
407         {
408           double x = fs->node[1].x - fl->cx + 0.025;
409           double y = fs->node[1].z + 0.02;
410           double angle = atan2(y, x) + fl->psi;
411           long tipcolor = (int)(MI_NPIXELS(mi)*angle/(2*M_PI)) % MI_NPIXELS(mi);
412           long fibercolor;
413           long tiplen;
414
415       if (tipcolor < 0) tipcolor += MI_NPIXELS(mi);
416       tipcolor = MI_PIXEL(mi, tipcolor);
417
418           if(fs->node[1].z < 0.0) { /* Back */
419                 tiplen = 2;
420                 fibercolor = fl->dim;
421           }else if(fs->node[NODES-1].z < 0.7) { /* Middle */
422                 tiplen = 3;
423                 fibercolor = fl->medium;
424           } else {                 /* Front */
425                 tiplen = 3;
426                 fibercolor = fl->bright;
427           }
428
429           XSetForeground(MI_DISPLAY(mi), MI_GC(mi), fibercolor);
430           XDrawLines(MI_DISPLAY(mi), fl->buffer, MI_GC(mi),
431                                  fs->draw, NODES-tiplen, CoordModeOrigin);
432
433           XSetForeground(MI_DISPLAY(mi), MI_GC(mi), tipcolor);
434           XDrawLines(MI_DISPLAY(mi), fl->buffer, MI_GC(mi),
435                                  fs->draw+NODES-1-tiplen, tiplen, CoordModeOrigin);
436         }
437   }
438
439   /* Update the screen from the double-buffer */
440   if (fl->dbufp)
441     XCopyArea(MI_DISPLAY(mi), fl->buffer, MI_WINDOW(mi), MI_GC(mi), 0, 0,
442               MI_WIDTH(mi), MI_HEIGHT(mi), 0, 0);
443
444   fl->rx = x;
445   fl->ry = y;
446
447   if(fl->count++ > MI_CYCLES(mi)) {
448         change_fiberlamp(mi);
449   }
450 }
451
452 ENTRYPOINT void
453 release_fiberlamp(ModeInfo * mi)
454 {
455         if (fiberlamps != NULL) {
456                 int         screen;
457
458                 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
459                         free_fiberlamp(mi, &fiberlamps[screen]);
460                 free(fiberlamps);
461                 fiberlamps = (fiberlampstruct *) NULL;
462         }
463 }
464
465 ENTRYPOINT void
466 refresh_fiberlamp(ModeInfo * mi)
467 {
468         MI_CLEARWINDOW(mi);
469 }
470
471 XSCREENSAVER_MODULE ("Fiberlamp", fiberlamp)
472
473 #endif /* MODE_fiberlamp */