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 PROGCLASS "Flow"
102 # define HACK_INIT init_flow
103 # define HACK_DRAW draw_flow
104 # define flow_opts xlockmore_opts
105 # define DEFAULTS "*delay: 10000 \n" \
108 "*cycles: 10000 \n" \
111 # include "xlockmore.h" /* in xscreensaver distribution */
113 # define MI_DEPTH MI_WIN_DEPTH
115 #else /* STANDALONE */
116 # include "xlock.h" /* in xlockmore distribution */
117 #endif /* STANDALONE */
121 #define DEF_ROTATE "TRUE"
122 #define DEF_RIDE "TRUE"
123 #define DEF_BOX "TRUE"
124 #define DEF_PERIODIC "TRUE"
125 #define DEF_SEARCH "TRUE"
126 #define DEF_DBUF "TRUE"
131 static Bool periodicp;
135 static XrmOptionDescRec opts[] = {
136 {"-rotate", ".flow.rotate", XrmoptionNoArg, "on"},
137 {"+rotate", ".flow.rotate", XrmoptionNoArg, "off"},
138 {"-ride", ".flow.ride", XrmoptionNoArg, "on"},
139 {"+ride", ".flow.ride", XrmoptionNoArg, "off"},
140 {"-box", ".flow.box", XrmoptionNoArg, "on"},
141 {"+box", ".flow.box", XrmoptionNoArg, "off"},
142 {"-periodic", ".flow.periodic", XrmoptionNoArg, "on"},
143 {"+periodic", ".flow.periodic", XrmoptionNoArg, "off"},
144 {"-search", ".flow.search", XrmoptionNoArg, "on"},
145 {"+search", ".flow.search", XrmoptionNoArg, "off"},
146 {"-dbuf", ".flow.dbuf", XrmoptionNoArg, "on"},
147 {"+dbuf", ".flow.dbuf", XrmoptionNoArg, "off"},
150 static argtype vars[] = {
151 {&rotatep, "rotate", "Rotate", DEF_ROTATE, t_Bool},
152 {&ridep, "ride", "Ride", DEF_RIDE, t_Bool},
153 {&boxp, "box", "Box", DEF_BOX, t_Bool},
154 {&periodicp, "periodic", "Periodic", DEF_PERIODIC, t_Bool},
155 {&searchp, "search", "Search", DEF_SEARCH, t_Bool},
156 {&dbufp, "dbuf", "Dbuf", DEF_DBUF, t_Bool},
159 static OptionStruct desc[] = {
160 {"-/+rotate", "turn on/off rotating around attractor."},
161 {"-/+ride", "turn on/off ride in the flow."},
162 {"-/+box", "turn on/off bounding box."},
163 {"-/+periodic", "turn on/off periodic attractors."},
164 {"-/+search", "turn on/off search for new attractors."},
165 {"-/+dbuf", "turn on/off double buffering."},
168 ModeSpecOpt flow_opts =
169 {sizeof opts / sizeof opts[0], opts,
170 sizeof vars / sizeof vars[0], vars, desc};
173 ModStruct flow_description = {
174 "flow", "init_flow", "draw_flow", "release_flow",
175 "refresh_flow", "init_flow", NULL, &flow_opts,
176 1000, 1024, 10000, -10, 200, 1.0, "",
177 "Shows dynamic strange attractors", 0, NULL
182 typedef struct { double x, y, z; } dvector;
184 #define N_PARS 20 /* Enough for Full Cubic or Periodic Cubic */
185 typedef dvector Par[N_PARS];
186 enum { /* Name the parameter indices to make it easier to write
189 X,XX,XXX,XXY,XXZ,XY,XYY,XYZ,XZ,XZZ,
192 SINY = XY /* OK to overlap in this case */
195 /* Camera target [TDA] */
202 #define IX(C) ((C) * segindex + sp->cnsegs[(C)])
203 #define B(t,b) (sp->p + (t) + (b) * sp->taillen)
204 #define X(t,b) (B((t),(b))->x)
205 #define Y(t,b) (B((t),(b))->y)
206 #define Z(t,b) (B((t),(b))->z)
207 #define balance_rand(v) ((LRAND()/MAXRAND*(v))-((v)/2)) /* random around 0 */
208 #define LOST_IN_SPACE 2000.0
209 #define INITIALSTEP 0.04
210 #define EYEHEIGHT 0.005
214 /* Points that make up the box (normalized coordinates) */
215 static const double box[][3] = {
250 /* Lines connecting the box dots */
251 static const double lines[][2] = {
252 {0,1}, {1,2}, {2,3}, {3,0}, /* box */
253 {4,5}, {5,6}, {6,7}, {7,4},
254 {0,4}, {1,5}, {2,6}, {3,7},
255 {4+4,5+4}, {5+4,6+4}, {6+4,7+4}, {7+4,4+4},
256 {4+8,5+8}, {5+8,6+8}, {6+8,7+8}, {7+8,4+8},
257 {4+12,5+12}, {5+12,6+12}, {6+12,7+12}, {7+12,4+12},
258 {4+16,5+16}, {5+16,6+16}, {6+16,7+16}, {7+16,4+16},
259 {4+20,5+20}, {5+20,6+20}, {6+20,7+20}, {7+20,4+20},
260 {4+24,5+24}, {5+24,6+24}, {6+24,7+24}, {7+24,4+24},
264 /* Variables used in rendering */
265 dvector cam[3]; /* camera flight path */
268 Pixmap buffer; /* Double Buffer */
269 dvector circle[2]; /* POV that circles around the scene */
270 dvector centre; /* centre */
271 int beecount; /* number of bees */
272 XSegment *csegs; /* bee lines */
274 XSegment *old_segs; /* old bee lines */
278 /* Variables common to iterators */
279 dvector (*ODE) (Par par, double x, double y, double z);
280 dvector range; /* Initial conditions */
281 double yperiod; /* ODE's where Y is periodic. */
283 /* Variables used in iterating main flow */
285 dvector *p; /* bee positions x[time][bee#] */
289 dvector mid; /* Effective bounding box */
292 /* second set of variables, used for parallel search */
303 static flowstruct *flows = (flowstruct *) NULL;
312 /* Generic 3D Cubic Polynomial. Includes all the Quadratics (Lorentz,
313 Rossler) and much more! */
315 /* I considered offering a seperate 'Quadratic' option, since Cubic is
316 clearly overkill for the standard examples, but the performance
317 difference is too small to measure. The compute time is entirely
318 dominated by the XDrawSegments calls anyway. [TDA] */
320 Cubic(Par a, double x, double y, double z)
323 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 +
324 a[XXZ].x*x*x*z + a[XY].x*x*y + a[XYY].x*x*y*y + a[XYZ].x*x*y*z +
325 a[XZ].x*x*z + a[XZZ].x*x*z*z + a[Y].x*y + a[YY].x*y*y +
326 a[YYY].x*y*y*y + a[YYZ].x*y*y*z + a[YZ].x*y*z + a[YZZ].x*y*z*z +
327 a[Z].x*z + a[ZZ].x*z*z + a[ZZZ].x*z*z*z;
329 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 +
330 a[XXZ].y*x*x*z + a[XY].y*x*y + a[XYY].y*x*y*y + a[XYZ].y*x*y*z +
331 a[XZ].y*x*z + a[XZZ].y*x*z*z + a[Y].y*y + a[YY].y*y*y +
332 a[YYY].y*y*y*y + a[YYZ].y*y*y*z + a[YZ].y*y*z + a[YZZ].y*y*z*z +
333 a[Z].y*z + a[ZZ].y*z*z + a[ZZZ].y*z*z*z;
335 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 +
336 a[XXZ].z*x*x*z + a[XY].z*x*y + a[XYY].z*x*y*y + a[XYZ].z*x*y*z +
337 a[XZ].z*x*z + a[XZZ].z*x*z*z + a[Y].z*y + a[YY].z*y*y +
338 a[YYY].z*y*y*y + a[YYZ].z*y*y*z + a[YZ].z*y*z + a[YZZ].z*y*z*z +
339 a[Z].z*z + a[ZZ].z*z*z + a[ZZZ].z*z*z*z;
344 /* 3D Cubic in (x,z) with periodic sinusoidal forcing term in x. y is
345 the independent periodic (time) axis. This includes Birkhoff's
346 Bagel and Duffing's Attractor */
348 Periodic(Par a, double x, double y, double z)
352 d.x = a[C].x + a[X].x*x + a[XX].x*x*x + a[XXX].x*x*x*x +
353 a[XXZ].x*x*x*z + a[XZ].x*x*z + a[XZZ].x*x*z*z + a[Z].x*z +
354 a[ZZ].x*z*z + a[ZZZ].x*z*z*z + a[SINY].x*sin(y);
358 d.z = a[C].z + a[X].z*x + a[XX].z*x*x + a[XXX].z*x*x*x +
359 a[XXZ].z*x*x*z + a[XZ].z*x*z + a[XZZ].z*x*z*z + a[Z].z*z +
360 a[ZZ].z*z*z + a[ZZZ].z*z*z*z;
365 /* Numerical integration of the ODE using 2nd order Runge Kutta.
366 Returns length^2 of the update, so that we can detect if the step
367 size needs reducing. */
369 Iterate(dvector *p, dvector(*ODE)(Par par, double x, double y, double z),
370 Par par, double step)
374 k1 = ODE(par, p->x, p->y, p->z);
378 k2 = ODE(par, p->x + k1.x, p->y + k1.y, p->z + k1.z);
382 k3.x = (k1.x + k2.x) / 2.0;
383 k3.y = (k1.y + k2.y) / 2.0;
384 k3.z = (k1.z + k2.z) / 2.0;
390 return k3.x*k3.x + k3.y*k3.y + k3.z*k3.z;
393 /* Memory functions */
395 #define deallocate(p,t) if (p!=NULL) {free(p); p=(t*)NULL; }
396 #define allocate(p,t,s) if ((p=(t*)malloc(sizeof(t)*s))==NULL)\
397 {free_flow(sp);return;}
400 free_flow(flowstruct *sp)
402 deallocate(sp->csegs, XSegment);
403 deallocate(sp->cnsegs, int);
404 deallocate(sp->old_segs, XSegment);
405 deallocate(sp->p, dvector);
408 /* Generate Gaussian random number: mean 0, "amplitude" A (actually
409 A is 3*standard deviation). */
411 /* Note this generates a pair of gaussian variables, so it saves one
412 to give out next time it's called */
417 static Bool ready = 0;
424 x = 2.0 * (double)LRAND() / MAXRAND - 1.0;
425 y = 2.0 * (double)LRAND() / MAXRAND - 1.0;
429 w = sqrt((-2 * log(w))/w);
436 /* Attempt to discover new atractors by sending a pair of bees on a
437 fast trip through the new flow and computing their Lyapunov
438 exponent. Returns False if the bees fly away.
440 If the bees stay bounded, the new bounds and the Lyapunov exponent
441 are stored in sp and the function returns True.
443 Repeat invocations continue the flow and improve the accuracy of
444 the bounds and the Lyapunov exponent. Set sp->count2 to zero to
447 Acts on alternate variable set, so that it can be run in parallel
448 with the main flow */
451 discover(ModeInfo * mi)
457 double dl2, df, rs, lsum = 0, s, maxv2 = 0, v2;
463 sp = &flows[MI_SCREEN(mi)];
465 if(sp->count2 == 0) {
466 /* initial conditions */
467 sp->p2[0].x = Gauss_Rand(sp->range.x);
468 sp->p2[0].y = (sp->yperiod > 0)?
469 balance_rand(sp->range.y) : Gauss_Rand(sp->range.y);
470 sp->p2[0].z = Gauss_Rand(sp->range.z);
472 /* 1000 steps to find an attractor */
473 /* Most cases explode out here */
474 for(N=0; N < 1000; N++){
475 Iterate(sp->p2, sp->ODE, sp->par2, sp->step2);
476 if(sp->yperiod > 0 && sp->p2[0].y > sp->yperiod)
477 sp->p2[0].y -= sp->yperiod;
478 if(fabs(sp->p2[0].x) > LOST_IN_SPACE ||
479 fabs(sp->p2[0].y) > LOST_IN_SPACE ||
480 fabs(sp->p2[0].z) > LOST_IN_SPACE) {
485 /* Small perturbation */
486 sp->p2[1].x = sp->p2[0].x + 0.000001;
487 sp->p2[1].y = sp->p2[0].y;
488 sp->p2[1].z = sp->p2[0].z;
491 /* Reset bounding box */
492 max.x = min.x = sp->p2[0].x;
493 max.y = min.y = sp->p2[0].y;
494 max.z = min.z = sp->p2[0].z;
496 /* Compute Lyapunov Exponent */
498 /* (Technically, we're only estimating the largest Lyapunov
499 Exponent, but that's all we need to know to determine if we
500 have a strange attractor.) [TDA] */
502 /* Fly two bees close together */
503 for(N=0; N < 5000; N++){
504 for(i=0; i< 2; i++) {
505 v2 = Iterate(sp->p2+i, sp->ODE, sp->par2, sp->step2);
506 if(sp->yperiod > 0 && sp->p2[i].y > sp->yperiod)
507 sp->p2[i].y -= sp->yperiod;
509 if(fabs(sp->p2[i].x) > LOST_IN_SPACE ||
510 fabs(sp->p2[i].y) > LOST_IN_SPACE ||
511 fabs(sp->p2[i].z) > LOST_IN_SPACE) {
514 if(v2 > maxv2) maxv2 = v2; /* Track max v^2 */
517 /* find bounding box */
518 if ( sp->p2[0].x < min.x ) min.x = sp->p2[0].x;
519 else if ( sp->p2[0].x > max.x ) max.x = sp->p2[0].x;
520 if ( sp->p2[0].y < min.y ) min.y = sp->p2[0].y;
521 else if ( sp->p2[0].y > max.y ) max.y = sp->p2[0].y;
522 if ( sp->p2[0].z < min.z ) min.z = sp->p2[0].z;
523 else if ( sp->p2[0].z > max.z ) max.z = sp->p2[0].z;
525 /* Measure how much we have to pull the two bees to prevent
527 dl.x = sp->p2[1].x - sp->p2[0].x;
528 dl.y = sp->p2[1].y - sp->p2[0].y;
529 dl.z = sp->p2[1].z - sp->p2[0].z;
531 dl2 = dl.x*dl.x + dl.y*dl.y + dl.z*dl.z;
535 sp->p2[1].x = sp->p2[0].x + rs * dl.x;
536 sp->p2[1].y = sp->p2[0].y + rs * dl.y;
537 sp->p2[1].z = sp->p2[0].z + rs * dl.z;
538 lsum = lsum + log(df);
540 l = M_LOG2E / 2 * lsum / nl / sp->step2;
544 /* Anything that didn't explode has a finite attractor */
545 /* If Lyapunov is negative then it probably hit a fixed point or a
546 * limit cycle. Positive Lyapunov indicates a strange attractor. */
550 sp->size2 = max.x - min.x;
552 if(s > sp->size2) sp->size2 = s;
554 if(s > sp->size2) sp->size2 = s;
556 sp->mid2.x = (max.x + min.x) / 2;
557 sp->mid2.y = (max.y + min.y) / 2;
558 sp->mid2.z = (max.z + min.z) / 2;
560 if(sqrt(maxv2) > sp->size2 * 0.2) {
561 /* Flowing too fast, reduce step size. This
562 helps to eliminate high-speed limit cycles,
563 which can show +ve Lyapunov due to integration
570 /* Sets up initial conditions for a flow without all the extra baggage
571 that goes with init_flow */
573 restart_flow(ModeInfo * mi)
580 sp = &flows[MI_SCREEN(mi)];
583 /* Re-Initialize point positions, velocities, etc. */
584 for (b = 0; b < sp->beecount; b++) {
585 X(0, b) = Gauss_Rand(sp->range.x);
586 Y(0, b) = (sp->yperiod > 0)?
587 balance_rand(sp->range.y) : Gauss_Rand(sp->range.y);
588 Z(0, b) = Gauss_Rand(sp->range.z);
592 /* Returns true if line was behind a clip plane, or it clips the line */
593 /* nx,ny,nz is the normal to the plane. d is the distance from the origin */
594 /* s and e are the end points of the line to be clipped */
596 clip(double nx, double ny, double nz, double d, dvector *s, dvector *e)
602 front1 = (nx*s->x + ny*s->y + nz*s->z >= -d);
603 front2 = (nx*e->x + ny*e->y + nz*e->z >= -d);
604 if (!front1 && !front2) return 1;
605 if (front1 && front2) return 0;
610 /* Find t in line equation */
611 t = ( -d - nx*s->x - ny*s->y - nz*s->z) / ( nx*w.x + ny*w.y + nz*w.z);
613 p.x = s->x + w.x * t;
614 p.y = s->y + w.y * t;
615 p.z = s->z + w.z * t;
617 /* Move clipped point to the intersection */
631 init_flow(ModeInfo * mi)
637 if ((flows = (flowstruct *) calloc(MI_NUM_SCREENS(mi),
638 sizeof (flowstruct))) == NULL)
641 sp = &flows[MI_SCREEN(mi)];
645 sp->taillen = MI_SIZE(mi);
646 if (sp->taillen < -MINTRAIL) {
647 /* Change by sqrt so it seems more variable */
648 sp->taillen = NRAND((int)sqrt((double) (-sp->taillen - MINTRAIL + 1)));
649 sp->taillen = sp->taillen * sp->taillen + MINTRAIL;
650 } else if (sp->taillen < MINTRAIL) {
651 sp->taillen = MINTRAIL;
654 if(!rotatep && !ridep) rotatep = True; /* We need at least one viewpoint */
656 /* Start camera at Orbit or Bee */
662 sp->chasetime = 1; /* Go directly to target */
666 sp->step2 = INITIALSTEP;
668 /* Zero parameter set */
669 memset(sp->par2, 0, N_PARS * sizeof(dvector));
671 /* Set up standard examples */
672 switch (NRAND((periodicp) ? 5 : 3)) {
680 sp->par2[Y].x = 10 + balance_rand(5*0); /* a */
681 sp->par2[X].x = - sp->par2[Y].x; /* -a */
682 sp->par2[X].y = 28 + balance_rand(5*0); /* b */
686 sp->par2[Z].z = - 2 + balance_rand(1*0); /* -c */
696 sp->par2[Z].x = -2 + balance_rand(1); /* a */
698 sp->par2[Y].y = 0.2 + balance_rand(0.1); /* b */
699 sp->par2[C].z = 0.2 + balance_rand(0.1); /* c */
701 sp->par2[Z].z = -5.7;
707 z' = 0.2 + z(x - 5.7)
709 name = "RosslerCone";
711 sp->par2[Z].x = -2; /* a */
713 sp->par2[Y].y = 0.2; /* b */
714 sp->par2[ZZ].y = -0.331 + balance_rand(0.01); /* c */
717 sp->par2[Z].z = -5.7;
723 z' = 0.7x + az(0.1 - x^2)
727 sp->par2[SINY].x = 0.35 + balance_rand(0.25); /* b */
728 sp->par2[C].y = 1.57; /* c */
730 sp->par2[Z].z = 1 + balance_rand(0.5); /* a/10 */
731 sp->par2[XXZ].z = -10 * sp->par2[Z].z; /* -a */
732 sp->yperiod = 2 * M_PI;
736 x' = -ax - z/2 - z^3/8 + b sin(y)
741 sp->par2[X].x = -0.2 + balance_rand(0.1); /* a */
742 sp->par2[Z].x = -0.5;
743 sp->par2[ZZZ].x = -0.125;
744 sp->par2[SINY].x = 27.0 + balance_rand(3.0); /* b */
745 sp->par2[C].y = 1.33; /* c */
747 sp->yperiod = 2 * M_PI;
755 if(sp->yperiod > 0) {
757 /* periodic flows show either uniform distribution or a
758 snapshot on the 'time' axis */
759 sp->range.y = NRAND(2)? sp->yperiod : 0;
765 /* Run discoverer to set up bounding box, etc. Lyapunov will
766 probably be innaccurate, since we're only running it once, but
767 we're using known strange attractors so it should be ok. */
769 if(MI_IS_VERBOSE(mi))
771 "flow: Lyapunov exponent: %g, step: %g, size: %g (%s)\n",
772 sp->lyap2, sp->step2, sp->size2, name);
773 /* Install new params */
774 sp->lyap = sp->lyap2;
775 sp->size = sp->size2;
777 sp->step = sp->step2;
778 memcpy(sp->par, sp->par2, sizeof(sp->par2));
780 sp->count2 = 0; /* Reset search */
783 sp->beecount = MI_COUNT(mi);
784 if (sp->beecount < 0) { /* random variations */
785 sp->beecount = NRAND(-sp->beecount) + 1; /* Minimum 1 */
788 if(dbufp) { /* Set up double buffer */
789 if (sp->buffer != None)
790 XFreePixmap(MI_DISPLAY(mi), sp->buffer);
791 sp->buffer = XCreatePixmap(MI_DISPLAY(mi), MI_WINDOW(mi),
792 MI_WIDTH(mi), MI_HEIGHT(mi), MI_DEPTH(mi));
794 sp->buffer = MI_WINDOW(mi);
796 /* no "NoExpose" events from XCopyArea wanted */
797 XSetGraphicsExposures(MI_DISPLAY(mi), MI_GC(mi), False);
799 /* Make sure we're using 'thin' lines */
800 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 0, LineSolid, CapNotLast,
803 /* Clear the background (may be slow depending on user prefs). */
806 /* Allocate memory. */
807 if (sp->csegs == NULL) {
808 allocate(sp->csegs, XSegment,
809 (sp->beecount + BOX_L) * MI_NPIXELS(mi) * sp->taillen);
810 allocate(sp->cnsegs, int, MI_NPIXELS(mi));
811 allocate(sp->old_segs, XSegment, sp->beecount * sp->taillen);
812 allocate(sp->p, dvector, sp->beecount * sp->taillen);
815 /* Initialize point positions, velocities, etc. */
818 /* Set up camera tail */
819 X(1, 0) = sp->cam[1].x = 0;
820 Y(1, 0) = sp->cam[1].y = 0;
821 Z(1, 0) = sp->cam[1].z = 0;
825 draw_flow(ModeInfo * mi)
829 double M[3][3]; /* transformation matrix */
830 flowstruct *sp = NULL;
836 sp = &flows[MI_SCREEN(mi)];
837 if (sp->csegs == NULL)
840 /* multiplier for indexing segment arrays. Used in IX macro, etc. */
841 segindex = (sp->beecount + BOX_L) * sp->taillen;
844 if(sp->count2 == 0) { /* start new search */
845 sp->step2 = INITIALSTEP;
846 /* Pick random parameters. Actual range is irrelevant
847 since parameter scale determines flow speed but not
849 for(i=0; i< N_PARS; i++) {
850 sp->par2[i].x = Gauss_Rand(1.0);
851 sp->par2[i].y = Gauss_Rand(1.0);
852 sp->par2[i].z = Gauss_Rand(1.0);
855 if(!discover(mi)) { /* Flow exploded, reset. */
859 sp->count2 = 0; /* Attractor found, but it's not strange */
860 }else if(sp->count2 > 1000000) { /* This one will do */
861 sp->count2 = 0; /* Reset search */
862 if(MI_IS_VERBOSE(mi))
864 "flow: Lyapunov exponent: %g, step: %g, size: %g (unnamed)\n",
865 sp->lyap2, sp->step2, sp->size2);
866 /* Install new params */
867 sp->lyap = sp->lyap2;
868 sp->size = sp->size2;
870 sp->step = sp->step2;
871 memcpy(sp->par, sp->par2, sizeof(sp->par2));
873 /* If we're allowed to zoom out, do so now, so that we
874 get a look at the new attractor. */
875 if(sp->chaseto == BEE && rotatep) {
879 /* Reset initial conditions, so we don't get
880 misleading artifacts in the particle density. */
886 /* Reset segment buffers */
887 for (col = 0; col < MI_NPIXELS(mi); col++)
890 MI_IS_DRAWN(mi) = True;
892 /* Calculate circling POV [Chalky]*/
893 sp->circle[1] = sp->circle[0];
894 sp->circle[0].x = sp->size * 2 * sin(sp->count / 100.0) *
895 (-0.6 + 0.4 *cos(sp->count / 500.0)) + sp->mid.x;
896 sp->circle[0].y = sp->size * 2 * cos(sp->count / 100.0) *
897 (0.6 + 0.4 *cos(sp->count / 500.0)) + sp->mid.y;
898 sp->circle[0].z = sp->size * 2 * sin(sp->count / 421.0) + sp->mid.z;
900 /* Timed chase instead of Chalkie's Bistable oscillator [TDA] */
901 if(rotatep && ridep) {
902 if(sp->chaseto == BEE && NRAND(1000) == 0){
905 }else if(NRAND(4000) == 0){
911 /* Set up orientation matrix */
913 double x[3], p[3], x2=0, xp=0;
916 /* Chasetime is here to guarantee the camera makes it all the
917 way to the target in a finite number of steps. */
918 if(sp->chasetime > 1)
921 if(sp->chaseto == BEE){
922 /* Camera Head targets bee 0 */
923 sp->cam[0].x += (X(0, 0) - sp->cam[0].x)/sp->chasetime;
924 sp->cam[0].y += (Y(0, 0) - sp->cam[0].y)/sp->chasetime;
925 sp->cam[0].z += (Z(0, 0) - sp->cam[0].z)/sp->chasetime;
927 /* Camera Tail targets previous position of bee 0 */
928 sp->cam[1].x += (X(1, 0) - sp->cam[1].x)/sp->chasetime;
929 sp->cam[1].y += (Y(1, 0) - sp->cam[1].y)/sp->chasetime;
930 sp->cam[1].z += (Z(1, 0) - sp->cam[1].z)/sp->chasetime;
932 /* Camera Wing targets bee 1 */
933 sp->cam[2].x += (X(0, 1) - sp->cam[2].x)/sp->chasetime;
934 sp->cam[2].y += (Y(0, 1) - sp->cam[2].y)/sp->chasetime;
935 sp->cam[2].z += (Z(0, 1) - sp->cam[2].z)/sp->chasetime;
937 /* Camera Head targets Orbiter */
938 sp->cam[0].x += (sp->circle[0].x - sp->cam[0].x)/sp->chasetime;
939 sp->cam[0].y += (sp->circle[0].y - sp->cam[0].y)/sp->chasetime;
940 sp->cam[0].z += (sp->circle[0].z - sp->cam[0].z)/sp->chasetime;
942 /* Camera Tail targets diametrically opposite the middle
943 of the bounding box from the Orbiter */
945 (2*sp->circle[0].x - sp->mid.x - sp->cam[1].x)/sp->chasetime;
947 (2*sp->circle[0].y - sp->mid.y - sp->cam[1].y)/sp->chasetime;
949 (2*sp->circle[0].z - sp->mid.z - sp->cam[1].z)/sp->chasetime;
950 /* Camera Wing targets previous position of Orbiter */
951 sp->cam[2].x += (sp->circle[1].x - sp->cam[2].x)/sp->chasetime;
952 sp->cam[2].y += (sp->circle[1].y - sp->cam[2].y)/sp->chasetime;
953 sp->cam[2].z += (sp->circle[1].z - sp->cam[2].z)/sp->chasetime;
956 /* Viewpoint from Tail of camera */
957 sp->centre.x=sp->cam[1].x;
958 sp->centre.y=sp->cam[1].y;
959 sp->centre.z=sp->cam[1].z;
962 x[0] = sp->cam[0].x - sp->cam[1].x;
963 x[1] = sp->cam[0].y - sp->cam[1].y;
964 x[2] = sp->cam[0].z - sp->cam[1].z;
967 p[0] = sp->cam[2].x - sp->cam[1].x;
968 p[1] = sp->cam[2].y - sp->cam[1].y;
969 p[2] = sp->cam[2].z - sp->cam[1].z;
972 /* So long as X and P don't collide, these can be used to form
973 three mutually othogonal axes: X, (X x P) x X and X x P.
974 After being normalised to unit length, these form the
975 Orientation Matrix. */
978 x2+= x[i]*x[i]; /* X . X */
979 xp+= x[i]*p[i]; /* X . P */
980 M[0][i] = x[i]; /* X */
983 for(i=0; i<3; i++) /* (X x P) x X */
984 M[1][i] = x2*p[i] - xp*x[i]; /* == (X . X) P - (X . P) X */
986 M[2][0] = x[1]*p[2] - x[2]*p[1]; /* X x P */
987 M[2][1] = -x[0]*p[2] + x[2]*p[0];
988 M[2][2] = x[0]*p[1] - x[1]*p[0];
993 for(i=0; i<3; i++) A+=M[j][i]*M[j][i]; /* sum squares */
996 for(i=0; i<3; i++) M[j][i]/=A;
999 if(sp->chaseto == BEE) {
1000 X(0, 1)=X(0, 0)+M[1][0]*sp->step; /* adjust neighbour */
1001 Y(0, 1)=Y(0, 0)+M[1][1]*sp->step;
1002 Z(0, 1)=Z(0, 0)+M[1][2]*sp->step;
1006 /* <=- Bounding Box -=> */
1008 for (b = 0; b < BOX_L; b++) {
1010 /* Chalky's clipping code, Only used for the box */
1011 /* clipping trails is slow and of little benefit. [TDA] */
1012 int p1 = lines[b][0];
1013 int p2 = lines[b][1];
1015 double x1=box[p1][0]* sp->size/2 + sp->mid.x - sp->centre.x;
1016 double y1=box[p1][1]* sp->size/2 + sp->mid.y - sp->centre.y;
1017 double z1=box[p1][2]* sp->size/2 + sp->mid.z - sp->centre.z;
1018 double x2=box[p2][0]* sp->size/2 + sp->mid.x - sp->centre.x;
1019 double y2=box[p2][1]* sp->size/2 + sp->mid.y - sp->centre.y;
1020 double z2=box[p2][2]* sp->size/2 + sp->mid.z - sp->centre.z;
1022 A1.x=M[0][0]*x1 + M[0][1]*y1 + M[0][2]*z1;
1023 A1.y=M[1][0]*x1 + M[1][1]*y1 + M[1][2]*z1;
1024 A1.z=M[2][0]*x1 + M[2][1]*y1 + M[2][2]*z1 + EYEHEIGHT * sp->size;
1025 A2.x=M[0][0]*x2 + M[0][1]*y2 + M[0][2]*z2;
1026 A2.y=M[1][0]*x2 + M[1][1]*y2 + M[1][2]*z2;
1027 A2.z=M[2][0]*x2 + M[2][1]*y2 + M[2][2]*z2 + EYEHEIGHT * sp->size;
1029 /* Clip in 3D before projecting down to 2D. A 2D clip
1030 after projection wouldn't be able to handle lines that
1032 if (clip(1, 0, 0,-1, &A1, &A2) || /* Screen */
1033 clip(1, 2, 0, 0, &A1, &A2) || /* Left */
1034 clip(1,-2, 0, 0, &A1, &A2) || /* Right */
1035 clip(1,0, 2.0*MI_WIDTH(mi)/MI_HEIGHT(mi), 0, &A1, &A2)||/*UP*/
1036 clip(1,0,-2.0*MI_WIDTH(mi)/MI_HEIGHT(mi), 0, &A1, &A2))/*Down*/
1039 /* Colour according to bee */
1040 col = b % (MI_NPIXELS(mi) - 1);
1042 sp->csegs[IX(col)].x1 = MI_WIDTH(mi)/2 + MI_WIDTH(mi) * A1.y/A1.x;
1043 sp->csegs[IX(col)].y1 = MI_HEIGHT(mi)/2 + MI_WIDTH(mi) * A1.z/A1.x;
1044 sp->csegs[IX(col)].x2 = MI_WIDTH(mi)/2 + MI_WIDTH(mi) * A2.y/A2.x;
1045 sp->csegs[IX(col)].y2 = MI_HEIGHT(mi)/2 + MI_WIDTH(mi) * A2.z/A2.x;
1051 for (b = 0; b < sp->beecount; b++) {
1052 if(fabs(X(0, b)) > LOST_IN_SPACE ||
1053 fabs(Y(0, b)) > LOST_IN_SPACE ||
1054 fabs(Z(0, b)) > LOST_IN_SPACE){
1055 if(sp->chaseto == BEE && b == 0){
1056 /* Lost camera bee. Need to replace it since
1057 rerunning init_flow could lose us a hard-won new
1058 attractor. Try moving it very close to a random
1059 other bee. This way we have a good chance of being
1060 close to the attractor and not forming a false
1061 artifact. If we've lost many bees this may need to
1063 /* Don't worry about camera wingbee. It stays close
1064 to the main camera bee no matter what happens. */
1065 int newb = 1 + NRAND(sp->beecount - 1);
1066 X(0, 0) = X(0, newb) + 0.001;
1067 Y(0, 0) = Y(0, newb);
1068 Z(0, 0) = Z(0, newb);
1069 if(MI_IS_VERBOSE(mi))
1071 "flow: resetting lost camera near bee %d\n",
1077 /* Age the tail. It's critical this be fast since
1078 beecount*taillen can be large. */
1079 memmove(B(1, b), B(0, b), (sp->taillen - 1) * sizeof(dvector));
1081 Iterate(B(0,b), sp->ODE, sp->par, sp->step);
1083 /* Don't show wingbee since he's not quite in the flow. */
1084 if(sp->chaseto == BEE && b == 1) continue;
1086 /* Colour according to bee */
1087 col = b % (MI_NPIXELS(mi) - 1);
1089 /* Fill the segment lists. */
1091 begin = 0; /* begin new trail */
1092 end = MIN(sp->taillen, sp->count); /* short trails at first */
1093 for(i=0; i < end; i++){
1094 double x = X(i,b)-sp->centre.x;
1095 double y = Y(i,b)*(sp->yperiod < 0? (sp->size/sp->yperiod) :1)
1097 double z = Z(i,b)-sp->centre.z;
1098 double XM=M[0][0]*x + M[0][1]*y + M[0][2]*z;
1099 double YM=M[1][0]*x + M[1][1]*y + M[1][2]*z;
1100 double ZM=M[2][0]*x + M[2][1]*y + M[2][2]*z + EYEHEIGHT * sp->size;
1103 swarm++; /* count the remaining bees */
1104 if(sp->yperiod > 0 && Y(i,b) > sp->yperiod){
1106 Y(i,b) -= sp->yperiod;
1107 /* hide tail to prevent streaks in Y. Streaks in X,Z
1108 are ok, they help to outline the Poincare'
1110 for(j = i; j < end; j++) Y(j,b) = Y(i,b);
1115 if(XM <= 0){ /* off screen - new trail */
1119 absx = MI_WIDTH(mi)/2 + MI_WIDTH(mi) * YM/XM;
1120 absy = MI_HEIGHT(mi)/2 + MI_WIDTH(mi) * ZM/XM;
1121 /* Performance bottleneck */
1122 if(absx <= 0 || absx >= MI_WIDTH(mi) ||
1123 absy <= 0 || absy >= MI_HEIGHT(mi)) {
1124 /* off screen - new trail */
1128 if(i > begin) { /* complete previous segment */
1129 sp->csegs[IX(col)].x2 = absx;
1130 sp->csegs[IX(col)].y2 = absy;
1134 if(i < end -1){ /* start new segment */
1135 sp->csegs[IX(col)].x1 = absx;
1136 sp->csegs[IX(col)].y1 = absy;
1142 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
1143 if (dbufp) { /* In Double Buffer case, prepare off-screen copy */
1144 /* For slow systems, this can be the single biggest bottleneck
1145 in the program. These systems may be better of not using
1146 the double buffer. */
1147 XFillRectangle(MI_DISPLAY(mi), sp->buffer, MI_GC(mi), 0, 0,
1148 MI_WIDTH(mi), MI_HEIGHT(mi));
1149 } else { /* Otherwise, erase previous segment list directly */
1150 XDrawSegments(MI_DISPLAY(mi), sp->buffer, MI_GC(mi),
1151 sp->old_segs, sp->nold_segs);
1155 if (MI_NPIXELS(mi) > 2){ /* colour */
1157 for (col = 0; col < MI_NPIXELS(mi) - 1; col++)
1158 if (sp->cnsegs[col] > 0) {
1159 if(sp->cnsegs[col] > mn) mn = sp->cnsegs[col];
1160 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_PIXEL(mi, col+1));
1161 /* This is usually the biggest bottleneck on most
1162 systems. The maths load is insignificant compared
1164 XDrawSegments(MI_DISPLAY(mi), sp->buffer, MI_GC(mi),
1165 sp->csegs + col * segindex, sp->cnsegs[col]);
1167 } else { /* mono handled seperately since xlockmore uses '1' for
1169 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
1170 XDrawSegments(MI_DISPLAY(mi), sp->buffer, MI_GC(mi),
1171 sp->csegs, sp->cnsegs[0]);
1173 if (dbufp) { /* In Double Buffer case, this updates the screen */
1174 XCopyArea(MI_DISPLAY(mi), sp->buffer, MI_WINDOW(mi), MI_GC(mi), 0, 0,
1175 MI_WIDTH(mi), MI_HEIGHT(mi), 0, 0);
1176 } else { /* Otherwise, screen is already updated. Copy segments
1177 to erase-list to be erased directly next time. */
1179 for (col = 0; col < MI_NPIXELS(mi) - 1; col++) {
1180 memcpy(sp->old_segs + c, sp->csegs + col * segindex,
1181 sp->cnsegs[col] * sizeof(XSegment));
1182 c += sp->cnsegs[col];
1187 if(sp->count > 1 && swarm == 0) { /* all gone */
1188 if(MI_IS_VERBOSE(mi))
1189 fprintf(stdout, "flow: all gone at %d\n", sp->count);
1193 if(sp->count++ > MI_CYCLES(mi)){ /* Time's up. If we haven't
1194 found anything new by now we
1195 should pick a new standard
1202 release_flow(ModeInfo * mi)
1204 if (flows != NULL) {
1207 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
1208 free_flow(&flows[screen]);
1210 flows = (flowstruct *) NULL;
1215 refresh_flow(ModeInfo * mi)
1217 if(!dbufp) MI_CLEARWINDOW(mi);
1220 #endif /* MODE_flow */