db894da6e40250aae6b90ea2f3774ca79d8b68e8
[xscreensaver] / hacks / fiberlamp.c
1 /* -*- Mode: C; tab-width: 4 -*- */
2 /* fiberlamp --- A Fiber Optic Lamp */
3
4 #if !defined( lint ) && !defined( SABER )
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 # define UNIFORM_COLORS
39 # define fiberlamp_handle_event 0
40 # include "xlockmore.h"         /* in xscreensaver distribution */
41 #else /* STANDALONE */
42 # include "xlock.h"             /* in xlockmore distribution */
43 #endif /* STANDALONE */
44
45 #ifdef MODE_fiberlamp
46
47 ENTRYPOINT ModeSpecOpt fiberlamp_opts =
48 {0, (XrmOptionDescRec *) NULL, 0, (argtype *) NULL, (OptionStruct *) NULL};
49
50 #ifdef USE_MODULES
51 ModStruct   fiberlamp_description =
52 {"fiberlamp", "init_fiberlamp", "draw_fiberlamp", "release_fiberlamp",
53  "draw_fiberlamp", "change_fiberlamp", (char *) NULL, &fiberlamp_opts,
54  1000, 500, 10000, 0, 64, 1.0, "", "Shows a Fiber Optic Lamp", 0, NULL};
55
56 #endif
57
58 #define SPREAD (30.0) /* Angular spread at the base */
59 #define SCALE (MI_WIDTH(mi)/2) /* Screen size */
60 #define NODES (20) /* Number of nodes in a fiber.  Variable with range
61                                           10 .. 30, if desired.  High values have
62                                           stability problems unless you use small DT */
63
64 /* Physics parameters.  Tune carefully to keep realism and avoid instability*/
65 #define DT (0.5) /* Time increment: Low is slow, High is less stable. */
66 #define PY (0.12) /* Rigidity: Low droops, High is stiff. */
67 #define DAMPING (0.055) /* Damping: Low allows oscillations, High is boring. */
68
69 #undef PLAN /* Plan view (for debugging) */
70 #undef CHECKCOLORWHEEL /* Plan view with no spread */
71
72 #define DRAND(v)        (LRAND()/MAXRAND*(v))   /* double random 0 - v */
73
74 /* Length of nodes.  Uniform except for shorter notes at the tips for
75    colour highlights.  Sum from 0..NODES-1 should exactly 1.0 */
76 #define LEN(A) ((A<NODES-3) ? 1.0/(NODES-2.5) : 0.25/(NODES-2.5))
77
78
79 typedef struct {
80   double phi, phidash;
81   double eta, etadash;
82   double x;
83   double y;
84   double z;
85 } nodestruct;
86
87 typedef struct {
88   nodestruct *node;
89   XPoint *draw;
90 } fiberstruct;
91
92 typedef struct {
93   int     init;
94   double  psi;
95   double  dpsi;
96   int     count, nfibers;
97   double  cx;
98   double  rx, ry; /* Coordinates relative to root */
99   fiberstruct *fiber;
100   Bool dbufp;
101   Pixmap      buffer; /* Double Buffer */
102   long    bright, medium, dim; /* "White" colors */
103 } fiberlampstruct;
104
105 static fiberlampstruct *fiberlamps = (fiberlampstruct *) NULL;
106
107 static void
108 change_fiberlamp(ModeInfo * mi)
109 {
110   fiberlampstruct *fl;
111   if (fiberlamps == NULL)
112         return;
113   fl = &fiberlamps[MI_SCREEN(mi)];
114   fl->cx = (DRAND(SCALE/4)-SCALE/8)/SCALE; /* Knock the lamp */
115   fl->count = 0; /* Reset counter */
116   if (fl->dbufp) {
117     XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
118     XFillRectangle(MI_DISPLAY(mi), fl->buffer, MI_GC(mi), 0, 0,
119                    MI_WIDTH(mi), MI_HEIGHT(mi));
120   }
121 }
122
123 static void
124 free_fiber(fiberlampstruct *fl)
125 {
126         if (fl->fiber) {
127                 int f;
128
129                 for (f = 0; f < fl->nfibers; f++) {
130                         fiberstruct *fs = fl->fiber + f;
131
132                         if (fs->node)
133                                 free(fs->node);
134                         if (fs->draw)
135                                 free(fs->draw);
136                 }
137                 free(fl->fiber);
138                 fl->fiber = NULL;
139         }
140 }
141
142 static void
143 free_fiberlamp(ModeInfo *mi, fiberlampstruct *fl)
144 {
145         if (fl->buffer != None && fl->dbufp) {
146                 XFreePixmap(MI_DISPLAY(mi), fl->buffer);
147                 fl->buffer = None;
148         }
149         free_fiber(fl);
150 }
151
152 ENTRYPOINT void
153 init_fiberlamp(ModeInfo * mi)
154 {
155   fiberlampstruct *fl;
156
157   if (fiberlamps == NULL) {
158         if ((fiberlamps =
159                  (fiberlampstruct *) calloc(MI_NUM_SCREENS(mi),
160                         sizeof (fiberlampstruct))) == NULL)
161           return;
162   }
163   fl = &fiberlamps[MI_SCREEN(mi)];
164
165   /* Create or Resize double buffer */
166 #ifdef HAVE_COCOA       /* 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, fl);
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, fl);
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, fl);
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 /* Used by xscreensaver.  xlock just uses init_fiberlamp */
278 ENTRYPOINT void
279 reshape_fiberlamp(ModeInfo * mi, int width, int height)
280 {
281   init_fiberlamp(mi);
282 }
283
284 /* sort fibers so they get drawn back-to-front, one bubble pass is
285    enough as the order only changes slowly */
286 static void
287 sort_fibers(fiberlampstruct *fl)
288 {
289         int i;
290
291         for(i = 1; i < fl->nfibers; i++) {
292                 if (fl->fiber[i - 1].node[NODES - 1].z > fl->fiber[i].node[NODES - 1].z) {
293                         fiberstruct tmp = fl->fiber[i - 1];
294                         fl->fiber[i - 1] = fl->fiber[i];
295                         fl->fiber[i] = tmp;
296                 }
297         }
298 }
299
300 ENTRYPOINT void
301 draw_fiberlamp (ModeInfo * mi)
302 {
303   fiberlampstruct *fl;
304   int f, i;
305   int x, y;
306   Window unused;
307
308   short cx = MI_WIDTH(mi)/2;
309 #if defined PLAN || defined CHECKCOLORWHEEL
310   short cy = MI_HEIGHT(mi)/2;
311 #else
312   short cy = MI_HEIGHT(mi);
313 #endif
314
315   if (fiberlamps == NULL)
316         return;
317   fl = &fiberlamps[MI_SCREEN(mi)];
318
319   fl->psi += fl->dpsi;      /* turn colorwheel */
320
321   XTranslateCoordinates(MI_DISPLAY(mi), MI_WINDOW(mi),
322                                                 RootWindow(MI_DISPLAY(mi),0/*#### MI_SCREEN(mi)*/),
323                                                 cx, cy, &x, &y, &unused);
324   sort_fibers(fl);
325
326   for(f = 0; f < fl->nfibers; f++) {
327         fiberstruct *fs = fl->fiber + f;
328
329         fs->node[0].eta += DT*fs->node[0].etadash;
330         fs->node[0].x = fl->cx; /* Handle center movement */
331         /* Handle window move.  NOTE, only x is deflected, since y doesn't
332          directly affect the physics */
333         fs->node[NODES-2].x *= 0.1*(fl->ry - y);
334         fs->node[NODES-2].x += 0.05*(fl->rx - x);
335
336         /* 2nd order diff equation */
337         for(i = 1; i < NODES; i++) {
338           nodestruct *n = fs->node+i;
339           nodestruct *p = fs->node+i-1;
340           double pload = 0;
341           double eload = 0;
342           double pstress = (n->phi - p->phi)*PY;
343           double estress = (n->eta - p->eta)*PY;
344           double dxi = n->x - p->x;
345           double dzi = n->z - p->z;
346           double li = sqrt(dxi*dxi + dzi*dzi)/LEN(i);
347           double drag = DAMPING*LEN(i)*LEN(i)*NODES*NODES;
348
349           if(li > 0) {
350                 int j;
351                 for(j = i+1; j < NODES; j++) {
352                   nodestruct *nn = fs->node+j;
353                   double dxj = nn->x - n->x;
354                   double dzj = nn->z - n->z;
355
356                   pload += LEN(j)*(dxi*dxj + dzi*dzj)/li; /* Radial load */
357                   eload += LEN(j)*(dxi*dzj - dzi*dxj)/li; /* Transverse load */
358                   /* Not a perfect simulation: in reality the transverse load
359                          is only indirectly coupled to the eta deflection, but of
360                          all the approaches I've tried this produces the most
361                          stable model and looks the most realistic. */
362                 }
363           }
364
365 #ifndef CHECKCOLORWHEEL
366           n->phidash += DT*(pload - pstress - drag*n->phidash)/LEN(i);
367           n->phi += DT*n->phidash;
368 #endif
369
370           n->etadash += DT*(eload - estress - drag*n->etadash)/LEN(i);
371           n->eta += DT*n->etadash;
372
373           {
374                 double sp = sin(p->phi);
375                 double cp = cos(p->phi);
376                 double se = sin(p->eta);
377                 double ce = cos(p->eta);
378
379                 n->x = p->x + LEN(i-1) * ce * sp;
380                 n->y = p->y - LEN(i-1) * cp;
381                 n->z = p->z + LEN(i-1) * se * sp;
382           }
383
384           fs->draw[i-1].x = cx + MI_WIDTH(mi)/2*n->x;
385 #if defined PLAN || defined CHECKCOLORWHEEL /* Plan */
386           fs->draw[i-1].y = cy + MI_WIDTH(mi)/2*n->z;
387 #else /* Elevation */
388           fs->draw[i-1].y = cy + MI_WIDTH(mi)/2*n->y;
389 #endif
390         }
391         MI_IS_DRAWN(mi) = True;
392
393         /* Erase: this may only be erasing an off-screen buffer, but on a
394            slow system it may still be faster than XFillRectangle() */
395     /* That's unpossible. -jwz */
396   }
397
398   XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
399   XFillRectangle(MI_DISPLAY(mi), fl->buffer, MI_GC(mi),
400                  0, 0, MI_WIDTH(mi), MI_HEIGHT(mi));
401
402   for(f = 0; f < fl->nfibers; f++) {
403         fiberstruct *fs = fl->fiber + f;
404
405         {
406           double x = fs->node[1].x - fl->cx + 0.025;
407           double y = fs->node[1].z + 0.02;
408           double angle = atan2(y, x) + fl->psi;
409           int tipcolor = (int)(MI_NPIXELS(mi)*angle/(2*M_PI)) % MI_NPIXELS(mi);
410           int fibercolor;
411           int tiplen;
412
413       if (tipcolor < 0) tipcolor += MI_NPIXELS(mi);
414       tipcolor = MI_PIXEL(mi, tipcolor);
415
416           if(fs->node[1].z < 0.0) { /* Back */
417                 tiplen = 2;
418                 fibercolor = fl->dim;
419           }else if(fs->node[NODES-1].z < 0.7) { /* Middle */
420                 tiplen = 3;
421                 fibercolor = fl->medium;
422           } else {                 /* Front */
423                 tiplen = 3;
424                 fibercolor = fl->bright;
425           }
426
427           XSetForeground(MI_DISPLAY(mi), MI_GC(mi), fibercolor);
428           XDrawLines(MI_DISPLAY(mi), fl->buffer, MI_GC(mi),
429                                  fs->draw, NODES-tiplen, CoordModeOrigin);
430
431           XSetForeground(MI_DISPLAY(mi), MI_GC(mi), tipcolor);
432           XDrawLines(MI_DISPLAY(mi), fl->buffer, MI_GC(mi),
433                                  fs->draw+NODES-1-tiplen, tiplen, CoordModeOrigin);
434         }
435   }
436
437   /* Update the screen from the double-buffer */
438   if (fl->dbufp)
439     XCopyArea(MI_DISPLAY(mi), fl->buffer, MI_WINDOW(mi), MI_GC(mi), 0, 0,
440               MI_WIDTH(mi), MI_HEIGHT(mi), 0, 0);
441
442   fl->rx = x;
443   fl->ry = y;
444
445   if(fl->count++ > MI_CYCLES(mi)) {
446         change_fiberlamp(mi);
447   }
448 }
449
450 ENTRYPOINT void
451 release_fiberlamp(ModeInfo * mi)
452 {
453         if (fiberlamps != NULL) {
454                 int         screen;
455
456                 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
457                         free_fiberlamp(mi, &fiberlamps[screen]);
458                 free(fiberlamps);
459                 fiberlamps = (fiberlampstruct *) NULL;
460         }
461 }
462
463 ENTRYPOINT void
464 refresh_fiberlamp(ModeInfo * mi)
465 {
466         MI_CLEARWINDOW(mi);
467 }
468
469 XSCREENSAVER_MODULE ("Fiberlamp", fiberlamp)
470
471 #endif /* MODE_fiberlamp */