1 /* xscreensaver, Copyright (c) 1992, 1995, 1996, 1997, 1998
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 simulation,
21 but, with constant simulation step size, I quickly realized the field being
22 simulated while grossly gravitational was, in fact, non-conservative. It
23 also had the rather annoying behavior of dealing very badly with colliding
24 orbs. Therefore, I implemented a negative-gravity region (with two
25 thresholds; as I read your code, you only implemented one) to prevent orbs
26 from every coming too close together, and added a viscosity factor if the
27 speed of any orb got too fast. This provides a nice stable system with
30 I had experimented with a number of fields including the van der Waals
31 force (very interesting orbiting behavior) and 1/r^3 gravity (not as
32 interesting as 1/r^2). An even normal viscosity (rather than the
33 thresholded version to bleed excess energy) is also not interesting.
34 The 1/r^2, -1/r^2, -10/r^2 thresholds proved not only robust but also
35 interesting -- the orbs never collided and the threshold viscosity fixed
36 the non-conservational problem.
39 > An even normal viscosity (rather than the thresholded version to
40 > bleed excess energy) is also not interesting.
42 unless you make about 200 points.... set the viscosity to about .8
43 and drag the mouse through it. it makes a nice wave which travels
46 And (always the troublemaker) Joe Keane <jgk@jgk.org> sez:
48 Despite what John sez, the field being simulated is always conservative.
49 The real problem is that it uses a simple hack, computing acceleration
50 *based only on the starting position*, instead of a real differential
51 equation solver. Thus you'll always have energy coming out of nowhere,
52 although it's most blatant when balls get close together. If it were
53 done right, you wouldn't need viscosity or artificial limits on how
54 close the balls can get.
59 #include "screenhack.h"
72 static struct ball *balls;
76 static int global_size;
80 static XPoint *point_stack;
81 static int point_stack_size, point_stack_fp;
82 static XColor *colors;
85 static int color_shift;
87 /*flip mods for mouse interaction*/
89 int mouse_x, mouse_y, mouse_mass, root_x, root_y;
90 static double viscosity;
92 static enum object_mode {
93 ball_mode, line_mode, polygon_mode, spline_mode, spline_filled_mode,
97 static GC draw_gc, erase_gc;
101 #define min(a,b) ((a)<(b)?(a):(b))
102 #define max(a,b) ((a)>(b)?(a):(b))
105 init_balls (Display *dpy, Window window)
108 XWindowAttributes xgwa;
110 int xlim, ylim, midx, midy, r, vx, vy;
114 XGetWindowAttributes (dpy, window, &xgwa);
117 cmap = xgwa.colormap;
120 r = get_integer_resource ("radius", "Integer");
121 if (r <= 0 || r > min (xlim/2, ylim/2))
122 r = min (xlim/2, ylim/2) - 50;
123 vx = get_integer_resource ("vx", "Integer");
124 vy = get_integer_resource ("vy", "Integer");
125 npoints = get_integer_resource ("points", "Integer");
127 npoints = 3 + (random () % 5);
128 balls = (struct ball *) malloc (npoints * sizeof (struct ball));
129 segments = get_integer_resource ("segments", "Integer");
130 if (segments < 0) segments = 1;
131 threshold = get_integer_resource ("threshold", "Integer");
132 if (threshold < 0) threshold = 0;
133 delay = get_integer_resource ("delay", "Integer");
134 if (delay < 0) delay = 0;
135 global_size = get_integer_resource ("size", "Integer");
136 if (global_size < 0) global_size = 0;
137 glow_p = get_boolean_resource ("glow", "Boolean");
138 orbit_p = get_boolean_resource ("orbit", "Boolean");
139 color_shift = get_integer_resource ("colorShift", "Integer");
140 if (color_shift <= 0) color_shift = 5;
142 /*flip mods for mouse interaction*/
143 mouse_p = get_boolean_resource ("mouse", "Boolean");
144 mouse_mass = get_integer_resource ("mouseSize", "Integer");
145 mouse_mass = mouse_mass * mouse_mass *10;
147 viscosity = get_float_resource ("viscosity", "Float");
149 mode_str = get_string_resource ("mode", "Mode");
150 if (! mode_str) mode = ball_mode;
151 else if (!strcmp (mode_str, "balls")) mode = ball_mode;
152 else if (!strcmp (mode_str, "lines")) mode = line_mode;
153 else if (!strcmp (mode_str, "polygons")) mode = polygon_mode;
154 else if (!strcmp (mode_str, "tails")) mode = tail_mode;
155 else if (!strcmp (mode_str, "splines")) mode = spline_mode;
156 else if (!strcmp (mode_str, "filled-splines")) mode = spline_filled_mode;
159 "%s: mode must be balls, lines, tails, polygons, splines, or\n\
160 filled-splines, not \"%s\"\n",
165 if (mode != ball_mode && mode != tail_mode) glow_p = False;
167 if (mode == polygon_mode && npoints < 3)
170 ncolors = get_integer_resource ("colors", "Colors");
171 if (ncolors < 2) ncolors = 2;
172 if (ncolors <= 2) mono_p = True;
183 int H = random() % 360;
186 double V = frand(0.25) + 0.75;
187 colors = (XColor *) malloc(sizeof(*colors) * (ncolors+1));
188 make_color_ramp (dpy, cmap, H, S1, V, H, S2, V, colors, &ncolors,
194 colors = (XColor *) malloc(sizeof(*colors) * (ncolors+1));
195 make_random_colormap (dpy, xgwa.visual, cmap, colors, &ncolors,
196 True, True, False, True);
202 case spline_filled_mode:
204 colors = (XColor *) malloc(sizeof(*colors) * (ncolors+1));
205 make_smooth_colormap (dpy, xgwa.visual, cmap, colors, &ncolors,
213 if (!mono_p && ncolors <= 2)
215 if (colors) free (colors);
220 if (mode != ball_mode)
222 int size = (segments ? segments : 1);
223 point_stack_size = size * (npoints + 1);
224 point_stack = (XPoint *) calloc (point_stack_size, sizeof (XPoint));
228 gcv.line_width = (mode == tail_mode
229 ? (global_size ? global_size : (MAX_SIZE * 2 / 3))
231 gcv.cap_style = (mode == tail_mode ? CapRound : CapButt);
234 gcv.foreground = get_pixel_resource("foreground", "Foreground", dpy, cmap);
236 gcv.foreground = colors[fg_index].pixel;
237 draw_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle, &gcv);
239 gcv.foreground = get_pixel_resource("background", "Background", dpy, cmap);
240 erase_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle,&gcv);
243 #define rand_size() min (MAX_SIZE, 8 + (random () % (MAX_SIZE - 9)))
245 if (orbit_p && !global_size)
246 /* To orbit, all objects must be the same mass, or the math gets
248 global_size = rand_size ();
250 th = frand (M_PI+M_PI);
251 for (i = 0; i < npoints; i++)
253 int new_size = (global_size ? global_size : rand_size ());
256 balls [i].size = new_size;
257 balls [i].mass = (new_size * new_size * 10);
258 balls [i].x = midx + r * cos (i * ((M_PI+M_PI) / npoints) + th);
259 balls [i].y = midy + r * sin (i * ((M_PI+M_PI) / npoints) + th);
262 balls [i].vx = vx ? vx : ((6.0 - (random () % 11)) / 8.0);
263 balls [i].vy = vy ? vy : ((6.0 - (random () % 11)) / 8.0);
265 if (mono_p || mode != ball_mode)
266 balls [i].pixel_index = -1;
268 balls [i].pixel_index = 0;
270 balls [i].pixel_index = random() % ncolors;
277 double v_mult = get_float_resource ("vMult", "Float");
278 if (v_mult == 0.0) v_mult = 1.0;
280 for (i = 1; i < npoints; i++)
282 double _2ipi_n = (2 * i * M_PI / npoints);
283 double x = r * cos (_2ipi_n);
284 double y = r * sin (_2ipi_n);
285 double distx = r - x;
286 double dist2 = (distx * distx) + (y * y);
287 double dist = sqrt (dist2);
288 double a1 = ((balls[i].mass / dist2) *
289 ((dist < threshold) ? -1.0 : 1.0) *
295 fprintf (stderr, "%s: domain error: forces on balls too great\n",
299 v = sqrt (a * r) * v_mult;
300 for (i = 0; i < npoints; i++)
302 double k = ((2 * i * M_PI / npoints) + th);
303 balls [i].vx = -v * sin (k);
304 balls [i].vy = v * cos (k);
308 if (mono_p) glow_p = False;
309 XClearWindow (dpy, window);
313 compute_force (int i, double *dx_ret, double *dy_ret)
316 double x_dist, y_dist, dist, dist2;
319 for (j = 0; j < npoints; j++)
321 if (i == j) continue;
322 x_dist = balls [j].x - balls [i].x;
323 y_dist = balls [j].y - balls [i].y;
324 dist2 = (x_dist * x_dist) + (y_dist * y_dist);
327 if (dist > 0.1) /* the balls are not overlapping */
329 double new_acc = ((balls[j].mass / dist2) *
330 ((dist < threshold) ? -1.0 : 1.0));
331 double new_acc_dist = new_acc / dist;
332 *dx_ret += new_acc_dist * x_dist;
333 *dy_ret += new_acc_dist * y_dist;
336 { /* the balls are overlapping; move randomly */
337 *dx_ret += (frand (10.0) - 5.0);
338 *dy_ret += (frand (10.0) - 5.0);
344 x_dist = mouse_x - balls [i].x;
345 y_dist = mouse_y - balls [i].y;
346 dist2 = (x_dist * x_dist) + (y_dist * y_dist);
349 if (dist > 0.1) /* the balls are not overlapping */
351 double new_acc = ((mouse_mass / dist2) *
352 ((dist < threshold) ? -1.0 : 1.0));
353 double new_acc_dist = new_acc / dist;
354 *dx_ret += new_acc_dist * x_dist;
355 *dy_ret += new_acc_dist * y_dist;
358 { /* the balls are overlapping; move randomly */
359 *dx_ret += (frand (10.0) - 5.0);
360 *dy_ret += (frand (10.0) - 5.0);
366 run_balls (Display *dpy, Window window)
368 int last_point_stack_fp = point_stack_fp;
369 static int tick = 500, xlim, ylim;
370 static Colormap cmap;
373 /*flip mods for mouse interaction*/
374 Window root1, child1;
378 XQueryPointer(dpy, window, &root1, &child1,
379 &root_x, &root_y, &mouse_x, &mouse_y, &mask);
384 XWindowAttributes xgwa;
385 XGetWindowAttributes (dpy, window, &xgwa);
389 cmap = xgwa.colormap;
392 /* compute the force of attraction/repulsion among all balls */
393 for (i = 0; i < npoints; i++)
394 compute_force (i, &balls[i].dx, &balls[i].dy);
396 /* move the balls according to the forces now in effect */
397 for (i = 0; i < npoints; i++)
399 double old_x = balls[i].x;
400 double old_y = balls[i].y;
402 int size = balls[i].size;
403 balls[i].vx += balls[i].dx;
404 balls[i].vy += balls[i].dy;
406 /* don't let them get too fast: impose a terminal velocity
407 (actually, make the medium have friction) */
408 if (balls[i].vx > 10)
413 else if (viscosity != 1)
415 balls[i].vx *= viscosity;
418 if (balls[i].vy > 10)
423 else if (viscosity != 1)
425 balls[i].vy *= viscosity;
428 balls[i].x += balls[i].vx;
429 balls[i].y += balls[i].vy;
431 /* bounce off the walls */
432 if (balls[i].x >= (xlim - balls[i].size))
434 balls[i].x = (xlim - balls[i].size - 1);
436 balls[i].vx = -balls[i].vx;
438 if (balls[i].y >= (ylim - balls[i].size))
440 balls[i].y = (ylim - balls[i].size - 1);
442 balls[i].vy = -balls[i].vy;
448 balls[i].vx = -balls[i].vx;
454 balls[i].vy = -balls[i].vy;
462 if (mode == ball_mode)
466 /* make color saturation be related to particle
470 double vx = balls [i].dx;
471 double vy = balls [i].dy;
472 if (vx < 0) vx = -vx;
473 if (vy < 0) vy = -vy;
475 if (fraction > limit) fraction = limit;
477 s = 1 - (fraction / limit);
478 balls[i].pixel_index = (ncolors * s);
480 XSetForeground (dpy, draw_gc,
481 colors[balls[i].pixel_index].pixel);
485 if (mode == ball_mode)
487 XFillArc (dpy, window, erase_gc, (int) old_x, (int) old_y,
488 size, size, 0, 360*64);
489 XFillArc (dpy, window, draw_gc, (int) new_x, (int) new_y,
490 size, size, 0, 360*64);
494 point_stack [point_stack_fp].x = new_x;
495 point_stack [point_stack_fp].y = new_y;
500 /* draw the lines or polygons after computing all points */
501 if (mode != ball_mode)
503 point_stack [point_stack_fp].x = balls [0].x; /* close the polygon */
504 point_stack [point_stack_fp].y = balls [0].y;
506 if (point_stack_fp == point_stack_size)
508 else if (point_stack_fp > point_stack_size) /* better be aligned */
513 if (tick++ == color_shift)
516 fg_index = (fg_index + 1) % ncolors;
517 XSetForeground (dpy, draw_gc, colors[fg_index].pixel);
528 XDrawLines (dpy, window, erase_gc, point_stack + point_stack_fp,
529 npoints + 1, CoordModeOrigin);
530 XDrawLines (dpy, window, draw_gc, point_stack + last_point_stack_fp,
531 npoints + 1, CoordModeOrigin);
535 XFillPolygon (dpy, window, erase_gc, point_stack + point_stack_fp,
536 npoints + 1, (npoints == 3 ? Convex : Complex),
538 XFillPolygon (dpy, window, draw_gc, point_stack + last_point_stack_fp,
539 npoints + 1, (npoints == 3 ? Convex : Complex),
544 for (i = 0; i < npoints; i++)
546 int index = point_stack_fp + i;
547 int next_index = (index + (npoints + 1)) % point_stack_size;
548 XDrawLine (dpy, window, erase_gc,
549 point_stack [index].x,
550 point_stack [index].y,
551 point_stack [next_index].x,
552 point_stack [next_index].y);
554 index = last_point_stack_fp + i;
555 next_index = (index - (npoints + 1)) % point_stack_size;
556 if (next_index < 0) next_index += point_stack_size;
557 if (point_stack [next_index].x == 0 &&
558 point_stack [next_index].y == 0)
560 XDrawLine (dpy, window, draw_gc,
561 point_stack [index].x,
562 point_stack [index].y,
563 point_stack [next_index].x,
564 point_stack [next_index].y);
569 case spline_filled_mode:
571 static spline *s = 0;
572 if (! s) s = make_spline (npoints);
575 for (i = 0; i < npoints; i++)
577 s->control_x [i] = point_stack [point_stack_fp + i].x;
578 s->control_y [i] = point_stack [point_stack_fp + i].y;
580 compute_closed_spline (s);
581 if (mode == spline_filled_mode)
582 XFillPolygon (dpy, window, erase_gc, s->points, s->n_points,
583 (s->n_points == 3 ? Convex : Complex),
586 XDrawLines (dpy, window, erase_gc, s->points, s->n_points,
589 for (i = 0; i < npoints; i++)
591 s->control_x [i] = point_stack [last_point_stack_fp + i].x;
592 s->control_y [i] = point_stack [last_point_stack_fp + i].y;
594 compute_closed_spline (s);
595 if (mode == spline_filled_mode)
596 XFillPolygon (dpy, window, draw_gc, s->points, s->n_points,
597 (s->n_points == 3 ? Convex : Complex),
600 XDrawLines (dpy, window, draw_gc, s->points, s->n_points,
612 char *progclass = "Attraction";
614 char *defaults [] = {
615 ".background: black",
616 ".foreground: white",
634 XrmOptionDescRec options [] = {
635 { "-mode", ".mode", XrmoptionSepArg, 0 },
636 { "-colors", ".colors", XrmoptionSepArg, 0 },
637 { "-points", ".points", XrmoptionSepArg, 0 },
638 { "-color-shift", ".colorShift", XrmoptionSepArg, 0 },
639 { "-threshold", ".threshold", XrmoptionSepArg, 0 },
640 { "-segments", ".segments", XrmoptionSepArg, 0 },
641 { "-delay", ".delay", XrmoptionSepArg, 0 },
642 { "-size", ".size", XrmoptionSepArg, 0 },
643 { "-radius", ".radius", XrmoptionSepArg, 0 },
644 { "-vx", ".vx", XrmoptionSepArg, 0 },
645 { "-vy", ".vy", XrmoptionSepArg, 0 },
646 { "-vmult", ".vMult", XrmoptionSepArg, 0 },
647 { "-mouse-size", ".mouseSize", XrmoptionSepArg, 0 },
648 { "-mouse", ".mouse", XrmoptionNoArg, "true" },
649 { "-nomouse", ".mouse", XrmoptionNoArg, "false" },
650 { "-viscosity", ".viscosity", XrmoptionSepArg, 0 },
651 { "-glow", ".glow", XrmoptionNoArg, "true" },
652 { "-noglow", ".glow", XrmoptionNoArg, "false" },
653 { "-orbit", ".orbit", XrmoptionNoArg, "true" },
658 screenhack (Display *dpy, Window window)
660 init_balls (dpy, window);
663 run_balls (dpy, window);
664 screenhack_handle_events (dpy);
665 if (delay) usleep (delay);