1 | /* xscreensaver, Copyright (c) 1992, 1995, 1996, 1997, 1998, 2001 |
---|
2 | * Jamie Zawinski <jwz@jwz.org> |
---|
3 | * |
---|
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 |
---|
10 | * implied warranty. |
---|
11 | */ |
---|
12 | |
---|
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>. |
---|
17 | |
---|
18 | John sez: |
---|
19 | |
---|
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. |
---|
30 | |
---|
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. |
---|
39 | |
---|
40 | Philip sez: |
---|
41 | > An even normal viscosity (rather than the thresholded version to |
---|
42 | > bleed excess energy) is also not interesting. |
---|
43 | |
---|
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 |
---|
46 | through the field. |
---|
47 | |
---|
48 | And (always the troublemaker) Joe Keane <jgk@jgk.org> sez: |
---|
49 | |
---|
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 |
---|
57 | the balls can get. |
---|
58 | |
---|
59 | Matt <straitm@carleton.edu> sez: |
---|
60 | |
---|
61 | Added a switch to remove the walls. |
---|
62 | |
---|
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. |
---|
66 | |
---|
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 |
---|
69 | each point. |
---|
70 | |
---|
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 |
---|
75 | before hitting them.) |
---|
76 | |
---|
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. */ |
---|
84 | |
---|
85 | #include <stdio.h> |
---|
86 | #include <math.h> |
---|
87 | #include "screenhack.h" |
---|
88 | #include "spline.h" |
---|
89 | |
---|
90 | struct ball { |
---|
91 | double x, y; |
---|
92 | double vx, vy; |
---|
93 | double dx, dy; |
---|
94 | double mass; |
---|
95 | int size; |
---|
96 | int pixel_index; |
---|
97 | int hue; |
---|
98 | }; |
---|
99 | |
---|
100 | static struct ball *balls; |
---|
101 | static double *x_vels; |
---|
102 | static double *y_vels; |
---|
103 | static double *speeds; |
---|
104 | static int npoints; |
---|
105 | static int threshold; |
---|
106 | static int delay; |
---|
107 | static int global_size; |
---|
108 | static int segments; |
---|
109 | static Bool glow_p; |
---|
110 | static Bool orbit_p; |
---|
111 | static Bool walls_p; |
---|
112 | static Bool maxspeed_p; |
---|
113 | static Bool cbounce_p; |
---|
114 | static XPoint *point_stack; |
---|
115 | static int point_stack_size, point_stack_fp; |
---|
116 | static XColor *colors; |
---|
117 | static int ncolors; |
---|
118 | static int fg_index; |
---|
119 | static int color_shift; |
---|
120 | Bool no_erase_yet; /* for tail mode fix */ |
---|
121 | |
---|
122 | /*flip mods for mouse interaction*/ |
---|
123 | static Bool mouse_p; |
---|
124 | int mouse_x, mouse_y, mouse_mass, root_x, root_y; |
---|
125 | static double viscosity; |
---|
126 | |
---|
127 | static enum object_mode { |
---|
128 | ball_mode, line_mode, polygon_mode, spline_mode, spline_filled_mode, |
---|
129 | tail_mode |
---|
130 | } mode; |
---|
131 | |
---|
132 | static enum graph_mode { |
---|
133 | graph_none, graph_x, graph_y, graph_both, graph_speed |
---|
134 | } graph_mode; |
---|
135 | |
---|
136 | static GC draw_gc, erase_gc; |
---|
137 | |
---|
138 | /* The normal (and max) width for a graph bar */ |
---|
139 | #define BAR_SIZE 11 |
---|
140 | #define MAX_SIZE 16 |
---|
141 | #define min(a,b) ((a)<(b)?(a):(b)) |
---|
142 | #define max(a,b) ((a)>(b)?(a):(b)) |
---|
143 | |
---|
144 | static void |
---|
145 | init_balls (Display *dpy, Window window) |
---|
146 | { |
---|
147 | int i; |
---|
148 | XWindowAttributes xgwa; |
---|
149 | XGCValues gcv; |
---|
150 | int xlim, ylim, midx, midy, r, vx, vy; |
---|
151 | double th; |
---|
152 | Colormap cmap; |
---|
153 | char *mode_str, *graph_mode_str; |
---|
154 | |
---|
155 | XGetWindowAttributes (dpy, window, &xgwa); |
---|
156 | xlim = xgwa.width; |
---|
157 | ylim = xgwa.height; |
---|
158 | cmap = xgwa.colormap; |
---|
159 | midx = xlim/2; |
---|
160 | midy = ylim/2; |
---|
161 | walls_p = get_boolean_resource ("walls", "Boolean"); |
---|
162 | |
---|
163 | /* if there aren't walls, don't set a limit on the radius */ |
---|
164 | r = get_integer_resource ("radius", "Integer"); |
---|
165 | if (r <= 0 || (r > min (xlim/2, ylim/2) && walls_p) ) |
---|
166 | r = min (xlim/2, ylim/2) - 50; |
---|
167 | |
---|
168 | vx = get_integer_resource ("vx", "Integer"); |
---|
169 | vy = get_integer_resource ("vy", "Integer"); |
---|
170 | |
---|
171 | npoints = get_integer_resource ("points", "Integer"); |
---|
172 | if (npoints < 1) |
---|
173 | npoints = 3 + (random () % 5); |
---|
174 | balls = (struct ball *) malloc (npoints * sizeof (struct ball)); |
---|
175 | |
---|
176 | no_erase_yet = 1; /* for tail mode fix */ |
---|
177 | |
---|
178 | segments = get_integer_resource ("segments", "Integer"); |
---|
179 | if (segments < 0) segments = 1; |
---|
180 | |
---|
181 | threshold = get_integer_resource ("threshold", "Integer"); |
---|
182 | if (threshold < 0) threshold = 0; |
---|
183 | |
---|
184 | delay = get_integer_resource ("delay", "Integer"); |
---|
185 | if (delay < 0) delay = 0; |
---|
186 | |
---|
187 | global_size = get_integer_resource ("size", "Integer"); |
---|
188 | if (global_size < 0) global_size = 0; |
---|
189 | |
---|
190 | glow_p = get_boolean_resource ("glow", "Boolean"); |
---|
191 | |
---|
192 | orbit_p = get_boolean_resource ("orbit", "Boolean"); |
---|
193 | |
---|
194 | maxspeed_p = get_boolean_resource ("maxspeed", "Boolean"); |
---|
195 | |
---|
196 | cbounce_p = get_boolean_resource ("cbounce", "Boolean"); |
---|
197 | |
---|
198 | color_shift = get_integer_resource ("colorShift", "Integer"); |
---|
199 | if (color_shift <= 0) color_shift = 5; |
---|
200 | |
---|
201 | /*flip mods for mouse interaction*/ |
---|
202 | mouse_p = get_boolean_resource ("mouse", "Boolean"); |
---|
203 | mouse_mass = get_integer_resource ("mouseSize", "Integer"); |
---|
204 | mouse_mass = mouse_mass * mouse_mass *10; |
---|
205 | |
---|
206 | viscosity = get_float_resource ("viscosity", "Float"); |
---|
207 | |
---|
208 | mode_str = get_string_resource ("mode", "Mode"); |
---|
209 | if (! mode_str) mode = ball_mode; |
---|
210 | else if (!strcmp (mode_str, "balls")) mode = ball_mode; |
---|
211 | else if (!strcmp (mode_str, "lines")) mode = line_mode; |
---|
212 | else if (!strcmp (mode_str, "polygons")) mode = polygon_mode; |
---|
213 | else if (!strcmp (mode_str, "tails")) mode = tail_mode; |
---|
214 | else if (!strcmp (mode_str, "splines")) mode = spline_mode; |
---|
215 | else if (!strcmp (mode_str, "filled-splines"))mode = spline_filled_mode; |
---|
216 | else { |
---|
217 | fprintf (stderr, |
---|
218 | "%s: mode must be balls, lines, tails, polygons, splines, or\n\ |
---|
219 | filled-splines, not \"%s\"\n", |
---|
220 | progname, mode_str); |
---|
221 | exit (1); |
---|
222 | } |
---|
223 | |
---|
224 | graph_mode_str = get_string_resource ("graphmode", "Mode"); |
---|
225 | if (! graph_mode_str) graph_mode = graph_none; |
---|
226 | else if (!strcmp (graph_mode_str, "x")) graph_mode = graph_x; |
---|
227 | else if (!strcmp (graph_mode_str, "y")) graph_mode = graph_y; |
---|
228 | else if (!strcmp (graph_mode_str, "both")) graph_mode = graph_both; |
---|
229 | else if (!strcmp (graph_mode_str, "speed")) graph_mode = graph_speed; |
---|
230 | else if (!strcmp (graph_mode_str, "none")) graph_mode = graph_none; |
---|
231 | else { |
---|
232 | fprintf (stderr, |
---|
233 | "%s: graphmode must be speed, x, y, both, or none, not \"%s\"\n", |
---|
234 | progname, graph_mode_str); |
---|
235 | exit (1); |
---|
236 | } |
---|
237 | |
---|
238 | /* only allocate memory if it is needed */ |
---|
239 | if(graph_mode != graph_none) |
---|
240 | { |
---|
241 | if(graph_mode == graph_x || graph_mode == graph_both) |
---|
242 | x_vels = (double *) malloc (npoints * sizeof (double)); |
---|
243 | if(graph_mode == graph_y || graph_mode == graph_both) |
---|
244 | y_vels = (double *) malloc (npoints * sizeof (double)); |
---|
245 | if(graph_mode == graph_speed) |
---|
246 | speeds = (double *) malloc (npoints * sizeof (double)); |
---|
247 | } |
---|
248 | |
---|
249 | if (mode != ball_mode && mode != tail_mode) glow_p = False; |
---|
250 | |
---|
251 | if (mode == polygon_mode && npoints < 3) |
---|
252 | mode = line_mode; |
---|
253 | |
---|
254 | ncolors = get_integer_resource ("colors", "Colors"); |
---|
255 | if (ncolors < 2) ncolors = 2; |
---|
256 | if (ncolors <= 2) mono_p = True; |
---|
257 | colors = 0; |
---|
258 | |
---|
259 | if (!mono_p) |
---|
260 | { |
---|
261 | fg_index = 0; |
---|
262 | switch (mode) |
---|
263 | { |
---|
264 | case ball_mode: |
---|
265 | if (glow_p) |
---|
266 | { |
---|
267 | int H = random() % 360; |
---|
268 | double S1 = 0.25; |
---|
269 | double S2 = 1.00; |
---|
270 | double V = frand(0.25) + 0.75; |
---|
271 | colors = (XColor *) malloc(sizeof(*colors) * (ncolors+1)); |
---|
272 | make_color_ramp (dpy, cmap, H, S1, V, H, S2, V, colors, &ncolors, |
---|
273 | False, True, False); |
---|
274 | } |
---|
275 | else |
---|
276 | { |
---|
277 | ncolors = npoints; |
---|
278 | colors = (XColor *) malloc(sizeof(*colors) * (ncolors+1)); |
---|
279 | make_random_colormap (dpy, xgwa.visual, cmap, colors, &ncolors, |
---|
280 | True, True, False, True); |
---|
281 | } |
---|
282 | break; |
---|
283 | case line_mode: |
---|
284 | case polygon_mode: |
---|
285 | case spline_mode: |
---|
286 | case spline_filled_mode: |
---|
287 | case tail_mode: |
---|
288 | colors = (XColor *) malloc(sizeof(*colors) * (ncolors+1)); |
---|
289 | make_smooth_colormap (dpy, xgwa.visual, cmap, colors, &ncolors, |
---|
290 | True, False, True); |
---|
291 | break; |
---|
292 | default: |
---|
293 | abort (); |
---|
294 | } |
---|
295 | } |
---|
296 | |
---|
297 | if (!mono_p && ncolors <= 2) |
---|
298 | { |
---|
299 | if (colors) free (colors); |
---|
300 | colors = 0; |
---|
301 | mono_p = True; |
---|
302 | } |
---|
303 | |
---|
304 | if (mode != ball_mode) |
---|
305 | { |
---|
306 | int size = (segments ? segments : 1); |
---|
307 | point_stack_size = size * (npoints + 1); |
---|
308 | point_stack = (XPoint *) calloc (point_stack_size, sizeof (XPoint)); |
---|
309 | point_stack_fp = 0; |
---|
310 | } |
---|
311 | |
---|
312 | gcv.line_width = (mode == tail_mode |
---|
313 | ? (global_size ? global_size : (MAX_SIZE * 2 / 3)) |
---|
314 | : 1); |
---|
315 | gcv.cap_style = (mode == tail_mode ? CapRound : CapButt); |
---|
316 | |
---|
317 | if (mono_p) |
---|
318 | gcv.foreground = get_pixel_resource("foreground", "Foreground", dpy, cmap); |
---|
319 | else |
---|
320 | gcv.foreground = colors[fg_index].pixel; |
---|
321 | draw_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle, &gcv); |
---|
322 | |
---|
323 | gcv.foreground = get_pixel_resource("background", "Background", dpy, cmap); |
---|
324 | erase_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle,&gcv); |
---|
325 | |
---|
326 | |
---|
327 | #define rand_size() min (MAX_SIZE, 8 + (random () % (MAX_SIZE - 9))) |
---|
328 | |
---|
329 | if (orbit_p && !global_size) |
---|
330 | /* To orbit, all objects must be the same mass, or the math gets |
---|
331 | really hairy... */ |
---|
332 | global_size = rand_size (); |
---|
333 | |
---|
334 | th = frand (M_PI+M_PI); |
---|
335 | for (i = 0; i < npoints; i++) |
---|
336 | { |
---|
337 | int new_size = (global_size ? global_size : rand_size ()); |
---|
338 | balls [i].dx = 0; |
---|
339 | balls [i].dy = 0; |
---|
340 | balls [i].size = new_size; |
---|
341 | balls [i].mass = (new_size * new_size * 10); |
---|
342 | balls [i].x = midx + r * cos (i * ((M_PI+M_PI) / npoints) + th); |
---|
343 | balls [i].y = midy + r * sin (i * ((M_PI+M_PI) / npoints) + th); |
---|
344 | if (! orbit_p) |
---|
345 | { |
---|
346 | balls [i].vx = vx ? vx : ((6.0 - (random () % 11)) / 8.0); |
---|
347 | balls [i].vy = vy ? vy : ((6.0 - (random () % 11)) / 8.0); |
---|
348 | } |
---|
349 | if (mono_p || mode != ball_mode) |
---|
350 | balls [i].pixel_index = -1; |
---|
351 | else if (glow_p) |
---|
352 | balls [i].pixel_index = 0; |
---|
353 | else |
---|
354 | balls [i].pixel_index = random() % ncolors; |
---|
355 | } |
---|
356 | |
---|
357 | /* This lets modes where the points don't really have any size use the whole |
---|
358 | window. Otherwise, since the points still have a positive size |
---|
359 | assigned to them, they will be bounced somewhat early. Mass and size are |
---|
360 | seperate, so this shouldn't cause problems. It's a bit kludgy, tho. |
---|
361 | */ |
---|
362 | if(mode == line_mode || mode == spline_mode || |
---|
363 | mode == spline_filled_mode || mode == polygon_mode) |
---|
364 | { |
---|
365 | for(i = 1; i < npoints; i++) |
---|
366 | { |
---|
367 | balls[i].size = 0; |
---|
368 | } |
---|
369 | } |
---|
370 | |
---|
371 | if (orbit_p) |
---|
372 | { |
---|
373 | double a = 0; |
---|
374 | double v; |
---|
375 | double v_mult = get_float_resource ("vMult", "Float"); |
---|
376 | if (v_mult == 0.0) v_mult = 1.0; |
---|
377 | |
---|
378 | for (i = 1; i < npoints; i++) |
---|
379 | { |
---|
380 | double _2ipi_n = (2 * i * M_PI / npoints); |
---|
381 | double x = r * cos (_2ipi_n); |
---|
382 | double y = r * sin (_2ipi_n); |
---|
383 | double distx = r - x; |
---|
384 | double dist2 = (distx * distx) + (y * y); |
---|
385 | double dist = sqrt (dist2); |
---|
386 | double a1 = ((balls[i].mass / dist2) * |
---|
387 | ((dist < threshold) ? -1.0 : 1.0) * |
---|
388 | (distx / dist)); |
---|
389 | a += a1; |
---|
390 | } |
---|
391 | if (a < 0.0) |
---|
392 | { |
---|
393 | fprintf (stderr, "%s: domain error: forces on balls too great\n", |
---|
394 | progname); |
---|
395 | exit (-1); |
---|
396 | } |
---|
397 | v = sqrt (a * r) * v_mult; |
---|
398 | for (i = 0; i < npoints; i++) |
---|
399 | { |
---|
400 | double k = ((2 * i * M_PI / npoints) + th); |
---|
401 | balls [i].vx = -v * sin (k); |
---|
402 | balls [i].vy = v * cos (k); |
---|
403 | } |
---|
404 | } |
---|
405 | |
---|
406 | if (mono_p) glow_p = False; |
---|
407 | |
---|
408 | XClearWindow (dpy, window); |
---|
409 | } |
---|
410 | |
---|
411 | static void |
---|
412 | compute_force (int i, double *dx_ret, double *dy_ret) |
---|
413 | { |
---|
414 | int j; |
---|
415 | double x_dist, y_dist, dist, dist2; |
---|
416 | *dx_ret = 0; |
---|
417 | *dy_ret = 0; |
---|
418 | for (j = 0; j < npoints; j++) |
---|
419 | { |
---|
420 | if (i == j) continue; |
---|
421 | x_dist = balls [j].x - balls [i].x; |
---|
422 | y_dist = balls [j].y - balls [i].y; |
---|
423 | dist2 = (x_dist * x_dist) + (y_dist * y_dist); |
---|
424 | dist = sqrt (dist2); |
---|
425 | |
---|
426 | if (dist > 0.1) /* the balls are not overlapping */ |
---|
427 | { |
---|
428 | double new_acc = ((balls[j].mass / dist2) * |
---|
429 | ((dist < threshold) ? -1.0 : 1.0)); |
---|
430 | double new_acc_dist = new_acc / dist; |
---|
431 | *dx_ret += new_acc_dist * x_dist; |
---|
432 | *dy_ret += new_acc_dist * y_dist; |
---|
433 | } |
---|
434 | else |
---|
435 | { /* the balls are overlapping; move randomly */ |
---|
436 | *dx_ret += (frand (10.0) - 5.0); |
---|
437 | *dy_ret += (frand (10.0) - 5.0); |
---|
438 | } |
---|
439 | } |
---|
440 | |
---|
441 | if (mouse_p) |
---|
442 | { |
---|
443 | x_dist = mouse_x - balls [i].x; |
---|
444 | y_dist = mouse_y - balls [i].y; |
---|
445 | dist2 = (x_dist * x_dist) + (y_dist * y_dist); |
---|
446 | dist = sqrt (dist2); |
---|
447 | |
---|
448 | if (dist > 0.1) /* the balls are not overlapping */ |
---|
449 | { |
---|
450 | double new_acc = ((mouse_mass / dist2) * |
---|
451 | ((dist < 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; |
---|
455 | } |
---|
456 | else |
---|
457 | { /* the balls are overlapping; move randomly */ |
---|
458 | *dx_ret += (frand (10.0) - 5.0); |
---|
459 | *dy_ret += (frand (10.0) - 5.0); |
---|
460 | } |
---|
461 | } |
---|
462 | } |
---|
463 | |
---|
464 | |
---|
465 | /* Draws meters along the diagonal for the x velocity */ |
---|
466 | static void |
---|
467 | draw_meter_x(Display *dpy, Window window, GC draw_gc, |
---|
468 | struct ball *balls, int i, int alone) |
---|
469 | { |
---|
470 | XWindowAttributes xgwa; |
---|
471 | int x1,x2,y,w1,w2,h; |
---|
472 | XGetWindowAttributes (dpy, window, &xgwa); |
---|
473 | |
---|
474 | /* set the width of the bars to use */ |
---|
475 | if(xgwa.height < BAR_SIZE*npoints) |
---|
476 | { |
---|
477 | y = i*(xgwa.height/npoints); |
---|
478 | h = (xgwa.height/npoints) - 2; |
---|
479 | } |
---|
480 | else |
---|
481 | { |
---|
482 | y = BAR_SIZE*i; |
---|
483 | h = BAR_SIZE - 2; |
---|
484 | } |
---|
485 | |
---|
486 | if(alone) |
---|
487 | { |
---|
488 | x1 = xgwa.width/2; |
---|
489 | x2 = x1; |
---|
490 | } |
---|
491 | else |
---|
492 | { |
---|
493 | x1 = i*(h+2); |
---|
494 | if(x1 < i) |
---|
495 | x1 = i; |
---|
496 | x2 = x1; |
---|
497 | } |
---|
498 | |
---|
499 | if(y<1) y=i; |
---|
500 | if(h<1) h=1; |
---|
501 | |
---|
502 | w1 = (int)(20*x_vels[i]); |
---|
503 | w2 = (int)(20*balls[i].vx); |
---|
504 | x_vels[i] = balls[i].vx; |
---|
505 | |
---|
506 | if (w1<0) { |
---|
507 | w1=-w1; |
---|
508 | x1=x1-w1; |
---|
509 | } |
---|
510 | if (w2<0) { |
---|
511 | w2=-w2; |
---|
512 | x2=x2-w2; |
---|
513 | } |
---|
514 | XDrawRectangle(dpy,window,erase_gc,x1+(h+2)/2,y,w1,h); |
---|
515 | XDrawRectangle(dpy,window,draw_gc,x2+(h+2)/2,y,w2,h); |
---|
516 | } |
---|
517 | |
---|
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? |
---|
521 | */ |
---|
522 | static void |
---|
523 | draw_meter_y (Display *dpy, Window window, GC draw_gc, |
---|
524 | struct ball *balls, int i, int alone) |
---|
525 | { |
---|
526 | XWindowAttributes xgwa; |
---|
527 | int y1,y2,x,h1,h2,w; |
---|
528 | XGetWindowAttributes (dpy, window, &xgwa); |
---|
529 | |
---|
530 | if(xgwa.height < BAR_SIZE*npoints){ /*needs to be height still */ |
---|
531 | x = i*(xgwa.height/npoints); |
---|
532 | w = (xgwa.height/npoints) - 2; |
---|
533 | } |
---|
534 | else{ |
---|
535 | x = BAR_SIZE*i; |
---|
536 | w = BAR_SIZE - 2; |
---|
537 | } |
---|
538 | |
---|
539 | if(alone) |
---|
540 | { |
---|
541 | y1 = xgwa.height/2; |
---|
542 | y2 = y1; |
---|
543 | } |
---|
544 | else |
---|
545 | { |
---|
546 | y1 = i*(w+2); |
---|
547 | if(y1 < i) |
---|
548 | y1 = i; |
---|
549 | y2 = y1; |
---|
550 | } |
---|
551 | |
---|
552 | if(x < 1) x = i; |
---|
553 | if(w < 1) w = 1; |
---|
554 | |
---|
555 | h1 = (int)(20*y_vels[i]); |
---|
556 | h2 = (int)(20*balls[i].vy); |
---|
557 | y_vels[i] = balls[i].vy; |
---|
558 | |
---|
559 | if (h1<0) { |
---|
560 | h1=-h1; |
---|
561 | y1=y1-h1; |
---|
562 | } |
---|
563 | if (h2<0) { |
---|
564 | h2=-h2; |
---|
565 | y2=y2-h2; |
---|
566 | } |
---|
567 | XDrawRectangle(dpy,window,erase_gc,x,y1+(w+2)/2,w,h1); |
---|
568 | XDrawRectangle(dpy,window,draw_gc,x,y2+(w+2)/2,w,h2); |
---|
569 | } |
---|
570 | |
---|
571 | |
---|
572 | /* Draws meters of the total speed of the balls */ |
---|
573 | static void |
---|
574 | draw_meter_speed (Display *dpy, Window window, GC draw_gc, |
---|
575 | struct ball *balls, int i) |
---|
576 | { |
---|
577 | XWindowAttributes xgwa; |
---|
578 | int y,x1,x2,h,w1,w2; |
---|
579 | XGetWindowAttributes (dpy, window, &xgwa); |
---|
580 | |
---|
581 | if(xgwa.height < BAR_SIZE*npoints) |
---|
582 | { |
---|
583 | y = i*(xgwa.height/npoints); |
---|
584 | h = (xgwa.height/npoints) - 2; |
---|
585 | } |
---|
586 | else{ |
---|
587 | y = BAR_SIZE*i; |
---|
588 | h = BAR_SIZE - 2; |
---|
589 | } |
---|
590 | |
---|
591 | x1 = 0; |
---|
592 | x2 = x1; |
---|
593 | |
---|
594 | if(y < 1) y = i; |
---|
595 | if(h < 1) h = 1; |
---|
596 | |
---|
597 | w1 = (int)(5*speeds[i]); |
---|
598 | w2 = (int)(5*(balls[i].vy*balls[i].vy+balls[i].vx*balls[i].vx)); |
---|
599 | speeds[i] = balls[i].vy*balls[i].vy+balls[i].vx*balls[i].vx; |
---|
600 | |
---|
601 | XDrawRectangle(dpy,window,erase_gc,x1,y,w1,h); |
---|
602 | XDrawRectangle(dpy,window,draw_gc, x2,y,w2,h); |
---|
603 | } |
---|
604 | |
---|
605 | static void |
---|
606 | run_balls (Display *dpy, Window window, int total_ticks) |
---|
607 | { |
---|
608 | int last_point_stack_fp = point_stack_fp; |
---|
609 | static int tick = 500, xlim, ylim; |
---|
610 | static Colormap cmap; |
---|
611 | |
---|
612 | Window root1, child1; /*flip mods for mouse interaction*/ |
---|
613 | unsigned int mask; |
---|
614 | |
---|
615 | int i, radius = global_size/2; |
---|
616 | if(global_size == 0) |
---|
617 | radius = (MAX_SIZE / 3); |
---|
618 | |
---|
619 | if(graph_mode != graph_none) |
---|
620 | { |
---|
621 | if(graph_mode == graph_both) |
---|
622 | { |
---|
623 | for(i = 0; i < npoints; i++) |
---|
624 | { |
---|
625 | draw_meter_x(dpy,window,draw_gc, balls, i, 0); |
---|
626 | draw_meter_y(dpy,window,draw_gc, balls, i, 0); |
---|
627 | } |
---|
628 | } |
---|
629 | else if(graph_mode == graph_x) |
---|
630 | { |
---|
631 | for(i = 0; i < npoints; i++) |
---|
632 | { |
---|
633 | draw_meter_x(dpy,window,draw_gc, balls, i, 1); |
---|
634 | } |
---|
635 | } |
---|
636 | else if(graph_mode == graph_y) |
---|
637 | { |
---|
638 | for(i = 0; i < npoints; i++) |
---|
639 | { |
---|
640 | draw_meter_y(dpy,window,draw_gc, balls, i, 1); |
---|
641 | } |
---|
642 | } |
---|
643 | else if(graph_mode == graph_speed) |
---|
644 | { |
---|
645 | for(i = 0; i < npoints; i++) |
---|
646 | { |
---|
647 | draw_meter_speed(dpy,window,draw_gc, balls, i); |
---|
648 | } |
---|
649 | } |
---|
650 | |
---|
651 | } |
---|
652 | |
---|
653 | if (mouse_p) |
---|
654 | { |
---|
655 | XQueryPointer(dpy, window, &root1, &child1, |
---|
656 | &root_x, &root_y, &mouse_x, &mouse_y, &mask); |
---|
657 | } |
---|
658 | |
---|
659 | if (tick++ == 500) |
---|
660 | { |
---|
661 | XWindowAttributes xgwa; |
---|
662 | XGetWindowAttributes (dpy, window, &xgwa); |
---|
663 | tick = 0; |
---|
664 | xlim = xgwa.width; |
---|
665 | ylim = xgwa.height; |
---|
666 | cmap = xgwa.colormap; |
---|
667 | } |
---|
668 | |
---|
669 | /* compute the force of attraction/repulsion among all balls */ |
---|
670 | for (i = 0; i < npoints; i++) |
---|
671 | compute_force (i, &balls[i].dx, &balls[i].dy); |
---|
672 | |
---|
673 | /* move the balls according to the forces now in effect */ |
---|
674 | for (i = 0; i < npoints; i++) |
---|
675 | { |
---|
676 | double old_x = balls[i].x; |
---|
677 | double old_y = balls[i].y; |
---|
678 | double new_x, new_y; |
---|
679 | int size = balls[i].size; |
---|
680 | balls[i].vx += balls[i].dx; |
---|
681 | balls[i].vy += balls[i].dy; |
---|
682 | |
---|
683 | /* "don't let them get too fast: impose a terminal velocity |
---|
684 | (actually, make the medium have friction)" |
---|
685 | Well, what this first step really does is give the medium a |
---|
686 | viscosity of .9 for balls going over the speed limit. Anyway, |
---|
687 | this is now optional |
---|
688 | */ |
---|
689 | if (balls[i].vx > 10 && maxspeed_p) |
---|
690 | { |
---|
691 | balls[i].vx *= 0.9; |
---|
692 | balls[i].dx = 0; |
---|
693 | } |
---|
694 | else if (viscosity != 1) |
---|
695 | { |
---|
696 | balls[i].vx *= viscosity; |
---|
697 | } |
---|
698 | |
---|
699 | if (balls[i].vy > 10 && maxspeed_p) |
---|
700 | { |
---|
701 | balls[i].vy *= 0.9; |
---|
702 | balls[i].dy = 0; |
---|
703 | } |
---|
704 | else if (viscosity != 1) |
---|
705 | { |
---|
706 | balls[i].vy *= viscosity; |
---|
707 | } |
---|
708 | |
---|
709 | balls[i].x += balls[i].vx; |
---|
710 | balls[i].y += balls[i].vy; |
---|
711 | |
---|
712 | |
---|
713 | /* bounce off the walls if desired |
---|
714 | note: a ball is actually its upper left corner */ |
---|
715 | if(walls_p) |
---|
716 | { |
---|
717 | if(cbounce_p) /* with correct bouncing */ |
---|
718 | { |
---|
719 | /* so long as it's out of range, keep bouncing */ |
---|
720 | |
---|
721 | while( (balls[i].x >= (xlim - balls[i].size)) || |
---|
722 | (balls[i].y >= (ylim - balls[i].size)) || |
---|
723 | (balls[i].x <= 0) || |
---|
724 | (balls[i].y <= 0) ) |
---|
725 | { |
---|
726 | if (balls[i].x >= (xlim - balls[i].size)) |
---|
727 | { |
---|
728 | balls[i].x = (2*(xlim - balls[i].size) - balls[i].x); |
---|
729 | balls[i].vx = -balls[i].vx; |
---|
730 | } |
---|
731 | if (balls[i].y >= (ylim - balls[i].size)) |
---|
732 | { |
---|
733 | balls[i].y = (2*(ylim - balls[i].size) - balls[i].y); |
---|
734 | balls[i].vy = -balls[i].vy; |
---|
735 | } |
---|
736 | if (balls[i].x <= 0) |
---|
737 | { |
---|
738 | balls[i].x = -balls[i].x; |
---|
739 | balls[i].vx = -balls[i].vx; |
---|
740 | } |
---|
741 | if (balls[i].y <= 0) |
---|
742 | { |
---|
743 | balls[i].y = -balls[i].y; |
---|
744 | balls[i].vy = -balls[i].vy; |
---|
745 | } |
---|
746 | } |
---|
747 | } |
---|
748 | else /* with old bouncing */ |
---|
749 | { |
---|
750 | if (balls[i].x >= (xlim - balls[i].size)) |
---|
751 | { |
---|
752 | balls[i].x = (xlim - balls[i].size - 1); |
---|
753 | if (balls[i].vx > 0) /* why is this check here? */ |
---|
754 | balls[i].vx = -balls[i].vx; |
---|
755 | } |
---|
756 | if (balls[i].y >= (ylim - balls[i].size)) |
---|
757 | { |
---|
758 | balls[i].y = (ylim - balls[i].size - 1); |
---|
759 | if (balls[i].vy > 0) |
---|
760 | balls[i].vy = -balls[i].vy; |
---|
761 | } |
---|
762 | if (balls[i].x <= 0) |
---|
763 | { |
---|
764 | balls[i].x = 0; |
---|
765 | if (balls[i].vx < 0) |
---|
766 | balls[i].vx = -balls[i].vx; |
---|
767 | } |
---|
768 | if (balls[i].y <= 0) |
---|
769 | { |
---|
770 | balls[i].y = 0; |
---|
771 | if (balls[i].vy < 0) |
---|
772 | balls[i].vy = -balls[i].vy; |
---|
773 | } |
---|
774 | } |
---|
775 | } |
---|
776 | new_x = balls[i].x; |
---|
777 | new_y = balls[i].y; |
---|
778 | |
---|
779 | if (!mono_p) |
---|
780 | { |
---|
781 | if (mode == ball_mode) |
---|
782 | { |
---|
783 | if (glow_p) |
---|
784 | { |
---|
785 | /* make color saturation be related to particle |
---|
786 | acceleration. */ |
---|
787 | double limit = 0.5; |
---|
788 | double s, fraction; |
---|
789 | double vx = balls [i].dx; |
---|
790 | double vy = balls [i].dy; |
---|
791 | if (vx < 0) vx = -vx; |
---|
792 | if (vy < 0) vy = -vy; |
---|
793 | fraction = vx + vy; |
---|
794 | if (fraction > limit) fraction = limit; |
---|
795 | |
---|
796 | s = 1 - (fraction / limit); |
---|
797 | balls[i].pixel_index = (ncolors * s); |
---|
798 | } |
---|
799 | XSetForeground (dpy, draw_gc, |
---|
800 | colors[balls[i].pixel_index].pixel); |
---|
801 | } |
---|
802 | } |
---|
803 | |
---|
804 | if (mode == ball_mode) |
---|
805 | { |
---|
806 | XFillArc (dpy, window, erase_gc, (int) old_x, (int) old_y, |
---|
807 | size, size, 0, 360*64); |
---|
808 | XFillArc (dpy, window, draw_gc, (int) new_x, (int) new_y, |
---|
809 | size, size, 0, 360*64); |
---|
810 | } |
---|
811 | else |
---|
812 | { |
---|
813 | point_stack [point_stack_fp].x = new_x; |
---|
814 | point_stack [point_stack_fp].y = new_y; |
---|
815 | point_stack_fp++; |
---|
816 | } |
---|
817 | } |
---|
818 | |
---|
819 | /* draw the lines or polygons after computing all points */ |
---|
820 | if (mode != ball_mode) |
---|
821 | { |
---|
822 | point_stack [point_stack_fp].x = balls [0].x; /* close the polygon */ |
---|
823 | point_stack [point_stack_fp].y = balls [0].y; |
---|
824 | point_stack_fp++; |
---|
825 | if (point_stack_fp == point_stack_size) |
---|
826 | point_stack_fp = 0; |
---|
827 | else if (point_stack_fp > point_stack_size) /* better be aligned */ |
---|
828 | abort (); |
---|
829 | if (!mono_p) |
---|
830 | { |
---|
831 | static int tick = 0; |
---|
832 | if (tick++ == color_shift) |
---|
833 | { |
---|
834 | tick = 0; |
---|
835 | fg_index = (fg_index + 1) % ncolors; |
---|
836 | XSetForeground (dpy, draw_gc, colors[fg_index].pixel); |
---|
837 | } |
---|
838 | } |
---|
839 | } |
---|
840 | |
---|
841 | switch (mode) |
---|
842 | { |
---|
843 | case ball_mode: |
---|
844 | break; |
---|
845 | case line_mode: |
---|
846 | if (segments > 0) |
---|
847 | XDrawLines (dpy, window, erase_gc, point_stack + point_stack_fp, |
---|
848 | npoints + 1, CoordModeOrigin); |
---|
849 | XDrawLines (dpy, window, draw_gc, point_stack + last_point_stack_fp, |
---|
850 | npoints + 1, CoordModeOrigin); |
---|
851 | break; |
---|
852 | case polygon_mode: |
---|
853 | if (segments > 0) |
---|
854 | XFillPolygon (dpy, window, erase_gc, point_stack + point_stack_fp, |
---|
855 | npoints + 1, (npoints == 3 ? Convex : Complex), |
---|
856 | CoordModeOrigin); |
---|
857 | XFillPolygon (dpy, window, draw_gc, point_stack + last_point_stack_fp, |
---|
858 | npoints + 1, (npoints == 3 ? Convex : Complex), |
---|
859 | CoordModeOrigin); |
---|
860 | break; |
---|
861 | case tail_mode: |
---|
862 | { |
---|
863 | for (i = 0; i < npoints; i++) |
---|
864 | { |
---|
865 | int index = point_stack_fp + i; |
---|
866 | int next_index = (index + (npoints + 1)) % point_stack_size; |
---|
867 | if(no_erase_yet == 1) |
---|
868 | { |
---|
869 | if(total_ticks >= segments) |
---|
870 | { |
---|
871 | no_erase_yet = 0; |
---|
872 | XDrawLine (dpy, window, erase_gc, |
---|
873 | point_stack [index].x + radius, |
---|
874 | point_stack [index].y + radius, |
---|
875 | point_stack [next_index].x + radius, |
---|
876 | point_stack [next_index].y + radius); |
---|
877 | } |
---|
878 | } |
---|
879 | else |
---|
880 | { |
---|
881 | XDrawLine (dpy, window, erase_gc, |
---|
882 | point_stack [index].x + radius, |
---|
883 | point_stack [index].y + radius, |
---|
884 | point_stack [next_index].x + radius, |
---|
885 | point_stack [next_index].y + radius); |
---|
886 | } |
---|
887 | index = last_point_stack_fp + i; |
---|
888 | next_index = (index - (npoints + 1)) % point_stack_size; |
---|
889 | if (next_index < 0) next_index += point_stack_size; |
---|
890 | if (point_stack [next_index].x == 0 && |
---|
891 | point_stack [next_index].y == 0) |
---|
892 | continue; |
---|
893 | XDrawLine (dpy, window, draw_gc, |
---|
894 | point_stack [index].x + radius, |
---|
895 | point_stack [index].y + radius, |
---|
896 | point_stack [next_index].x + radius, |
---|
897 | point_stack [next_index].y + radius); |
---|
898 | } |
---|
899 | } |
---|
900 | break; |
---|
901 | case spline_mode: |
---|
902 | case spline_filled_mode: |
---|
903 | { |
---|
904 | static spline *s = 0; |
---|
905 | if (! s) s = make_spline (npoints); |
---|
906 | if (segments > 0) |
---|
907 | { |
---|
908 | for (i = 0; i < npoints; i++) |
---|
909 | { |
---|
910 | s->control_x [i] = point_stack [point_stack_fp + i].x; |
---|
911 | s->control_y [i] = point_stack [point_stack_fp + i].y; |
---|
912 | } |
---|
913 | compute_closed_spline (s); |
---|
914 | if (mode == spline_filled_mode) |
---|
915 | XFillPolygon (dpy, window, erase_gc, s->points, s->n_points, |
---|
916 | (s->n_points == 3 ? Convex : Complex), |
---|
917 | CoordModeOrigin); |
---|
918 | else |
---|
919 | XDrawLines (dpy, window, erase_gc, s->points, s->n_points, |
---|
920 | CoordModeOrigin); |
---|
921 | } |
---|
922 | for (i = 0; i < npoints; i++) |
---|
923 | { |
---|
924 | s->control_x [i] = point_stack [last_point_stack_fp + i].x; |
---|
925 | s->control_y [i] = point_stack [last_point_stack_fp + i].y; |
---|
926 | } |
---|
927 | compute_closed_spline (s); |
---|
928 | if (mode == spline_filled_mode) |
---|
929 | XFillPolygon (dpy, window, draw_gc, s->points, s->n_points, |
---|
930 | (s->n_points == 3 ? Convex : Complex), |
---|
931 | CoordModeOrigin); |
---|
932 | else |
---|
933 | XDrawLines (dpy, window, draw_gc, s->points, s->n_points, |
---|
934 | CoordModeOrigin); |
---|
935 | } |
---|
936 | break; |
---|
937 | default: |
---|
938 | abort (); |
---|
939 | } |
---|
940 | |
---|
941 | XSync (dpy, False); |
---|
942 | } |
---|
943 | |
---|
944 | |
---|
945 | char *progclass = "Attraction"; |
---|
946 | |
---|
947 | char *defaults [] = { |
---|
948 | ".background: black", |
---|
949 | ".foreground: white", |
---|
950 | "*mode: balls", |
---|
951 | "*graphmode: none", |
---|
952 | "*points: 0", |
---|
953 | "*size: 0", |
---|
954 | "*colors: 200", |
---|
955 | "*threshold: 100", |
---|
956 | "*delay: 10000", |
---|
957 | "*glow: false", |
---|
958 | "*mouseSize: 10", |
---|
959 | "*walls: true", |
---|
960 | "*maxspeed: true", |
---|
961 | "*cbounce: true", |
---|
962 | "*mouse: false", |
---|
963 | "*viscosity: 1", |
---|
964 | "*orbit: false", |
---|
965 | "*colorShift: 3", |
---|
966 | "*segments: 500", |
---|
967 | "*vMult: 0.9", |
---|
968 | 0 |
---|
969 | }; |
---|
970 | |
---|
971 | XrmOptionDescRec options [] = { |
---|
972 | { "-mode", ".mode", XrmoptionSepArg, 0 }, |
---|
973 | { "-graphmode", ".graphmode", XrmoptionSepArg, 0 }, |
---|
974 | { "-colors", ".colors", XrmoptionSepArg, 0 }, |
---|
975 | { "-points", ".points", XrmoptionSepArg, 0 }, |
---|
976 | { "-color-shift", ".colorShift", XrmoptionSepArg, 0 }, |
---|
977 | { "-threshold", ".threshold", XrmoptionSepArg, 0 }, |
---|
978 | { "-segments", ".segments", XrmoptionSepArg, 0 }, |
---|
979 | { "-delay", ".delay", XrmoptionSepArg, 0 }, |
---|
980 | { "-size", ".size", XrmoptionSepArg, 0 }, |
---|
981 | { "-radius", ".radius", XrmoptionSepArg, 0 }, |
---|
982 | { "-vx", ".vx", XrmoptionSepArg, 0 }, |
---|
983 | { "-vy", ".vy", XrmoptionSepArg, 0 }, |
---|
984 | { "-vmult", ".vMult", XrmoptionSepArg, 0 }, |
---|
985 | { "-mouse-size", ".mouseSize", XrmoptionSepArg, 0 }, |
---|
986 | { "-viscosity", ".viscosity", XrmoptionSepArg, 0 }, |
---|
987 | { "-mouse", ".mouse", XrmoptionNoArg, "true" }, |
---|
988 | { "-nomouse", ".mouse", XrmoptionNoArg, "false" }, |
---|
989 | { "-glow", ".glow", XrmoptionNoArg, "true" }, |
---|
990 | { "-noglow", ".glow", XrmoptionNoArg, "false" }, |
---|
991 | { "-orbit", ".orbit", XrmoptionNoArg, "true" }, |
---|
992 | { "-nowalls", ".walls", XrmoptionNoArg, "false" }, |
---|
993 | { "-walls", ".walls", XrmoptionNoArg, "true" }, |
---|
994 | { "-nomaxspeed", ".maxspeed", XrmoptionNoArg, "false" }, |
---|
995 | { "-maxspeed", ".maxspeed", XrmoptionNoArg, "true" }, |
---|
996 | { "-correct-bounce", ".cbounce", XrmoptionNoArg, "false" }, |
---|
997 | { "-fast-bounce", ".cbounce", XrmoptionNoArg, "true" }, |
---|
998 | { 0, 0, 0, 0 } |
---|
999 | }; |
---|
1000 | |
---|
1001 | void |
---|
1002 | screenhack (Display *dpy, Window window) |
---|
1003 | { |
---|
1004 | /* for tail mode fix */ |
---|
1005 | int total_ticks = 0; |
---|
1006 | |
---|
1007 | init_balls (dpy, window); |
---|
1008 | while (1) |
---|
1009 | { |
---|
1010 | total_ticks++; |
---|
1011 | run_balls (dpy, window, total_ticks); |
---|
1012 | screenhack_handle_events (dpy); |
---|
1013 | if (delay) |
---|
1014 | usleep (delay); |
---|
1015 | } |
---|
1016 | } |
---|