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