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 <Tim.Auckland@Procket.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" \
113 "*periodic: True \n" \
116 # include "xlockmore.h" /* in xscreensaver distribution */
118 # define MI_DEPTH MI_WIN_DEPTH
120 #else /* STANDALONE */
121 # include "xlock.h" /* in xlockmore distribution */
122 #endif /* STANDALONE */
126 #define DEF_ROTATE "TRUE"
127 #define DEF_RIDE "TRUE"
128 #define DEF_BOX "TRUE"
129 #define DEF_PERIODIC "TRUE"
130 #define DEF_SEARCH "TRUE"
131 #define DEF_DBUF "TRUE"
136 static Bool periodicp;
140 static XrmOptionDescRec opts[] = {
141 {"-rotate", ".flow.rotate", XrmoptionNoArg, "on"},
142 {"+rotate", ".flow.rotate", XrmoptionNoArg, "off"},
143 {"-ride", ".flow.ride", XrmoptionNoArg, "on"},
144 {"+ride", ".flow.ride", XrmoptionNoArg, "off"},
145 {"-box", ".flow.box", XrmoptionNoArg, "on"},
146 {"+box", ".flow.box", XrmoptionNoArg, "off"},
147 {"-periodic", ".flow.periodic", XrmoptionNoArg, "on"},
148 {"+periodic", ".flow.periodic", XrmoptionNoArg, "off"},
149 {"-search", ".flow.search", XrmoptionNoArg, "on"},
150 {"+search", ".flow.search", XrmoptionNoArg, "off"},
151 {"-dbuf", ".flow.dbuf", XrmoptionNoArg, "on"},
152 {"+dbuf", ".flow.dbuf", XrmoptionNoArg, "off"},
155 static argtype vars[] = {
156 {&rotatep, "rotate", "Rotate", DEF_ROTATE, t_Bool},
157 {&ridep, "ride", "Ride", DEF_RIDE, t_Bool},
158 {&boxp, "box", "Box", DEF_BOX, t_Bool},
159 {&periodicp, "periodic", "Periodic", DEF_PERIODIC, t_Bool},
160 {&searchp, "search", "Search", DEF_SEARCH, t_Bool},
161 {&dbufp, "dbuf", "Dbuf", DEF_DBUF, t_Bool},
164 static OptionStruct desc[] = {
165 {"-/+rotate", "turn on/off rotating around attractor."},
166 {"-/+ride", "turn on/off ride in the flow."},
167 {"-/+box", "turn on/off bounding box."},
168 {"-/+periodic", "turn on/off periodic attractors."},
169 {"-/+search", "turn on/off search for new attractors."},
170 {"-/+dbuf", "turn on/off double buffering."},
173 ModeSpecOpt flow_opts =
174 {sizeof opts / sizeof opts[0], opts,
175 sizeof vars / sizeof vars[0], vars, desc};
178 ModStruct flow_description = {
179 "flow", "init_flow", "draw_flow", "release_flow",
180 "refresh_flow", "init_flow", NULL, &flow_opts,
181 1000, 1024, 10000, -10, 200, 1.0, "",
182 "Shows dynamic strange attractors", 0, NULL
187 typedef struct { double x, y, z; } dvector;
189 #define N_PARS 20 /* Enough for Full Cubic or Periodic Cubic */
190 typedef dvector Par[N_PARS];
191 enum { /* Name the parameter indices to make it easier to write
194 X,XX,XXX,XXY,XXZ,XY,XYY,XYZ,XZ,XZZ,
197 SINY = XY /* OK to overlap in this case */
200 /* Camera target [TDA] */
207 #define IX(C) ((C) * segindex + sp->cnsegs[(C)])
208 #define B(t,b) (sp->p + (t) + (b) * sp->taillen)
209 #define X(t,b) (B((t),(b))->x)
210 #define Y(t,b) (B((t),(b))->y)
211 #define Z(t,b) (B((t),(b))->z)
212 #define balance_rand(v) ((LRAND()/MAXRAND*(v))-((v)/2)) /* random around 0 */
213 #define LOST_IN_SPACE 2000.0
214 #define INITIALSTEP 0.04
215 #define EYEHEIGHT 0.005
219 /* Points that make up the box (normalized coordinates) */
220 static const double box[][3] = {
255 /* Lines connecting the box dots */
256 static const double lines[][2] = {
257 {0,1}, {1,2}, {2,3}, {3,0}, /* box */
258 {4,5}, {5,6}, {6,7}, {7,4},
259 {0,4}, {1,5}, {2,6}, {3,7},
260 {4+4,5+4}, {5+4,6+4}, {6+4,7+4}, {7+4,4+4},
261 {4+8,5+8}, {5+8,6+8}, {6+8,7+8}, {7+8,4+8},
262 {4+12,5+12}, {5+12,6+12}, {6+12,7+12}, {7+12,4+12},
263 {4+16,5+16}, {5+16,6+16}, {6+16,7+16}, {7+16,4+16},
264 {4+20,5+20}, {5+20,6+20}, {6+20,7+20}, {7+20,4+20},
265 {4+24,5+24}, {5+24,6+24}, {6+24,7+24}, {7+24,4+24},
269 /* Variables used in rendering */
270 dvector cam[3]; /* camera flight path */
273 Pixmap buffer; /* Double Buffer */
274 dvector circle[2]; /* POV that circles around the scene */
275 dvector centre; /* centre */
276 int beecount; /* number of bees */
277 XSegment *csegs; /* bee lines */
279 XSegment *old_segs; /* old bee lines */
283 /* Variables common to iterators */
284 dvector (*ODE) (Par par, double x, double y, double z);
285 dvector range; /* Initial conditions */
286 double yperiod; /* ODE's where Y is periodic. */
288 /* Variables used in iterating main flow */
290 dvector *p; /* bee positions x[time][bee#] */
294 dvector mid; /* Effective bounding box */
297 /* second set of variables, used for parallel search */
308 static flowstruct *flows = (flowstruct *) NULL;
317 /* Generic 3D Cubic Polynomial. Includes all the Quadratics (Lorentz,
318 Rossler) and much more! */
320 /* I considered offering a seperate 'Quadratic' option, since Cubic is
321 clearly overkill for the standard examples, but the performance
322 difference is too small to measure. The compute time is entirely
323 dominated by the XDrawSegments calls anyway. [TDA] */
325 Cubic(Par a, double x, double y, double z)
328 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 +
329 a[XXZ].x*x*x*z + a[XY].x*x*y + a[XYY].x*x*y*y + a[XYZ].x*x*y*z +
330 a[XZ].x*x*z + a[XZZ].x*x*z*z + a[Y].x*y + a[YY].x*y*y +
331 a[YYY].x*y*y*y + a[YYZ].x*y*y*z + a[YZ].x*y*z + a[YZZ].x*y*z*z +
332 a[Z].x*z + a[ZZ].x*z*z + a[ZZZ].x*z*z*z;
334 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 +
335 a[XXZ].y*x*x*z + a[XY].y*x*y + a[XYY].y*x*y*y + a[XYZ].y*x*y*z +
336 a[XZ].y*x*z + a[XZZ].y*x*z*z + a[Y].y*y + a[YY].y*y*y +
337 a[YYY].y*y*y*y + a[YYZ].y*y*y*z + a[YZ].y*y*z + a[YZZ].y*y*z*z +
338 a[Z].y*z + a[ZZ].y*z*z + a[ZZZ].y*z*z*z;
340 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 +
341 a[XXZ].z*x*x*z + a[XY].z*x*y + a[XYY].z*x*y*y + a[XYZ].z*x*y*z +
342 a[XZ].z*x*z + a[XZZ].z*x*z*z + a[Y].z*y + a[YY].z*y*y +
343 a[YYY].z*y*y*y + a[YYZ].z*y*y*z + a[YZ].z*y*z + a[YZZ].z*y*z*z +
344 a[Z].z*z + a[ZZ].z*z*z + a[ZZZ].z*z*z*z;
349 /* 3D Cubic in (x,z) with periodic sinusoidal forcing term in x. y is
350 the independent periodic (time) axis. This includes Birkhoff's
351 Bagel and Duffing's Attractor */
353 Periodic(Par a, double x, double y, double z)
357 d.x = a[C].x + a[X].x*x + a[XX].x*x*x + a[XXX].x*x*x*x +
358 a[XXZ].x*x*x*z + a[XZ].x*x*z + a[XZZ].x*x*z*z + a[Z].x*z +
359 a[ZZ].x*z*z + a[ZZZ].x*z*z*z + a[SINY].x*sin(y);
363 d.z = a[C].z + a[X].z*x + a[XX].z*x*x + a[XXX].z*x*x*x +
364 a[XXZ].z*x*x*z + a[XZ].z*x*z + a[XZZ].z*x*z*z + a[Z].z*z +
365 a[ZZ].z*z*z + a[ZZZ].z*z*z*z;
370 /* Numerical integration of the ODE using 2nd order Runge Kutta.
371 Returns length^2 of the update, so that we can detect if the step
372 size needs reducing. */
374 Iterate(dvector *p, dvector(*ODE)(Par par, double x, double y, double z),
375 Par par, double step)
379 k1 = ODE(par, p->x, p->y, p->z);
383 k2 = ODE(par, p->x + k1.x, p->y + k1.y, p->z + k1.z);
387 k3.x = (k1.x + k2.x) / 2.0;
388 k3.y = (k1.y + k2.y) / 2.0;
389 k3.z = (k1.z + k2.z) / 2.0;
395 return k3.x*k3.x + k3.y*k3.y + k3.z*k3.z;
398 /* Memory functions */
400 #define deallocate(p,t) if (p!=NULL) {free(p); p=(t*)NULL; }
401 #define allocate(p,t,s) if ((p=(t*)malloc(sizeof(t)*s))==NULL)\
402 {free_flow(sp);return;}
405 free_flow(flowstruct *sp)
407 deallocate(sp->csegs, XSegment);
408 deallocate(sp->cnsegs, int);
409 deallocate(sp->old_segs, XSegment);
410 deallocate(sp->p, dvector);
413 /* Generate Gaussian random number: mean 0, "amplitude" A (actually
414 A is 3*standard deviation). */
416 /* Note this generates a pair of gaussian variables, so it saves one
417 to give out next time it's called */
422 static Bool ready = 0;
429 x = 2.0 * (double)LRAND() / MAXRAND - 1.0;
430 y = 2.0 * (double)LRAND() / MAXRAND - 1.0;
434 w = sqrt((-2 * log(w))/w);
441 /* Attempt to discover new atractors by sending a pair of bees on a
442 fast trip through the new flow and computing their Lyapunov
443 exponent. Returns False if the bees fly away.
445 If the bees stay bounded, the new bounds and the Lyapunov exponent
446 are stored in sp and the function returns True.
448 Repeat invocations continue the flow and improve the accuracy of
449 the bounds and the Lyapunov exponent. Set sp->count2 to zero to
452 Acts on alternate variable set, so that it can be run in parallel
453 with the main flow */
456 discover(ModeInfo * mi)
462 double dl2, df, rs, lsum = 0, s, maxv2 = 0, v2;
468 sp = &flows[MI_SCREEN(mi)];
470 if(sp->count2 == 0) {
471 /* initial conditions */
472 sp->p2[0].x = Gauss_Rand(sp->range.x);
473 sp->p2[0].y = (sp->yperiod > 0)?
474 balance_rand(sp->range.y) : Gauss_Rand(sp->range.y);
475 sp->p2[0].z = Gauss_Rand(sp->range.z);
477 /* 1000 steps to find an attractor */
478 /* Most cases explode out here */
479 for(N=0; N < 1000; N++){
480 Iterate(sp->p2, sp->ODE, sp->par2, sp->step2);
481 if(sp->yperiod > 0 && sp->p2[0].y > sp->yperiod)
482 sp->p2[0].y -= sp->yperiod;
483 if(fabs(sp->p2[0].x) > LOST_IN_SPACE ||
484 fabs(sp->p2[0].y) > LOST_IN_SPACE ||
485 fabs(sp->p2[0].z) > LOST_IN_SPACE) {
490 /* Small perturbation */
491 sp->p2[1].x = sp->p2[0].x + 0.000001;
492 sp->p2[1].y = sp->p2[0].y;
493 sp->p2[1].z = sp->p2[0].z;
496 /* Reset bounding box */
497 max.x = min.x = sp->p2[0].x;
498 max.y = min.y = sp->p2[0].y;
499 max.z = min.z = sp->p2[0].z;
501 /* Compute Lyapunov Exponent */
503 /* (Technically, we're only estimating the largest Lyapunov
504 Exponent, but that's all we need to know to determine if we
505 have a strange attractor.) [TDA] */
507 /* Fly two bees close together */
508 for(N=0; N < 5000; N++){
509 for(i=0; i< 2; i++) {
510 v2 = Iterate(sp->p2+i, sp->ODE, sp->par2, sp->step2);
511 if(sp->yperiod > 0 && sp->p2[i].y > sp->yperiod)
512 sp->p2[i].y -= sp->yperiod;
514 if(fabs(sp->p2[i].x) > LOST_IN_SPACE ||
515 fabs(sp->p2[i].y) > LOST_IN_SPACE ||
516 fabs(sp->p2[i].z) > LOST_IN_SPACE) {
519 if(v2 > maxv2) maxv2 = v2; /* Track max v^2 */
522 /* find bounding box */
523 if ( sp->p2[0].x < min.x ) min.x = sp->p2[0].x;
524 else if ( sp->p2[0].x > max.x ) max.x = sp->p2[0].x;
525 if ( sp->p2[0].y < min.y ) min.y = sp->p2[0].y;
526 else if ( sp->p2[0].y > max.y ) max.y = sp->p2[0].y;
527 if ( sp->p2[0].z < min.z ) min.z = sp->p2[0].z;
528 else if ( sp->p2[0].z > max.z ) max.z = sp->p2[0].z;
530 /* Measure how much we have to pull the two bees to prevent
532 dl.x = sp->p2[1].x - sp->p2[0].x;
533 dl.y = sp->p2[1].y - sp->p2[0].y;
534 dl.z = sp->p2[1].z - sp->p2[0].z;
536 dl2 = dl.x*dl.x + dl.y*dl.y + dl.z*dl.z;
540 sp->p2[1].x = sp->p2[0].x + rs * dl.x;
541 sp->p2[1].y = sp->p2[0].y + rs * dl.y;
542 sp->p2[1].z = sp->p2[0].z + rs * dl.z;
543 lsum = lsum + log(df);
545 l = M_LOG2E / 2 * lsum / nl / sp->step2;
549 /* Anything that didn't explode has a finite attractor */
550 /* If Lyapunov is negative then it probably hit a fixed point or a
551 * limit cycle. Positive Lyapunov indicates a strange attractor. */
555 sp->size2 = max.x - min.x;
557 if(s > sp->size2) sp->size2 = s;
559 if(s > sp->size2) sp->size2 = s;
561 sp->mid2.x = (max.x + min.x) / 2;
562 sp->mid2.y = (max.y + min.y) / 2;
563 sp->mid2.z = (max.z + min.z) / 2;
565 if(sqrt(maxv2) > sp->size2 * 0.2) {
566 /* Flowing too fast, reduce step size. This
567 helps to eliminate high-speed limit cycles,
568 which can show +ve Lyapunov due to integration
575 /* Sets up initial conditions for a flow without all the extra baggage
576 that goes with init_flow */
578 restart_flow(ModeInfo * mi)
585 sp = &flows[MI_SCREEN(mi)];
588 /* Re-Initialize point positions, velocities, etc. */
589 for (b = 0; b < sp->beecount; b++) {
590 X(0, b) = Gauss_Rand(sp->range.x);
591 Y(0, b) = (sp->yperiod > 0)?
592 balance_rand(sp->range.y) : Gauss_Rand(sp->range.y);
593 Z(0, b) = Gauss_Rand(sp->range.z);
597 /* Returns true if line was behind a clip plane, or it clips the line */
598 /* nx,ny,nz is the normal to the plane. d is the distance from the origin */
599 /* s and e are the end points of the line to be clipped */
601 clip(double nx, double ny, double nz, double d, dvector *s, dvector *e)
607 front1 = (nx*s->x + ny*s->y + nz*s->z >= -d);
608 front2 = (nx*e->x + ny*e->y + nz*e->z >= -d);
609 if (!front1 && !front2) return 1;
610 if (front1 && front2) return 0;
615 /* Find t in line equation */
616 t = ( -d - nx*s->x - ny*s->y - nz*s->z) / ( nx*w.x + ny*w.y + nz*w.z);
618 p.x = s->x + w.x * t;
619 p.y = s->y + w.y * t;
620 p.z = s->z + w.z * t;
622 /* Move clipped point to the intersection */
636 init_flow(ModeInfo * mi)
642 if ((flows = (flowstruct *) calloc(MI_NUM_SCREENS(mi),
643 sizeof (flowstruct))) == NULL)
646 sp = &flows[MI_SCREEN(mi)];
650 sp->taillen = MI_SIZE(mi);
651 if (sp->taillen < -MINTRAIL) {
652 /* Change by sqrt so it seems more variable */
653 sp->taillen = NRAND((int)sqrt((double) (-sp->taillen - MINTRAIL + 1)));
654 sp->taillen = sp->taillen * sp->taillen + MINTRAIL;
655 } else if (sp->taillen < MINTRAIL) {
656 sp->taillen = MINTRAIL;
659 if(!rotatep && !ridep) rotatep = True; /* We need at least one viewpoint */
661 /* Start camera at Orbit or Bee */
667 sp->chasetime = 1; /* Go directly to target */
671 sp->step2 = INITIALSTEP;
673 /* Zero parameter set */
674 memset(sp->par2, 0, N_PARS * sizeof(dvector));
676 /* Set up standard examples */
677 switch (NRAND((periodicp) ? 5 : 3)) {
685 sp->par2[Y].x = 10 + balance_rand(5*0); /* a */
686 sp->par2[X].x = - sp->par2[Y].x; /* -a */
687 sp->par2[X].y = 28 + balance_rand(5*0); /* b */
691 sp->par2[Z].z = - 2 + balance_rand(1*0); /* -c */
701 sp->par2[Z].x = -2 + balance_rand(1); /* a */
703 sp->par2[Y].y = 0.2 + balance_rand(0.1); /* b */
704 sp->par2[C].z = 0.2 + balance_rand(0.1); /* c */
706 sp->par2[Z].z = -5.7;
712 z' = 0.2 + z(x - 5.7)
714 name = "RosslerCone";
716 sp->par2[Z].x = -2; /* a */
718 sp->par2[Y].y = 0.2; /* b */
719 sp->par2[ZZ].y = -0.331 + balance_rand(0.01); /* c */
722 sp->par2[Z].z = -5.7;
728 z' = 0.7x + az(0.1 - x^2)
732 sp->par2[SINY].x = 0.35 + balance_rand(0.25); /* b */
733 sp->par2[C].y = 1.57; /* c */
735 sp->par2[Z].z = 1 + balance_rand(0.5); /* a/10 */
736 sp->par2[XXZ].z = -10 * sp->par2[Z].z; /* -a */
737 sp->yperiod = 2 * M_PI;
741 x' = -ax - z/2 - z^3/8 + b sin(y)
746 sp->par2[X].x = -0.2 + balance_rand(0.1); /* a */
747 sp->par2[Z].x = -0.5;
748 sp->par2[ZZZ].x = -0.125;
749 sp->par2[SINY].x = 27.0 + balance_rand(3.0); /* b */
750 sp->par2[C].y = 1.33; /* c */
752 sp->yperiod = 2 * M_PI;
760 if(sp->yperiod > 0) {
762 /* periodic flows show either uniform distribution or a
763 snapshot on the 'time' axis */
764 sp->range.y = NRAND(2)? sp->yperiod : 0;
770 /* Run discoverer to set up bounding box, etc. Lyapunov will
771 probably be innaccurate, since we're only running it once, but
772 we're using known strange attractors so it should be ok. */
774 if(MI_IS_VERBOSE(mi))
776 "flow: Lyapunov exponent: %g, step: %g, size: %g (%s)\n",
777 sp->lyap2, sp->step2, sp->size2, name);
778 /* Install new params */
779 sp->lyap = sp->lyap2;
780 sp->size = sp->size2;
782 sp->step = sp->step2;
783 memcpy(sp->par, sp->par2, sizeof(sp->par2));
785 sp->count2 = 0; /* Reset search */
788 sp->beecount = MI_COUNT(mi);
789 if (sp->beecount < 0) { /* random variations */
790 sp->beecount = NRAND(-sp->beecount) + 1; /* Minimum 1 */
793 if(dbufp) { /* Set up double buffer */
794 if (sp->buffer != None)
795 XFreePixmap(MI_DISPLAY(mi), sp->buffer);
796 sp->buffer = XCreatePixmap(MI_DISPLAY(mi), MI_WINDOW(mi),
797 MI_WIDTH(mi), MI_HEIGHT(mi), MI_DEPTH(mi));
799 sp->buffer = MI_WINDOW(mi);
801 /* no "NoExpose" events from XCopyArea wanted */
802 XSetGraphicsExposures(MI_DISPLAY(mi), MI_GC(mi), False);
804 /* Make sure we're using 'thin' lines */
805 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 0, LineSolid, CapNotLast,
808 /* Clear the background (may be slow depending on user prefs). */
811 /* Allocate memory. */
812 if (sp->csegs == NULL) {
813 allocate(sp->csegs, XSegment,
814 (sp->beecount + BOX_L) * MI_NPIXELS(mi) * sp->taillen);
815 allocate(sp->cnsegs, int, MI_NPIXELS(mi));
816 allocate(sp->old_segs, XSegment, sp->beecount * sp->taillen);
817 allocate(sp->p, dvector, sp->beecount * sp->taillen);
820 /* Initialize point positions, velocities, etc. */
823 /* Set up camera tail */
824 X(1, 0) = sp->cam[1].x = 0;
825 Y(1, 0) = sp->cam[1].y = 0;
826 Z(1, 0) = sp->cam[1].z = 0;
830 draw_flow(ModeInfo * mi)
834 double M[3][3]; /* transformation matrix */
835 flowstruct *sp = NULL;
841 sp = &flows[MI_SCREEN(mi)];
842 if (sp->csegs == NULL)
845 /* multiplier for indexing segment arrays. Used in IX macro, etc. */
846 segindex = (sp->beecount + BOX_L) * sp->taillen;
849 if(sp->count2 == 0) { /* start new search */
850 sp->step2 = INITIALSTEP;
851 /* Pick random parameters. Actual range is irrelevant
852 since parameter scale determines flow speed but not
854 for(i=0; i< N_PARS; i++) {
855 sp->par2[i].x = Gauss_Rand(1.0);
856 sp->par2[i].y = Gauss_Rand(1.0);
857 sp->par2[i].z = Gauss_Rand(1.0);
860 if(!discover(mi)) { /* Flow exploded, reset. */
864 sp->count2 = 0; /* Attractor found, but it's not strange */
865 }else if(sp->count2 > 1000000) { /* This one will do */
866 sp->count2 = 0; /* Reset search */
867 if(MI_IS_VERBOSE(mi))
869 "flow: Lyapunov exponent: %g, step: %g, size: %g (unnamed)\n",
870 sp->lyap2, sp->step2, sp->size2);
871 /* Install new params */
872 sp->lyap = sp->lyap2;
873 sp->size = sp->size2;
875 sp->step = sp->step2;
876 memcpy(sp->par, sp->par2, sizeof(sp->par2));
878 /* If we're allowed to zoom out, do so now, so that we
879 get a look at the new attractor. */
880 if(sp->chaseto == BEE && rotatep) {
884 /* Reset initial conditions, so we don't get
885 misleading artifacts in the particle density. */
891 /* Reset segment buffers */
892 for (col = 0; col < MI_NPIXELS(mi); col++)
895 MI_IS_DRAWN(mi) = True;
897 /* Calculate circling POV [Chalky]*/
898 sp->circle[1] = sp->circle[0];
899 sp->circle[0].x = sp->size * 2 * sin(sp->count / 100.0) *
900 (-0.6 + 0.4 *cos(sp->count / 500.0)) + sp->mid.x;
901 sp->circle[0].y = sp->size * 2 * cos(sp->count / 100.0) *
902 (0.6 + 0.4 *cos(sp->count / 500.0)) + sp->mid.y;
903 sp->circle[0].z = sp->size * 2 * sin(sp->count / 421.0) + sp->mid.z;
905 /* Timed chase instead of Chalkie's Bistable oscillator [TDA] */
906 if(rotatep && ridep) {
907 if(sp->chaseto == BEE && NRAND(1000) == 0){
910 }else if(NRAND(4000) == 0){
916 /* Set up orientation matrix */
918 double x[3], p[3], x2=0, xp=0;
921 /* Chasetime is here to guarantee the camera makes it all the
922 way to the target in a finite number of steps. */
923 if(sp->chasetime > 1)
926 if(sp->chaseto == BEE){
927 /* Camera Head targets bee 0 */
928 sp->cam[0].x += (X(0, 0) - sp->cam[0].x)/sp->chasetime;
929 sp->cam[0].y += (Y(0, 0) - sp->cam[0].y)/sp->chasetime;
930 sp->cam[0].z += (Z(0, 0) - sp->cam[0].z)/sp->chasetime;
932 /* Camera Tail targets previous position of bee 0 */
933 sp->cam[1].x += (X(1, 0) - sp->cam[1].x)/sp->chasetime;
934 sp->cam[1].y += (Y(1, 0) - sp->cam[1].y)/sp->chasetime;
935 sp->cam[1].z += (Z(1, 0) - sp->cam[1].z)/sp->chasetime;
937 /* Camera Wing targets bee 1 */
938 sp->cam[2].x += (X(0, 1) - sp->cam[2].x)/sp->chasetime;
939 sp->cam[2].y += (Y(0, 1) - sp->cam[2].y)/sp->chasetime;
940 sp->cam[2].z += (Z(0, 1) - sp->cam[2].z)/sp->chasetime;
942 /* Camera Head targets Orbiter */
943 sp->cam[0].x += (sp->circle[0].x - sp->cam[0].x)/sp->chasetime;
944 sp->cam[0].y += (sp->circle[0].y - sp->cam[0].y)/sp->chasetime;
945 sp->cam[0].z += (sp->circle[0].z - sp->cam[0].z)/sp->chasetime;
947 /* Camera Tail targets diametrically opposite the middle
948 of the bounding box from the Orbiter */
950 (2*sp->circle[0].x - sp->mid.x - sp->cam[1].x)/sp->chasetime;
952 (2*sp->circle[0].y - sp->mid.y - sp->cam[1].y)/sp->chasetime;
954 (2*sp->circle[0].z - sp->mid.z - sp->cam[1].z)/sp->chasetime;
955 /* Camera Wing targets previous position of Orbiter */
956 sp->cam[2].x += (sp->circle[1].x - sp->cam[2].x)/sp->chasetime;
957 sp->cam[2].y += (sp->circle[1].y - sp->cam[2].y)/sp->chasetime;
958 sp->cam[2].z += (sp->circle[1].z - sp->cam[2].z)/sp->chasetime;
961 /* Viewpoint from Tail of camera */
962 sp->centre.x=sp->cam[1].x;
963 sp->centre.y=sp->cam[1].y;
964 sp->centre.z=sp->cam[1].z;
967 x[0] = sp->cam[0].x - sp->cam[1].x;
968 x[1] = sp->cam[0].y - sp->cam[1].y;
969 x[2] = sp->cam[0].z - sp->cam[1].z;
972 p[0] = sp->cam[2].x - sp->cam[1].x;
973 p[1] = sp->cam[2].y - sp->cam[1].y;
974 p[2] = sp->cam[2].z - sp->cam[1].z;
977 /* So long as X and P don't collide, these can be used to form
978 three mutually othogonal axes: X, (X x P) x X and X x P.
979 After being normalised to unit length, these form the
980 Orientation Matrix. */
983 x2+= x[i]*x[i]; /* X . X */
984 xp+= x[i]*p[i]; /* X . P */
985 M[0][i] = x[i]; /* X */
988 for(i=0; i<3; i++) /* (X x P) x X */
989 M[1][i] = x2*p[i] - xp*x[i]; /* == (X . X) P - (X . P) X */
991 M[2][0] = x[1]*p[2] - x[2]*p[1]; /* X x P */
992 M[2][1] = -x[0]*p[2] + x[2]*p[0];
993 M[2][2] = x[0]*p[1] - x[1]*p[0];
998 for(i=0; i<3; i++) A+=M[j][i]*M[j][i]; /* sum squares */
1001 for(i=0; i<3; i++) M[j][i]/=A;
1004 if(sp->chaseto == BEE) {
1005 X(0, 1)=X(0, 0)+M[1][0]*sp->step; /* adjust neighbour */
1006 Y(0, 1)=Y(0, 0)+M[1][1]*sp->step;
1007 Z(0, 1)=Z(0, 0)+M[1][2]*sp->step;
1011 /* <=- Bounding Box -=> */
1013 for (b = 0; b < BOX_L; b++) {
1015 /* Chalky's clipping code, Only used for the box */
1016 /* clipping trails is slow and of little benefit. [TDA] */
1017 int p1 = lines[b][0];
1018 int p2 = lines[b][1];
1020 double x1=box[p1][0]* sp->size/2 + sp->mid.x - sp->centre.x;
1021 double y1=box[p1][1]* sp->size/2 + sp->mid.y - sp->centre.y;
1022 double z1=box[p1][2]* sp->size/2 + sp->mid.z - sp->centre.z;
1023 double x2=box[p2][0]* sp->size/2 + sp->mid.x - sp->centre.x;
1024 double y2=box[p2][1]* sp->size/2 + sp->mid.y - sp->centre.y;
1025 double z2=box[p2][2]* sp->size/2 + sp->mid.z - sp->centre.z;
1027 A1.x=M[0][0]*x1 + M[0][1]*y1 + M[0][2]*z1;
1028 A1.y=M[1][0]*x1 + M[1][1]*y1 + M[1][2]*z1;
1029 A1.z=M[2][0]*x1 + M[2][1]*y1 + M[2][2]*z1 + EYEHEIGHT * sp->size;
1030 A2.x=M[0][0]*x2 + M[0][1]*y2 + M[0][2]*z2;
1031 A2.y=M[1][0]*x2 + M[1][1]*y2 + M[1][2]*z2;
1032 A2.z=M[2][0]*x2 + M[2][1]*y2 + M[2][2]*z2 + EYEHEIGHT * sp->size;
1034 /* Clip in 3D before projecting down to 2D. A 2D clip
1035 after projection wouldn't be able to handle lines that
1037 if (clip(1, 0, 0,-1, &A1, &A2) || /* Screen */
1038 clip(1, 2, 0, 0, &A1, &A2) || /* Left */
1039 clip(1,-2, 0, 0, &A1, &A2) || /* Right */
1040 clip(1,0, 2.0*MI_WIDTH(mi)/MI_HEIGHT(mi), 0, &A1, &A2)||/*UP*/
1041 clip(1,0,-2.0*MI_WIDTH(mi)/MI_HEIGHT(mi), 0, &A1, &A2))/*Down*/
1044 /* Colour according to bee */
1045 col = b % (MI_NPIXELS(mi) - 1);
1047 sp->csegs[IX(col)].x1 = MI_WIDTH(mi)/2 + MI_WIDTH(mi) * A1.y/A1.x;
1048 sp->csegs[IX(col)].y1 = MI_HEIGHT(mi)/2 + MI_WIDTH(mi) * A1.z/A1.x;
1049 sp->csegs[IX(col)].x2 = MI_WIDTH(mi)/2 + MI_WIDTH(mi) * A2.y/A2.x;
1050 sp->csegs[IX(col)].y2 = MI_HEIGHT(mi)/2 + MI_WIDTH(mi) * A2.z/A2.x;
1056 for (b = 0; b < sp->beecount; b++) {
1057 if(fabs(X(0, b)) > LOST_IN_SPACE ||
1058 fabs(Y(0, b)) > LOST_IN_SPACE ||
1059 fabs(Z(0, b)) > LOST_IN_SPACE){
1060 if(sp->chaseto == BEE && b == 0){
1061 /* Lost camera bee. Need to replace it since
1062 rerunning init_flow could lose us a hard-won new
1063 attractor. Try moving it very close to a random
1064 other bee. This way we have a good chance of being
1065 close to the attractor and not forming a false
1066 artifact. If we've lost many bees this may need to
1068 /* Don't worry about camera wingbee. It stays close
1069 to the main camera bee no matter what happens. */
1070 int newb = 1 + NRAND(sp->beecount - 1);
1071 X(0, 0) = X(0, newb) + 0.001;
1072 Y(0, 0) = Y(0, newb);
1073 Z(0, 0) = Z(0, newb);
1074 if(MI_IS_VERBOSE(mi))
1076 "flow: resetting lost camera near bee %d\n",
1082 /* Age the tail. It's critical this be fast since
1083 beecount*taillen can be large. */
1084 memmove(B(1, b), B(0, b), (sp->taillen - 1) * sizeof(dvector));
1086 Iterate(B(0,b), sp->ODE, sp->par, sp->step);
1088 /* Don't show wingbee since he's not quite in the flow. */
1089 if(sp->chaseto == BEE && b == 1) continue;
1091 /* Colour according to bee */
1092 col = b % (MI_NPIXELS(mi) - 1);
1094 /* Fill the segment lists. */
1096 begin = 0; /* begin new trail */
1097 end = MIN(sp->taillen, sp->count); /* short trails at first */
1098 for(i=0; i < end; i++){
1099 double x = X(i,b)-sp->centre.x;
1100 double y = Y(i,b)*(sp->yperiod < 0? (sp->size/sp->yperiod) :1)
1102 double z = Z(i,b)-sp->centre.z;
1103 double XM=M[0][0]*x + M[0][1]*y + M[0][2]*z;
1104 double YM=M[1][0]*x + M[1][1]*y + M[1][2]*z;
1105 double ZM=M[2][0]*x + M[2][1]*y + M[2][2]*z + EYEHEIGHT * sp->size;
1108 swarm++; /* count the remaining bees */
1109 if(sp->yperiod > 0 && Y(i,b) > sp->yperiod){
1111 Y(i,b) -= sp->yperiod;
1112 /* hide tail to prevent streaks in Y. Streaks in X,Z
1113 are ok, they help to outline the Poincare'
1115 for(j = i; j < end; j++) Y(j,b) = Y(i,b);
1120 if(XM <= 0){ /* off screen - new trail */
1124 absx = MI_WIDTH(mi)/2 + MI_WIDTH(mi) * YM/XM;
1125 absy = MI_HEIGHT(mi)/2 + MI_WIDTH(mi) * ZM/XM;
1126 /* Performance bottleneck */
1127 if(absx <= 0 || absx >= MI_WIDTH(mi) ||
1128 absy <= 0 || absy >= MI_HEIGHT(mi)) {
1129 /* off screen - new trail */
1133 if(i > begin) { /* complete previous segment */
1134 sp->csegs[IX(col)].x2 = absx;
1135 sp->csegs[IX(col)].y2 = absy;
1139 if(i < end -1){ /* start new segment */
1140 sp->csegs[IX(col)].x1 = absx;
1141 sp->csegs[IX(col)].y1 = absy;
1147 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
1148 if (dbufp) { /* In Double Buffer case, prepare off-screen copy */
1149 /* For slow systems, this can be the single biggest bottleneck
1150 in the program. These systems may be better of not using
1151 the double buffer. */
1152 XFillRectangle(MI_DISPLAY(mi), sp->buffer, MI_GC(mi), 0, 0,
1153 MI_WIDTH(mi), MI_HEIGHT(mi));
1154 } else { /* Otherwise, erase previous segment list directly */
1155 XDrawSegments(MI_DISPLAY(mi), sp->buffer, MI_GC(mi),
1156 sp->old_segs, sp->nold_segs);
1160 if (MI_NPIXELS(mi) > 2){ /* colour */
1162 for (col = 0; col < MI_NPIXELS(mi) - 1; col++)
1163 if (sp->cnsegs[col] > 0) {
1164 if(sp->cnsegs[col] > mn) mn = sp->cnsegs[col];
1165 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_PIXEL(mi, col+1));
1166 /* This is usually the biggest bottleneck on most
1167 systems. The maths load is insignificant compared
1169 XDrawSegments(MI_DISPLAY(mi), sp->buffer, MI_GC(mi),
1170 sp->csegs + col * segindex, sp->cnsegs[col]);
1172 } else { /* mono handled seperately since xlockmore uses '1' for
1174 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
1175 XDrawSegments(MI_DISPLAY(mi), sp->buffer, MI_GC(mi),
1176 sp->csegs, sp->cnsegs[0]);
1178 if (dbufp) { /* In Double Buffer case, this updates the screen */
1179 XCopyArea(MI_DISPLAY(mi), sp->buffer, MI_WINDOW(mi), MI_GC(mi), 0, 0,
1180 MI_WIDTH(mi), MI_HEIGHT(mi), 0, 0);
1181 } else { /* Otherwise, screen is already updated. Copy segments
1182 to erase-list to be erased directly next time. */
1184 for (col = 0; col < MI_NPIXELS(mi) - 1; col++) {
1185 memcpy(sp->old_segs + c, sp->csegs + col * segindex,
1186 sp->cnsegs[col] * sizeof(XSegment));
1187 c += sp->cnsegs[col];
1192 if(sp->count > 1 && swarm == 0) { /* all gone */
1193 if(MI_IS_VERBOSE(mi))
1194 fprintf(stdout, "flow: all gone at %d\n", sp->count);
1198 if(sp->count++ > MI_CYCLES(mi)){ /* Time's up. If we haven't
1199 found anything new by now we
1200 should pick a new standard
1207 release_flow(ModeInfo * mi)
1209 if (flows != NULL) {
1212 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
1213 free_flow(&flows[screen]);
1215 flows = (flowstruct *) NULL;
1220 refresh_flow(ModeInfo * mi)
1222 if(!dbufp) MI_CLEARWINDOW(mi);
1225 #endif /* MODE_flow */