#include #include #include #define NPJW 48 #define NSPLIT 6 #define NDOT 14 #define MAXTRACKS 128 #define MINV 6 #define PDUP 1 /* useful for debugging */ #define nels(a) (sizeof(a) / sizeof(a[0])) #define imin(a,b) (((a) <= (b)) ? (a) : (b)) #define imax(a,b) (((a) >= (b)) ? (a) : (b)) #define sqr(x) ((x) * (x)) typedef struct vector vector; struct vector { double x; double y; }; typedef struct dot Dot; struct dot { Point pos; Point ivel; vector vel; double mass; int charge; int height; /* precalculated for speed */ int width; /* precalculated for speed */ int facei; int spin; Image *face; Image *faces[4]; Image *mask; Image *masks[4]; Image *save; int ntracks; Point track[MAXTRACKS]; int next_clear; int next_write; }; Dot dot[NDOT]; Dot *edot = &dot[NDOT]; int total_spin = 3; vector no_gravity; vector gravity = { 0.0, 1.0, }; /* static Image *track; */ static Image *im; static int track_length; static int track_width; static int iflag; static double k_floor = 0.9; Image *screen; #include "andrew.bits" #include "bart.bits" #include "bwk.bits" #include "dmr.bits" #include "doug.bits" #include "gerard.bits" #include "howard.bits" #include "ken.bits" #include "philw.bits" #include "pjw.bits" #include "presotto.bits" #include "rob.bits" #include "sean.bits" #include "td.bits" Image* eallocimage(Display *d, Rectangle r, ulong chan, int repl, int col) { Image *i; i = allocimage(d, r, chan, repl, col); if(i == nil) { fprint(2, "cannot allocate image\n"); exits("allocimage"); } return i; } /* * p1 dot p2 * inner product */ static double dotprod(vector *p1, vector *p2) { return p1->x * p2->x + p1->y * p2->y; } /* * r = p1 + p2 */ static void vecaddpt(vector *r, vector *p1, vector *p2) { r->x = p1->x + p2->x; r->y = p1->y + p2->y; } /* * r = p1 - p2 */ static void vecsub(vector *r, vector *p1, vector *p2) { r->x = p1->x - p2->x; r->y = p1->y - p2->y; } /* * r = v * s * multiplication of a vector by a scalar */ static void scalmult(vector *r, vector *v, double s) { r->x = v->x * s; r->y = v->y * s; } static double separation(Dot *p, Dot *q) { return sqrt((double)(sqr(q->pos.x - p->pos.x) + sqr(q->pos.y - p->pos.y))); } static int close_enough(Dot *p, Dot *q, double *s) { int sepx; int width; int sepy; sepx = p->pos.x - q->pos.x; width = p->width; if (sepx < -width || sepx > width) return 0; sepy = p->pos.y - q->pos.y; if (sepy < -width || sepy > width) return 0; if ((*s = separation(p, q)) > (double)width) return 0; return 1; } static void ptov(vector *v, Point *p) { v->x = (double)p->x; v->y = (double)p->y; } static void vtop(Point *p, vector *v) { p->x = (int)(v->x + 0.5); p->y = (int)(v->y + 0.5); } static void exchange_spin(Dot *p, Dot *q) { int tspin; if (p->spin == 0 && q->spin == 0) return; tspin = p->spin + q->spin; p->spin = imax(nrand(imin(3, tspin + 1)), tspin + 1 - 3); if (p->spin == 0) { p->face = p->faces[0]; p->mask = p->masks[0]; } q->spin = tspin - p->spin; if (q->spin == 0) { q->face = q->faces[0]; q->mask = q->masks[0]; } } static void exchange_charge(Dot *p, Dot *q) { if (p->charge == 0 && q->charge == 0) { switch (nrand(16)) { case 0: p->charge = 1; q->charge = -1; break; case 1: p->charge = -1; q->charge = 1; break; } } } /* * The aim is to completely determine the final * velocities of the two interacting particles as * a function of their initial velocities, masses * and point of collision. We start with the two * quantities that we know are conserved in * elastic collisions -- momentum and kinetic energy. * * Let the masses of the particles be m1 and m2; * their initial velocities V1 and V2 (vector quantities * represented by upper case variables, scalars lower * case); their final velocities V1' and V2'. * * Conservation of momentum gives us: * * (1) m1 * V1 + m2 * V2 = m1 * V1' + m2 * V2' * * and conservation of kinetic energy gives: * * (2) 1/2 * m1 * |V1| * |V1| + 1/2 * m2 * |V2| * |V2| = * 1/2 * m1 * |V1'| * |V1'| + 1/2 * m2 * |V2'| * |V2'| * * Then, decomposing (1) into its 2D scalar components: * * (1a) m1 * v1x + m2 * v2x = m1 * v1x' + m2 * v2x' * (1b) m1 * v1y + m2 * v2y = m1 * v1y' + m2 * v2y' * * and simplifying and expanding (2): * * (2a) m1 * (v1x * v1x + v1y * v1y) + * m2 * (v2x * v2x + v2y * v2y) * = * m1 * (v1x' * v1x' + v1y' * v1y') + * m2 * (v2x' * v2x' + v2y' * v2y') * * We know m1, m2, V1 and V2 which leaves four unknowns: * * v1x', v1y', v2x' and v2y' * * but we have just three equations: * * (1a), (1b) and (2a) * * To remove this extra degree of freedom we add the assumption that * the forces during the collision act instantaneously and exactly * along the (displacement) vector joining the centres of the two objects. * * And unfortunately, I've forgotten the extra equation that this * adds to the system(!), but it turns out that this extra constraint * does allow us to solve the augmented system of equations. * (I guess I could reverse engineer it from the code ...) */ static void rebound(Dot *p, Dot *q, double s) { vector pposn; vector qposn; vector pvel; vector qvel; vector newqp; /* vector difference of the positions */ vector newqpu; /* unit vector parallel to newqp */ vector newqv; /* " " " " velocities */ double projp; /* projection of p's velocity in newqp direction */ double projq; /* " " q's velocity in newqp direction */ double pmass; /* mass of p */ double qmass; /* mass of q */ double tmass; /* sum of mass of p and q */ double dmass; /* difference of mass of p and q */ double newp; double newq; if (s == 0.0) return; ptov(&pposn, &p->pos); ptov(&qposn, &q->pos); pvel = p->vel; qvel = q->vel; /* * Transform so that p is stationary at (0,0,0); */ vecsub(&newqp, &qposn, &pposn); vecsub(&newqv, &qvel, &pvel); scalmult(&newqpu, &newqp, -1.0 / s); if (dotprod(&newqv, &newqpu) <= 0.0) return; projp = dotprod(&pvel, &newqpu); projq = dotprod(&qvel, &newqpu); pmass = p->mass; qmass = q->mass; tmass = pmass + qmass; dmass = pmass - qmass; newp = (2.0 * qmass * projq + dmass * projp) / tmass; newq = (2.0 * pmass * projp - dmass * projq) / tmass; scalmult(&newqp, &newqpu, projp - newp); scalmult(&newqv, &newqpu, projq - newq); vecsub(&pvel, &pvel, &newqp); vecsub(&qvel, &qvel, &newqv); p->vel = pvel; vtop(&p->ivel, &p->vel); q->vel = qvel; vtop(&q->ivel, &q->vel); exchange_spin(p, q); if (iflag) exchange_charge(p, q); } static int rrand(int lo, int hi) { return lo + nrand(hi + 1 - lo); } static void drawdot(Dot *d) { Rectangle r; char buf[10]; r = d->face->r; r = rectsubpt(r, d->face->r.min); r = rectaddpt(r, d->pos); r = rectaddpt(r, screen->r.min); draw(screen, r, d->face, d->mask, d->face->r.min); if(PDUP > 1) { /* assume debugging */ sprint(buf, "(%d,%d)", d->pos.x, d->pos.y); string(screen, r.min, display->black, ZP, font, buf); } } static void undraw(Dot *d) { Rectangle r; r = d->face->r; r = rectsubpt(r, d->face->r.min); r = rectaddpt(r, d->pos); r = rectaddpt(r, screen->r.min); draw(screen, r, display->black, d->mask, d->face->r.min); /* if (track_width > 0) { if (d->ntracks >= track_length) { bitblt(&screen, d->track[d->next_clear], track, track->r, D ^ ~S); d->next_clear = (d->next_clear + 1) % track_length; d->ntracks--; } d->track[d->next_write] = Pt(d->pos.x + d->width / 2 - track_width / 2, d->pos.y + d->height / 2 - track_width / 2); bitblt(&screen, d->track[d->next_write], track, track->r, D ^ ~S); d->next_write = (d->next_write + 1) % track_length; d->ntracks++; } */ } static void copy(Image *a, Image *b) { if(PDUP==1 || eqrect(a->r, b->r)) draw(a, a->r, b, nil, b->r.min); else { int x, y; for(x = a->r.min.x; x < a->r.max.x; x++) for(y = a->r.min.y; y < a->r.max.y; y++) draw(a, Rect(x,y,x+1,y+1), b, nil, Pt(x/PDUP,y/PDUP)); } } static void transpose(Image *b) { int x, y; for(x = b->r.min.x; x < b->r.max.x; x++) for(y = b->r.min.y; y < b->r.max.y; y++) draw(im, Rect(y,x,y+1,x+1), b, nil, Pt(x,y)); draw(b, b->r, im, nil, ZP); } static void reflect1_lr(Image *b) { int x; for(x = 0; x < PDUP*NPJW; x++) draw(im, Rect(x, 0, x, PDUP*NPJW), b, nil, Pt(b->r.max.x-x,0)); draw(b, b->r, im, nil, ZP); } static void reflect1_ud(Image *b) { int y; for(y = 0; y < PDUP*NPJW; y++) draw(im, Rect(0, y, PDUP*NPJW, y), b, nil, Pt(0,b->r.max.y-y)); draw(b, b->r, im, nil, ZP); } static void rotate_clockwise(Image *b) { transpose(b); reflect1_lr(b); } static void spin(Dot *d) { int i; if (d->spin > 0) { i = (d->facei + d->spin) % nels(d->faces); d->face = d->faces[i]; d->mask = d->masks[i]; d->facei = i; } sleep(0); } static void restart(Dot *d) { static int beam; d->charge = 0; d->pos.x = 0; d->vel.x = 20.0 + (double)rrand(-2, 2); if (beam ^= 1) { d->pos.y = screen->r.max.y - d->height; d->vel.y = -20.0 + (double)rrand(-2, 2); } else { d->pos.y = 0; d->vel.y = 20.0 + (double)rrand(-2, 2); } vtop(&d->ivel, &d->vel); } static void upd(Dot *d) { Dot *dd; spin(d); /* * Bounce off others? */ for (dd = d + 1; dd != edot; dd++) { double s; if (close_enough(d, dd, &s)) rebound(d, dd, s); } if (iflag) { /* * Going off-screen? */ if ( d->pos.x + d->width < 0 || d->pos.x >= Dx(screen->r) || d->pos.y + d->height < 0 || d->pos.y >= Dy(screen->r) ) restart(d); /* * Charge. */ if (d->charge != 0) { /* * TODO: This is wrong -- should * generate closed paths. Can't * simulate effect of B field just * by adding in an orthogonal * velocity component... (sigh) */ vector f; double s; f.x = -d->vel.y; f.y = d->vel.x; if ((s = sqrt(sqr(f.x) + sqr(f.y))) != 0.0) { scalmult(&f, &f, -((double)d->charge) / s); vecaddpt(&d->vel, &f, &d->vel); vtop(&d->ivel, &d->vel); } } } else { /* * Bounce off left or right border? */ if (d->pos.x < 0) { d->vel.x = fabs(d->vel.x); vtop(&d->ivel, &d->vel); } else if (d->pos.x + d->width >= Dx(screen->r)) { d->vel.x = -fabs(d->vel.x); vtop(&d->ivel, &d->vel); } /* * Bounce off top or bottom border? * (bottom is slightly inelastic) */ if (d->pos.y < 0) { d->vel.y = fabs(d->vel.y); vtop(&d->ivel, &d->vel); } else if (d->pos.y + d->height >= Dy(screen->r)) { if (gravity.y == 0.0) d->vel.y = -fabs(d->vel.y); else if (d->ivel.y >= -MINV && d->ivel.y <= MINV) { /* * y-velocity is too small -- * give it a random kick. */ d->vel.y = (double)-rrand(30, 40); d->vel.x = (double)rrand(-10, 10); } else d->vel.y = -fabs(d->vel.y) * k_floor; vtop(&d->ivel, &d->vel); } } if (gravity.x != 0.0 || gravity.y != 0.0) { vecaddpt(&d->vel, &gravity, &d->vel); vtop(&d->ivel, &d->vel); } d->pos = addpt(d->pos, d->ivel); drawdot(d); } static void setup(Dot *d, char *who, uchar *face, int n_els) { int i, j, k, n; int repl; uchar mask; int nbits, bits; uchar tmpface[NPJW*NPJW]; uchar tmpmask[NPJW*NPJW]; static Image *im; /* not the global */ static Image *imask; if(im == nil) { im = eallocimage(display, Rect(0,0,NPJW,NPJW), CMAP8, 0, DNofill); imask = eallocimage(display, Rect(0,0,NPJW,NPJW), CMAP8, 0, DNofill); } repl = (NPJW*NPJW)/n_els; if(repl > 8) { fprint(2, "can't happen --rsc\n"); exits("repl"); } nbits = 8/repl; mask = (1<<(nbits))-1; if(0) print("converting %s... n_els=%d repl=%d mask=%x nbits=%d...\n", who, n_els, repl, mask, nbits); n = 0; for (i = 0; i < n_els; i++) { for(j = repl; j--; ) { bits = (face[i] >> (j*nbits)) & mask; tmpface[n] = 0; tmpmask[n] = 0; for(k = 0; k < repl; k++) { tmpface[n] |= (mask-bits) << (k*nbits); tmpmask[n] |= (bits==mask ? 0 : mask) << (k*nbits); } n++; } } if(n != sizeof tmpface) { fprint(2, "can't happen2 --rsc\n"); exits("n!=tmpface"); } loadimage(im, im->r, tmpface, n); loadimage(imask, imask->r, tmpmask, n); for (i = 0; i < nels(d->faces); i++) { d->faces[i] = eallocimage(display, Rect(0,0,PDUP*NPJW, PDUP*NPJW), screen->chan, 0, DNofill); d->masks[i] = eallocimage(display, Rect(0,0,PDUP*NPJW, PDUP*NPJW), GREY1, 0, DNofill); switch (i) { case 0: copy(d->faces[i], im); copy(d->masks[i], imask); break; case 1: copy(d->faces[i], im); copy(d->masks[i], imask); rotate_clockwise(d->faces[i]); rotate_clockwise(d->masks[i]); break; default: copy(d->faces[i], d->faces[i-2]); copy(d->masks[i], d->masks[i-2]); reflect1_lr(d->faces[i]); reflect1_ud(d->faces[i]); reflect1_lr(d->masks[i]); reflect1_ud(d->masks[i]); break; } } d->face = d->faces[0]; d->mask = d->masks[0]; d->height = Dy(im->r); d->width = Dx(im->r); d->mass = 1.0; d->spin = nrand(imin(3, total_spin + 1)); total_spin -= d->spin; d->pos.x = nrand(screen->r.max.x - d->width); d->pos.y = nrand(screen->r.max.y - d->height); d->vel.x = (double)rrand(-20, 20); d->vel.y = (double)rrand(-20, 20); vtop(&d->ivel, &d->vel); drawdot(d); } int msec(void) { static int fd; int n; char buf[64]; if(fd <= 0) fd = open("/dev/msec", OREAD); if(fd < 0) return 0; if(seek(fd, 0, 0) < 0) return 0; if((n=read(fd, buf, sizeof(buf)-1)) < 0) return 0; buf[n] = 0; return atoi(buf); } /* * debugging: make del pause so that we can * inspect window. */ jmp_buf j; static void myhandler(void *v, char *s) { if(strcmp(s, "interrupt") == 0) notejmp(v, j, -1); noted(NDFLT); } void main(int argc, char *argv[]) { int c; long now, then; ARGBEGIN { case 'i': iflag = 1; gravity = no_gravity; track_length = 64; track_width = 2; break; case 'k': k_floor = atof(ARGF()); break; case 'n': track_length = atoi(ARGF()); break; case 'w': track_width = atoi(ARGF()); break; case 'x': gravity.x = atof(ARGF()); break; case 'y': gravity.y = atof(ARGF()); break; default: fprint(2, "Usage: %s [-i] [-k k_floor] [-n track_length] [-w track_width] [-x gravityx] [-y gravityy]\n", argv0); exits("usage"); } ARGEND if (track_length > MAXTRACKS) track_length = MAXTRACKS; if (track_length == 0) track_width = 0; srand(time(0)); initdraw(0,0,0); im = eallocimage(display, Rect(0, 0, PDUP*NPJW, PDUP*NPJW), CMAP8, 0, DNofill); draw(screen, screen->r, display->black, nil, ZP); /* track = balloc(Rect(0, 0, track_width, track_width), 0); */ edot = &dot[0]; setup(edot++, "andrew", andrewbits, nels(andrewbits)); setup(edot++, "bart", bartbits, nels(bartbits)); setup(edot++, "bwk", bwkbits, nels(bwkbits)); setup(edot++, "dmr", dmrbits, nels(dmrbits)); setup(edot++, "doug", dougbits, nels(dougbits)); setup(edot++, "gerard", gerardbits, nels(gerardbits)); setup(edot++, "howard", howardbits, nels(howardbits)); setup(edot++, "ken", kenbits, nels(kenbits)); setup(edot++, "philw", philwbits, nels(philwbits)); setup(edot++, "pjw", pjwbits, nels(pjwbits)); setup(edot++, "presotto", presottobits, nels(presottobits)); setup(edot++, "rob", robbits, nels(robbits)); setup(edot++, "sean", seanbits, nels(seanbits)); setup(edot++, "td", tdbits, nels(tdbits)); if(PDUP > 1) { /* assume debugging */ setjmp(j); read(0, &c, 1); /* make DEL pause so that we can inspect window */ notify(myhandler); } SET(c); USED(c); #define DELAY 100 for (then = msec();; then = msec()) { Dot *d; for (d = dot; d != edot; d++) undraw(d); for (d = dot; d != edot; d++) upd(d); draw(screen, screen->r, screen, nil, screen->r.min); flushimage(display, 1); now = msec(); if(now - then < DELAY) sleep(DELAY - (now - then)); } }