1 /* xscreensaver, Copyright (c) 1992-2008 Jamie Zawinski <jwz@jwz.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
12 /* Simulation of a pair of quasi-gravitational fields, maybe sorta kinda
13 a little like the strong and weak electromagnetic forces. Derived from
14 a Lispm screensaver by John Pezaris <pz@mit.edu>. Mouse control and
15 viscosity added by "Philip Edward Cutone, III" <pc2d+@andrew.cmu.edu>.
19 The simulation started out as a purely accurate gravitational
20 simulation, but, with constant simulation step size, I quickly
21 realized the field being simulated while grossly gravitational
22 was, in fact, non-conservative. It also had the rather annoying
23 behavior of dealing very badly with colliding orbs. Therefore,
24 I implemented a negative-gravity region (with two thresholds; as
25 I read your code, you only implemented one) to prevent orbs from
26 every coming too close together, and added a viscosity factor if
27 the speed of any orb got too fast. This provides a nice stable
28 system with interesting behavior.
30 I had experimented with a number of fields including the van der
31 Waals force (very interesting orbiting behavior) and 1/r^3
32 gravity (not as interesting as 1/r^2). An even normal viscosity
33 (rather than the thresholded version to bleed excess energy) is
34 also not interesting. The 1/r^2, -1/r^2, -10/r^2 thresholds
35 proved not only robust but also interesting -- the orbs never
36 collided and the threshold viscosity fixed the
37 non-conservational problem.
40 > An even normal viscosity (rather than the thresholded version to
41 > bleed excess energy) is also not interesting.
43 unless you make about 200 points.... set the viscosity to about .8
44 and drag the mouse through it. it makes a nice wave which travels
47 And (always the troublemaker) Joe Keane <jgk@jgk.org> sez:
49 Despite what John sez, the field being simulated is always
50 conservative. The real problem is that it uses a simple hack,
51 computing acceleration *based only on the starting position*,
52 instead of a real differential equation solver. Thus you'll
53 always have energy coming out of nowhere, although it's most
54 blatant when balls get close together. If it were done right,
55 you wouldn't need viscosity or artificial limits on how close
58 Matt <straitm@carleton.edu> sez:
60 Added a switch to remove the walls.
62 Added a switch to make the threshold viscosity optional. If
63 nomaxspeed is specified, then balls going really fast do not
64 recieve special treatment.
66 I've made tail mode prettier by eliminating the first erase line
67 that drew from the upper left corner to the starting position of
70 Made the balls in modes other than "balls" bounce exactly at the
71 walls. (Because the graphics for different modes are drawn
72 differently with respect to the "actual" position of the point,
73 they used to be able to run somewhat past the walls, or bounce
76 Added an option to output each ball's speed in the form of a bar
77 graph drawn on the same window as the balls. If only x or y is
78 selected, they will be represented on the appropriate axis down
79 the center of the window. If both are selected, they will both
80 be displayed along the diagonal such that the x and y bars for
81 each point start at the same place. If speed is selected, the
82 speed will be displayed down the left side. */
86 #include "screenhack.h"
89 /* The normal (and max) width for a graph bar */
92 #define min(a,b) ((a)<(b)?(a):(b))
93 #define max(a,b) ((a)>(b)?(a):(b))
97 ball_mode, line_mode, polygon_mode, spline_mode, spline_filled_mode,
102 graph_none, graph_x, graph_y, graph_both, graph_speed
131 int point_stack_size, point_stack_fp;
137 Bool no_erase_yet; /* for tail mode fix */
139 /*flip mods for mouse interaction*/
141 int mouse_x, mouse_y, mouse_mass, root_x, root_y;
144 enum object_mode mode;
145 enum graph_mode graph_mode;
147 GC draw_gc, erase_gc;
156 attraction_init (Display *dpy, Window window)
158 struct state *st = (struct state *) calloc (1, sizeof(*st));
160 XWindowAttributes xgwa;
162 int midx, midy, r, vx, vy;
165 char *mode_str, *graph_mode_str;
167 XGetWindowAttributes (dpy, window, &xgwa);
168 st->xlim = xgwa.width;
169 st->ylim = xgwa.height;
170 cmap = xgwa.colormap;
173 st->walls_p = get_boolean_resource (dpy, "walls", "Boolean");
175 /* if there aren't walls, don't set a limit on the radius */
176 r = get_integer_resource (dpy, "radius", "Integer");
177 if (r <= 0 || (r > min (st->xlim/2, st->ylim/2) && st->walls_p) )
178 r = min (st->xlim/2, st->ylim/2) - 50;
180 vx = get_integer_resource (dpy, "vx", "Integer");
181 vy = get_integer_resource (dpy, "vy", "Integer");
183 st->npoints = get_integer_resource (dpy, "points", "Integer");
185 st->npoints = 3 + (random () % 5);
186 st->balls = (struct ball *) malloc (st->npoints * sizeof (struct ball));
188 st->no_erase_yet = 1; /* for tail mode fix */
190 st->segments = get_integer_resource (dpy, "segments", "Integer");
191 if (st->segments < 0) st->segments = 1;
193 st->threshold = get_integer_resource (dpy, "threshold", "Integer");
194 if (st->threshold < 0) st->threshold = 0;
196 st->delay = get_integer_resource (dpy, "delay", "Integer");
197 if (st->delay < 0) st->delay = 0;
199 st->global_size = get_integer_resource (dpy, "size", "Integer");
200 if (st->global_size < 0) st->global_size = 0;
202 st->glow_p = get_boolean_resource (dpy, "glow", "Boolean");
204 st->orbit_p = get_boolean_resource (dpy, "orbit", "Boolean");
206 st->maxspeed_p = get_boolean_resource (dpy, "maxspeed", "Boolean");
208 st->cbounce_p = get_boolean_resource (dpy, "cbounce", "Boolean");
210 st->color_shift = get_integer_resource (dpy, "colorShift", "Integer");
211 if (st->color_shift <= 0) st->color_shift = 5;
213 /*flip mods for mouse interaction*/
214 st->mouse_p = get_boolean_resource (dpy, "mouse", "Boolean");
215 st->mouse_mass = get_integer_resource (dpy, "mouseSize", "Integer");
216 st->mouse_mass = st->mouse_mass * st->mouse_mass *10;
218 st->viscosity = get_float_resource (dpy, "viscosity", "Float");
220 mode_str = get_string_resource (dpy, "mode", "Mode");
221 if (! mode_str) st->mode = ball_mode;
222 else if (!strcmp (mode_str, "balls")) st->mode = ball_mode;
223 else if (!strcmp (mode_str, "lines")) st->mode = line_mode;
224 else if (!strcmp (mode_str, "polygons")) st->mode = polygon_mode;
225 else if (!strcmp (mode_str, "tails")) st->mode = tail_mode;
226 else if (!strcmp (mode_str, "splines")) st->mode = spline_mode;
227 else if (!strcmp (mode_str, "filled-splines"))st->mode = spline_filled_mode;
230 "%s: mode must be balls, lines, tails, polygons, splines, or\n\
231 filled-splines, not \"%s\"\n",
236 graph_mode_str = get_string_resource (dpy, "graphmode", "Mode");
237 if (! graph_mode_str) st->graph_mode = graph_none;
238 else if (!strcmp (graph_mode_str, "x")) st->graph_mode = graph_x;
239 else if (!strcmp (graph_mode_str, "y")) st->graph_mode = graph_y;
240 else if (!strcmp (graph_mode_str, "both")) st->graph_mode = graph_both;
241 else if (!strcmp (graph_mode_str, "speed")) st->graph_mode = graph_speed;
242 else if (!strcmp (graph_mode_str, "none")) st->graph_mode = graph_none;
245 "%s: graphmode must be speed, x, y, both, or none, not \"%s\"\n",
246 progname, graph_mode_str);
250 /* only allocate memory if it is needed */
251 if(st->graph_mode != graph_none)
253 if(st->graph_mode == graph_x || st->graph_mode == graph_both)
254 st->x_vels = (double *) malloc (st->npoints * sizeof (double));
255 if(st->graph_mode == graph_y || st->graph_mode == graph_both)
256 st->y_vels = (double *) malloc (st->npoints * sizeof (double));
257 if(st->graph_mode == graph_speed)
258 st->speeds = (double *) malloc (st->npoints * sizeof (double));
261 if (st->mode != ball_mode && st->mode != tail_mode) st->glow_p = False;
263 if (st->mode == polygon_mode && st->npoints < 3)
264 st->mode = line_mode;
266 st->ncolors = get_integer_resource (dpy, "colors", "Colors");
267 if (st->ncolors < 2) st->ncolors = 2;
268 if (st->ncolors <= 2) mono_p = True;
279 int H = random() % 360;
282 double V = frand(0.25) + 0.75;
283 st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1));
284 make_color_ramp (dpy, cmap, H, S1, V, H, S2, V, st->colors, &st->ncolors,
289 st->ncolors = st->npoints;
290 st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1));
291 make_random_colormap (dpy, xgwa.visual, cmap, st->colors, &st->ncolors,
292 True, True, False, True);
298 case spline_filled_mode:
300 st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1));
301 make_smooth_colormap (dpy, xgwa.visual, cmap, st->colors, &st->ncolors,
309 if (!mono_p && st->ncolors <= 2)
311 if (st->colors) free (st->colors);
316 if (st->mode != ball_mode)
318 int size = (st->segments ? st->segments : 1);
319 st->point_stack_size = size * (st->npoints + 1);
320 st->point_stack = (XPoint *) calloc (st->point_stack_size, sizeof (XPoint));
321 st->point_stack_fp = 0;
324 gcv.line_width = (st->mode == tail_mode
325 ? (st->global_size ? st->global_size : (MAX_SIZE * 2 / 3))
327 gcv.cap_style = (st->mode == tail_mode ? CapRound : CapButt);
330 gcv.foreground = get_pixel_resource(dpy, cmap, "foreground", "Foreground");
332 gcv.foreground = st->colors[st->fg_index].pixel;
333 st->draw_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle, &gcv);
335 gcv.foreground = get_pixel_resource(dpy, cmap, "background", "Background");
336 st->erase_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle,&gcv);
340 jwxyz_XSetAntiAliasing (dpy, st->draw_gc, False);
341 jwxyz_XSetAntiAliasing (dpy, st->erase_gc, False);
344 /* let's make the balls bigger by default */
345 #define rand_size() (3 * (8 + (random () % 7)))
347 if (st->orbit_p && !st->global_size)
348 /* To orbit, all objects must be the same mass, or the math gets
350 st->global_size = rand_size ();
353 th = frand (M_PI+M_PI);
354 for (i = 0; i < st->npoints; i++)
356 int new_size = (st->global_size ? st->global_size : rand_size ());
357 st->balls [i].dx = 0;
358 st->balls [i].dy = 0;
359 st->balls [i].size = new_size;
360 st->balls [i].mass = (new_size * new_size * 10);
361 st->balls [i].x = midx + r * cos (i * ((M_PI+M_PI) / st->npoints) + th);
362 st->balls [i].y = midy + r * sin (i * ((M_PI+M_PI) / st->npoints) + th);
365 st->balls [i].vx = vx ? vx : ((6.0 - (random () % 11)) / 8.0);
366 st->balls [i].vy = vy ? vy : ((6.0 - (random () % 11)) / 8.0);
368 if (mono_p || st->mode != ball_mode)
369 st->balls [i].pixel_index = -1;
371 st->balls [i].pixel_index = 0;
373 st->balls [i].pixel_index = random() % st->ncolors;
376 /* This lets modes where the points don't really have any size use the whole
377 window. Otherwise, since the points still have a positive size
378 assigned to them, they will be bounced somewhat early. Mass and size are
379 seperate, so this shouldn't cause problems. It's a bit kludgy, tho.
381 if(st->mode == line_mode || st->mode == spline_mode ||
382 st->mode == spline_filled_mode || st->mode == polygon_mode)
384 for(i = 1; i < st->npoints; i++)
386 st->balls[i].size = 0;
394 double v_mult = get_float_resource (dpy, "vMult", "Float");
395 if (v_mult == 0.0) v_mult = 1.0;
397 for (i = 1; i < st->npoints; i++)
399 double _2ipi_n = (2 * i * M_PI / st->npoints);
400 double x = r * cos (_2ipi_n);
401 double y = r * sin (_2ipi_n);
402 double distx = r - x;
403 double dist2 = (distx * distx) + (y * y);
404 double dist = sqrt (dist2);
405 double a1 = ((st->balls[i].mass / dist2) *
406 ((dist < st->threshold) ? -1.0 : 1.0) *
412 /* "domain error: forces on balls too great" */
413 fprintf (stderr, "%s: window too small for these orbit settings.\n",
418 v = sqrt (a * r) * v_mult;
419 for (i = 0; i < st->npoints; i++)
421 double k = ((2 * i * M_PI / st->npoints) + th);
422 st->balls [i].vx = -v * sin (k);
423 st->balls [i].vy = v * cos (k);
427 if (mono_p) st->glow_p = False;
429 XClearWindow (dpy, window);
434 compute_force (struct state *st, int i, double *dx_ret, double *dy_ret)
437 double x_dist, y_dist, dist, dist2;
440 for (j = 0; j < st->npoints; j++)
442 if (i == j) continue;
443 x_dist = st->balls [j].x - st->balls [i].x;
444 y_dist = st->balls [j].y - st->balls [i].y;
445 dist2 = (x_dist * x_dist) + (y_dist * y_dist);
448 if (dist > 0.1) /* the balls are not overlapping */
450 double new_acc = ((st->balls[j].mass / dist2) *
451 ((dist < st->threshold) ? -1.0 : 1.0));
452 double new_acc_dist = new_acc / dist;
453 *dx_ret += new_acc_dist * x_dist;
454 *dy_ret += new_acc_dist * y_dist;
457 { /* the balls are overlapping; move randomly */
458 *dx_ret += (frand (10.0) - 5.0);
459 *dy_ret += (frand (10.0) - 5.0);
465 x_dist = st->mouse_x - st->balls [i].x;
466 y_dist = st->mouse_y - st->balls [i].y;
467 dist2 = (x_dist * x_dist) + (y_dist * y_dist);
470 if (dist > 0.1) /* the balls are not overlapping */
472 double new_acc = ((st->mouse_mass / dist2) *
473 ((dist < st->threshold) ? -1.0 : 1.0));
474 double new_acc_dist = new_acc / dist;
475 *dx_ret += new_acc_dist * x_dist;
476 *dy_ret += new_acc_dist * y_dist;
479 { /* the balls are overlapping; move randomly */
480 *dx_ret += (frand (10.0) - 5.0);
481 *dy_ret += (frand (10.0) - 5.0);
487 /* Draws meters along the diagonal for the x velocity */
489 draw_meter_x(Display *dpy, Window window, struct state *st, int i, int alone)
491 XWindowAttributes xgwa;
493 XGetWindowAttributes (dpy, window, &xgwa);
495 /* set the width of the bars to use */
496 if(xgwa.height < BAR_SIZE*st->npoints)
498 y = i*(xgwa.height/st->npoints);
499 h = (xgwa.height/st->npoints) - 2;
523 w1 = (int)(20*st->x_vels[i]);
524 w2 = (int)(20*st->balls[i].vx);
525 st->x_vels[i] = st->balls[i].vx;
535 XDrawRectangle(dpy,window,st->erase_gc,x1+(h+2)/2,y,w1,h);
536 XDrawRectangle(dpy,window,st->draw_gc,x2+(h+2)/2,y,w2,h);
539 /* Draws meters along the diagonal for the y velocity.
540 Is there some way to make draw_meter_x and draw_meter_y
541 one function instead of two without making them completely unreadable?
544 draw_meter_y (Display *dpy, Window window, struct state *st, int i, int alone)
546 XWindowAttributes xgwa;
548 XGetWindowAttributes (dpy, window, &xgwa);
550 if(xgwa.height < BAR_SIZE*st->npoints){ /*needs to be height still */
551 x = i*(xgwa.height/st->npoints);
552 w = (xgwa.height/st->npoints) - 2;
575 h1 = (int)(20*st->y_vels[i]);
576 h2 = (int)(20*st->balls[i].vy);
577 st->y_vels[i] = st->balls[i].vy;
587 XDrawRectangle(dpy,window,st->erase_gc,x,y1+(w+2)/2,w,h1);
588 XDrawRectangle(dpy,window,st->draw_gc,x,y2+(w+2)/2,w,h2);
592 /* Draws meters of the total speed of the balls */
594 draw_meter_speed (Display *dpy, Window window, struct state *st, int i)
596 XWindowAttributes xgwa;
598 XGetWindowAttributes (dpy, window, &xgwa);
600 if(xgwa.height < BAR_SIZE*st->npoints)
602 y = i*(xgwa.height/st->npoints);
603 h = (xgwa.height/st->npoints) - 2;
616 w1 = (int)(5*st->speeds[i]);
617 w2 = (int)(5*(st->balls[i].vy*st->balls[i].vy+st->balls[i].vx*st->balls[i].vx));
618 st->speeds[i] = st->balls[i].vy*st->balls[i].vy+st->balls[i].vx*st->balls[i].vx;
620 XDrawRectangle(dpy,window,st->erase_gc,x1,y,w1,h);
621 XDrawRectangle(dpy,window,st->draw_gc, x2,y,w2,h);
625 attraction_draw (Display *dpy, Window window, void *closure)
627 struct state *st = (struct state *) closure;
628 int last_point_stack_fp = st->point_stack_fp;
630 Window root1, child1; /*flip mods for mouse interaction*/
633 int i, radius = st->global_size/2;
637 if(st->global_size == 0)
638 radius = (MAX_SIZE / 3);
640 if(st->graph_mode != graph_none)
642 if(st->graph_mode == graph_both)
644 for(i = 0; i < st->npoints; i++)
646 draw_meter_x(dpy, window, st, i, 0);
647 draw_meter_y(dpy, window, st, i, 0);
650 else if(st->graph_mode == graph_x)
652 for(i = 0; i < st->npoints; i++)
654 draw_meter_x(dpy, window, st, i, 1);
657 else if(st->graph_mode == graph_y)
659 for(i = 0; i < st->npoints; i++)
661 draw_meter_y(dpy, window, st, i, 1);
664 else if(st->graph_mode == graph_speed)
666 for(i = 0; i < st->npoints; i++)
668 draw_meter_speed(dpy, window, st, i);
676 XQueryPointer(dpy, window, &root1, &child1,
677 &st->root_x, &st->root_y, &st->mouse_x, &st->mouse_y, &mask);
680 /* compute the force of attraction/repulsion among all balls */
681 for (i = 0; i < st->npoints; i++)
682 compute_force (st, i, &st->balls[i].dx, &st->balls[i].dy);
684 /* move the balls according to the forces now in effect */
685 for (i = 0; i < st->npoints; i++)
687 double old_x = st->balls[i].x;
688 double old_y = st->balls[i].y;
690 int size = st->balls[i].size;
691 st->balls[i].vx += st->balls[i].dx;
692 st->balls[i].vy += st->balls[i].dy;
694 /* "don't let them get too fast: impose a terminal velocity
695 (actually, make the medium have friction)"
696 Well, what this first step really does is give the medium a
697 viscosity of .9 for balls going over the speed limit. Anyway,
700 if (fabs(st->balls[i].vx) > 10 && st->maxspeed_p)
702 st->balls[i].vx *= 0.9;
705 if (st->viscosity != 1)
707 st->balls[i].vx *= st->viscosity;
710 if (fabs(st->balls[i].vy) > 10 && st->maxspeed_p)
712 st->balls[i].vy *= 0.9;
715 if (st->viscosity != 1)
717 st->balls[i].vy *= st->viscosity;
720 st->balls[i].x += st->balls[i].vx;
721 st->balls[i].y += st->balls[i].vy;
724 /* bounce off the walls if desired
725 note: a ball is actually its upper left corner */
728 if(st->cbounce_p) /* with correct bouncing */
730 /* so long as it's out of range, keep bouncing */
731 /* limit the maximum number to bounce to 4.*/
732 int bounce_allowed = 4;
734 while( bounce_allowed && (
735 (st->balls[i].x >= (st->xlim - st->balls[i].size)) ||
736 (st->balls[i].y >= (st->ylim - st->balls[i].size)) ||
737 (st->balls[i].x <= 0) ||
738 (st->balls[i].y <= 0) )
742 if (st->balls[i].x >= (st->xlim - st->balls[i].size))
744 st->balls[i].x = (2*(st->xlim - st->balls[i].size) - st->balls[i].x);
745 st->balls[i].vx = -st->balls[i].vx;
747 if (st->balls[i].y >= (st->ylim - st->balls[i].size))
749 st->balls[i].y = (2*(st->ylim - st->balls[i].size) - st->balls[i].y);
750 st->balls[i].vy = -st->balls[i].vy;
752 if (st->balls[i].x <= 0)
754 st->balls[i].x = -st->balls[i].x;
755 st->balls[i].vx = -st->balls[i].vx;
757 if (st->balls[i].y <= 0)
759 st->balls[i].y = -st->balls[i].y;
760 st->balls[i].vy = -st->balls[i].vy;
764 else /* with old bouncing */
766 if (st->balls[i].x >= (st->xlim - st->balls[i].size))
768 st->balls[i].x = (st->xlim - st->balls[i].size - 1);
769 if (st->balls[i].vx > 0) /* why is this check here? */
770 st->balls[i].vx = -st->balls[i].vx;
772 if (st->balls[i].y >= (st->ylim - st->balls[i].size))
774 st->balls[i].y = (st->ylim - st->balls[i].size - 1);
775 if (st->balls[i].vy > 0)
776 st->balls[i].vy = -st->balls[i].vy;
778 if (st->balls[i].x <= 0)
781 if (st->balls[i].vx < 0)
782 st->balls[i].vx = -st->balls[i].vx;
784 if (st->balls[i].y <= 0)
787 if (st->balls[i].vy < 0)
788 st->balls[i].vy = -st->balls[i].vy;
792 new_x = st->balls[i].x;
793 new_y = st->balls[i].y;
797 if (st->mode == ball_mode)
801 /* make color saturation be related to particle
805 double vx = st->balls [i].dx;
806 double vy = st->balls [i].dy;
807 if (vx < 0) vx = -vx;
808 if (vy < 0) vy = -vy;
810 if (fraction > limit) fraction = limit;
812 s = 1 - (fraction / limit);
813 st->balls[i].pixel_index = (st->ncolors * s);
815 XSetForeground (dpy, st->draw_gc,
816 st->colors[st->balls[i].pixel_index].pixel);
820 if (st->mode == ball_mode)
822 XFillArc (dpy, window, st->erase_gc, (int) old_x, (int) old_y,
823 size, size, 0, 360*64);
824 XFillArc (dpy, window, st->draw_gc, (int) new_x, (int) new_y,
825 size, size, 0, 360*64);
829 st->point_stack [st->point_stack_fp].x = new_x;
830 st->point_stack [st->point_stack_fp].y = new_y;
831 st->point_stack_fp++;
835 /* draw the lines or polygons after computing all points */
836 if (st->mode != ball_mode)
838 st->point_stack [st->point_stack_fp].x = st->balls [0].x; /* close the polygon */
839 st->point_stack [st->point_stack_fp].y = st->balls [0].y;
840 st->point_stack_fp++;
841 if (st->point_stack_fp == st->point_stack_size)
842 st->point_stack_fp = 0;
843 else if (st->point_stack_fp > st->point_stack_size) /* better be aligned */
847 if (st->color_tick++ == st->color_shift)
850 st->fg_index = (st->fg_index + 1) % st->ncolors;
851 XSetForeground (dpy, st->draw_gc, st->colors[st->fg_index].pixel);
861 if (st->segments > 0)
862 XDrawLines (dpy, window, st->erase_gc, st->point_stack + st->point_stack_fp,
863 st->npoints + 1, CoordModeOrigin);
864 XDrawLines (dpy, window, st->draw_gc, st->point_stack + last_point_stack_fp,
865 st->npoints + 1, CoordModeOrigin);
868 if (st->segments > 0)
869 XFillPolygon (dpy, window, st->erase_gc, st->point_stack + st->point_stack_fp,
870 st->npoints + 1, (st->npoints == 3 ? Convex : Complex),
872 XFillPolygon (dpy, window, st->draw_gc, st->point_stack + last_point_stack_fp,
873 st->npoints + 1, (st->npoints == 3 ? Convex : Complex),
878 for (i = 0; i < st->npoints; i++)
880 int index = st->point_stack_fp + i;
881 int next_index = (index + (st->npoints + 1)) % st->point_stack_size;
882 if(st->no_erase_yet == 1)
884 if(st->total_ticks >= st->segments)
886 st->no_erase_yet = 0;
887 XDrawLine (dpy, window, st->erase_gc,
888 st->point_stack [index].x + radius,
889 st->point_stack [index].y + radius,
890 st->point_stack [next_index].x + radius,
891 st->point_stack [next_index].y + radius);
896 XDrawLine (dpy, window, st->erase_gc,
897 st->point_stack [index].x + radius,
898 st->point_stack [index].y + radius,
899 st->point_stack [next_index].x + radius,
900 st->point_stack [next_index].y + radius);
902 index = last_point_stack_fp + i;
903 next_index = (index - (st->npoints + 1)) % st->point_stack_size;
904 if (next_index < 0) next_index += st->point_stack_size;
905 if (st->point_stack [next_index].x == 0 &&
906 st->point_stack [next_index].y == 0)
908 XDrawLine (dpy, window, st->draw_gc,
909 st->point_stack [index].x + radius,
910 st->point_stack [index].y + radius,
911 st->point_stack [next_index].x + radius,
912 st->point_stack [next_index].y + radius);
917 case spline_filled_mode:
919 if (! st->spl) st->spl = make_spline (st->npoints);
920 if (st->segments > 0)
922 for (i = 0; i < st->npoints; i++)
924 st->spl->control_x [i] = st->point_stack [st->point_stack_fp + i].x;
925 st->spl->control_y [i] = st->point_stack [st->point_stack_fp + i].y;
927 compute_closed_spline (st->spl);
928 if (st->mode == spline_filled_mode)
929 XFillPolygon (dpy, window, st->erase_gc, st->spl->points, st->spl->n_points,
930 (st->spl->n_points == 3 ? Convex : Complex),
933 XDrawLines (dpy, window, st->erase_gc, st->spl->points, st->spl->n_points,
936 for (i = 0; i < st->npoints; i++)
938 st->spl->control_x [i] = st->point_stack [last_point_stack_fp + i].x;
939 st->spl->control_y [i] = st->point_stack [last_point_stack_fp + i].y;
941 compute_closed_spline (st->spl);
942 if (st->mode == spline_filled_mode)
943 XFillPolygon (dpy, window, st->draw_gc, st->spl->points, st->spl->n_points,
944 (st->spl->n_points == 3 ? Convex : Complex),
947 XDrawLines (dpy, window, st->draw_gc, st->spl->points, st->spl->n_points,
959 attraction_reshape (Display *dpy, Window window, void *closure,
960 unsigned int w, unsigned int h)
962 struct state *st = (struct state *) closure;
968 attraction_event (Display *dpy, Window window, void *closure, XEvent *event)
974 attraction_free (Display *dpy, Window window, void *closure)
976 struct state *st = (struct state *) closure;
978 if (st->balls) free (st->balls);
979 if (st->x_vels) free (st->x_vels);
980 if (st->y_vels) free (st->y_vels);
981 if (st->speeds) free (st->speeds);
982 if (st->point_stack) free (st->point_stack);
983 if (st->colors) free (st->colors);
984 if (st->spl) free_spline (st->spl);
990 static const char *attraction_defaults [] = {
991 ".background: black",
992 ".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)