1 /* xscreensaver, Copyright (c) 1992, 1995, 1996, 1997, 1998, 2001, 2006
2 * Jamie Zawinski <jwz@jwz.org>
4 * Permission to use, copy, modify, distribute, and sell this software and its
5 * documentation for any purpose is hereby granted without fee, provided that
6 * the above copyright notice appear in all copies and that both that
7 * copyright notice and this permission notice appear in supporting
8 * documentation. No representations are made about the suitability of this
9 * software for any purpose. It is provided "as is" without express or
13 /* Simulation of a pair of quasi-gravitational fields, maybe sorta kinda
14 a little like the strong and weak electromagnetic forces. Derived from
15 a Lispm screensaver by John Pezaris <pz@mit.edu>. Mouse control and
16 viscosity added by "Philip Edward Cutone, III" <pc2d+@andrew.cmu.edu>.
20 The simulation started out as a purely accurate gravitational
21 simulation, but, with constant simulation step size, I quickly
22 realized the field being simulated while grossly gravitational
23 was, in fact, non-conservative. It also had the rather annoying
24 behavior of dealing very badly with colliding orbs. Therefore,
25 I implemented a negative-gravity region (with two thresholds; as
26 I read your code, you only implemented one) to prevent orbs from
27 every coming too close together, and added a viscosity factor if
28 the speed of any orb got too fast. This provides a nice stable
29 system with interesting behavior.
31 I had experimented with a number of fields including the van der
32 Waals force (very interesting orbiting behavior) and 1/r^3
33 gravity (not as interesting as 1/r^2). An even normal viscosity
34 (rather than the thresholded version to bleed excess energy) is
35 also not interesting. The 1/r^2, -1/r^2, -10/r^2 thresholds
36 proved not only robust but also interesting -- the orbs never
37 collided and the threshold viscosity fixed the
38 non-conservational problem.
41 > An even normal viscosity (rather than the thresholded version to
42 > bleed excess energy) is also not interesting.
44 unless you make about 200 points.... set the viscosity to about .8
45 and drag the mouse through it. it makes a nice wave which travels
48 And (always the troublemaker) Joe Keane <jgk@jgk.org> sez:
50 Despite what John sez, the field being simulated is always
51 conservative. The real problem is that it uses a simple hack,
52 computing acceleration *based only on the starting position*,
53 instead of a real differential equation solver. Thus you'll
54 always have energy coming out of nowhere, although it's most
55 blatant when balls get close together. If it were done right,
56 you wouldn't need viscosity or artificial limits on how close
59 Matt <straitm@carleton.edu> sez:
61 Added a switch to remove the walls.
63 Added a switch to make the threshold viscosity optional. If
64 nomaxspeed is specified, then balls going really fast do not
65 recieve special treatment.
67 I've made tail mode prettier by eliminating the first erase line
68 that drew from the upper left corner to the starting position of
71 Made the balls in modes other than "balls" bounce exactly at the
72 walls. (Because the graphics for different modes are drawn
73 differently with respect to the "actual" position of the point,
74 they used to be able to run somewhat past the walls, or bounce
77 Added an option to output each ball's speed in the form of a bar
78 graph drawn on the same window as the balls. If only x or y is
79 selected, they will be represented on the appropriate axis down
80 the center of the window. If both are selected, they will both
81 be displayed along the diagonal such that the x and y bars for
82 each point start at the same place. If speed is selected, the
83 speed will be displayed down the left side. */
87 #include "screenhack.h"
90 /* The normal (and max) width for a graph bar */
93 #define min(a,b) ((a)<(b)?(a):(b))
94 #define max(a,b) ((a)>(b)?(a):(b))
98 ball_mode, line_mode, polygon_mode, spline_mode, spline_filled_mode,
103 graph_none, graph_x, graph_y, graph_both, graph_speed
132 int point_stack_size, point_stack_fp;
138 Bool no_erase_yet; /* for tail mode fix */
140 /*flip mods for mouse interaction*/
142 int mouse_x, mouse_y, mouse_mass, root_x, root_y;
145 enum object_mode mode;
146 enum graph_mode graph_mode;
148 GC draw_gc, erase_gc;
157 attraction_init (Display *dpy, Window window)
159 struct state *st = (struct state *) calloc (1, sizeof(*st));
161 XWindowAttributes xgwa;
163 int midx, midy, r, vx, vy;
166 char *mode_str, *graph_mode_str;
168 XGetWindowAttributes (dpy, window, &xgwa);
169 st->xlim = xgwa.width;
170 st->ylim = xgwa.height;
171 cmap = xgwa.colormap;
174 st->walls_p = get_boolean_resource (dpy, "walls", "Boolean");
176 /* if there aren't walls, don't set a limit on the radius */
177 r = get_integer_resource (dpy, "radius", "Integer");
178 if (r <= 0 || (r > min (st->xlim/2, st->ylim/2) && st->walls_p) )
179 r = min (st->xlim/2, st->ylim/2) - 50;
181 vx = get_integer_resource (dpy, "vx", "Integer");
182 vy = get_integer_resource (dpy, "vy", "Integer");
184 st->npoints = get_integer_resource (dpy, "points", "Integer");
186 st->npoints = 3 + (random () % 5);
187 st->balls = (struct ball *) malloc (st->npoints * sizeof (struct ball));
189 st->no_erase_yet = 1; /* for tail mode fix */
191 st->segments = get_integer_resource (dpy, "segments", "Integer");
192 if (st->segments < 0) st->segments = 1;
194 st->threshold = get_integer_resource (dpy, "threshold", "Integer");
195 if (st->threshold < 0) st->threshold = 0;
197 st->delay = get_integer_resource (dpy, "delay", "Integer");
198 if (st->delay < 0) st->delay = 0;
200 st->global_size = get_integer_resource (dpy, "size", "Integer");
201 if (st->global_size < 0) st->global_size = 0;
203 st->glow_p = get_boolean_resource (dpy, "glow", "Boolean");
205 st->orbit_p = get_boolean_resource (dpy, "orbit", "Boolean");
207 st->maxspeed_p = get_boolean_resource (dpy, "maxspeed", "Boolean");
209 st->cbounce_p = get_boolean_resource (dpy, "cbounce", "Boolean");
211 st->color_shift = get_integer_resource (dpy, "colorShift", "Integer");
212 if (st->color_shift <= 0) st->color_shift = 5;
214 /*flip mods for mouse interaction*/
215 st->mouse_p = get_boolean_resource (dpy, "mouse", "Boolean");
216 st->mouse_mass = get_integer_resource (dpy, "mouseSize", "Integer");
217 st->mouse_mass = st->mouse_mass * st->mouse_mass *10;
219 st->viscosity = get_float_resource (dpy, "viscosity", "Float");
221 mode_str = get_string_resource (dpy, "mode", "Mode");
222 if (! mode_str) st->mode = ball_mode;
223 else if (!strcmp (mode_str, "balls")) st->mode = ball_mode;
224 else if (!strcmp (mode_str, "lines")) st->mode = line_mode;
225 else if (!strcmp (mode_str, "polygons")) st->mode = polygon_mode;
226 else if (!strcmp (mode_str, "tails")) st->mode = tail_mode;
227 else if (!strcmp (mode_str, "splines")) st->mode = spline_mode;
228 else if (!strcmp (mode_str, "filled-splines"))st->mode = spline_filled_mode;
231 "%s: mode must be balls, lines, tails, polygons, splines, or\n\
232 filled-splines, not \"%s\"\n",
237 graph_mode_str = get_string_resource (dpy, "graphmode", "Mode");
238 if (! graph_mode_str) st->graph_mode = graph_none;
239 else if (!strcmp (graph_mode_str, "x")) st->graph_mode = graph_x;
240 else if (!strcmp (graph_mode_str, "y")) st->graph_mode = graph_y;
241 else if (!strcmp (graph_mode_str, "both")) st->graph_mode = graph_both;
242 else if (!strcmp (graph_mode_str, "speed")) st->graph_mode = graph_speed;
243 else if (!strcmp (graph_mode_str, "none")) st->graph_mode = graph_none;
246 "%s: graphmode must be speed, x, y, both, or none, not \"%s\"\n",
247 progname, graph_mode_str);
251 /* only allocate memory if it is needed */
252 if(st->graph_mode != graph_none)
254 if(st->graph_mode == graph_x || st->graph_mode == graph_both)
255 st->x_vels = (double *) malloc (st->npoints * sizeof (double));
256 if(st->graph_mode == graph_y || st->graph_mode == graph_both)
257 st->y_vels = (double *) malloc (st->npoints * sizeof (double));
258 if(st->graph_mode == graph_speed)
259 st->speeds = (double *) malloc (st->npoints * sizeof (double));
262 if (st->mode != ball_mode && st->mode != tail_mode) st->glow_p = False;
264 if (st->mode == polygon_mode && st->npoints < 3)
265 st->mode = line_mode;
267 st->ncolors = get_integer_resource (dpy, "colors", "Colors");
268 if (st->ncolors < 2) st->ncolors = 2;
269 if (st->ncolors <= 2) mono_p = True;
280 int H = random() % 360;
283 double V = frand(0.25) + 0.75;
284 st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1));
285 make_color_ramp (dpy, cmap, H, S1, V, H, S2, V, st->colors, &st->ncolors,
290 st->ncolors = st->npoints;
291 st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1));
292 make_random_colormap (dpy, xgwa.visual, cmap, st->colors, &st->ncolors,
293 True, True, False, True);
299 case spline_filled_mode:
301 st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1));
302 make_smooth_colormap (dpy, xgwa.visual, cmap, st->colors, &st->ncolors,
310 if (!mono_p && st->ncolors <= 2)
312 if (st->colors) free (st->colors);
317 if (st->mode != ball_mode)
319 int size = (st->segments ? st->segments : 1);
320 st->point_stack_size = size * (st->npoints + 1);
321 st->point_stack = (XPoint *) calloc (st->point_stack_size, sizeof (XPoint));
322 st->point_stack_fp = 0;
325 gcv.line_width = (st->mode == tail_mode
326 ? (st->global_size ? st->global_size : (MAX_SIZE * 2 / 3))
328 gcv.cap_style = (st->mode == tail_mode ? CapRound : CapButt);
331 gcv.foreground = get_pixel_resource(dpy, cmap, "foreground", "Foreground");
333 gcv.foreground = st->colors[st->fg_index].pixel;
334 st->draw_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle, &gcv);
336 gcv.foreground = get_pixel_resource(dpy, cmap, "background", "Background");
337 st->erase_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle,&gcv);
341 jwxyz_XSetAntiAliasing (dpy, st->draw_gc, False);
342 jwxyz_XSetAntiAliasing (dpy, st->erase_gc, False);
345 /* let's make the balls bigger by default */
346 #define rand_size() (3 * (8 + (random () % 7)))
348 if (st->orbit_p && !st->global_size)
349 /* To orbit, all objects must be the same mass, or the math gets
351 st->global_size = rand_size ();
354 th = frand (M_PI+M_PI);
355 for (i = 0; i < st->npoints; i++)
357 int new_size = (st->global_size ? st->global_size : rand_size ());
358 st->balls [i].dx = 0;
359 st->balls [i].dy = 0;
360 st->balls [i].size = new_size;
361 st->balls [i].mass = (new_size * new_size * 10);
362 st->balls [i].x = midx + r * cos (i * ((M_PI+M_PI) / st->npoints) + th);
363 st->balls [i].y = midy + r * sin (i * ((M_PI+M_PI) / st->npoints) + th);
366 st->balls [i].vx = vx ? vx : ((6.0 - (random () % 11)) / 8.0);
367 st->balls [i].vy = vy ? vy : ((6.0 - (random () % 11)) / 8.0);
369 if (mono_p || st->mode != ball_mode)
370 st->balls [i].pixel_index = -1;
372 st->balls [i].pixel_index = 0;
374 st->balls [i].pixel_index = random() % st->ncolors;
377 /* This lets modes where the points don't really have any size use the whole
378 window. Otherwise, since the points still have a positive size
379 assigned to them, they will be bounced somewhat early. Mass and size are
380 seperate, so this shouldn't cause problems. It's a bit kludgy, tho.
382 if(st->mode == line_mode || st->mode == spline_mode ||
383 st->mode == spline_filled_mode || st->mode == polygon_mode)
385 for(i = 1; i < st->npoints; i++)
387 st->balls[i].size = 0;
395 double v_mult = get_float_resource (dpy, "vMult", "Float");
396 if (v_mult == 0.0) v_mult = 1.0;
398 for (i = 1; i < st->npoints; i++)
400 double _2ipi_n = (2 * i * M_PI / st->npoints);
401 double x = r * cos (_2ipi_n);
402 double y = r * sin (_2ipi_n);
403 double distx = r - x;
404 double dist2 = (distx * distx) + (y * y);
405 double dist = sqrt (dist2);
406 double a1 = ((st->balls[i].mass / dist2) *
407 ((dist < st->threshold) ? -1.0 : 1.0) *
413 /* "domain error: forces on balls too great" */
414 fprintf (stderr, "%s: window too small for these orbit settings.\n",
419 v = sqrt (a * r) * v_mult;
420 for (i = 0; i < st->npoints; i++)
422 double k = ((2 * i * M_PI / st->npoints) + th);
423 st->balls [i].vx = -v * sin (k);
424 st->balls [i].vy = v * cos (k);
428 if (mono_p) st->glow_p = False;
430 XClearWindow (dpy, window);
435 compute_force (struct state *st, int i, double *dx_ret, double *dy_ret)
438 double x_dist, y_dist, dist, dist2;
441 for (j = 0; j < st->npoints; j++)
443 if (i == j) continue;
444 x_dist = st->balls [j].x - st->balls [i].x;
445 y_dist = st->balls [j].y - st->balls [i].y;
446 dist2 = (x_dist * x_dist) + (y_dist * y_dist);
449 if (dist > 0.1) /* the balls are not overlapping */
451 double new_acc = ((st->balls[j].mass / dist2) *
452 ((dist < st->threshold) ? -1.0 : 1.0));
453 double new_acc_dist = new_acc / dist;
454 *dx_ret += new_acc_dist * x_dist;
455 *dy_ret += new_acc_dist * y_dist;
458 { /* the balls are overlapping; move randomly */
459 *dx_ret += (frand (10.0) - 5.0);
460 *dy_ret += (frand (10.0) - 5.0);
466 x_dist = st->mouse_x - st->balls [i].x;
467 y_dist = st->mouse_y - st->balls [i].y;
468 dist2 = (x_dist * x_dist) + (y_dist * y_dist);
471 if (dist > 0.1) /* the balls are not overlapping */
473 double new_acc = ((st->mouse_mass / dist2) *
474 ((dist < st->threshold) ? -1.0 : 1.0));
475 double new_acc_dist = new_acc / dist;
476 *dx_ret += new_acc_dist * x_dist;
477 *dy_ret += new_acc_dist * y_dist;
480 { /* the balls are overlapping; move randomly */
481 *dx_ret += (frand (10.0) - 5.0);
482 *dy_ret += (frand (10.0) - 5.0);
488 /* Draws meters along the diagonal for the x velocity */
490 draw_meter_x(Display *dpy, Window window, struct state *st, int i, int alone)
492 XWindowAttributes xgwa;
494 XGetWindowAttributes (dpy, window, &xgwa);
496 /* set the width of the bars to use */
497 if(xgwa.height < BAR_SIZE*st->npoints)
499 y = i*(xgwa.height/st->npoints);
500 h = (xgwa.height/st->npoints) - 2;
524 w1 = (int)(20*st->x_vels[i]);
525 w2 = (int)(20*st->balls[i].vx);
526 st->x_vels[i] = st->balls[i].vx;
536 XDrawRectangle(dpy,window,st->erase_gc,x1+(h+2)/2,y,w1,h);
537 XDrawRectangle(dpy,window,st->draw_gc,x2+(h+2)/2,y,w2,h);
540 /* Draws meters along the diagonal for the y velocity.
541 Is there some way to make draw_meter_x and draw_meter_y
542 one function instead of two without making them completely unreadable?
545 draw_meter_y (Display *dpy, Window window, struct state *st, int i, int alone)
547 XWindowAttributes xgwa;
549 XGetWindowAttributes (dpy, window, &xgwa);
551 if(xgwa.height < BAR_SIZE*st->npoints){ /*needs to be height still */
552 x = i*(xgwa.height/st->npoints);
553 w = (xgwa.height/st->npoints) - 2;
576 h1 = (int)(20*st->y_vels[i]);
577 h2 = (int)(20*st->balls[i].vy);
578 st->y_vels[i] = st->balls[i].vy;
588 XDrawRectangle(dpy,window,st->erase_gc,x,y1+(w+2)/2,w,h1);
589 XDrawRectangle(dpy,window,st->draw_gc,x,y2+(w+2)/2,w,h2);
593 /* Draws meters of the total speed of the balls */
595 draw_meter_speed (Display *dpy, Window window, struct state *st, int i)
597 XWindowAttributes xgwa;
599 XGetWindowAttributes (dpy, window, &xgwa);
601 if(xgwa.height < BAR_SIZE*st->npoints)
603 y = i*(xgwa.height/st->npoints);
604 h = (xgwa.height/st->npoints) - 2;
617 w1 = (int)(5*st->speeds[i]);
618 w2 = (int)(5*(st->balls[i].vy*st->balls[i].vy+st->balls[i].vx*st->balls[i].vx));
619 st->speeds[i] = st->balls[i].vy*st->balls[i].vy+st->balls[i].vx*st->balls[i].vx;
621 XDrawRectangle(dpy,window,st->erase_gc,x1,y,w1,h);
622 XDrawRectangle(dpy,window,st->draw_gc, x2,y,w2,h);
626 attraction_draw (Display *dpy, Window window, void *closure)
628 struct state *st = (struct state *) closure;
629 int last_point_stack_fp = st->point_stack_fp;
631 Window root1, child1; /*flip mods for mouse interaction*/
634 int i, radius = st->global_size/2;
638 if(st->global_size == 0)
639 radius = (MAX_SIZE / 3);
641 if(st->graph_mode != graph_none)
643 if(st->graph_mode == graph_both)
645 for(i = 0; i < st->npoints; i++)
647 draw_meter_x(dpy, window, st, i, 0);
648 draw_meter_y(dpy, window, st, i, 0);
651 else if(st->graph_mode == graph_x)
653 for(i = 0; i < st->npoints; i++)
655 draw_meter_x(dpy, window, st, i, 1);
658 else if(st->graph_mode == graph_y)
660 for(i = 0; i < st->npoints; i++)
662 draw_meter_y(dpy, window, st, i, 1);
665 else if(st->graph_mode == graph_speed)
667 for(i = 0; i < st->npoints; i++)
669 draw_meter_speed(dpy, window, st, i);
677 XQueryPointer(dpy, window, &root1, &child1,
678 &st->root_x, &st->root_y, &st->mouse_x, &st->mouse_y, &mask);
681 /* compute the force of attraction/repulsion among all balls */
682 for (i = 0; i < st->npoints; i++)
683 compute_force (st, i, &st->balls[i].dx, &st->balls[i].dy);
685 /* move the balls according to the forces now in effect */
686 for (i = 0; i < st->npoints; i++)
688 double old_x = st->balls[i].x;
689 double old_y = st->balls[i].y;
691 int size = st->balls[i].size;
692 st->balls[i].vx += st->balls[i].dx;
693 st->balls[i].vy += st->balls[i].dy;
695 /* "don't let them get too fast: impose a terminal velocity
696 (actually, make the medium have friction)"
697 Well, what this first step really does is give the medium a
698 viscosity of .9 for balls going over the speed limit. Anyway,
701 if (fabs(st->balls[i].vx) > 10 && st->maxspeed_p)
703 st->balls[i].vx *= 0.9;
706 if (st->viscosity != 1)
708 st->balls[i].vx *= st->viscosity;
711 if (fabs(st->balls[i].vy) > 10 && st->maxspeed_p)
713 st->balls[i].vy *= 0.9;
716 if (st->viscosity != 1)
718 st->balls[i].vy *= st->viscosity;
721 st->balls[i].x += st->balls[i].vx;
722 st->balls[i].y += st->balls[i].vy;
725 /* bounce off the walls if desired
726 note: a ball is actually its upper left corner */
729 if(st->cbounce_p) /* with correct bouncing */
731 /* so long as it's out of range, keep bouncing */
733 while( (st->balls[i].x >= (st->xlim - st->balls[i].size)) ||
734 (st->balls[i].y >= (st->ylim - st->balls[i].size)) ||
735 (st->balls[i].x <= 0) ||
736 (st->balls[i].y <= 0) )
738 if (st->balls[i].x >= (st->xlim - st->balls[i].size))
740 st->balls[i].x = (2*(st->xlim - st->balls[i].size) - st->balls[i].x);
741 st->balls[i].vx = -st->balls[i].vx;
743 if (st->balls[i].y >= (st->ylim - st->balls[i].size))
745 st->balls[i].y = (2*(st->ylim - st->balls[i].size) - st->balls[i].y);
746 st->balls[i].vy = -st->balls[i].vy;
748 if (st->balls[i].x <= 0)
750 st->balls[i].x = -st->balls[i].x;
751 st->balls[i].vx = -st->balls[i].vx;
753 if (st->balls[i].y <= 0)
755 st->balls[i].y = -st->balls[i].y;
756 st->balls[i].vy = -st->balls[i].vy;
760 else /* with old bouncing */
762 if (st->balls[i].x >= (st->xlim - st->balls[i].size))
764 st->balls[i].x = (st->xlim - st->balls[i].size - 1);
765 if (st->balls[i].vx > 0) /* why is this check here? */
766 st->balls[i].vx = -st->balls[i].vx;
768 if (st->balls[i].y >= (st->ylim - st->balls[i].size))
770 st->balls[i].y = (st->ylim - st->balls[i].size - 1);
771 if (st->balls[i].vy > 0)
772 st->balls[i].vy = -st->balls[i].vy;
774 if (st->balls[i].x <= 0)
777 if (st->balls[i].vx < 0)
778 st->balls[i].vx = -st->balls[i].vx;
780 if (st->balls[i].y <= 0)
783 if (st->balls[i].vy < 0)
784 st->balls[i].vy = -st->balls[i].vy;
788 new_x = st->balls[i].x;
789 new_y = st->balls[i].y;
793 if (st->mode == ball_mode)
797 /* make color saturation be related to particle
801 double vx = st->balls [i].dx;
802 double vy = st->balls [i].dy;
803 if (vx < 0) vx = -vx;
804 if (vy < 0) vy = -vy;
806 if (fraction > limit) fraction = limit;
808 s = 1 - (fraction / limit);
809 st->balls[i].pixel_index = (st->ncolors * s);
811 XSetForeground (dpy, st->draw_gc,
812 st->colors[st->balls[i].pixel_index].pixel);
816 if (st->mode == ball_mode)
818 XFillArc (dpy, window, st->erase_gc, (int) old_x, (int) old_y,
819 size, size, 0, 360*64);
820 XFillArc (dpy, window, st->draw_gc, (int) new_x, (int) new_y,
821 size, size, 0, 360*64);
825 st->point_stack [st->point_stack_fp].x = new_x;
826 st->point_stack [st->point_stack_fp].y = new_y;
827 st->point_stack_fp++;
831 /* draw the lines or polygons after computing all points */
832 if (st->mode != ball_mode)
834 st->point_stack [st->point_stack_fp].x = st->balls [0].x; /* close the polygon */
835 st->point_stack [st->point_stack_fp].y = st->balls [0].y;
836 st->point_stack_fp++;
837 if (st->point_stack_fp == st->point_stack_size)
838 st->point_stack_fp = 0;
839 else if (st->point_stack_fp > st->point_stack_size) /* better be aligned */
843 if (st->color_tick++ == st->color_shift)
846 st->fg_index = (st->fg_index + 1) % st->ncolors;
847 XSetForeground (dpy, st->draw_gc, st->colors[st->fg_index].pixel);
857 if (st->segments > 0)
858 XDrawLines (dpy, window, st->erase_gc, st->point_stack + st->point_stack_fp,
859 st->npoints + 1, CoordModeOrigin);
860 XDrawLines (dpy, window, st->draw_gc, st->point_stack + last_point_stack_fp,
861 st->npoints + 1, CoordModeOrigin);
864 if (st->segments > 0)
865 XFillPolygon (dpy, window, st->erase_gc, st->point_stack + st->point_stack_fp,
866 st->npoints + 1, (st->npoints == 3 ? Convex : Complex),
868 XFillPolygon (dpy, window, st->draw_gc, st->point_stack + last_point_stack_fp,
869 st->npoints + 1, (st->npoints == 3 ? Convex : Complex),
874 for (i = 0; i < st->npoints; i++)
876 int index = st->point_stack_fp + i;
877 int next_index = (index + (st->npoints + 1)) % st->point_stack_size;
878 if(st->no_erase_yet == 1)
880 if(st->total_ticks >= st->segments)
882 st->no_erase_yet = 0;
883 XDrawLine (dpy, window, st->erase_gc,
884 st->point_stack [index].x + radius,
885 st->point_stack [index].y + radius,
886 st->point_stack [next_index].x + radius,
887 st->point_stack [next_index].y + radius);
892 XDrawLine (dpy, window, st->erase_gc,
893 st->point_stack [index].x + radius,
894 st->point_stack [index].y + radius,
895 st->point_stack [next_index].x + radius,
896 st->point_stack [next_index].y + radius);
898 index = last_point_stack_fp + i;
899 next_index = (index - (st->npoints + 1)) % st->point_stack_size;
900 if (next_index < 0) next_index += st->point_stack_size;
901 if (st->point_stack [next_index].x == 0 &&
902 st->point_stack [next_index].y == 0)
904 XDrawLine (dpy, window, st->draw_gc,
905 st->point_stack [index].x + radius,
906 st->point_stack [index].y + radius,
907 st->point_stack [next_index].x + radius,
908 st->point_stack [next_index].y + radius);
913 case spline_filled_mode:
915 if (! st->spl) st->spl = make_spline (st->npoints);
916 if (st->segments > 0)
918 for (i = 0; i < st->npoints; i++)
920 st->spl->control_x [i] = st->point_stack [st->point_stack_fp + i].x;
921 st->spl->control_y [i] = st->point_stack [st->point_stack_fp + i].y;
923 compute_closed_spline (st->spl);
924 if (st->mode == spline_filled_mode)
925 XFillPolygon (dpy, window, st->erase_gc, st->spl->points, st->spl->n_points,
926 (st->spl->n_points == 3 ? Convex : Complex),
929 XDrawLines (dpy, window, st->erase_gc, st->spl->points, st->spl->n_points,
932 for (i = 0; i < st->npoints; i++)
934 st->spl->control_x [i] = st->point_stack [last_point_stack_fp + i].x;
935 st->spl->control_y [i] = st->point_stack [last_point_stack_fp + i].y;
937 compute_closed_spline (st->spl);
938 if (st->mode == spline_filled_mode)
939 XFillPolygon (dpy, window, st->draw_gc, st->spl->points, st->spl->n_points,
940 (st->spl->n_points == 3 ? Convex : Complex),
943 XDrawLines (dpy, window, st->draw_gc, st->spl->points, st->spl->n_points,
955 attraction_reshape (Display *dpy, Window window, void *closure,
956 unsigned int w, unsigned int h)
958 struct state *st = (struct state *) closure;
964 attraction_event (Display *dpy, Window window, void *closure, XEvent *event)
970 attraction_free (Display *dpy, Window window, void *closure)
972 struct state *st = (struct state *) closure;
974 if (st->balls) free (st->balls);
975 if (st->x_vels) free (st->x_vels);
976 if (st->y_vels) free (st->y_vels);
977 if (st->speeds) free (st->speeds);
978 if (st->point_stack) free (st->point_stack);
979 if (st->colors) free (st->colors);
980 if (st->spl) free_spline (st->spl);
986 static const char *attraction_defaults [] = {
987 ".background: black",
988 ".foreground: white",
1013 static XrmOptionDescRec attraction_options [] = {
1014 { "-mode", ".mode", XrmoptionSepArg, 0 },
1015 { "-graphmode", ".graphmode", XrmoptionSepArg, 0 },
1016 { "-colors", ".colors", XrmoptionSepArg, 0 },
1017 { "-points", ".points", XrmoptionSepArg, 0 },
1018 { "-color-shift", ".colorShift", XrmoptionSepArg, 0 },
1019 { "-threshold", ".threshold", XrmoptionSepArg, 0 },
1020 { "-segments", ".segments", XrmoptionSepArg, 0 },
1021 { "-delay", ".delay", XrmoptionSepArg, 0 },
1022 { "-size", ".size", XrmoptionSepArg, 0 },
1023 { "-radius", ".radius", XrmoptionSepArg, 0 },
1024 { "-vx", ".vx", XrmoptionSepArg, 0 },
1025 { "-vy", ".vy", XrmoptionSepArg, 0 },
1026 { "-vmult", ".vMult", XrmoptionSepArg, 0 },
1027 { "-mouse-size", ".mouseSize", XrmoptionSepArg, 0 },
1028 { "-viscosity", ".viscosity", XrmoptionSepArg, 0 },
1029 { "-mouse", ".mouse", XrmoptionNoArg, "true" },
1030 { "-nomouse", ".mouse", XrmoptionNoArg, "false" },
1031 { "-glow", ".glow", XrmoptionNoArg, "true" },
1032 { "-noglow", ".glow", XrmoptionNoArg, "false" },
1033 { "-orbit", ".orbit", XrmoptionNoArg, "true" },
1034 { "-nowalls", ".walls", XrmoptionNoArg, "false" },
1035 { "-walls", ".walls", XrmoptionNoArg, "true" },
1036 { "-nomaxspeed", ".maxspeed", XrmoptionNoArg, "false" },
1037 { "-maxspeed", ".maxspeed", XrmoptionNoArg, "true" },
1038 { "-correct-bounce", ".cbounce", XrmoptionNoArg, "false" },
1039 { "-fast-bounce", ".cbounce", XrmoptionNoArg, "true" },
1044 XSCREENSAVER_MODULE ("Attraction", attraction)