1 /* xscreensaver, Copyright (c) 1992, 1995 Jamie Zawinski <jwz@netscape.com>
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 simulation,
20 but, with constant simulation step size, I quickly realized the field being
21 simulated while grossly gravitational was, in fact, non-conservative. It
22 also had the rather annoying behavior of dealing very badly with colliding
23 orbs. Therefore, I implemented a negative-gravity region (with two
24 thresholds; as I read your code, you only implemented one) to prevent orbs
25 from every coming too close together, and added a viscosity factor if the
26 speed of any orb got too fast. This provides a nice stable system with
29 I had experimented with a number of fields including the van der Waals
30 force (very interesting orbiting behavior) and 1/r^3 gravity (not as
31 interesting as 1/r^2). An even normal viscosity (rather than the
32 thresholded version to bleed excess energy) is also not interesting.
33 The 1/r^2, -1/r^2, -10/r^2 thresholds proved not only robust but also
34 interesting -- the orbs never collided and the threshold viscosity fixed
35 the non-conservational problem.
38 > An even normal viscosity (rather than the thresholded version to
39 > bleed excess energy) is also not interesting.
41 unless you make about 200 points.... set the viscosity to about .8
42 and drag the mouse through it. it makes a nice wave which travels
48 #include "screenhack.h"
61 static unsigned int default_fg_pixel;
62 static struct ball *balls;
66 static int global_size;
70 static XPoint *point_stack;
71 static int point_stack_size, point_stack_fp, pixel_stack_fp, pixel_stack_size;
72 static unsigned long *pixel_stack;
73 static unsigned int color_shift;
75 /*flip mods for mouse interaction*/
77 int mouse_x, mouse_y, mouse_mass, root_x, root_y;
78 static float viscosity;
80 static enum object_mode {
81 ball_mode, line_mode, polygon_mode, spline_mode, spline_filled_mode,
85 static enum color_mode {
86 cycle_mode, random_mode
89 static GC draw_gc, erase_gc;
93 #define min(a,b) ((a)<(b)?(a):(b))
94 #define max(a,b) ((a)>(b)?(a):(b))
97 init_balls (dpy, window)
102 XWindowAttributes xgwa;
104 int xlim, ylim, midx, midy, r, vx, vy;
108 XGetWindowAttributes (dpy, window, &xgwa);
111 cmap = xgwa.colormap;
114 r = get_integer_resource ("radius", "Integer");
115 if (r <= 0 || r > min (xlim/2, ylim/2))
116 r = min (xlim/2, ylim/2) - 50;
117 vx = get_integer_resource ("vx", "Integer");
118 vy = get_integer_resource ("vy", "Integer");
119 npoints = get_integer_resource ("points", "Integer");
121 npoints = 3 + (random () % 5);
122 balls = (struct ball *) malloc (npoints * sizeof (struct ball));
123 segments = get_integer_resource ("segments", "Integer");
124 if (segments < 0) segments = 1;
125 threshold = get_integer_resource ("threshold", "Integer");
126 if (threshold < 0) threshold = 0;
127 delay = get_integer_resource ("delay", "Integer");
128 if (delay < 0) delay = 0;
129 global_size = get_integer_resource ("size", "Integer");
130 if (global_size < 0) global_size = 0;
131 glow_p = get_boolean_resource ("glow", "Boolean");
132 orbit_p = get_boolean_resource ("orbit", "Boolean");
133 color_shift = get_integer_resource ("colorShift", "Integer");
134 if (color_shift >= 360) color_shift = 5;
136 /*flip mods for mouse interaction*/
137 mouse_p = get_boolean_resource ("mouse", "Boolean");
138 mouse_mass = get_integer_resource ("mouseSize", "Integer");
139 mouse_mass = mouse_mass * mouse_mass *10;
141 viscosity = get_float_resource ("viscosity", "Float");
143 mode_str = get_string_resource ("mode", "Mode");
144 if (! mode_str) mode = ball_mode;
145 else if (!strcmp (mode_str, "balls")) mode = ball_mode;
146 else if (!strcmp (mode_str, "lines")) mode = line_mode;
147 else if (!strcmp (mode_str, "polygons")) mode = polygon_mode;
148 else if (!strcmp (mode_str, "tails")) mode = tail_mode;
149 else if (!strcmp (mode_str, "splines")) mode = spline_mode;
150 else if (!strcmp (mode_str, "filled-splines")) mode = spline_filled_mode;
153 "%s: mode must be balls, lines, tails, polygons, splines, or\n\
154 filled-splines, not \"%s\"\n",
159 mode_str = get_string_resource ("colorMode", "ColorMode");
160 if (! mode_str) cmode = cycle_mode;
161 else if (!strcmp (mode_str, "cycle")) cmode = cycle_mode;
162 else if (!strcmp (mode_str, "random")) cmode = random_mode;
164 fprintf (stderr, "%s: colorMode must be cycle or random, not \"%s\"\n",
169 if (mode != ball_mode && mode != tail_mode) glow_p = False;
171 if (mode == polygon_mode && npoints < 3)
174 if (mode != ball_mode)
176 int size = (segments ? segments : 1);
177 point_stack_size = size * (npoints + 1);
178 point_stack = (XPoint *) calloc (point_stack_size, sizeof (XPoint));
181 pixel_stack_size = segments;
183 pixel_stack_size = (360 / color_shift);
184 pixel_stack = (unsigned long *)
185 calloc (pixel_stack_size, sizeof (unsigned int));
189 gcv.line_width = (mode == tail_mode
190 ? (global_size ? global_size : (MAX_SIZE * 2 / 3))
192 gcv.cap_style = (mode == tail_mode ? CapRound : CapButt);
194 gcv.foreground = default_fg_pixel =
195 get_pixel_resource ("foreground", "Foreground", dpy, cmap);
196 draw_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle, &gcv);
197 gcv.foreground = get_pixel_resource ("background", "Background", dpy, cmap);
198 erase_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle,&gcv);
200 if (!mono_p && mode != ball_mode)
201 for (i = 0; i < pixel_stack_size; i++)
204 color.pixel = default_fg_pixel;
205 XQueryColor (dpy, cmap, &color);
206 if (!XAllocColor (dpy, cmap, &color)) abort ();
207 pixel_stack [i] = color.pixel;
210 #define rand_size() min (MAX_SIZE, 8 + (random () % (MAX_SIZE - 9)))
212 if (orbit_p && !global_size)
213 /* To orbit, all objects must be the same mass, or the math gets
215 global_size = rand_size ();
217 th = frand (M_PI+M_PI);
218 for (i = 0; i < npoints; i++)
220 int new_size = (global_size ? global_size : rand_size ());
223 balls [i].size = new_size;
224 balls [i].mass = (new_size * new_size * 10);
225 balls [i].x = midx + r * cos (i * ((M_PI+M_PI) / npoints) + th);
226 balls [i].y = midy + r * sin (i * ((M_PI+M_PI) / npoints) + th);
229 balls [i].vx = vx ? vx : ((6.0 - (random () % 11)) / 8.0);
230 balls [i].vy = vy ? vy : ((6.0 - (random () % 11)) / 8.0);
232 balls [i].color.pixel = default_fg_pixel;
233 balls [i].color.flags = DoRed | DoGreen | DoBlue;
236 if (i != 0 && (glow_p || mode != ball_mode))
237 balls [i].hue = balls [0].hue;
239 balls [i].hue = random () % 360;
240 hsv_to_rgb (balls [i].hue, 1.0, 1.0,
241 &balls [i].color.red, &balls [i].color.green,
242 &balls [i].color.blue);
243 if (!XAllocColor (dpy, cmap, &balls [i].color))
244 mono_p = True; /* just give up */
252 double v_mult = get_float_resource ("vMult", "Float");
253 if (v_mult == 0.0) v_mult = 1.0;
255 for (i = 1; i < npoints; i++)
257 double _2ipi_n = (2 * i * M_PI / npoints);
258 double x = r * cos (_2ipi_n);
259 double y = r * sin (_2ipi_n);
260 double distx = r - x;
261 double dist2 = (distx * distx) + (y * y);
262 double dist = sqrt (dist2);
263 double a1 = ((balls[i].mass / dist2) *
264 ((dist < threshold) ? -1.0 : 1.0) *
270 fprintf (stderr, "%s: domain error: forces on balls too great\n",
274 v = sqrt (a * r) * v_mult;
275 for (i = 0; i < npoints; i++)
277 double k = ((2 * i * M_PI / npoints) + th);
278 balls [i].vx = -v * sin (k);
279 balls [i].vy = v * cos (k);
283 if (mono_p) glow_p = False;
284 XClearWindow (dpy, window);
288 compute_force (i, dx_ret, dy_ret)
290 float *dx_ret, *dy_ret;
293 float x_dist, y_dist, dist, dist2;
296 for (j = 0; j < npoints; j++)
298 float x_dist, y_dist, dist, dist2;
300 if (i == j) continue;
301 x_dist = balls [j].x - balls [i].x;
302 y_dist = balls [j].y - balls [i].y;
303 dist2 = (x_dist * x_dist) + (y_dist * y_dist);
306 if (dist > 0.1) /* the balls are not overlapping */
308 float new_acc = ((balls[j].mass / dist2) *
309 ((dist < threshold) ? -1.0 : 1.0));
310 float new_acc_dist = new_acc / dist;
311 *dx_ret += new_acc_dist * x_dist;
312 *dy_ret += new_acc_dist * y_dist;
315 { /* the balls are overlapping; move randomly */
316 *dx_ret += (frand (10.0) - 5.0);
317 *dy_ret += (frand (10.0) - 5.0);
323 x_dist = mouse_x - balls [i].x;
324 y_dist = mouse_y - balls [i].y;
325 dist2 = (x_dist * x_dist) + (y_dist * y_dist);
328 if (dist > 0.1) /* the balls are not overlapping */
330 float new_acc = ((mouse_mass / dist2) *
331 ((dist < threshold) ? -1.0 : 1.0));
332 float new_acc_dist = new_acc / dist;
333 *dx_ret += new_acc_dist * x_dist;
334 *dy_ret += new_acc_dist * y_dist;
337 { /* the balls are overlapping; move randomly */
338 *dx_ret += (frand (10.0) - 5.0);
339 *dy_ret += (frand (10.0) - 5.0);
345 run_balls (dpy, window)
349 int last_point_stack_fp = point_stack_fp;
350 static int tick = 500, xlim, ylim;
351 static Colormap cmap;
354 /*flip mods for mouse interaction*/
355 Window root1, child1;
359 XQueryPointer(dpy, window, &root1, &child1,
360 &root_x, &root_y, &mouse_x, &mouse_y, &mask);
365 XWindowAttributes xgwa;
366 XGetWindowAttributes (dpy, window, &xgwa);
370 cmap = xgwa.colormap;
373 /* compute the force of attraction/repulsion among all balls */
374 for (i = 0; i < npoints; i++)
375 compute_force (i, &balls[i].dx, &balls[i].dy);
377 /* move the balls according to the forces now in effect */
378 for (i = 0; i < npoints; i++)
380 float old_x = balls[i].x;
381 float old_y = balls[i].y;
383 int size = balls[i].size;
384 balls[i].vx += balls[i].dx;
385 balls[i].vy += balls[i].dy;
387 /* don't let them get too fast: impose a terminal velocity
388 (actually, make the medium have friction) */
389 if (balls[i].vx > 10)
394 else if (viscosity != 1)
396 balls[i].vx *= viscosity;
399 if (balls[i].vy > 10)
404 else if (viscosity != 1)
406 balls[i].vy *= viscosity;
409 balls[i].x += balls[i].vx;
410 balls[i].y += balls[i].vy;
412 /* bounce off the walls */
413 if (balls[i].x >= (xlim - balls[i].size))
415 balls[i].x = (xlim - balls[i].size - 1);
417 balls[i].vx = -balls[i].vx;
419 if (balls[i].y >= (ylim - balls[i].size))
421 balls[i].y = (ylim - balls[i].size - 1);
423 balls[i].vy = -balls[i].vy;
429 balls[i].vx = -balls[i].vx;
435 balls[i].vy = -balls[i].vy;
441 /* make color saturation be related to particle acceleration. */
445 double s, v, fraction;
446 float vx = balls [i].dx;
447 float vy = balls [i].dy;
449 if (vx < 0) vx = -vx;
450 if (vy < 0) vy = -vy;
452 if (fraction > limit) fraction = limit;
454 s = 1 - (fraction / limit);
457 s = (s * 0.75) + 0.25;
459 hsv_to_rgb (balls [i].hue, s, v,
460 &new_color.red, &new_color.green, &new_color.blue);
461 if (XAllocColor (dpy, cmap, &new_color))
463 XFreeColors (dpy, cmap, &balls [i].color.pixel, 1, 0);
464 balls [i].color = new_color;
468 if (mode == ball_mode)
471 XSetForeground (dpy, draw_gc, balls [i].color.pixel);
472 XFillArc (dpy, window, erase_gc, (int) old_x, (int) old_y,
473 size, size, 0, 360*64);
474 XFillArc (dpy, window, draw_gc, (int) new_x, (int) new_y,
475 size, size, 0, 360*64);
477 if (mode != ball_mode)
479 point_stack [point_stack_fp].x = new_x;
480 point_stack [point_stack_fp].y = new_y;
485 /* draw the lines or polygons after computing all points */
486 if (mode != ball_mode)
488 point_stack [point_stack_fp].x = balls [0].x; /* close the polygon */
489 point_stack [point_stack_fp].y = balls [0].y;
491 if (point_stack_fp == point_stack_size)
493 else if (point_stack_fp > point_stack_size) /* better be aligned */
497 XColor color2, desired;
498 color2 = balls [0].color;
502 cycle_hue (&color2, color_shift);
505 color2.red = random () % 65535;
506 color2.green = random () % 65535;
507 color2.blue = random () % 65535;
514 if (XAllocColor (dpy, cmap, &color2))
516 /* XAllocColor returns the actual RGB that the hardware let us
517 allocate. Restore the requested values into the XColor struct
518 so that limited-resolution hardware doesn't cause cycle_hue to
520 color2.red = desired.red;
521 color2.green = desired.green;
522 color2.blue = desired.blue;
526 color2 = balls [0].color;
527 if (!XAllocColor (dpy, cmap, &balls [0].color))
530 pixel_stack [pixel_stack_fp++] = balls [0].color.pixel;
531 if (pixel_stack_fp >= pixel_stack_size)
533 XFreeColors (dpy, cmap, pixel_stack + pixel_stack_fp, 1, 0);
534 balls [0].color = color2;
535 XSetForeground (dpy, draw_gc, balls [0].color.pixel);
545 XDrawLines (dpy, window, erase_gc, point_stack + point_stack_fp,
546 npoints + 1, CoordModeOrigin);
547 XDrawLines (dpy, window, draw_gc, point_stack + last_point_stack_fp,
548 npoints + 1, CoordModeOrigin);
552 XFillPolygon (dpy, window, erase_gc, point_stack + point_stack_fp,
553 npoints + 1, (npoints == 3 ? Convex : Complex),
555 XFillPolygon (dpy, window, draw_gc, point_stack + last_point_stack_fp,
556 npoints + 1, (npoints == 3 ? Convex : Complex),
562 for (i = 0; i < npoints; i++)
564 int index = point_stack_fp + i;
565 int next_index = (index + (npoints + 1)) % point_stack_size;
566 XDrawLine (dpy, window, erase_gc,
567 point_stack [index].x,
568 point_stack [index].y,
569 point_stack [next_index].x,
570 point_stack [next_index].y);
572 index = last_point_stack_fp + i;
573 next_index = (index - (npoints + 1)) % point_stack_size;
574 if (next_index < 0) next_index += point_stack_size;
575 if (point_stack [next_index].x == 0 &&
576 point_stack [next_index].y == 0)
578 XDrawLine (dpy, window, draw_gc,
579 point_stack [index].x,
580 point_stack [index].y,
581 point_stack [next_index].x,
582 point_stack [next_index].y);
587 case spline_filled_mode:
590 static spline *s = 0;
591 if (! s) s = make_spline (npoints);
594 for (i = 0; i < npoints; i++)
596 s->control_x [i] = point_stack [point_stack_fp + i].x;
597 s->control_y [i] = point_stack [point_stack_fp + i].y;
599 compute_closed_spline (s);
600 if (mode == spline_filled_mode)
601 XFillPolygon (dpy, window, erase_gc, s->points, s->n_points,
602 (s->n_points == 3 ? Convex : Complex),
605 XDrawLines (dpy, window, erase_gc, s->points, s->n_points,
608 for (i = 0; i < npoints; i++)
610 s->control_x [i] = point_stack [last_point_stack_fp + i].x;
611 s->control_y [i] = point_stack [last_point_stack_fp + i].y;
613 compute_closed_spline (s);
614 if (mode == spline_filled_mode)
615 XFillPolygon (dpy, window, draw_gc, s->points, s->n_points,
616 (s->n_points == 3 ? Convex : Complex),
619 XDrawLines (dpy, window, draw_gc, s->points, s->n_points,
631 char *progclass = "Attraction";
633 char *defaults [] = {
634 "Attraction.background: black", /* to placate SGI */
635 "Attraction.foreground: white",
651 XrmOptionDescRec options [] = {
652 { "-mode", ".mode", XrmoptionSepArg, 0 },
653 { "-points", ".points", XrmoptionSepArg, 0 },
654 { "-threshold", ".threshold", XrmoptionSepArg, 0 },
655 { "-segments", ".segments", XrmoptionSepArg, 0 },
656 { "-delay", ".delay", XrmoptionSepArg, 0 },
657 { "-size", ".size", XrmoptionSepArg, 0 },
658 { "-color-mode", ".colorMode", XrmoptionSepArg, 0 },
659 { "-color-shift", ".colorShift", XrmoptionSepArg, 0 },
660 { "-radius", ".radius", XrmoptionSepArg, 0 },
661 { "-vx", ".vx", XrmoptionSepArg, 0 },
662 { "-vy", ".vy", XrmoptionSepArg, 0 },
663 { "-vmult", ".vMult", XrmoptionSepArg, 0 },
664 { "-mouse-size", ".mouseSize", XrmoptionSepArg, 0 },
665 { "-mouse", ".mouse", XrmoptionNoArg, "true" },
666 { "-nomouse", ".mouse", XrmoptionNoArg, "false" },
667 { "-viscosity", ".viscosity", XrmoptionSepArg, 0 },
668 { "-glow", ".glow", XrmoptionNoArg, "true" },
669 { "-noglow", ".glow", XrmoptionNoArg, "false" },
670 { "-orbit", ".orbit", XrmoptionNoArg, "true" }
672 int options_size = (sizeof (options) / sizeof (options[0]));
675 screenhack (dpy, window)
679 init_balls (dpy, window);
682 run_balls (dpy, window);
683 if (delay) usleep (delay);