1 /* xscreensaver, Copyright (c) 1992-2013 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>. Viscosity added by
15 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 */
140 int mouse_ball; /* index of ball being dragged, or 0 if none. */
141 unsigned long mouse_pixel;
143 enum object_mode mode;
144 enum graph_mode graph_mode;
146 GC draw_gc, erase_gc;
155 attraction_init (Display *dpy, Window window)
157 struct state *st = (struct state *) calloc (1, sizeof(*st));
159 XWindowAttributes xgwa;
161 int midx, midy, r, vx, vy;
164 char *mode_str, *graph_mode_str;
166 XGetWindowAttributes (dpy, window, &xgwa);
167 st->xlim = xgwa.width;
168 st->ylim = xgwa.height;
169 cmap = xgwa.colormap;
172 st->walls_p = get_boolean_resource (dpy, "walls", "Boolean");
174 /* if there aren't walls, don't set a limit on the radius */
175 r = get_integer_resource (dpy, "radius", "Integer");
176 if (r <= 0 || (r > min (st->xlim/2, st->ylim/2) && st->walls_p) )
177 r = min (st->xlim/2, st->ylim/2) - 50;
179 vx = get_integer_resource (dpy, "vx", "Integer");
180 vy = get_integer_resource (dpy, "vy", "Integer");
182 st->npoints = get_integer_resource (dpy, "points", "Integer");
184 st->npoints = 3 + (random () % 5);
185 st->balls = (struct ball *) malloc (st->npoints * sizeof (struct ball));
187 st->no_erase_yet = 1; /* for tail mode fix */
189 st->segments = get_integer_resource (dpy, "segments", "Integer");
190 if (st->segments < 0) st->segments = 1;
192 st->threshold = get_integer_resource (dpy, "threshold", "Integer");
193 if (st->threshold < 0) st->threshold = 0;
195 st->delay = get_integer_resource (dpy, "delay", "Integer");
196 if (st->delay < 0) st->delay = 0;
198 st->global_size = get_integer_resource (dpy, "size", "Integer");
199 if (st->global_size < 0) st->global_size = 0;
201 st->glow_p = get_boolean_resource (dpy, "glow", "Boolean");
203 st->orbit_p = get_boolean_resource (dpy, "orbit", "Boolean");
205 st->maxspeed_p = get_boolean_resource (dpy, "maxspeed", "Boolean");
207 st->cbounce_p = get_boolean_resource (dpy, "cbounce", "Boolean");
209 st->color_shift = get_integer_resource (dpy, "colorShift", "Integer");
210 if (st->color_shift <= 0) st->color_shift = 5;
212 st->viscosity = get_float_resource (dpy, "viscosity", "Float");
214 mode_str = get_string_resource (dpy, "mode", "Mode");
215 if (! mode_str) st->mode = ball_mode;
216 else if (!strcmp (mode_str, "balls")) st->mode = ball_mode;
217 else if (!strcmp (mode_str, "lines")) st->mode = line_mode;
218 else if (!strcmp (mode_str, "polygons")) st->mode = polygon_mode;
219 else if (!strcmp (mode_str, "tails")) st->mode = tail_mode;
220 else if (!strcmp (mode_str, "splines")) st->mode = spline_mode;
221 else if (!strcmp (mode_str, "filled-splines"))st->mode = spline_filled_mode;
224 "%s: mode must be balls, lines, tails, polygons, splines, or\n\
225 filled-splines, not \"%s\"\n",
230 graph_mode_str = get_string_resource (dpy, "graphmode", "Mode");
231 if (! graph_mode_str) st->graph_mode = graph_none;
232 else if (!strcmp (graph_mode_str, "x")) st->graph_mode = graph_x;
233 else if (!strcmp (graph_mode_str, "y")) st->graph_mode = graph_y;
234 else if (!strcmp (graph_mode_str, "both")) st->graph_mode = graph_both;
235 else if (!strcmp (graph_mode_str, "speed")) st->graph_mode = graph_speed;
236 else if (!strcmp (graph_mode_str, "none")) st->graph_mode = graph_none;
239 "%s: graphmode must be speed, x, y, both, or none, not \"%s\"\n",
240 progname, graph_mode_str);
244 /* only allocate memory if it is needed */
245 if(st->graph_mode != graph_none)
247 if(st->graph_mode == graph_x || st->graph_mode == graph_both)
248 st->x_vels = (double *) malloc (st->npoints * sizeof (double));
249 if(st->graph_mode == graph_y || st->graph_mode == graph_both)
250 st->y_vels = (double *) malloc (st->npoints * sizeof (double));
251 if(st->graph_mode == graph_speed)
252 st->speeds = (double *) malloc (st->npoints * sizeof (double));
255 if (st->mode != ball_mode && st->mode != tail_mode) st->glow_p = False;
257 if (st->mode == polygon_mode && st->npoints < 3)
258 st->mode = line_mode;
260 st->ncolors = get_integer_resource (dpy, "colors", "Colors");
261 if (st->ncolors < 2) st->ncolors = 2;
262 if (st->ncolors <= 2) mono_p = True;
273 int H = random() % 360;
276 double V = frand(0.25) + 0.75;
277 st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1));
278 make_color_ramp (xgwa.screen, xgwa.visual, cmap,
279 H, S1, V, H, S2, V, st->colors, &st->ncolors,
284 st->ncolors = st->npoints;
285 st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1));
286 make_random_colormap (xgwa.screen, xgwa.visual, cmap,
287 st->colors, &st->ncolors,
288 True, True, False, True);
294 case spline_filled_mode:
296 st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1));
297 make_smooth_colormap (xgwa.screen, xgwa.visual, cmap,
298 st->colors, &st->ncolors,
306 if (!mono_p && st->ncolors <= 2)
308 if (st->colors) free (st->colors);
314 get_pixel_resource (dpy, cmap, "mouseForeground", "MouseForeground");
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 /* Draws meters along the diagonal for the x velocity */
468 draw_meter_x(Display *dpy, Window window, struct state *st, int i, int alone)
470 XWindowAttributes xgwa;
472 XGetWindowAttributes (dpy, window, &xgwa);
474 /* set the width of the bars to use */
475 if(xgwa.height < BAR_SIZE*st->npoints)
477 y = i*(xgwa.height/st->npoints);
478 h = (xgwa.height/st->npoints) - 2;
502 w1 = (int)(20*st->x_vels[i]);
503 w2 = (int)(20*st->balls[i].vx);
504 st->x_vels[i] = st->balls[i].vx;
514 XDrawRectangle(dpy,window,st->erase_gc,x1+(h+2)/2,y,w1,h);
515 XDrawRectangle(dpy,window,st->draw_gc,x2+(h+2)/2,y,w2,h);
518 /* Draws meters along the diagonal for the y velocity.
519 Is there some way to make draw_meter_x and draw_meter_y
520 one function instead of two without making them completely unreadable?
523 draw_meter_y (Display *dpy, Window window, struct state *st, int i, int alone)
525 XWindowAttributes xgwa;
527 XGetWindowAttributes (dpy, window, &xgwa);
529 if(xgwa.height < BAR_SIZE*st->npoints){ /*needs to be height still */
530 x = i*(xgwa.height/st->npoints);
531 w = (xgwa.height/st->npoints) - 2;
554 h1 = (int)(20*st->y_vels[i]);
555 h2 = (int)(20*st->balls[i].vy);
556 st->y_vels[i] = st->balls[i].vy;
566 XDrawRectangle(dpy,window,st->erase_gc,x,y1+(w+2)/2,w,h1);
567 XDrawRectangle(dpy,window,st->draw_gc,x,y2+(w+2)/2,w,h2);
571 /* Draws meters of the total speed of the balls */
573 draw_meter_speed (Display *dpy, Window window, struct state *st, int i)
575 XWindowAttributes xgwa;
577 XGetWindowAttributes (dpy, window, &xgwa);
579 if(xgwa.height < BAR_SIZE*st->npoints)
581 y = i*(xgwa.height/st->npoints);
582 h = (xgwa.height/st->npoints) - 2;
595 w1 = (int)(5*st->speeds[i]);
596 w2 = (int)(5*(st->balls[i].vy*st->balls[i].vy+st->balls[i].vx*st->balls[i].vx));
597 st->speeds[i] = st->balls[i].vy*st->balls[i].vy+st->balls[i].vx*st->balls[i].vx;
599 XDrawRectangle(dpy,window,st->erase_gc,x1,y,w1,h);
600 XDrawRectangle(dpy,window,st->draw_gc, x2,y,w2,h);
603 /* Returns the position of the mouse relative to the root window.
606 query_mouse (Display *dpy, Window win, int *x, int *y)
608 Window root1, child1;
609 int mouse_x, mouse_y, root_x, root_y;
611 if (XQueryPointer (dpy, win, &root1, &child1,
612 &root_x, &root_y, &mouse_x, &mouse_y, &mask))
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 int i, radius = st->global_size/2;
634 if(st->global_size == 0)
635 radius = (MAX_SIZE / 3);
637 if(st->graph_mode != graph_none)
639 if(st->graph_mode == graph_both)
641 for(i = 0; i < st->npoints; i++)
643 draw_meter_x(dpy, window, st, i, 0);
644 draw_meter_y(dpy, window, st, i, 0);
647 else if(st->graph_mode == graph_x)
649 for(i = 0; i < st->npoints; i++)
651 draw_meter_x(dpy, window, st, i, 1);
654 else if(st->graph_mode == graph_y)
656 for(i = 0; i < st->npoints; i++)
658 draw_meter_y(dpy, window, st, i, 1);
661 else if(st->graph_mode == graph_speed)
663 for(i = 0; i < st->npoints; i++)
665 draw_meter_speed(dpy, window, st, i);
671 /* compute the force of attraction/repulsion among all balls */
672 for (i = 0; i < st->npoints; i++)
673 compute_force (st, i, &st->balls[i].dx, &st->balls[i].dy);
675 /* move the balls according to the forces now in effect */
676 for (i = 0; i < st->npoints; i++)
678 double old_x = st->balls[i].x;
679 double old_y = st->balls[i].y;
681 int size = st->balls[i].size;
683 st->balls[i].vx += st->balls[i].dx;
684 st->balls[i].vy += st->balls[i].dy;
686 /* "don't let them get too fast: impose a terminal velocity
687 (actually, make the medium have friction)"
688 Well, what this first step really does is give the medium a
689 viscosity of .9 for balls going over the speed limit. Anyway,
692 if (fabs(st->balls[i].vx) > 10 && st->maxspeed_p)
694 st->balls[i].vx *= 0.9;
697 if (st->viscosity != 1)
699 st->balls[i].vx *= st->viscosity;
702 if (fabs(st->balls[i].vy) > 10 && st->maxspeed_p)
704 st->balls[i].vy *= 0.9;
707 if (st->viscosity != 1)
709 st->balls[i].vy *= st->viscosity;
712 st->balls[i].x += st->balls[i].vx;
713 st->balls[i].y += st->balls[i].vy;
716 /* bounce off the walls if desired
717 note: a ball is actually its upper left corner */
720 if(st->cbounce_p) /* with correct bouncing */
722 /* so long as it's out of range, keep bouncing */
723 /* limit the maximum number to bounce to 4.*/
724 int bounce_allowed = 4;
726 while( bounce_allowed && (
727 (st->balls[i].x >= (st->xlim - st->balls[i].size)) ||
728 (st->balls[i].y >= (st->ylim - st->balls[i].size)) ||
729 (st->balls[i].x <= 0) ||
730 (st->balls[i].y <= 0) )
734 if (st->balls[i].x >= (st->xlim - st->balls[i].size))
736 st->balls[i].x = (2*(st->xlim - st->balls[i].size) - st->balls[i].x);
737 st->balls[i].vx = -st->balls[i].vx;
739 if (st->balls[i].y >= (st->ylim - st->balls[i].size))
741 st->balls[i].y = (2*(st->ylim - st->balls[i].size) - st->balls[i].y);
742 st->balls[i].vy = -st->balls[i].vy;
744 if (st->balls[i].x <= 0)
746 st->balls[i].x = -st->balls[i].x;
747 st->balls[i].vx = -st->balls[i].vx;
749 if (st->balls[i].y <= 0)
751 st->balls[i].y = -st->balls[i].y;
752 st->balls[i].vy = -st->balls[i].vy;
756 else /* with old bouncing */
758 if (st->balls[i].x >= (st->xlim - st->balls[i].size))
760 st->balls[i].x = (st->xlim - st->balls[i].size - 1);
761 if (st->balls[i].vx > 0) /* why is this check here? */
762 st->balls[i].vx = -st->balls[i].vx;
764 if (st->balls[i].y >= (st->ylim - st->balls[i].size))
766 st->balls[i].y = (st->ylim - st->balls[i].size - 1);
767 if (st->balls[i].vy > 0)
768 st->balls[i].vy = -st->balls[i].vy;
770 if (st->balls[i].x <= 0)
773 if (st->balls[i].vx < 0)
774 st->balls[i].vx = -st->balls[i].vx;
776 if (st->balls[i].y <= 0)
779 if (st->balls[i].vy < 0)
780 st->balls[i].vy = -st->balls[i].vy;
785 if (i == st->mouse_ball)
788 query_mouse (dpy, window, &x, &y);
789 if (st->mode == ball_mode)
791 x -= st->balls[i].size / 2;
792 y -= st->balls[i].size / 2;
799 new_x = st->balls[i].x;
800 new_y = st->balls[i].y;
804 if (st->mode == ball_mode)
808 /* make color saturation be related to particle
812 double vx = st->balls [i].dx;
813 double vy = st->balls [i].dy;
814 if (vx < 0) vx = -vx;
815 if (vy < 0) vy = -vy;
817 if (fraction > limit) fraction = limit;
819 s = 1 - (fraction / limit);
820 st->balls[i].pixel_index = (st->ncolors * s);
822 XSetForeground (dpy, st->draw_gc,
825 : st->colors[st->balls[i].pixel_index].pixel));
829 if (st->mode == ball_mode)
831 XFillArc (dpy, window, st->erase_gc, (int) old_x, (int) old_y,
832 size, size, 0, 360*64);
833 XFillArc (dpy, window, st->draw_gc, (int) new_x, (int) new_y,
834 size, size, 0, 360*64);
838 st->point_stack [st->point_stack_fp].x = new_x;
839 st->point_stack [st->point_stack_fp].y = new_y;
840 st->point_stack_fp++;
844 /* draw the lines or polygons after computing all points */
845 if (st->mode != ball_mode)
847 st->point_stack [st->point_stack_fp].x = st->balls [0].x; /* close the polygon */
848 st->point_stack [st->point_stack_fp].y = st->balls [0].y;
849 st->point_stack_fp++;
850 if (st->point_stack_fp == st->point_stack_size)
851 st->point_stack_fp = 0;
852 else if (st->point_stack_fp > st->point_stack_size) /* better be aligned */
856 if (st->color_tick++ == st->color_shift)
859 st->fg_index = (st->fg_index + 1) % st->ncolors;
860 XSetForeground (dpy, st->draw_gc, st->colors[st->fg_index].pixel);
870 if (st->segments > 0)
871 XDrawLines (dpy, window, st->erase_gc, st->point_stack + st->point_stack_fp,
872 st->npoints + 1, CoordModeOrigin);
873 XDrawLines (dpy, window, st->draw_gc, st->point_stack + last_point_stack_fp,
874 st->npoints + 1, CoordModeOrigin);
877 if (st->segments > 0)
878 XFillPolygon (dpy, window, st->erase_gc, st->point_stack + st->point_stack_fp,
879 st->npoints + 1, (st->npoints == 3 ? Convex : Complex),
881 XFillPolygon (dpy, window, st->draw_gc, st->point_stack + last_point_stack_fp,
882 st->npoints + 1, (st->npoints == 3 ? Convex : Complex),
887 for (i = 0; i < st->npoints; i++)
889 int index = st->point_stack_fp + i;
890 int next_index = (index + (st->npoints + 1)) % st->point_stack_size;
891 if(st->no_erase_yet == 1)
893 if(st->total_ticks >= st->segments)
895 st->no_erase_yet = 0;
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);
905 XDrawLine (dpy, window, st->erase_gc,
906 st->point_stack [index].x + radius,
907 st->point_stack [index].y + radius,
908 st->point_stack [next_index].x + radius,
909 st->point_stack [next_index].y + radius);
911 index = last_point_stack_fp + i;
912 next_index = (index - (st->npoints + 1)) % st->point_stack_size;
913 if (next_index < 0) next_index += st->point_stack_size;
914 if (st->point_stack [next_index].x == 0 &&
915 st->point_stack [next_index].y == 0)
917 XDrawLine (dpy, window, st->draw_gc,
918 st->point_stack [index].x + radius,
919 st->point_stack [index].y + radius,
920 st->point_stack [next_index].x + radius,
921 st->point_stack [next_index].y + radius);
926 case spline_filled_mode:
928 if (! st->spl) st->spl = make_spline (st->npoints);
929 if (st->segments > 0)
931 for (i = 0; i < st->npoints; i++)
933 st->spl->control_x [i] = st->point_stack [st->point_stack_fp + i].x;
934 st->spl->control_y [i] = st->point_stack [st->point_stack_fp + i].y;
936 compute_closed_spline (st->spl);
937 if (st->mode == spline_filled_mode)
938 XFillPolygon (dpy, window, st->erase_gc, st->spl->points, st->spl->n_points,
939 (st->spl->n_points == 3 ? Convex : Complex),
942 XDrawLines (dpy, window, st->erase_gc, st->spl->points, st->spl->n_points,
945 for (i = 0; i < st->npoints; i++)
947 st->spl->control_x [i] = st->point_stack [last_point_stack_fp + i].x;
948 st->spl->control_y [i] = st->point_stack [last_point_stack_fp + i].y;
950 compute_closed_spline (st->spl);
951 if (st->mode == spline_filled_mode)
952 XFillPolygon (dpy, window, st->draw_gc, st->spl->points, st->spl->n_points,
953 (st->spl->n_points == 3 ? Convex : Complex),
956 XDrawLines (dpy, window, st->draw_gc, st->spl->points, st->spl->n_points,
968 attraction_reshape (Display *dpy, Window window, void *closure,
969 unsigned int w, unsigned int h)
971 struct state *st = (struct state *) closure;
977 attraction_event (Display *dpy, Window window, void *closure, XEvent *event)
979 struct state *st = (struct state *) closure;
981 if (event->xany.type == ButtonPress)
984 if (st->mouse_ball != -1) /* second down-click? drop the ball. */
991 /* When trying to pick up a ball, first look for a click directly
992 inside the ball; but if we don't find it, expand the radius
993 outward until we find something nearby.
995 int x = event->xbutton.x;
996 int y = event->xbutton.y;
997 float max = 10 * (st->global_size ? st->global_size : MAX_SIZE);
998 float step = max / 100;
1000 for (r2 = step; r2 < max; r2 += step)
1002 for (i = 0; i < st->npoints; i++)
1004 float d = ((st->balls[i].x - x) * (st->balls[i].x - x) +
1005 (st->balls[i].y - y) * (st->balls[i].y - y));
1006 float r = st->balls[i].size;
1018 else if (event->xany.type == ButtonRelease) /* drop the ball */
1020 st->mouse_ball = -1;
1030 attraction_free (Display *dpy, Window window, void *closure)
1032 struct state *st = (struct state *) closure;
1034 if (st->balls) free (st->balls);
1035 if (st->x_vels) free (st->x_vels);
1036 if (st->y_vels) free (st->y_vels);
1037 if (st->speeds) free (st->speeds);
1038 if (st->point_stack) free (st->point_stack);
1039 if (st->colors) free (st->colors);
1040 if (st->spl) free_spline (st->spl);
1046 static const char *attraction_defaults [] = {
1047 ".background: black",
1048 ".foreground: white",
1069 "*mouseForeground: white",
1071 "*ignoreRotation: True",
1076 static XrmOptionDescRec attraction_options [] = {
1077 { "-mode", ".mode", XrmoptionSepArg, 0 },
1078 { "-graphmode", ".graphmode", XrmoptionSepArg, 0 },
1079 { "-colors", ".colors", XrmoptionSepArg, 0 },
1080 { "-points", ".points", XrmoptionSepArg, 0 },
1081 { "-color-shift", ".colorShift", XrmoptionSepArg, 0 },
1082 { "-threshold", ".threshold", XrmoptionSepArg, 0 },
1083 { "-segments", ".segments", XrmoptionSepArg, 0 },
1084 { "-delay", ".delay", XrmoptionSepArg, 0 },
1085 { "-size", ".size", XrmoptionSepArg, 0 },
1086 { "-radius", ".radius", XrmoptionSepArg, 0 },
1087 { "-vx", ".vx", XrmoptionSepArg, 0 },
1088 { "-vy", ".vy", XrmoptionSepArg, 0 },
1089 { "-vmult", ".vMult", XrmoptionSepArg, 0 },
1090 { "-viscosity", ".viscosity", XrmoptionSepArg, 0 },
1091 { "-glow", ".glow", XrmoptionNoArg, "true" },
1092 { "-noglow", ".glow", XrmoptionNoArg, "false" },
1093 { "-orbit", ".orbit", XrmoptionNoArg, "true" },
1094 { "-nowalls", ".walls", XrmoptionNoArg, "false" },
1095 { "-walls", ".walls", XrmoptionNoArg, "true" },
1096 { "-nomaxspeed", ".maxspeed", XrmoptionNoArg, "false" },
1097 { "-maxspeed", ".maxspeed", XrmoptionNoArg, "true" },
1098 { "-correct-bounce", ".cbounce", XrmoptionNoArg, "false" },
1099 { "-fast-bounce", ".cbounce", XrmoptionNoArg, "true" },
1104 XSCREENSAVER_MODULE ("Attraction", attraction)