Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

#include        <u.h>
#include        <libc.h>
#include        <draw.h>

#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));
        }
}