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