1 /* -*- Mode: C; tab-width: 4; c-basic-offset: 4 -*- */
2 /* flow --- flow of strange bees */
5 #if !defined( lint ) && !defined( SABER )
6 static const char sccsid[] = "@(#)flow.c 5.00 2000/11/01 xlockmore";
11 * Copyright (c) 1996 by Tim Auckland <tda10.geo@yahoo.com>
12 * Incorporating some code from Stephen Davies Copyright (c) 2000
14 * Search code based on techniques described in "Strange Attractors:
15 * Creating Patterns in Chaos" by Julien C. Sprott
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation.
23 * This file is provided AS IS with no warranties of any kind. The author
24 * shall have no liability with respect to the infringement of copyrights,
25 * trade secrets or any patents by this file or any part thereof. In no
26 * event will the author be liable for any lost revenue or profits or
27 * other special, indirect and consequential damages.
29 * "flow" shows a variety of continuous phase-space flows around strange
30 * attractors. It includes the well-known Lorentz mask (the "Butterfly"
31 * of chaos fame), two forms of Rossler's "Folded Band" and Poincare'
32 * sections of the "Birkhoff Bagel" and Duffing's forced occilator. "flow"
33 * can now discover new attractors.
37 * 29-Oct-2004: [TDA] Discover Attractors unknown to science.
38 * Replace 2D rendering of Periodic Attractors with a 3D
39 * 'interrupted' rendering. Replace "-/+allow2d" with "-/+periodic"
40 * Replace all ODE formulae with completely generic forms.
41 * Add '-search' option to perform background high-speed discovery
42 * for completely new attractors without impacting rendering
44 * Use gaussian distribution for initial point positions and for
46 * Add "+dbuf" option to allow Double-Buffering to be turned off on
48 * Remove redundant '-zoom' option. Now automatically zooms if both
49 * rotation and riding are permitted.
50 * Replace dynamic bounding box with static one pre-calculated
51 * during discovery phase.
52 * Simplify and fix bounding box clipping code. Should now be safe
53 * to run without double buffer on all XFree86 servers if desired.
54 * 12-Oct-2004: [TDA] Merge Xscreensaver and Xlockmore branches
55 * Added Chalky's orbital camera, but made the zooming work by
56 * flying the camera rather than interpolating the view transforms.
57 * Added Chalky's Bounding Box, but time-averaged the boundaries to
58 * let the lost bees escape.
59 * Added Chalky's 'view-frustrum' clipping, but only applying it to
60 * the Bounding Box. Trails make clipping less useful.
61 * Added Chalky's "-slow" and "-freeze" options for compatibility,
62 * but haven't implemented the features, since the results are ugly
63 * and make no mathematical contribution.
64 * Added Double-Buffering as a work-around for a persistent XFree86
65 * bug that left debris on the screen.
66 * 21-Mar-2003: [TDA] Trails added (XLockmore branch)
67 * 01-Nov-2000: [TDA] Allocation checks (XLockmore branch)
68 * 21-Feb-2000: [Chalky] Major hackage (Stephen Davies, chalky@null.net)
69 * (Xscreensaver branch)
70 * Forced perspective mode, added 3d box around attractor which
71 * involved coding 3d-planar-clipping against the view-frustrum
72 * thingy. Also made view alternate between piggybacking on a 'bee'
73 * to zooming around outside the attractor. Most bees slow down and
74 * stop, to make the structure of the attractor more obvious.
75 * 28-Jan-1999: [TDA] Catch 'lost' bees in flow.c and disable them.
77 * I chose to disable them rather than reinitialise them because
78 * reinitialising can produce fake attractors.
79 * This has allowed me to relax some of the parameters and initial
80 * conditions slightly to catch some of the more extreme cases. As a
81 * result you may see some bees fly away at the start - these are the ones
82 * that 'missed' the attractor. If the bee with the camera should fly
83 * away the mode will restart :-)
84 * 31-Nov-1998: [TDA] Added Duffing (what a strange day that was :) DAB)
85 * Duffing's forced oscillator has been added to the formula list and
86 * the parameters section has been updated to display it in Poincare'
88 * 30-Nov-1998: [TDA] Added travelling perspective option
89 * A more exciting point-of-view has been added to all autonomous flows.
90 * This views the flow as seen by a particle moving with the flow. In the
91 * metaphor of the original code, I've attached a camera to one of the
93 * 30-Nov-1998: [TDA] Much code cleanup.
94 * 09-Apr-1997: [TDA] Ported to xlockmore-4
95 * 18-Jul-1996: Adapted from swarm.c Copyright (c) 1991 by Patrick J. Naughton.
96 * 31-Aug-1990: Adapted from xswarm by Jeff Butterworth. (butterwo@ncsc.org).
101 # define DEFAULTS "*delay: 10000 \n" \
104 "*cycles: 10000 \n" \
107 # define reshape_flow 0
108 # define flow_handle_event 0
109 # include "xlockmore.h" /* in xscreensaver distribution */
110 #else /* STANDALONE */
111 # include "xlock.h" /* in xlockmore distribution */
112 #endif /* STANDALONE */
116 #define DEF_ROTATE "TRUE"
117 #define DEF_RIDE "TRUE"
118 #define DEF_BOX "TRUE"
119 #define DEF_PERIODIC "TRUE"
120 #define DEF_SEARCH "TRUE"
121 #define DEF_DBUF "TRUE"
126 static Bool periodicp;
130 static XrmOptionDescRec opts[] = {
131 {"-rotate", ".flow.rotate", XrmoptionNoArg, "on"},
132 {"+rotate", ".flow.rotate", XrmoptionNoArg, "off"},
133 {"-ride", ".flow.ride", XrmoptionNoArg, "on"},
134 {"+ride", ".flow.ride", XrmoptionNoArg, "off"},
135 {"-box", ".flow.box", XrmoptionNoArg, "on"},
136 {"+box", ".flow.box", XrmoptionNoArg, "off"},
137 {"-periodic", ".flow.periodic", XrmoptionNoArg, "on"},
138 {"+periodic", ".flow.periodic", XrmoptionNoArg, "off"},
139 {"-search", ".flow.search", XrmoptionNoArg, "on"},
140 {"+search", ".flow.search", XrmoptionNoArg, "off"},
141 {"-dbuf", ".flow.dbuf", XrmoptionNoArg, "on"},
142 {"+dbuf", ".flow.dbuf", XrmoptionNoArg, "off"},
145 static argtype vars[] = {
146 {&rotatep, "rotate", "Rotate", DEF_ROTATE, t_Bool},
147 {&ridep, "ride", "Ride", DEF_RIDE, t_Bool},
148 {&boxp, "box", "Box", DEF_BOX, t_Bool},
149 {&periodicp, "periodic", "Periodic", DEF_PERIODIC, t_Bool},
150 {&searchp, "search", "Search", DEF_SEARCH, t_Bool},
151 {&dbufp, "dbuf", "Dbuf", DEF_DBUF, t_Bool},
154 static OptionStruct desc[] = {
155 {"-/+rotate", "turn on/off rotating around attractor."},
156 {"-/+ride", "turn on/off ride in the flow."},
157 {"-/+box", "turn on/off bounding box."},
158 {"-/+periodic", "turn on/off periodic attractors."},
159 {"-/+search", "turn on/off search for new attractors."},
160 {"-/+dbuf", "turn on/off double buffering."},
163 ENTRYPOINT ModeSpecOpt flow_opts =
164 {sizeof opts / sizeof opts[0], opts,
165 sizeof vars / sizeof vars[0], vars, desc};
168 ModStruct flow_description = {
169 "flow", "init_flow", "draw_flow", "release_flow",
170 "refresh_flow", "init_flow", NULL, &flow_opts,
171 1000, 1024, 10000, -10, 200, 1.0, "",
172 "Shows dynamic strange attractors", 0, NULL
177 typedef struct { double x, y, z; } dvector;
179 #define N_PARS 20 /* Enough for Full Cubic or Periodic Cubic */
180 typedef dvector Par[N_PARS];
181 enum { /* Name the parameter indices to make it easier to write
184 X,XX,XXX,XXY,XXZ,XY,XYY,XYZ,XZ,XZZ,
187 SINY = XY /* OK to overlap in this case */
190 /* Camera target [TDA] */
197 #define IX(C) ((C) * segindex + sp->cnsegs[(C)])
198 #define B(t,b) (sp->p + (t) + (b) * sp->taillen)
199 #define X(t,b) (B((t),(b))->x)
200 #define Y(t,b) (B((t),(b))->y)
201 #define Z(t,b) (B((t),(b))->z)
202 #define balance_rand(v) ((LRAND()/MAXRAND*(v))-((v)/2)) /* random around 0 */
203 #define LOST_IN_SPACE 2000.0
204 #define INITIALSTEP 0.04
205 #define EYEHEIGHT 0.005
209 /* Points that make up the box (normalized coordinates) */
210 static const double box[][3] = {
245 /* Lines connecting the box dots */
246 static const double lines[][2] = {
247 {0,1}, {1,2}, {2,3}, {3,0}, /* box */
248 {4,5}, {5,6}, {6,7}, {7,4},
249 {0,4}, {1,5}, {2,6}, {3,7},
250 {4+4,5+4}, {5+4,6+4}, {6+4,7+4}, {7+4,4+4},
251 {4+8,5+8}, {5+8,6+8}, {6+8,7+8}, {7+8,4+8},
252 {4+12,5+12}, {5+12,6+12}, {6+12,7+12}, {7+12,4+12},
253 {4+16,5+16}, {5+16,6+16}, {6+16,7+16}, {7+16,4+16},
254 {4+20,5+20}, {5+20,6+20}, {6+20,7+20}, {7+20,4+20},
255 {4+24,5+24}, {5+24,6+24}, {6+24,7+24}, {7+24,4+24},
259 /* Variables used in rendering */
260 dvector cam[3]; /* camera flight path */
263 Pixmap buffer; /* Double Buffer */
264 dvector circle[2]; /* POV that circles around the scene */
265 dvector centre; /* centre */
266 int beecount; /* number of bees */
267 XSegment *csegs; /* bee lines */
269 XSegment *old_segs; /* old bee lines */
273 /* Variables common to iterators */
274 dvector (*ODE) (Par par, double x, double y, double z);
275 dvector range; /* Initial conditions */
276 double yperiod; /* ODE's where Y is periodic. */
278 /* Variables used in iterating main flow */
280 dvector *p; /* bee positions x[time][bee#] */
284 dvector mid; /* Effective bounding box */
287 /* second set of variables, used for parallel search */
298 static flowstruct *flows = (flowstruct *) NULL;
307 /* Generic 3D Cubic Polynomial. Includes all the Quadratics (Lorentz,
308 Rossler) and much more! */
310 /* I considered offering a seperate 'Quadratic' option, since Cubic is
311 clearly overkill for the standard examples, but the performance
312 difference is too small to measure. The compute time is entirely
313 dominated by the XDrawSegments calls anyway. [TDA] */
315 Cubic(Par a, double x, double y, double z)
318 d.x = a[C].x + a[X].x*x + a[XX].x*x*x + a[XXX].x*x*x*x + a[XXY].x*x*x*y +
319 a[XXZ].x*x*x*z + a[XY].x*x*y + a[XYY].x*x*y*y + a[XYZ].x*x*y*z +
320 a[XZ].x*x*z + a[XZZ].x*x*z*z + a[Y].x*y + a[YY].x*y*y +
321 a[YYY].x*y*y*y + a[YYZ].x*y*y*z + a[YZ].x*y*z + a[YZZ].x*y*z*z +
322 a[Z].x*z + a[ZZ].x*z*z + a[ZZZ].x*z*z*z;
324 d.y = a[C].y + a[X].y*x + a[XX].y*x*x + a[XXX].y*x*x*x + a[XXY].y*x*x*y +
325 a[XXZ].y*x*x*z + a[XY].y*x*y + a[XYY].y*x*y*y + a[XYZ].y*x*y*z +
326 a[XZ].y*x*z + a[XZZ].y*x*z*z + a[Y].y*y + a[YY].y*y*y +
327 a[YYY].y*y*y*y + a[YYZ].y*y*y*z + a[YZ].y*y*z + a[YZZ].y*y*z*z +
328 a[Z].y*z + a[ZZ].y*z*z + a[ZZZ].y*z*z*z;
330 d.z = a[C].z + a[X].z*x + a[XX].z*x*x + a[XXX].z*x*x*x + a[XXY].z*x*x*y +
331 a[XXZ].z*x*x*z + a[XY].z*x*y + a[XYY].z*x*y*y + a[XYZ].z*x*y*z +
332 a[XZ].z*x*z + a[XZZ].z*x*z*z + a[Y].z*y + a[YY].z*y*y +
333 a[YYY].z*y*y*y + a[YYZ].z*y*y*z + a[YZ].z*y*z + a[YZZ].z*y*z*z +
334 a[Z].z*z + a[ZZ].z*z*z + a[ZZZ].z*z*z*z;
339 /* 3D Cubic in (x,z) with periodic sinusoidal forcing term in x. y is
340 the independent periodic (time) axis. This includes Birkhoff's
341 Bagel and Duffing's Attractor */
343 Periodic(Par a, double x, double y, double z)
347 d.x = a[C].x + a[X].x*x + a[XX].x*x*x + a[XXX].x*x*x*x +
348 a[XXZ].x*x*x*z + a[XZ].x*x*z + a[XZZ].x*x*z*z + a[Z].x*z +
349 a[ZZ].x*z*z + a[ZZZ].x*z*z*z + a[SINY].x*sin(y);
353 d.z = a[C].z + a[X].z*x + a[XX].z*x*x + a[XXX].z*x*x*x +
354 a[XXZ].z*x*x*z + a[XZ].z*x*z + a[XZZ].z*x*z*z + a[Z].z*z +
355 a[ZZ].z*z*z + a[ZZZ].z*z*z*z;
360 /* Numerical integration of the ODE using 2nd order Runge Kutta.
361 Returns length^2 of the update, so that we can detect if the step
362 size needs reducing. */
364 Iterate(dvector *p, dvector(*ODE)(Par par, double x, double y, double z),
365 Par par, double step)
369 k1 = ODE(par, p->x, p->y, p->z);
373 k2 = ODE(par, p->x + k1.x, p->y + k1.y, p->z + k1.z);
377 k3.x = (k1.x + k2.x) / 2.0;
378 k3.y = (k1.y + k2.y) / 2.0;
379 k3.z = (k1.z + k2.z) / 2.0;
385 return k3.x*k3.x + k3.y*k3.y + k3.z*k3.z;
388 /* Memory functions */
390 #define deallocate(p,t) if (p!=NULL) {free(p); p=(t*)NULL; }
391 #define allocate(p,t,s) if ((p=(t*)malloc(sizeof(t)*s))==NULL)\
392 {free_flow(sp);return;}
395 free_flow(flowstruct *sp)
397 deallocate(sp->csegs, XSegment);
398 deallocate(sp->cnsegs, int);
399 deallocate(sp->old_segs, XSegment);
400 deallocate(sp->p, dvector);
403 /* Generate Gaussian random number: mean 0, "amplitude" A (actually
404 A is 3*standard deviation). */
406 /* Note this generates a pair of gaussian variables, so it saves one
407 to give out next time it's called */
412 static Bool ready = 0;
419 x = 2.0 * (double)LRAND() / MAXRAND - 1.0;
420 y = 2.0 * (double)LRAND() / MAXRAND - 1.0;
424 w = sqrt((-2 * log(w))/w);
431 /* Attempt to discover new atractors by sending a pair of bees on a
432 fast trip through the new flow and computing their Lyapunov
433 exponent. Returns False if the bees fly away.
435 If the bees stay bounded, the new bounds and the Lyapunov exponent
436 are stored in sp and the function returns True.
438 Repeat invocations continue the flow and improve the accuracy of
439 the bounds and the Lyapunov exponent. Set sp->count2 to zero to
442 Acts on alternate variable set, so that it can be run in parallel
443 with the main flow */
446 discover(ModeInfo * mi)
452 double dl2, df, rs, lsum = 0, s, maxv2 = 0, v2;
458 sp = &flows[MI_SCREEN(mi)];
460 if(sp->count2 == 0) {
461 /* initial conditions */
462 sp->p2[0].x = Gauss_Rand(sp->range.x);
463 sp->p2[0].y = (sp->yperiod > 0)?
464 balance_rand(sp->range.y) : Gauss_Rand(sp->range.y);
465 sp->p2[0].z = Gauss_Rand(sp->range.z);
467 /* 1000 steps to find an attractor */
468 /* Most cases explode out here */
469 for(N=0; N < 1000; N++){
470 Iterate(sp->p2, sp->ODE, sp->par2, sp->step2);
471 if(sp->yperiod > 0 && sp->p2[0].y > sp->yperiod)
472 sp->p2[0].y -= sp->yperiod;
473 if(fabs(sp->p2[0].x) > LOST_IN_SPACE ||
474 fabs(sp->p2[0].y) > LOST_IN_SPACE ||
475 fabs(sp->p2[0].z) > LOST_IN_SPACE) {
480 /* Small perturbation */
481 sp->p2[1].x = sp->p2[0].x + 0.000001;
482 sp->p2[1].y = sp->p2[0].y;
483 sp->p2[1].z = sp->p2[0].z;
486 /* Reset bounding box */
487 max.x = min.x = sp->p2[0].x;
488 max.y = min.y = sp->p2[0].y;
489 max.z = min.z = sp->p2[0].z;
491 /* Compute Lyapunov Exponent */
493 /* (Technically, we're only estimating the largest Lyapunov
494 Exponent, but that's all we need to know to determine if we
495 have a strange attractor.) [TDA] */
497 /* Fly two bees close together */
498 for(N=0; N < 5000; N++){
499 for(i=0; i< 2; i++) {
500 v2 = Iterate(sp->p2+i, sp->ODE, sp->par2, sp->step2);
501 if(sp->yperiod > 0 && sp->p2[i].y > sp->yperiod)
502 sp->p2[i].y -= sp->yperiod;
504 if(fabs(sp->p2[i].x) > LOST_IN_SPACE ||
505 fabs(sp->p2[i].y) > LOST_IN_SPACE ||
506 fabs(sp->p2[i].z) > LOST_IN_SPACE) {
509 if(v2 > maxv2) maxv2 = v2; /* Track max v^2 */
512 /* find bounding box */
513 if ( sp->p2[0].x < min.x ) min.x = sp->p2[0].x;
514 else if ( sp->p2[0].x > max.x ) max.x = sp->p2[0].x;
515 if ( sp->p2[0].y < min.y ) min.y = sp->p2[0].y;
516 else if ( sp->p2[0].y > max.y ) max.y = sp->p2[0].y;
517 if ( sp->p2[0].z < min.z ) min.z = sp->p2[0].z;
518 else if ( sp->p2[0].z > max.z ) max.z = sp->p2[0].z;
520 /* Measure how much we have to pull the two bees to prevent
522 dl.x = sp->p2[1].x - sp->p2[0].x;
523 dl.y = sp->p2[1].y - sp->p2[0].y;
524 dl.z = sp->p2[1].z - sp->p2[0].z;
526 dl2 = dl.x*dl.x + dl.y*dl.y + dl.z*dl.z;
530 sp->p2[1].x = sp->p2[0].x + rs * dl.x;
531 sp->p2[1].y = sp->p2[0].y + rs * dl.y;
532 sp->p2[1].z = sp->p2[0].z + rs * dl.z;
533 lsum = lsum + log(df);
535 l = M_LOG2E / 2 * lsum / nl / sp->step2;
539 /* Anything that didn't explode has a finite attractor */
540 /* If Lyapunov is negative then it probably hit a fixed point or a
541 * limit cycle. Positive Lyapunov indicates a strange attractor. */
545 sp->size2 = max.x - min.x;
547 if(s > sp->size2) sp->size2 = s;
549 if(s > sp->size2) sp->size2 = s;
551 sp->mid2.x = (max.x + min.x) / 2;
552 sp->mid2.y = (max.y + min.y) / 2;
553 sp->mid2.z = (max.z + min.z) / 2;
555 if(sqrt(maxv2) > sp->size2 * 0.2) {
556 /* Flowing too fast, reduce step size. This
557 helps to eliminate high-speed limit cycles,
558 which can show +ve Lyapunov due to integration
565 /* Sets up initial conditions for a flow without all the extra baggage
566 that goes with init_flow */
568 restart_flow(ModeInfo * mi)
575 sp = &flows[MI_SCREEN(mi)];
578 /* Re-Initialize point positions, velocities, etc. */
579 for (b = 0; b < sp->beecount; b++) {
580 X(0, b) = Gauss_Rand(sp->range.x);
581 Y(0, b) = (sp->yperiod > 0)?
582 balance_rand(sp->range.y) : Gauss_Rand(sp->range.y);
583 Z(0, b) = Gauss_Rand(sp->range.z);
587 /* Returns true if line was behind a clip plane, or it clips the line */
588 /* nx,ny,nz is the normal to the plane. d is the distance from the origin */
589 /* s and e are the end points of the line to be clipped */
591 clip(double nx, double ny, double nz, double d, dvector *s, dvector *e)
597 front1 = (nx*s->x + ny*s->y + nz*s->z >= -d);
598 front2 = (nx*e->x + ny*e->y + nz*e->z >= -d);
599 if (!front1 && !front2) return 1;
600 if (front1 && front2) return 0;
605 /* Find t in line equation */
606 t = ( -d - nx*s->x - ny*s->y - nz*s->z) / ( nx*w.x + ny*w.y + nz*w.z);
608 p.x = s->x + w.x * t;
609 p.y = s->y + w.y * t;
610 p.z = s->z + w.z * t;
612 /* Move clipped point to the intersection */
626 init_flow (ModeInfo * mi)
632 if ((flows = (flowstruct *) calloc(MI_NUM_SCREENS(mi),
633 sizeof (flowstruct))) == NULL)
636 sp = &flows[MI_SCREEN(mi)];
640 sp->taillen = MI_SIZE(mi);
641 if (sp->taillen < -MINTRAIL) {
642 /* Change by sqrt so it seems more variable */
643 sp->taillen = NRAND((int)sqrt((double) (-sp->taillen - MINTRAIL + 1)));
644 sp->taillen = sp->taillen * sp->taillen + MINTRAIL;
645 } else if (sp->taillen < MINTRAIL) {
646 sp->taillen = MINTRAIL;
649 if(!rotatep && !ridep) rotatep = True; /* We need at least one viewpoint */
651 /* Start camera at Orbit or Bee */
657 sp->chasetime = 1; /* Go directly to target */
661 sp->step2 = INITIALSTEP;
663 /* Zero parameter set */
664 memset(sp->par2, 0, N_PARS * sizeof(dvector));
666 /* Set up standard examples */
667 switch (NRAND((periodicp) ? 5 : 3)) {
675 sp->par2[Y].x = 10 + balance_rand(5*0); /* a */
676 sp->par2[X].x = - sp->par2[Y].x; /* -a */
677 sp->par2[X].y = 28 + balance_rand(5*0); /* b */
681 sp->par2[Z].z = - 2 + balance_rand(1*0); /* -c */
691 sp->par2[Z].x = -2 + balance_rand(1); /* a */
693 sp->par2[Y].y = 0.2 + balance_rand(0.1); /* b */
694 sp->par2[C].z = 0.2 + balance_rand(0.1); /* c */
696 sp->par2[Z].z = -5.7;
702 z' = 0.2 + z(x - 5.7)
704 name = "RosslerCone";
706 sp->par2[Z].x = -2; /* a */
708 sp->par2[Y].y = 0.2; /* b */
709 sp->par2[ZZ].y = -0.331 + balance_rand(0.01); /* c */
712 sp->par2[Z].z = -5.7;
718 z' = 0.7x + az(0.1 - x^2)
722 sp->par2[SINY].x = 0.35 + balance_rand(0.25); /* b */
723 sp->par2[C].y = 1.57; /* c */
725 sp->par2[Z].z = 1 + balance_rand(0.5); /* a/10 */
726 sp->par2[XXZ].z = -10 * sp->par2[Z].z; /* -a */
727 sp->yperiod = 2 * M_PI;
731 x' = -ax - z/2 - z^3/8 + b sin(y)
736 sp->par2[X].x = -0.2 + balance_rand(0.1); /* a */
737 sp->par2[Z].x = -0.5;
738 sp->par2[ZZZ].x = -0.125;
739 sp->par2[SINY].x = 27.0 + balance_rand(3.0); /* b */
740 sp->par2[C].y = 1.33; /* c */
742 sp->yperiod = 2 * M_PI;
750 if(sp->yperiod > 0) {
752 /* periodic flows show either uniform distribution or a
753 snapshot on the 'time' axis */
754 sp->range.y = NRAND(2)? sp->yperiod : 0;
760 /* Run discoverer to set up bounding box, etc. Lyapunov will
761 probably be innaccurate, since we're only running it once, but
762 we're using known strange attractors so it should be ok. */
764 if(MI_IS_VERBOSE(mi))
766 "flow: Lyapunov exponent: %g, step: %g, size: %g (%s)\n",
767 sp->lyap2, sp->step2, sp->size2, name);
768 /* Install new params */
769 sp->lyap = sp->lyap2;
770 sp->size = sp->size2;
772 sp->step = sp->step2;
773 memcpy(sp->par, sp->par2, sizeof(sp->par2));
775 sp->count2 = 0; /* Reset search */
778 sp->beecount = MI_COUNT(mi);
779 if (sp->beecount < 0) { /* random variations */
780 sp->beecount = NRAND(-sp->beecount) + 1; /* Minimum 1 */
783 # ifdef HAVE_COCOA /* Don't second-guess Quartz's double-buffering */
787 if(dbufp) { /* Set up double buffer */
788 if (sp->buffer != None)
789 XFreePixmap(MI_DISPLAY(mi), sp->buffer);
790 sp->buffer = XCreatePixmap(MI_DISPLAY(mi), MI_WINDOW(mi),
791 MI_WIDTH(mi), MI_HEIGHT(mi), MI_DEPTH(mi));
793 sp->buffer = MI_WINDOW(mi);
795 /* no "NoExpose" events from XCopyArea wanted */
796 XSetGraphicsExposures(MI_DISPLAY(mi), MI_GC(mi), False);
798 /* Make sure we're using 'thin' lines */
799 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 0, LineSolid, CapNotLast,
802 /* Clear the background (may be slow depending on user prefs). */
805 /* Allocate memory. */
806 if (sp->csegs == NULL) {
807 allocate(sp->csegs, XSegment,
808 (sp->beecount + BOX_L) * MI_NPIXELS(mi) * sp->taillen);
809 allocate(sp->cnsegs, int, MI_NPIXELS(mi));
810 allocate(sp->old_segs, XSegment, sp->beecount * sp->taillen);
811 allocate(sp->p, dvector, sp->beecount * sp->taillen);
814 /* Initialize point positions, velocities, etc. */
817 /* Set up camera tail */
818 X(1, 0) = sp->cam[1].x = 0;
819 Y(1, 0) = sp->cam[1].y = 0;
820 Z(1, 0) = sp->cam[1].z = 0;
824 draw_flow (ModeInfo * mi)
828 double M[3][3]; /* transformation matrix */
829 flowstruct *sp = NULL;
835 sp = &flows[MI_SCREEN(mi)];
836 if (sp->csegs == NULL)
839 #ifdef HAVE_COCOA /* Don't second-guess Quartz's double-buffering */
840 XClearWindow (MI_DISPLAY(mi), MI_WINDOW(mi));
843 /* multiplier for indexing segment arrays. Used in IX macro, etc. */
844 segindex = (sp->beecount + BOX_L) * sp->taillen;
847 if(sp->count2 == 0) { /* start new search */
848 sp->step2 = INITIALSTEP;
849 /* Pick random parameters. Actual range is irrelevant
850 since parameter scale determines flow speed but not
852 for(i=0; i< N_PARS; i++) {
853 sp->par2[i].x = Gauss_Rand(1.0);
854 sp->par2[i].y = Gauss_Rand(1.0);
855 sp->par2[i].z = Gauss_Rand(1.0);
858 if(!discover(mi)) { /* Flow exploded, reset. */
862 sp->count2 = 0; /* Attractor found, but it's not strange */
863 }else if(sp->count2 > 1000000) { /* This one will do */
864 sp->count2 = 0; /* Reset search */
865 if(MI_IS_VERBOSE(mi))
867 "flow: Lyapunov exponent: %g, step: %g, size: %g (unnamed)\n",
868 sp->lyap2, sp->step2, sp->size2);
869 /* Install new params */
870 sp->lyap = sp->lyap2;
871 sp->size = sp->size2;
873 sp->step = sp->step2;
874 memcpy(sp->par, sp->par2, sizeof(sp->par2));
876 /* If we're allowed to zoom out, do so now, so that we
877 get a look at the new attractor. */
878 if(sp->chaseto == BEE && rotatep) {
882 /* Reset initial conditions, so we don't get
883 misleading artifacts in the particle density. */
889 /* Reset segment buffers */
890 for (col = 0; col < MI_NPIXELS(mi); col++)
893 MI_IS_DRAWN(mi) = True;
895 /* Calculate circling POV [Chalky]*/
896 sp->circle[1] = sp->circle[0];
897 sp->circle[0].x = sp->size * 2 * sin(sp->count / 100.0) *
898 (-0.6 + 0.4 *cos(sp->count / 500.0)) + sp->mid.x;
899 sp->circle[0].y = sp->size * 2 * cos(sp->count / 100.0) *
900 (0.6 + 0.4 *cos(sp->count / 500.0)) + sp->mid.y;
901 sp->circle[0].z = sp->size * 2 * sin(sp->count / 421.0) + sp->mid.z;
903 /* Timed chase instead of Chalkie's Bistable oscillator [TDA] */
904 if(rotatep && ridep) {
905 if(sp->chaseto == BEE && NRAND(1000) == 0){
908 }else if(NRAND(4000) == 0){
914 /* Set up orientation matrix */
916 double x[3], p[3], x2=0, xp=0;
919 /* Chasetime is here to guarantee the camera makes it all the
920 way to the target in a finite number of steps. */
921 if(sp->chasetime > 1)
924 if(sp->chaseto == BEE){
925 /* Camera Head targets bee 0 */
926 sp->cam[0].x += (X(0, 0) - sp->cam[0].x)/sp->chasetime;
927 sp->cam[0].y += (Y(0, 0) - sp->cam[0].y)/sp->chasetime;
928 sp->cam[0].z += (Z(0, 0) - sp->cam[0].z)/sp->chasetime;
930 /* Camera Tail targets previous position of bee 0 */
931 sp->cam[1].x += (X(1, 0) - sp->cam[1].x)/sp->chasetime;
932 sp->cam[1].y += (Y(1, 0) - sp->cam[1].y)/sp->chasetime;
933 sp->cam[1].z += (Z(1, 0) - sp->cam[1].z)/sp->chasetime;
935 /* Camera Wing targets bee 1 */
936 sp->cam[2].x += (X(0, 1) - sp->cam[2].x)/sp->chasetime;
937 sp->cam[2].y += (Y(0, 1) - sp->cam[2].y)/sp->chasetime;
938 sp->cam[2].z += (Z(0, 1) - sp->cam[2].z)/sp->chasetime;
940 /* Camera Head targets Orbiter */
941 sp->cam[0].x += (sp->circle[0].x - sp->cam[0].x)/sp->chasetime;
942 sp->cam[0].y += (sp->circle[0].y - sp->cam[0].y)/sp->chasetime;
943 sp->cam[0].z += (sp->circle[0].z - sp->cam[0].z)/sp->chasetime;
945 /* Camera Tail targets diametrically opposite the middle
946 of the bounding box from the Orbiter */
948 (2*sp->circle[0].x - sp->mid.x - sp->cam[1].x)/sp->chasetime;
950 (2*sp->circle[0].y - sp->mid.y - sp->cam[1].y)/sp->chasetime;
952 (2*sp->circle[0].z - sp->mid.z - sp->cam[1].z)/sp->chasetime;
953 /* Camera Wing targets previous position of Orbiter */
954 sp->cam[2].x += (sp->circle[1].x - sp->cam[2].x)/sp->chasetime;
955 sp->cam[2].y += (sp->circle[1].y - sp->cam[2].y)/sp->chasetime;
956 sp->cam[2].z += (sp->circle[1].z - sp->cam[2].z)/sp->chasetime;
959 /* Viewpoint from Tail of camera */
960 sp->centre.x=sp->cam[1].x;
961 sp->centre.y=sp->cam[1].y;
962 sp->centre.z=sp->cam[1].z;
965 x[0] = sp->cam[0].x - sp->cam[1].x;
966 x[1] = sp->cam[0].y - sp->cam[1].y;
967 x[2] = sp->cam[0].z - sp->cam[1].z;
970 p[0] = sp->cam[2].x - sp->cam[1].x;
971 p[1] = sp->cam[2].y - sp->cam[1].y;
972 p[2] = sp->cam[2].z - sp->cam[1].z;
975 /* So long as X and P don't collide, these can be used to form
976 three mutually othogonal axes: X, (X x P) x X and X x P.
977 After being normalised to unit length, these form the
978 Orientation Matrix. */
981 x2+= x[i]*x[i]; /* X . X */
982 xp+= x[i]*p[i]; /* X . P */
983 M[0][i] = x[i]; /* X */
986 for(i=0; i<3; i++) /* (X x P) x X */
987 M[1][i] = x2*p[i] - xp*x[i]; /* == (X . X) P - (X . P) X */
989 M[2][0] = x[1]*p[2] - x[2]*p[1]; /* X x P */
990 M[2][1] = -x[0]*p[2] + x[2]*p[0];
991 M[2][2] = x[0]*p[1] - x[1]*p[0];
996 for(i=0; i<3; i++) A+=M[j][i]*M[j][i]; /* sum squares */
999 for(i=0; i<3; i++) M[j][i]/=A;
1002 if(sp->chaseto == BEE) {
1003 X(0, 1)=X(0, 0)+M[1][0]*sp->step; /* adjust neighbour */
1004 Y(0, 1)=Y(0, 0)+M[1][1]*sp->step;
1005 Z(0, 1)=Z(0, 0)+M[1][2]*sp->step;
1009 /* <=- Bounding Box -=> */
1011 for (b = 0; b < BOX_L; b++) {
1013 /* Chalky's clipping code, Only used for the box */
1014 /* clipping trails is slow and of little benefit. [TDA] */
1015 int p1 = lines[b][0];
1016 int p2 = lines[b][1];
1018 double x1=box[p1][0]* sp->size/2 + sp->mid.x - sp->centre.x;
1019 double y1=box[p1][1]* sp->size/2 + sp->mid.y - sp->centre.y;
1020 double z1=box[p1][2]* sp->size/2 + sp->mid.z - sp->centre.z;
1021 double x2=box[p2][0]* sp->size/2 + sp->mid.x - sp->centre.x;
1022 double y2=box[p2][1]* sp->size/2 + sp->mid.y - sp->centre.y;
1023 double z2=box[p2][2]* sp->size/2 + sp->mid.z - sp->centre.z;
1025 A1.x=M[0][0]*x1 + M[0][1]*y1 + M[0][2]*z1;
1026 A1.y=M[1][0]*x1 + M[1][1]*y1 + M[1][2]*z1;
1027 A1.z=M[2][0]*x1 + M[2][1]*y1 + M[2][2]*z1 + EYEHEIGHT * sp->size;
1028 A2.x=M[0][0]*x2 + M[0][1]*y2 + M[0][2]*z2;
1029 A2.y=M[1][0]*x2 + M[1][1]*y2 + M[1][2]*z2;
1030 A2.z=M[2][0]*x2 + M[2][1]*y2 + M[2][2]*z2 + EYEHEIGHT * sp->size;
1032 /* Clip in 3D before projecting down to 2D. A 2D clip
1033 after projection wouldn't be able to handle lines that
1035 if (clip(1, 0, 0,-1, &A1, &A2) || /* Screen */
1036 clip(1, 2, 0, 0, &A1, &A2) || /* Left */
1037 clip(1,-2, 0, 0, &A1, &A2) || /* Right */
1038 clip(1,0, 2.0*MI_WIDTH(mi)/MI_HEIGHT(mi), 0, &A1, &A2)||/*UP*/
1039 clip(1,0,-2.0*MI_WIDTH(mi)/MI_HEIGHT(mi), 0, &A1, &A2))/*Down*/
1042 /* Colour according to bee */
1043 col = b % (MI_NPIXELS(mi) - 1);
1045 sp->csegs[IX(col)].x1 = MI_WIDTH(mi)/2 + MI_WIDTH(mi) * A1.y/A1.x;
1046 sp->csegs[IX(col)].y1 = MI_HEIGHT(mi)/2 + MI_WIDTH(mi) * A1.z/A1.x;
1047 sp->csegs[IX(col)].x2 = MI_WIDTH(mi)/2 + MI_WIDTH(mi) * A2.y/A2.x;
1048 sp->csegs[IX(col)].y2 = MI_HEIGHT(mi)/2 + MI_WIDTH(mi) * A2.z/A2.x;
1054 for (b = 0; b < sp->beecount; b++) {
1055 if(fabs(X(0, b)) > LOST_IN_SPACE ||
1056 fabs(Y(0, b)) > LOST_IN_SPACE ||
1057 fabs(Z(0, b)) > LOST_IN_SPACE){
1058 if(sp->chaseto == BEE && b == 0){
1059 /* Lost camera bee. Need to replace it since
1060 rerunning init_flow could lose us a hard-won new
1061 attractor. Try moving it very close to a random
1062 other bee. This way we have a good chance of being
1063 close to the attractor and not forming a false
1064 artifact. If we've lost many bees this may need to
1066 /* Don't worry about camera wingbee. It stays close
1067 to the main camera bee no matter what happens. */
1068 int newb = 1 + NRAND(sp->beecount - 1);
1069 X(0, 0) = X(0, newb) + 0.001;
1070 Y(0, 0) = Y(0, newb);
1071 Z(0, 0) = Z(0, newb);
1072 if(MI_IS_VERBOSE(mi))
1074 "flow: resetting lost camera near bee %d\n",
1080 /* Age the tail. It's critical this be fast since
1081 beecount*taillen can be large. */
1082 memmove(B(1, b), B(0, b), (sp->taillen - 1) * sizeof(dvector));
1084 Iterate(B(0,b), sp->ODE, sp->par, sp->step);
1086 /* Don't show wingbee since he's not quite in the flow. */
1087 if(sp->chaseto == BEE && b == 1) continue;
1089 /* Colour according to bee */
1090 col = b % (MI_NPIXELS(mi) - 1);
1092 /* Fill the segment lists. */
1094 begin = 0; /* begin new trail */
1095 end = MIN(sp->taillen, sp->count); /* short trails at first */
1096 for(i=0; i < end; i++){
1097 double x = X(i,b)-sp->centre.x;
1098 double y = Y(i,b)*(sp->yperiod < 0? (sp->size/sp->yperiod) :1)
1100 double z = Z(i,b)-sp->centre.z;
1101 double XM=M[0][0]*x + M[0][1]*y + M[0][2]*z;
1102 double YM=M[1][0]*x + M[1][1]*y + M[1][2]*z;
1103 double ZM=M[2][0]*x + M[2][1]*y + M[2][2]*z + EYEHEIGHT * sp->size;
1106 swarm++; /* count the remaining bees */
1107 if(sp->yperiod > 0 && Y(i,b) > sp->yperiod){
1109 Y(i,b) -= sp->yperiod;
1110 /* hide tail to prevent streaks in Y. Streaks in X,Z
1111 are ok, they help to outline the Poincare'
1113 for(j = i; j < end; j++) Y(j,b) = Y(i,b);
1118 if(XM <= 0){ /* off screen - new trail */
1122 absx = MI_WIDTH(mi)/2 + MI_WIDTH(mi) * YM/XM;
1123 absy = MI_HEIGHT(mi)/2 + MI_WIDTH(mi) * ZM/XM;
1124 /* Performance bottleneck */
1125 if(absx <= 0 || absx >= MI_WIDTH(mi) ||
1126 absy <= 0 || absy >= MI_HEIGHT(mi)) {
1127 /* off screen - new trail */
1131 if(i > begin) { /* complete previous segment */
1132 sp->csegs[IX(col)].x2 = absx;
1133 sp->csegs[IX(col)].y2 = absy;
1137 if(i < end -1){ /* start new segment */
1138 sp->csegs[IX(col)].x1 = absx;
1139 sp->csegs[IX(col)].y1 = absy;
1145 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
1146 if (dbufp) { /* In Double Buffer case, prepare off-screen copy */
1147 /* For slow systems, this can be the single biggest bottleneck
1148 in the program. These systems may be better of not using
1149 the double buffer. */
1150 XFillRectangle(MI_DISPLAY(mi), sp->buffer, MI_GC(mi), 0, 0,
1151 MI_WIDTH(mi), MI_HEIGHT(mi));
1152 } else { /* Otherwise, erase previous segment list directly */
1153 XDrawSegments(MI_DISPLAY(mi), sp->buffer, MI_GC(mi),
1154 sp->old_segs, sp->nold_segs);
1158 if (MI_NPIXELS(mi) > 2){ /* colour */
1160 for (col = 0; col < MI_NPIXELS(mi) - 1; col++)
1161 if (sp->cnsegs[col] > 0) {
1162 if(sp->cnsegs[col] > mn) mn = sp->cnsegs[col];
1163 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_PIXEL(mi, col+1));
1164 /* This is usually the biggest bottleneck on most
1165 systems. The maths load is insignificant compared
1167 XDrawSegments(MI_DISPLAY(mi), sp->buffer, MI_GC(mi),
1168 sp->csegs + col * segindex, sp->cnsegs[col]);
1170 } else { /* mono handled seperately since xlockmore uses '1' for
1172 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
1173 XDrawSegments(MI_DISPLAY(mi), sp->buffer, MI_GC(mi),
1174 sp->csegs, sp->cnsegs[0]);
1176 if (dbufp) { /* In Double Buffer case, this updates the screen */
1177 XCopyArea(MI_DISPLAY(mi), sp->buffer, MI_WINDOW(mi), MI_GC(mi), 0, 0,
1178 MI_WIDTH(mi), MI_HEIGHT(mi), 0, 0);
1179 } else { /* Otherwise, screen is already updated. Copy segments
1180 to erase-list to be erased directly next time. */
1182 for (col = 0; col < MI_NPIXELS(mi) - 1; col++) {
1183 memcpy(sp->old_segs + c, sp->csegs + col * segindex,
1184 sp->cnsegs[col] * sizeof(XSegment));
1185 c += sp->cnsegs[col];
1190 if(sp->count > 1 && swarm == 0) { /* all gone */
1191 if(MI_IS_VERBOSE(mi))
1192 fprintf(stdout, "flow: all gone at %d\n", sp->count);
1196 if(sp->count++ > MI_CYCLES(mi)){ /* Time's up. If we haven't
1197 found anything new by now we
1198 should pick a new standard
1205 release_flow (ModeInfo * mi)
1207 if (flows != NULL) {
1210 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
1211 free_flow(&flows[screen]);
1213 flows = (flowstruct *) NULL;
1218 refresh_flow (ModeInfo * mi)
1220 if(!dbufp) MI_CLEARWINDOW(mi);
1223 XSCREENSAVER_MODULE ("Flow", flow)
1225 #endif /* MODE_flow */