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 */
732 /* limit the maximum number to bounce to 4.*/
733 int bounce_allowed = 4;
735 while( bounce_allowed && (
736 (st->balls[i].x >= (st->xlim - st->balls[i].size)) ||
737 (st->balls[i].y >= (st->ylim - st->balls[i].size)) ||
738 (st->balls[i].x <= 0) ||
739 (st->balls[i].y <= 0) )
743 if (st->balls[i].x >= (st->xlim - st->balls[i].size))
745 st->balls[i].x = (2*(st->xlim - st->balls[i].size) - st->balls[i].x);
746 st->balls[i].vx = -st->balls[i].vx;
748 if (st->balls[i].y >= (st->ylim - st->balls[i].size))
750 st->balls[i].y = (2*(st->ylim - st->balls[i].size) - st->balls[i].y);
751 st->balls[i].vy = -st->balls[i].vy;
753 if (st->balls[i].x <= 0)
755 st->balls[i].x = -st->balls[i].x;
756 st->balls[i].vx = -st->balls[i].vx;
758 if (st->balls[i].y <= 0)
760 st->balls[i].y = -st->balls[i].y;
761 st->balls[i].vy = -st->balls[i].vy;
765 else /* with old bouncing */
767 if (st->balls[i].x >= (st->xlim - st->balls[i].size))
769 st->balls[i].x = (st->xlim - st->balls[i].size - 1);
770 if (st->balls[i].vx > 0) /* why is this check here? */
771 st->balls[i].vx = -st->balls[i].vx;
773 if (st->balls[i].y >= (st->ylim - st->balls[i].size))
775 st->balls[i].y = (st->ylim - st->balls[i].size - 1);
776 if (st->balls[i].vy > 0)
777 st->balls[i].vy = -st->balls[i].vy;
779 if (st->balls[i].x <= 0)
782 if (st->balls[i].vx < 0)
783 st->balls[i].vx = -st->balls[i].vx;
785 if (st->balls[i].y <= 0)
788 if (st->balls[i].vy < 0)
789 st->balls[i].vy = -st->balls[i].vy;
793 new_x = st->balls[i].x;
794 new_y = st->balls[i].y;
798 if (st->mode == ball_mode)
802 /* make color saturation be related to particle
806 double vx = st->balls [i].dx;
807 double vy = st->balls [i].dy;
808 if (vx < 0) vx = -vx;
809 if (vy < 0) vy = -vy;
811 if (fraction > limit) fraction = limit;
813 s = 1 - (fraction / limit);
814 st->balls[i].pixel_index = (st->ncolors * s);
816 XSetForeground (dpy, st->draw_gc,
817 st->colors[st->balls[i].pixel_index].pixel);
821 if (st->mode == ball_mode)
823 XFillArc (dpy, window, st->erase_gc, (int) old_x, (int) old_y,
824 size, size, 0, 360*64);
825 XFillArc (dpy, window, st->draw_gc, (int) new_x, (int) new_y,
826 size, size, 0, 360*64);
830 st->point_stack [st->point_stack_fp].x = new_x;
831 st->point_stack [st->point_stack_fp].y = new_y;
832 st->point_stack_fp++;
836 /* draw the lines or polygons after computing all points */
837 if (st->mode != ball_mode)
839 st->point_stack [st->point_stack_fp].x = st->balls [0].x; /* close the polygon */
840 st->point_stack [st->point_stack_fp].y = st->balls [0].y;
841 st->point_stack_fp++;
842 if (st->point_stack_fp == st->point_stack_size)
843 st->point_stack_fp = 0;
844 else if (st->point_stack_fp > st->point_stack_size) /* better be aligned */
848 if (st->color_tick++ == st->color_shift)
851 st->fg_index = (st->fg_index + 1) % st->ncolors;
852 XSetForeground (dpy, st->draw_gc, st->colors[st->fg_index].pixel);
862 if (st->segments > 0)
863 XDrawLines (dpy, window, st->erase_gc, st->point_stack + st->point_stack_fp,
864 st->npoints + 1, CoordModeOrigin);
865 XDrawLines (dpy, window, st->draw_gc, st->point_stack + last_point_stack_fp,
866 st->npoints + 1, CoordModeOrigin);
869 if (st->segments > 0)
870 XFillPolygon (dpy, window, st->erase_gc, st->point_stack + st->point_stack_fp,
871 st->npoints + 1, (st->npoints == 3 ? Convex : Complex),
873 XFillPolygon (dpy, window, st->draw_gc, st->point_stack + last_point_stack_fp,
874 st->npoints + 1, (st->npoints == 3 ? Convex : Complex),
879 for (i = 0; i < st->npoints; i++)
881 int index = st->point_stack_fp + i;
882 int next_index = (index + (st->npoints + 1)) % st->point_stack_size;
883 if(st->no_erase_yet == 1)
885 if(st->total_ticks >= st->segments)
887 st->no_erase_yet = 0;
888 XDrawLine (dpy, window, st->erase_gc,
889 st->point_stack [index].x + radius,
890 st->point_stack [index].y + radius,
891 st->point_stack [next_index].x + radius,
892 st->point_stack [next_index].y + radius);
897 XDrawLine (dpy, window, st->erase_gc,
898 st->point_stack [index].x + radius,
899 st->point_stack [index].y + radius,
900 st->point_stack [next_index].x + radius,
901 st->point_stack [next_index].y + radius);
903 index = last_point_stack_fp + i;
904 next_index = (index - (st->npoints + 1)) % st->point_stack_size;
905 if (next_index < 0) next_index += st->point_stack_size;
906 if (st->point_stack [next_index].x == 0 &&
907 st->point_stack [next_index].y == 0)
909 XDrawLine (dpy, window, st->draw_gc,
910 st->point_stack [index].x + radius,
911 st->point_stack [index].y + radius,
912 st->point_stack [next_index].x + radius,
913 st->point_stack [next_index].y + radius);
918 case spline_filled_mode:
920 if (! st->spl) st->spl = make_spline (st->npoints);
921 if (st->segments > 0)
923 for (i = 0; i < st->npoints; i++)
925 st->spl->control_x [i] = st->point_stack [st->point_stack_fp + i].x;
926 st->spl->control_y [i] = st->point_stack [st->point_stack_fp + i].y;
928 compute_closed_spline (st->spl);
929 if (st->mode == spline_filled_mode)
930 XFillPolygon (dpy, window, st->erase_gc, st->spl->points, st->spl->n_points,
931 (st->spl->n_points == 3 ? Convex : Complex),
934 XDrawLines (dpy, window, st->erase_gc, st->spl->points, st->spl->n_points,
937 for (i = 0; i < st->npoints; i++)
939 st->spl->control_x [i] = st->point_stack [last_point_stack_fp + i].x;
940 st->spl->control_y [i] = st->point_stack [last_point_stack_fp + i].y;
942 compute_closed_spline (st->spl);
943 if (st->mode == spline_filled_mode)
944 XFillPolygon (dpy, window, st->draw_gc, st->spl->points, st->spl->n_points,
945 (st->spl->n_points == 3 ? Convex : Complex),
948 XDrawLines (dpy, window, st->draw_gc, st->spl->points, st->spl->n_points,
960 attraction_reshape (Display *dpy, Window window, void *closure,
961 unsigned int w, unsigned int h)
963 struct state *st = (struct state *) closure;
969 attraction_event (Display *dpy, Window window, void *closure, XEvent *event)
975 attraction_free (Display *dpy, Window window, void *closure)
977 struct state *st = (struct state *) closure;
979 if (st->balls) free (st->balls);
980 if (st->x_vels) free (st->x_vels);
981 if (st->y_vels) free (st->y_vels);
982 if (st->speeds) free (st->speeds);
983 if (st->point_stack) free (st->point_stack);
984 if (st->colors) free (st->colors);
985 if (st->spl) free_spline (st->spl);
991 static const char *attraction_defaults [] = {
992 ".background: black",
993 ".foreground: white",
1018 static XrmOptionDescRec attraction_options [] = {
1019 { "-mode", ".mode", XrmoptionSepArg, 0 },
1020 { "-graphmode", ".graphmode", XrmoptionSepArg, 0 },
1021 { "-colors", ".colors", XrmoptionSepArg, 0 },
1022 { "-points", ".points", XrmoptionSepArg, 0 },
1023 { "-color-shift", ".colorShift", XrmoptionSepArg, 0 },
1024 { "-threshold", ".threshold", XrmoptionSepArg, 0 },
1025 { "-segments", ".segments", XrmoptionSepArg, 0 },
1026 { "-delay", ".delay", XrmoptionSepArg, 0 },
1027 { "-size", ".size", XrmoptionSepArg, 0 },
1028 { "-radius", ".radius", XrmoptionSepArg, 0 },
1029 { "-vx", ".vx", XrmoptionSepArg, 0 },
1030 { "-vy", ".vy", XrmoptionSepArg, 0 },
1031 { "-vmult", ".vMult", XrmoptionSepArg, 0 },
1032 { "-mouse-size", ".mouseSize", XrmoptionSepArg, 0 },
1033 { "-viscosity", ".viscosity", XrmoptionSepArg, 0 },
1034 { "-mouse", ".mouse", XrmoptionNoArg, "true" },
1035 { "-nomouse", ".mouse", XrmoptionNoArg, "false" },
1036 { "-glow", ".glow", XrmoptionNoArg, "true" },
1037 { "-noglow", ".glow", XrmoptionNoArg, "false" },
1038 { "-orbit", ".orbit", XrmoptionNoArg, "true" },
1039 { "-nowalls", ".walls", XrmoptionNoArg, "false" },
1040 { "-walls", ".walls", XrmoptionNoArg, "true" },
1041 { "-nomaxspeed", ".maxspeed", XrmoptionNoArg, "false" },
1042 { "-maxspeed", ".maxspeed", XrmoptionNoArg, "true" },
1043 { "-correct-bounce", ".cbounce", XrmoptionNoArg, "false" },
1044 { "-fast-bounce", ".cbounce", XrmoptionNoArg, "true" },
1049 XSCREENSAVER_MODULE ("Attraction", attraction)