]> git.lizzy.rs Git - plan9front.git/blob - sys/src/games/galaxy/galaxy.c
merge
[plan9front.git] / sys / src / games / galaxy / galaxy.c
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <draw.h>
5 #include <thread.h>
6 #include <mouse.h>
7 #include <cursor.h>
8 #include <keyboard.h>
9 #include "galaxy.h"
10
11 Cursor crosscursor = {
12         {-7, -7},
13         {0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0,
14          0x03, 0xC0, 0x03, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF,
15          0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xC0, 0x03, 0xC0,
16          0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, },
17         {0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
18          0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE,
19          0x7F, 0xFE, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
20          0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x00, 0x00, }
21 };
22
23 Cursor zoomcursor = {
24         {-7, -7},
25         {0x1F, 0xF8, 0x3F, 0xFC, 0x7F, 0xFE, 0xFB, 0xDF,
26          0xF3, 0xCF, 0xE3, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF,
27          0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xC7, 0xF3, 0xCF,
28          0x7B, 0xDF, 0x7F, 0xFE, 0x3F, 0xFC, 0x1F, 0xF8, },
29         {0x00, 0x00, 0x0F, 0xF0, 0x31, 0x8C, 0x21, 0x84,
30          0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 0x7F, 0xFE,
31          0x7F, 0xFE, 0x41, 0x82, 0x41, 0x82, 0x41, 0x82,
32          0x21, 0x84, 0x31, 0x8C, 0x0F, 0xF0, 0x00, 0x00, }
33 };
34
35 Cursor pausecursor={
36         0, 0,
37         0x01, 0x80, 0x03, 0xC0, 0x07, 0xE0, 0x07, 0xe0,
38         0x07, 0xe0, 0x07, 0xe0, 0x03, 0xc0, 0x0F, 0xF0,
39         0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8,
40         0x0F, 0xF0, 0x1F, 0xF8, 0x3F, 0xFC, 0x3F, 0xFC,
41
42         0x01, 0x80, 0x03, 0xC0, 0x07, 0xE0, 0x04, 0x20,
43         0x04, 0x20, 0x06, 0x60, 0x02, 0x40, 0x0C, 0x30,
44         0x10, 0x08, 0x14, 0x08, 0x14, 0x28, 0x12, 0x28,
45         0x0A, 0x50, 0x16, 0x68, 0x20, 0x04, 0x3F, 0xFC,
46 };
47
48 enum {
49         STK = 8192,
50         DOBODY = 0,
51         SPEED,
52         GRAV,
53         SAVE,
54         LOAD,
55         EXIT,
56         MEND,
57 };
58
59 Cursor *cursor;
60 Mousectl *mc;
61 Keyboardctl kc;
62 double
63         G = 1,
64         θ = 1,
65         scale = 30,
66         ε = 500,
67         dt = .2,
68         LIM = 10,
69         dt²;
70 char *file;
71 int showv, showa, throttle, paused;
72
73 char *menustr[] = {
74         [DOBODY]        "new body",
75         [SAVE]  "save",
76         [LOAD]  "load",
77         [SPEED] "speed",
78         [GRAV]  "gravity",
79         [EXIT]  "exit",
80         [MEND]  nil
81 };
82 Menu menu = {
83         .item menustr
84 };
85
86 Image*
87 randcol(void)
88 {
89         static struct {
90                 ulong c;
91                 Image *i;
92         } cols[] = {
93                 DWhite, nil,
94                 DRed, nil,
95                 DGreen, nil,
96                 DCyan, nil,
97                 DMagenta, nil,
98                 DYellow, nil,
99                 DPaleyellow, nil,
100                 DDarkyellow, nil,
101                 DDarkgreen, nil,
102                 DPalegreen, nil,
103                 DPalebluegreen, nil,
104                 DPaleblue, nil,
105                 DPalegreygreen, nil,
106                 DYellowgreen, nil,
107                 DGreyblue, nil,
108                 DPalegreyblue, nil,
109         };
110         int r;
111
112         r = nrand(nelem(cols));
113         if(cols[r].i == nil)
114                 cols[r].i = allocimage(display, Rect(0,0,1,1), screen->chan, 1, cols[r].c);
115         return cols[r].i;
116 }
117
118 void
119 pause(int p, int id)
120 {
121         static int pid = -1;
122
123         switch(p) {
124         default:
125                 sysfatal("invalid pause value %d:", p);
126                 break;
127         case 0:
128                 if(pid != -1 && pid != id)
129                         break;
130                 pid = id;
131                 if(paused)
132                         break;
133                 paused = 1;
134                 qlock(&glxy);
135                 break;
136         case 1:
137                 if(!paused || pid != id)
138                         break;
139                 pid = -1;
140                 paused = 0;
141                 qunlock(&glxy);
142                 break;
143         }
144 }
145
146 void
147 drawstats(void)
148 {
149         Point p;
150         static char buf[1024];
151
152         snprint(buf, sizeof(buf), "Number of bodies: %d", glxy.l);
153         p = addpt(screen->r.min, (Point){5, 3});
154         string(screen, p, display->white, ZP, font, buf);
155
156         snprint(buf, sizeof(buf), "Avg. calculations per body: %g", avgcalcs);
157         p = addpt(p, (Point){0, font->height});
158         string(screen, p, display->white, ZP, font, buf);
159
160         snprint(buf, sizeof(buf), "Max depth of quad tree: %d", quaddepth);
161         p = addpt(p, (Point){0, font->height});
162         string(screen, p, display->white, ZP, font, buf);
163 }
164
165 void
166 drawglxy(void)
167 {
168         Point pos, va;
169         Body *b;
170         int s;
171
172         draw(screen, screen->r, display->black, 0, ZP);
173         for(b = glxy.a; b < glxy.a + glxy.l; b++) {
174                 pos.x = b->x / scale + orig.x;
175                 pos.y = b->y / scale + orig.y;
176                 s = b->size/scale;
177                 fillellipse(screen, pos, s, s, b->col, ZP);
178                 if(showv) {
179                         va.x = b->v.x/scale;
180                         va.y = b->v.y/scale;
181                         if(va.x != 0 || va.y != 0)
182                                 line(screen, pos, addpt(pos, va), Enddisc, Endarrow, 0, b->col, ZP);
183                 }
184                 if(showa) {
185                         va.x = b->a.x/scale*50;
186                         va.y = b->a.y/scale*50;
187                         if(va.x != 0 || va.y != 0)
188                                 line(screen, pos, addpt(pos, va), Enddisc, Endarrow, 0, b->col, ZP);
189                 }
190         }
191         STATS(drawstats();)
192         flushimage(display, 1);
193 }
194
195 void
196 setsize(Body *b)
197 {
198         Point pos, d;
199         double h;
200
201         pos.x = b->x / scale + orig.x;
202         pos.y = b->y / scale + orig.y;
203         d = subpt(mc->xy, pos);
204         h = hypot(d.x, d.y);
205         b->size = h == 0 ? scale : h*scale;
206         b->mass = b->size*b->size*b->size;
207 }
208
209 void
210 setvel(Body *b)
211 {
212         Point pos, d;
213
214         pos.x = b->x / scale + orig.x;
215         pos.y = b->y / scale + orig.y;
216         d = subpt(mc->xy, pos);
217         b->v.x = (double)d.x*scale/10;
218         b->v.y = (double)d.y*scale/10;
219 }
220
221 void
222 setpos(Body *b)
223 {
224         b->x = (mc->xy.x - orig.x) * scale;
225         b->y = (mc->xy.y - orig.y) * scale;
226 }
227
228 void
229 dosize(Body *b)
230 {
231         Point p;
232
233         p = mc->xy;
234         for(;;) {
235                 setsize(b);
236                 drawglxy();
237                 drawbody(b);
238                 readmouse(mc);
239                 if(mc->buttons != 3)
240                         break;
241         }
242         moveto(mc, p);
243 }
244
245 void
246 dovel(Body *b)
247 {
248         Point p;
249         p = mc->xy;
250         for(;;) {
251                 setvel(b);
252                 drawglxy();
253                 drawbody(b);
254                 readmouse(mc);
255                 if(mc->buttons != 5)
256                         break;
257         }
258         moveto(mc, p);
259 }
260
261 void
262 dobody(void)
263 {
264         Vector gc;
265         double f;
266         Body *b;
267
268         for(;;) {
269                 readmouse(mc);
270                 if(mc->buttons == 0)
271                         continue;
272                 if(mc->buttons == 1)
273                         break;
274                 return;
275         }
276
277         b = body();
278         setpos(b);
279         setvel(b);
280         setsize(b);
281         b->col = randcol();
282         for(;;) {
283                 drawglxy();
284                 drawbody(b);
285                 readmouse(mc);
286                 if(!(mc->buttons & 1))
287                         break;
288                 if(mc->buttons == 3)
289                         dosize(b);
290                 else if(mc->buttons == 5)
291                         dovel(b);
292                 else
293                         setpos(b);
294         }
295
296         CHECKLIM(b, f);
297
298         gc = center();
299         orig.x += gc.x / scale;
300         orig.y += gc.y / scale;
301 }
302
303 char*
304 getinput(char *info, char *sug)
305 {
306         static char buf[1024];
307         static Channel *rchan;
308         char *input;
309         int r;
310
311         if(rchan == nil)
312                 rchan = chancreate(sizeof(Rune), 20);
313
314         if(sug != nil)
315                 strecpy(buf, buf+1024, sug);
316         else
317                 buf[0] = '\0';
318
319         kc.c = rchan;
320         r = enter(info, buf, sizeof(buf), mc, &kc, nil);
321         kc.c = nil;
322         if(r < 0)
323                 sysfatal("save: could not get filename: %r");
324
325         input = strdup(buf);
326         if(input == nil)
327                 sysfatal("getinput: could not save input: %r");
328         return input;
329 }
330
331 void
332 domove(void)
333 {
334         Point oldp, off;
335
336         setcursor(mc, &crosscursor);
337         oldp = mc->xy;
338         for(;;) {
339                 readmouse(mc);
340                 if(mc->buttons != 1)
341                         break;
342                 off = subpt(mc->xy, oldp);
343                 oldp = mc->xy;
344                 pause(0, 0);
345                 orig = addpt(orig, off);
346                 drawglxy();
347                 pause(1, 0);
348         }
349         setcursor(mc, cursor);
350 }
351
352 void
353 dozoom(void)
354 {
355         Point z, d;
356         double f, olds;
357
358         setcursor(mc, &zoomcursor);
359
360         z = mc->xy;
361         olds = scale;
362         for(;;) {
363                 readmouse(mc);
364                 if(mc->buttons != 2)
365                         break;
366                 d = subpt(mc->xy, z);
367                 f = tanh((double)d.y/200) + 1;
368                 pause(0, 0);
369                 scale = f*olds;
370                 drawglxy();
371                 pause(1, 0);
372         }
373
374         setcursor(mc, cursor);
375         pause(1, 0);
376 }
377
378 void
379 load(int fd)
380 {
381         orig = divpt(subpt(screen->r.max, screen->r.min), 2);
382         orig = addpt(orig, screen->r.min);
383         readglxy(fd);
384         center();
385 }
386
387 void
388 domenu(void)
389 {
390         int fd;
391         char *s;
392         double z;
393
394         pause(0, 0);
395         switch(menuhit(3, mc, &menu, nil)) {
396         case DOBODY:
397                 dobody();
398                 break;
399         case SAVE:
400                 s = getinput("Enter file:", file);
401                 if(s == nil || *s == '\0')
402                         break;
403                 free(file);
404                 file = s;
405                 fd = create(file, OWRITE, 0666);
406                 if(fd < 0)
407                         sysfatal("domenu: could not create file %s: %r", file);
408                 writeglxy(fd);
409                 close(fd);
410                 break;
411         case LOAD:
412                 s = getinput("Enter file:", file);
413                 if(s == nil || *s == '\0')
414                         break;
415                 free(file);
416                 file = s;
417                 fd = open(file, OREAD);
418                 if(fd < 0)
419                         sysfatal("domenu: could not open file %s: %r", file);
420                 load(fd);
421                 close(fd);
422                 break;
423         case SPEED:
424                 s = getinput("Speed multiplier:", nil);
425                 if(s == nil || *s == '\0')
426                         break;
427                 z = strtod(s, nil);
428                 free(s);
429                 if(z <= 0)
430                         break;
431                 dt *= z;
432                 dt² = dt*dt;
433                 break;
434         case GRAV:
435                 s = getinput("Gravity multiplier:", nil);
436                 if(s == nil || *s == '\0')
437                         break;
438                 z = strtod(s, nil);
439                 free(s);
440                 if(z <= 0)
441                         break;
442                 G *= z;
443                 break;
444         case EXIT:
445                 threadexitsall(nil);
446                 break;
447         }
448         drawglxy();
449         pause(1, 0);
450 }
451
452 void
453 mousethread(void*)
454 {
455         threadsetname("mouse");
456         for(;;) {
457                 readmouse(mc);
458                 switch(mc->buttons) {
459                 case 1:
460                         domove();
461                         break;
462                 case 2:
463                         dozoom();
464                         break;
465                 case 4:
466                         domenu();
467                         break;
468                 }
469         }
470 }
471
472 void
473 resizethread(void*)
474 {
475         threadsetname("resize");
476         for(;;) {
477                 recv(mc->resizec, nil);
478                 pause(0, 0);
479                 if(getwindow(display, Refnone) < 0)
480                         sysfatal("resize failed: %r");
481                 drawglxy();
482                 pause(1, 0);
483         }
484 }
485
486 void
487 kbdthread(void*)
488 {
489         Keyboardctl *realkc;
490         Rune r;
491
492         threadsetname("keyboard");
493         realkc = initkeyboard(nil);
494         if(realkc == nil)
495                 sysfatal("kbdthread: could not initkeyboard: %r");
496
497         for(;;) {
498                 recv(realkc->c, &r);
499                 if(r == Kdel) {
500                         threadexitsall(nil);
501                 }
502                 if(kc.c != nil)
503                         send(kc.c, &r);
504                 else switch(r) {
505                 case 'q':
506                         threadexitsall(nil);
507                         break;
508                 case 's':
509                         stats ^= 1;
510                         break;
511                 case 'v':
512                         showv ^= 1;
513                         break;
514                 case 'a':
515                         showa ^= 1;
516                         break;
517                 case ' ':
518                         if(paused) {
519                                 cursor = nil;
520                                 pause(1, 1);
521                         } else {
522                                 cursor = &pausecursor;
523                                 pause(0, 1);
524                         }
525                         setcursor(mc, cursor);
526                 }
527                 drawglxy();
528         }
529 }
530
531 /* verlet barnes-hut */
532 void
533 simulate(void*)
534 {
535         Body *b;
536         double f;
537
538         threadsetname("simulate");
539
540         for(;;) {
541                 qlock(&glxy);
542
543                 if(throttle)
544                         sleep(throttle);
545
546                 drawglxy();
547
548 Again:
549                 space.t = EMPTY;
550                 quads.l = 0;
551                 STATS(quaddepth = 0;)
552                 for(b = glxy.a; b < glxy.a + glxy.l; b++) {
553                         if(quadins(b, LIM) == -1) {
554                                 growquads();
555                                 goto Again;
556                         }
557                 }
558
559                 STATS(avgcalcs = 0;)
560                 for(b = glxy.a; b < glxy.a + glxy.l; b++) {
561                         b->a.x = b->newa.x;
562                         b->a.y = b->newa.y;
563                         b->newa.x = b->newa.y = 0;
564                         STATS(calcs = 0;)
565                         quadcalc(b, space, LIM);
566                         STATS(avgcalcs += calcs;)
567                 }
568                 STATS(avgcalcs /= glxy.l;)
569
570                 for(b = glxy.a; b < glxy.a + glxy.l; b++) {
571                         b->x += dt*b->v.x + dt²*b->a.x/2;
572                         b->y += dt*b->v.y + dt²*b->a.y/2;
573                         b->v.x += dt*(b->a.x + b->newa.x)/2;
574                         b->v.y += dt*(b->a.y + b->newa.y)/2;
575                         CHECKLIM(b, f);
576                 }
577
578                 qunlock(&glxy);
579         }
580 }
581
582 void
583 usage(void)
584 {
585         fprint(2, "Usage: %s [-t throttle] [-G gravity] [-ε smooth] [-i] [file]\n", argv0);
586         threadexitsall("usage");
587 }
588
589 void
590 threadmain(int argc, char **argv)
591 {
592         int doload;
593
594         doload = 0;
595         ARGBEGIN {
596         default:
597                 usage();
598                 break;
599         case 't':
600                 throttle = strtol(EARGF(usage()), nil, 0);
601                 break;
602         case 'G':
603                 G = strtod(EARGF(usage()), nil);
604                 break;
605         case L'ε':
606                 ε = strtod(EARGF(usage()), nil);
607                 break;
608         case 'i':
609                 doload++;
610                 break;
611         } ARGEND
612
613         if(argc > 1)
614                 usage();
615
616         fmtinstall('B', Bfmt);
617
618         if(argc == 1) {
619                 if(doload++)
620                         usage();
621                 file = strdup(argv[0]);
622                 if(file == nil)
623                         sysfatal("threadmain: could not save file name: %r");
624                 close(0);
625                 if(open(file, OREAD) != 0)
626                         sysfatal("threadmain: could not open file: %r");
627         }
628
629         if(initdraw(nil, nil, "Galaxy") < 0)
630                 sysfatal("initdraw failed: %r");
631         if(mc = initmouse(nil, screen), mc == nil)
632                 sysfatal("initmouse failed: %r");
633
634         dt² = dt*dt;
635         orig = divpt(subpt(screen->r.max, screen->r.min), 2);
636         orig = addpt(orig, screen->r.min);
637         glxyinit();
638         quadsinit();
639         if(doload)
640                 load(0);
641         close(0);
642         threadcreate(mousethread, nil, STK);
643         threadcreate(resizethread, nil, STK);
644         threadcreate(kbdthread, nil, STK);
645         proccreate(simulate, nil, STK);
646         threadexits(nil);
647 }