diff --git a/sys/man/1/timmy b/sys/man/1/timmy new file mode 100644 index 000000000..bb348fab1 --- /dev/null +++ b/sys/man/1/timmy @@ -0,0 +1,51 @@ +.TH TIMMY 1 +.SH NAME +timmy \- physics sandbox +.SH SYNOPSIS +.B games/timmy +[ +.B -s +.I steps-per-frame +] +.SH DESCRIPTION +.I Timmy +is a simple 2D physics sandbox. +.PP +To pick up an object click on it with the LMB. +New objects can be created by picking up their archetypes in the gray area on the bottom (the "tray"). +To place an object in the working area click at the desired position with the LMB; +.I timmy +will refuse to place the object if it would collide with an existing one. +To abort the process \(em deleting the carried object \(em click anywhere with the RMB. +Picking up an object in the working area with the RMB will duplicate the object. +.PP +The following operations can be performed with the keyboard. +.IP w +Rotate carried object by 15° to the left. +.IP e +Rotate carried object by 15° to the right. +.IP space +Start or stop the simulation. +.IP del +Exit timmy. +.PP +The small circles on some objects are "hinges". +Two hinges can be connected by placing them on top of each other. +Their relative position will not change during the simulation; objects are however free to rotate around them. +To undo a hinge, pick up either of the objects. +.PP +The +.B -s +option adjusts the speed of the simulation; only integer values are permitted. +It does not compromise accuracy. +.SH SOURCE +.B /sys/src/games/timmy +.SH BUGS +.IR Timmy 's +physics may occasionally appear to originate from another universe. +.PP +.B -s +is a hack. +.SH HISTORY +.I Timmy +first appeared in 9front (June, 2016). diff --git a/sys/src/games/timmy/dat.h b/sys/src/games/timmy/dat.h new file mode 100644 index 000000000..459e7610b --- /dev/null +++ b/sys/src/games/timmy/dat.h @@ -0,0 +1,56 @@ +typedef struct Hinge Hinge; +typedef struct ObjT ObjT; +typedef struct Obj Obj; +typedef struct Poly Poly; +typedef struct Vec Vec; + +struct Vec { + double x, y; +}; + +struct Poly { + int nvert; + Vec *vert; + double invI; +}; + +struct Hinge { + Vec p; + Vec p0; + Obj *o; + Hinge *onext; + Hinge *cnext, *cprev; + Hinge *anext; +}; + +struct ObjT { + int flags; + Poly *poly; + Hinge *hinge; + Image *line, *fill; + double imass; + void (*draw)(Obj *, Image *); + void (*move)(Obj *, double, double, double); + void (*init)(Obj *); +}; + +struct Obj { + ObjT *tab; + Vec p, v; + double θ, ω; + Rectangle bbox; + Poly *poly; + Hinge *hinge; + Obj *next, *prev; + int idx; +}; + +enum { + TrayH = 100, + TraySpc = 20, +}; + +#define DEG 0.01745329251994330 +#define Slop 0.5 + +#define HingeSep 4.0 diff --git a/sys/src/games/timmy/fns.h b/sys/src/games/timmy/fns.h new file mode 100644 index 000000000..c8fcece3b --- /dev/null +++ b/sys/src/games/timmy/fns.h @@ -0,0 +1,25 @@ +void *emalloc(ulong); +Image *rgb(u32int); +Poly *mkpoly(int, ...); +Poly *polydup(Poly *); +void polytrans(Poly *, Poly *, double, double, double); +void polydraw(Poly *, Image *, Image *, Image *); +void polybbox(Poly *, Rectangle *); +void polyfix(Poly *); +Obj *mkobj(ObjT *); +Obj *objdup(Obj *); +void objcat(Obj *, Obj *); +Vec vecadd(Vec, Vec); +Vec vecsub(Vec, Vec); +Vec vecmul(Vec, double); +Vec vecnorm(Vec); +double vecdist(Vec, Vec); +double vecdot(Vec, Vec); +Vec vecnormal(Vec); +int objcoll(Obj *, Obj *); +void freeobj(Obj *); +void objexcise(Obj *); +void addtray(ObjT *, ...); +void physstep(void); +int hinged(Obj *, Obj *); +void copyhinges(Obj *, Obj *); diff --git a/sys/src/games/timmy/mkfile b/sys/src/games/timmy/mkfile new file mode 100644 index 000000000..5c4f5b950 --- /dev/null +++ b/sys/src/games/timmy/mkfile @@ -0,0 +1,14 @@ + +#include +#include +#include "dat.h" +#include "fns.h" + +extern Obj runo; +#define Grav 10 +#define Dt 0.01 +#define Beta 0.5 +int Steps = 4; + +typedef struct Contact Contact; +struct Contact { + Obj *vo, *eo; + Vec v, n; + double pen; + double impn, impt; + double targun; +}; +enum { CTSBLOCK = 64 }; + +Contact *cts; +int icts, ncts; + +static void +colldect(Obj *a, Obj *b) +{ + int i, j; + Vec *x, *y, *z; + double d, m; + Poly *ap, *bp; + + ap = a->poly; + bp = b->poly; + for(i = 0; i < ap->nvert; i++){ + z = &ap->vert[i]; + m = Inf(1); + for(j = 0; j < bp->nvert; j++){ + x = &bp->vert[j]; + y = x + 1; + d = -(z->x - x->x) * (y->y - x->y) + (z->y - x->y) * (y->x - x->x); + d /= vecdist(*x, *y); + if(d < -Slop) goto next; + if(d < m){ + if(m < 0) goto next; + m = d; + if(icts == ncts) + cts = realloc(cts, sizeof(Contact) * (ncts += CTSBLOCK)); + cts[icts] = (Contact){a, b, *z, vecnormal(vecsub(*x, *y)), d, 0, 0, 0}; + }else if(d < 0) goto next; + } + icts++; + next: ; + } +} + +static void +collresp(Contact *p) +{ + double μs, μd; + Vec n, t, u, r0, r1, Δp; + double ut, un, α, γ, γt, γn, pt, mt, mn; + double r0n, r0t, r1n, r1t; + double mv, me, Iv, Ie; + + n = p->n; + t = (Vec){n.y, -n.x}; + mv = p->vo->tab->imass; + me = p->eo->tab->imass; + Iv = mv * p->vo->poly->invI; + Ie = me * p->eo->poly->invI; + r0 = vecsub(p->v, p->vo->p); + r1 = vecsub(p->v, p->eo->p); + Δp.x = -(t.x * p->impt + n.x * p->impn); + Δp.y = -(t.y * p->impt + n.y * p->impn); + p->vo->v = vecadd(p->vo->v, vecmul(Δp, mv)); + p->vo->ω -= (Δp.x * r0.y - Δp.y * r0.x) * Iv; + p->eo->v = vecadd(p->eo->v, vecmul(Δp, -me)); + p->eo->ω += (Δp.x * r1.y - Δp.y * r1.x) * Ie; + u.x = p->vo->v.x - p->vo->ω * r0.y - p->eo->v.x + p->eo->ω * r1.y; + u.y = p->vo->v.y + p->vo->ω * r0.x - p->eo->v.y - p->eo->ω * r1.x; + ut = vecdot(u, t); + un = vecdot(u, n); + r0t = vecdot(r0, t); + r0n = vecdot(r0, n); + r1t = vecdot(r1, t); + r1n = vecdot(r1, n); + γ = 0; /* accumulated normal impulse */ + pt = 0; /* accumulated transverse impulse */ + μs = 0.5; + μd = 0.3; + + un += p->targun; + if(un >= 0 && p->pen <= 0){ + p->impt = 0; + p->impn = 0; + return; + } + if(p->pen > 0){ + un -= Beta * p->pen / Dt; + if(un >= 0){ + mn = mv + r0t * r0t * Iv; + mn += me + r1t * r1t * Ie; + γ = -un/mn; + un = 0; + } + } + while(un < 0){ + /* calculate α, the effective coefficient of friction */ + if(ut == 0){ + α = r0t * r0n * Iv + r1t * r1n * Ie; + α /= mv + r0n * r0n * Iv + me + r1n * r1n * Ie; + if(α > μs) α = μd; + else if(α < -μs) α = -μd; + }else + α = ut < 0 ? μd : -μd; + + mt = α * mv + (r0n * r0n * α - r0t * r0n) * Iv; + mt += α * me + (r1n * r1n * α - r1t * r1n) * Ie; + mn = mv + (r0t * r0t - r0n * r0t * α) * Iv; + mn += me + (r1t * r1t - r1n * r1t * α) * Ie; + /* determine events which would change α */ + if(ut == 0) γt = Inf(1); + else{ + γt = γ - ut / mt; + if(γt < γ) γt = Inf(1); + } + γn = γ - un / mn; + if(γn < γ) γn = Inf(1); + /* choose earlier one */ + if(γt < γn){ + ut = 0; + un += mn * (γt - γ); + pt += (γt - γ) * α; + γ = γt; + }else{ + assert(γn < Inf(1)); + un = 0; + ut += mt * (γn - γ); + pt += (γn - γ) * α; + γ = γn; + } + } + + p->impt = pt; + p->impn = γ; + Δp.x = t.x * pt + n.x * γ; + Δp.y = t.y * pt + n.y * γ; + p->vo->v = vecadd(p->vo->v, vecmul(Δp, mv)); + p->vo->ω -= (Δp.x * r0.y - Δp.y * r0.x) * Iv; + p->eo->v = vecadd(p->eo->v, vecmul(Δp, -me)); + p->eo->ω += (Δp.x * r1.y - Δp.y * r1.x) * Ie; +} + +extern Hinge *hinges; + +static void +hingeresp(Hinge *h) +{ + Obj *a, *b; + Vec u, Δp, r0, r1; + double ma, mb, Ia, Ib; + double mxx, mxy, myy, det; + + a = h->o; + b = h->cnext->o; + ma = a->tab->imass; + mb = b->tab->imass; + Ia = ma * a->poly->invI; + Ib = mb * b->poly->invI; + r0 = vecsub(h->p, a->p); + r1 = vecsub(h->cnext->p, b->p); + u.x = a->v.x - a->ω * r0.y - b->v.x + b->ω * r1.y; + u.y = a->v.y + a->ω * r0.x - b->v.y - b->ω * r1.x; + u.x += Beta * (h->p.x - h->cnext->p.x) / Dt; + u.y += Beta * (h->p.y - h->cnext->p.y) / Dt; + mxx = ma + Ia * r0.x * r0.x + mb + Ib * r1.x * r1.x; + mxy = Ia * r0.x * r0.y + Ib * r1.x * r1.y; + myy = ma + Ia * r0.y * r0.y + mb + Ib * r1.y * r1.y; + det = mxx * myy - mxy * mxy; + Δp.x = (mxx * u.x + mxy * u.y) / det; + Δp.y = (myy * u.y + mxy * u.x) / det; + a->v = vecadd(a->v, vecmul(Δp, -ma)); + a->ω += (Δp.x * r0.y - Δp.y * r0.x) * Ia; + b->v = vecadd(b->v, vecmul(Δp, mb)); + b->ω -= (Δp.x * r1.y - Δp.y * r1.x) * Ib; + u.x = a->v.x - a->ω * r0.y - b->v.x + b->ω * r1.y; + u.y = a->v.y + a->ω * r0.x - b->v.y - b->ω * r1.x; +} + +void +physstep(void) +{ + Obj *o, *a, *b; + int i, j, k; + Hinge *p; + + for(k = 0; k < Steps; k++){ + for(o = runo.next; o != &runo; o = o->next) + if(o->tab->imass != 0) + o->v.y += Grav * Dt; + icts = 0; + for(a = runo.next; a != &runo; a = a->next) + for(b = a->next; b != &runo; b = b->next){ + if(!rectXrect(a->bbox, b->bbox) || a->poly == nil || b->poly == nil || hinged(a, b)) continue; + colldect(a, b); + colldect(b, a); + } + for(j = 0; j < 10; j++){ + for(i = 0; i < icts; i++) + collresp(&cts[i]); + for(p = hinges; p != nil; p = p->anext) + hingeresp(p); + } + for(o = runo.next; o != &runo; o = o->next) + o->tab->move(o, o->p.x + o->v.x * Dt, o->p.y + o->v.y * Dt, o->θ + o->ω * Dt / DEG); + } +} diff --git a/sys/src/games/timmy/poly.c b/sys/src/games/timmy/poly.c new file mode 100644 index 000000000..544b9de93 --- /dev/null +++ b/sys/src/games/timmy/poly.c @@ -0,0 +1,323 @@ +#include +#include +#include +#include +#include +#include "dat.h" +#include "fns.h" + +Poly * +mkpoly(int n, ...) +{ + Poly *p; + int i; + va_list va; + + p = emalloc(sizeof(Poly)); + p->nvert = n; + p->vert = emalloc((n + 1) * sizeof(Vec)); + va_start(va, n); + for(i = 0; i < n; i++){ + p->vert[i].x = va_arg(va, double); + p->vert[i].y = va_arg(va, double); + } + p->vert[n] = p->vert[0]; + va_end(va); + polyfix(p); + return p; +} + +void +polyfix(Poly *o) +{ + double I, A, x, y, t; + Vec *p, *q; + int i; + + I = 0; + A = 0; + x = 0; + y = 0; + for(i = 0; i < o->nvert; i++){ + p = &o->vert[i]; + q = p + 1; + t = p->x * q->y - p->y * q->x; + A += t; + x += (p->x + q->x) * t / 3; + y += (p->y + q->y) * t / 3; + } + x /= A; + y /= A; + for(i = 0; i <= o->nvert; i++){ + o->vert[i].x -= x; + o->vert[i].y -= y; + } + for(i = 0; i < o->nvert; i++){ + p = &o->vert[i]; + q = p + 1; + t = p->x * q->y - p->y * q->x; + I += (p->x * (p->x + q->x) + q->x * q->x + p->y * (p->y + q->y) + q->y * q->y) * t / 6; + } + o->invI = A / I; +} + +Poly * +polydup(Poly *p) +{ + Poly *q; + int i; + + q = emalloc(sizeof(Poly)); + q->nvert = p->nvert; + q->vert = emalloc((p->nvert + 1) * sizeof(Vec)); + for(i = 0; i <= p->nvert; i++) + q->vert[i] = p->vert[i]; + q->invI = p->invI; + return q; +} + +void +polytrans(Poly *sp, Poly *dp, double x0, double y0, double θ) +{ + int i; + double c, s, x, y; + + assert(sp->nvert == dp->nvert); + c = cos(θ * DEG); + s = sin(θ * DEG); + for(i = 0; i <= sp->nvert; i++){ + x = sp->vert[i].x; + y = sp->vert[i].y; + dp->vert[i].x = x0 + x * c - y * s; + dp->vert[i].y = y0 + x * s + y * c; + } + dp->invI = sp->invI; +} + +void +polybbox(Poly *sp, Rectangle *t) +{ + int fx, fy, cx, cy, i; + + t->min.x = floor(sp->vert[0].x - Slop); + t->max.x = ceil(sp->vert[0].x + Slop) + 1; + t->min.y = floor(sp->vert[0].y - Slop); + t->max.y = ceil(sp->vert[0].y + Slop) + 1; + for(i = 1; i < sp->nvert; i++){ + fx = sp->vert[i].x; + cx = ceil(fx + Slop); fx = floor(fx - Slop); + fy = sp->vert[i].y + 1; + cy = ceil(fy + Slop); fy = floor(fy - Slop); + if(fx < t->min.x) t->min.x = fx; + if(cx > t->max.x) t->max.x = cx; + if(fy < t->min.y) t->min.y = fy; + if(cy > t->max.y) t->max.y = cy; + } +} + +void +polydraw(Poly *p, Image *d, Image *fill, Image *line) +{ + int i; + static int maxp; + static Point *buf; + + if(p->nvert + 1 > maxp){ + maxp = p->nvert + 1; + free(buf); + buf = emalloc((p->nvert + 1) * sizeof(Point)); + } + for(i = 0; i <= p->nvert; i++){ + buf[i].x = d->r.min.x + (int)(p->vert[i].x + 0.5); + buf[i].y = d->r.min.y + (int)(p->vert[i].y + 0.5); + } + if(fill != nil) fillpoly(d, buf, p->nvert + 1, 0, fill, ZP); + if(line != nil) poly(d, buf, p->nvert + 1, 0, 0, 0, line, ZP); +} + +void +freepoly(Poly *p) +{ + if(p == nil) return; + free(p->vert); + free(p); +} + +Hinge * +hingedup(Hinge *h, Obj *o) +{ + Hinge *p, **hp, *r; + + r = nil; + hp = &r; + for(; h != nil; h = h->onext){ + p = emalloc(sizeof(Hinge)); + p->p = h->p; + p->p0 = h->p0; + p->o = o; + p->cnext = p->cprev = p; + *hp = p; + hp = &p->onext; + } + return r; +} + +Obj * +mkobj(ObjT *t) +{ + Obj *o; + + o = emalloc(sizeof(Obj)); + o->tab = t; + o->hinge = hingedup(t->hinge, o); + o->tab->init(o); + o->next = o->prev = o; + return o; +} + +Obj * +objdup(Obj *o) +{ + Obj *p; + + p = emalloc(sizeof(Obj)); + *p = *o; + p->poly = polydup(o->poly); + p->next = p->prev = p; + p->hinge = hingedup(p->hinge, p); + return p; +} + +void +objcat(Obj *l, Obj *o) +{ + o->prev = l->prev; + o->next = l; + o->prev->next = o; + o->next->prev = o; +} + +static int +polycheck(Poly *a, Poly *b) +{ + int i, j; + Vec *x, *y, *z; + double d, m; + + for(i = 0; i < a->nvert; i++){ + z = &a->vert[i]; + m = Inf(1); + for(j = 0; j < b->nvert; j++){ + x = &b->vert[j]; + y = x + 1; + d = (z->y - x->y) * (y->x - x->x) - (z->x - x->x) * (y->y - x->y); + d /= vecdist(*x, *y); + if(d < -Slop) goto next; + if(d < m){ + if(m < 0) goto next; + m = d; + }else if(d < 0) goto next; + } + return 1; + next:; + } + return 0; +} + +int +objcoll(Obj *a, Obj *b) +{ + if(!rectXrect(a->bbox, b->bbox)) return 0; + if(a->poly == nil || b->poly == nil) return 0; + return polycheck(a->poly, b->poly) || polycheck(b->poly, a->poly); +} + +void +objexcise(Obj *o) +{ + Hinge *h; + + o->next->prev = o->prev; + o->prev->next = o->next; + o->next = o; + o->prev = o; + for(h = o->hinge; h != nil; h = h->onext){ + h->cprev->cnext = h->cnext; + h->cnext->cprev = h->cprev; + h->cprev = h; + h->cnext = h; + } +} + +void +freeobj(Obj *o) +{ + if(o == nil) return; + objexcise(o); + freepoly(o->poly); + free(o); +} + +int +hinged(Obj *a, Obj *b) +{ + Hinge *k, *l, *m; + + if(b->hinge == nil) return 0; + for(k = a->hinge; k != nil; k = k->onext) + for(l = k->cnext; l != k; l = l->cnext) + for(m = b->hinge; m != nil; m = m->onext) + if(m == l) + return 1; + return 0; +} + +Hinge *hinges; + +void +hingepairs(Obj *l) +{ + Obj *o; + Hinge *h, *hh; + Hinge **p; + + hinges = nil; + p = &hinges; + for(o = l->next; o != l; o = o->next) + for(h = o->hinge; h != nil; h = h->onext){ + for(hh = h->cnext; hh != h; hh = hh->cnext) + if(hh < h) + break; + if(hh == h) continue; + *p = h; + p = &h->anext; + } +} + +void +copyhinges(Obj *sl, Obj *dl) +{ + Obj *o, *p, **ol; + Hinge *h, *k, *l; + int n; + + for(n = 0, o = sl->next; o != sl; o = o->next) + o->idx = n++; + ol = emalloc(sizeof(Obj *) * n); + for(n = 0, o = dl->next; o != dl; o = o->next) + ol[n++] = o; + for(o = sl->next, p = dl->next; o != sl; o = o->next, p = p->next){ + for(h = o->hinge, k = p->hinge; h != nil; h = h->onext, k = k->onext){ + if(h->cnext == h) continue; + for(l = h->cnext->o->hinge, n = 0; l != h->cnext; l = l->onext) + n++; + for(l = ol[h->cnext->o->idx]->hinge; n != 0; n--) + l = l->onext; + l->cprev->cnext = k->cnext; + k->cnext->cprev = l->cprev; + k->cnext = l; + l->cprev = k; + } + } + hingepairs(dl); +} diff --git a/sys/src/games/timmy/simple.c b/sys/src/games/timmy/simple.c new file mode 100644 index 000000000..9a560abc7 --- /dev/null +++ b/sys/src/games/timmy/simple.c @@ -0,0 +1,107 @@ +#include +#include +#include +#include "dat.h" +#include "fns.h" + +void +objpolyinit(Obj *o) +{ + o->poly = polydup(o->tab->poly); +} + +void +objpolymove(Obj *o, double x, double y, double θ) +{ + Hinge *h; + double c, s; + + o->p.x = x; + o->p.y = y; + o->θ = θ; + polytrans(o->tab->poly, o->poly, x, y, θ); + polybbox(o->poly, &o->bbox); + if(o->hinge != nil){ + c = cos(θ * DEG); + s = sin(θ * DEG); + for(h = o->hinge; h != nil; h = h->onext){ + h->p.x = c * h->p0.x - s * h->p0.y + x; + h->p.y = s * h->p0.x + c * h->p0.y + y; + } + } +} + +void +objpolydraw(Obj *o, Image *i) +{ + Hinge *h; + Point p; + + polydraw(o->poly, i, o->tab->fill, o->tab->line); + for(h = o->hinge; h != nil; h = h->onext){ + p.x = i->r.min.x + (int)(h->p.x + 0.5); + p.y = i->r.min.y + (int)(h->p.y + 0.5); + ellipse(i, p, 2, 2, 0, display->black, ZP); + } +} + +void +mkobjpoly(ObjT *t, Poly *p, Image *fill, Image *line, double imass) +{ + t->init = objpolyinit; + t->draw = objpolydraw; + t->move = objpolymove; + t->poly = p; + t->line = line; + t->fill = fill; + t->imass = imass; +} + +void +addhinge(ObjT *t, double x, double y) +{ + Hinge *h, **hp; + + h = emalloc(sizeof(Hinge)); + h->p.x = x; + h->p.y = y; + h->p0 = h->p; + h->cnext = h->cprev = h; + for(hp = &t->hinge; *hp != nil; hp = &(*hp)->onext) + ; + *hp = h; +} + +Poly * +mkball(int n) +{ + Poly *p; + int i; + + p = emalloc(sizeof(Poly)); + p->nvert = n; + p->vert = emalloc(sizeof(Vec) * (n + 1)); + for(i = 0; i < n; i++){ + p->vert[i].x = 10 * cos(2 * PI * i / n); + p->vert[i].y = 10 * sin(2 * PI * i / n); + } + p->vert[n] = p->vert[0]; + polyfix(p); + return p; +} + +ObjT tdomino, tboard, thboard, tball, tfix; + +void +simpleinit(void) +{ + mkobjpoly(&tdomino, mkpoly(4, 0.0, 0.0, 10.0, 0.0, 10.0, 40.0, 0.0, 40.0), rgb(0xFF0000FF), display->black, 10); + mkobjpoly(&tboard, mkpoly(4, 0.0, 0.0, 100.0, 0.0, 100.0, 6.0, 0.0, 6.0), rgb(0x663300FF), nil, 0); + mkobjpoly(&thboard, mkpoly(4, 0.0, 0.0, 100.0, 0.0, 100.0, 6.0, 0.0, 6.0), rgb(0x884400FF), nil, 0.5); + addhinge(&thboard, 48.0, 0.0); + addhinge(&thboard, -48.0, 0.0); + mkobjpoly(&tball, mkball(17), rgb(0x00FF00FF), nil, 3); + mkobjpoly(&tfix, mkpoly(3, 0.0, 0.0, 10.0, -17.3, 20.0, 0.0), rgb(0x663300FF), display->black, 0); + addhinge(&tfix, 0.0, 0.0); + addtray(&tdomino, &tboard, &thboard, &tfix, &tball, nil); +} diff --git a/sys/src/games/timmy/timmy.c b/sys/src/games/timmy/timmy.c new file mode 100644 index 000000000..4d2aa79f7 --- /dev/null +++ b/sys/src/games/timmy/timmy.c @@ -0,0 +1,328 @@ +#include +#include +#include +#include +#include +#include +#include +#include "dat.h" +#include "fns.h" + +Screen *scr; +Image *work, *tray; +Image *grey; +Obj trayo; +Obj worko; +Obj runo; +Mousectl *mc; +Keyboardctl *kc; +Obj *carry; +int showcarry; +extern int Steps; + +void * +emalloc(ulong sz) +{ + void *v; + + v = malloc(sz); + if(v == nil) sysfatal("malloc: %r"); + memset(v, 0, sz); + setmalloctag(v, getcallerpc(&sz)); + return v; +} + +Image * +rgb(u32int c) +{ + return allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, c); +} + +void +addtray(ObjT *t, ...) +{ + Obj *o; + va_list va; + static double trayw; + + va_start(va, t); + for(; t != nil; t = va_arg(va, ObjT *)){ + o = mkobj(t); + o->tab->move(o, 0, 0, 0); + trayw += TraySpc; + o->tab->move(o, trayw + Dx(o->bbox)/2, TrayH/2, 0); + trayw += Dx(o->bbox); + objcat(&trayo, o); + } + va_end(va); +} + +static void +drawtray(void) +{ + Obj *o; + + for(o = trayo.next; o != &trayo; o = o->next) + o->tab->draw(o, tray); +} + +static void +screeninit(void) +{ + grey = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0xCCCCCCFF); + scr = allocscreen(screen, display->white, 0); + work = allocwindow(scr, Rect(screen->r.min.x, screen->r.min.y, screen->r.max.x, screen->r.max.y - TrayH), 0, 0xFFFFFFFF); + tray = allocwindow(scr, Rect(screen->r.min.x, screen->r.max.y - TrayH, screen->r.max.x, screen->r.max.y), 0, 0xCCCCCCFF); +} + +static Obj * +objclick(Point p, Obj *l) +{ + Obj *o; + + for(o = l->next; o != l; o = o->next) + if(ptinrect(p, o->bbox)) + return o; + return nil; +} + +static void +workdraw(void) +{ + Obj *o; + + draw(work, work->r, display->white, nil, ZP); + for(o = worko.next; o != &worko; o = o->next) + o->tab->draw(o, work); + if(carry != nil && showcarry) + carry->tab->draw(carry, work); + flushimage(display, 1); +} + +static void +rundraw(void) +{ + Obj *o; + + draw(work, work->r, display->white, nil, ZP); + for(o = runo.next; o != &runo; o = o->next) + o->tab->draw(o, work); + flushimage(display, 1); +} + +static int +canhinge(Obj *a, Obj *b) +{ + Hinge *h, *k; + + if(a->hinge == nil || b->hinge == nil) return 0; + for(h = a->hinge; h != nil; h = h->onext) + for(k = b->hinge; k != nil; k = k->onext) + if(vecdist(h->p, k->p) <= HingeSep) + return 1; + return 0; +} + +static int +hinge(Obj *a, Obj *b) +{ + Hinge *h, *k, *l; + + if(a->hinge == nil || b->hinge == nil) return 0; + for(h = a->hinge; h != nil; h = h->onext) + for(k = b->hinge; k != nil; k = k->onext) + if(vecdist(h->p, k->p) <= HingeSep){ + h->cprev->cnext = k; + k->cprev->cnext = h; + l = h->cprev; + h->cprev = k->cprev; + k->cprev = l; + b->tab->move(b, b->p.x + h->p.x - k->p.x, b->p.y + h->p.y - k->p.y, b->θ); + return 1; + } + return 0; +} + +static void +place(void) +{ + Obj *o; + int hinges; + + hinges = 0; + for(o = worko.next; o != &worko; o = o->next) + if(objcoll(o, carry)) + if(canhinge(o, carry)) + hinges++; + else + return; + for(o = worko.next; hinges > 0 && o != &worko; o = o->next) + if(objcoll(o, carry)) + hinges -= hinge(o, carry); + if(hinges != 0) print("hinge error\n"); + objcat(&worko, carry); + carry = nil; + workdraw(); +} + +static void +mouse(void) +{ + static int lbut = -1; + Point p; + + if(lbut < 0) + lbut = mc->buttons; + if(ptinrect(mc->xy, work->r)){ + p = subpt(mc->xy, work->r.min); + if(carry != nil && (carry->p.x != p.x || carry->p.y != p.y || !showcarry)){ + carry->tab->move(carry, p.x, p.y, carry->θ); + showcarry = 1; + workdraw(); + } + }else if(showcarry){ + showcarry = 0; + if(carry != nil) + workdraw(); + } + if((~mc->buttons & lbut & 1) != 0){ + if(ptinrect(mc->xy, tray->r)){ + carry = objclick(subpt(mc->xy, tray->r.min), &trayo); + if(carry != nil) + carry = objdup(carry); + }else if(ptinrect(mc->xy, work->r)){ + if(carry != nil) + place(); + else{ + carry = objclick(subpt(mc->xy, work->r.min), &worko); + if(carry != nil) + objexcise(carry); + } + } + } + if((~mc->buttons & lbut & 4) != 0){ + if(carry != nil){ + freeobj(carry); + carry = nil; + showcarry = 0; + workdraw(); + }else if(ptinrect(mc->xy, work->r)){ + carry = objclick(subpt(mc->xy, work->r.min), &worko); + if(carry != nil) + carry = objdup(carry); + } + } + lbut = mc->buttons; +} + +static void +run(void) +{ + Obj *o, *oo; + Rune r; + static Cursor cursor; + + for(o = runo.next; o != &runo; o = oo){ + oo = o->next; + freeobj(o); + } + for(o = worko.next; o != &worko; o = o->next) + objcat(&runo, objdup(o)); + copyhinges(&worko, &runo); + setcursor(mc, &cursor); + for(;;){ + Alt a[] = { + {mc->c, &mc->Mouse, CHANRCV}, + {kc->c, &r, CHANRCV}, + {nil, nil, CHANNOBLK} + }; + + switch(alt(a)){ + case 0: mouse(); break; + case 1: + switch(r){ + case ' ': goto out; + case Kdel: threadexitsall(nil); + } + } + + physstep(); + rundraw(); + } +out: + workdraw(); + setcursor(mc, nil); +} + +static void +key(Rune r) +{ + switch(r){ + case Kdel: + threadexitsall(nil); + case 'w': + if(carry != nil){ + carry->tab->move(carry, carry->p.x, carry->p.y, carry->θ - 15); + workdraw(); + } + break; + case 'e': + if(carry != nil){ + carry->tab->move(carry, carry->p.x, carry->p.y, carry->θ + 15); + workdraw(); + } + break; + case ' ': + run(); + break; + } +} + +static void +usage(void) +{ + fprint(2, "usage: %s [-s steps]\n", argv0); + threadexitsall("usage"); +} + +void +threadmain(int argc, char **argv) +{ + void simpleinit(void); + Rune r; + char *s; + + ARGBEGIN{ + case 's': + Steps = strtol(EARGF(usage()), &s, 0); + if(*s != 0) usage(); + break; + default: usage(); + }ARGEND; + + if(initdraw(nil, nil, nil) < 0) sysfatal("initdraw: %r"); + mc = initmouse(nil, screen); + if(mc == nil) sysfatal("initmouse: %r"); + kc = initkeyboard(nil); + if(kc == nil) sysfatal("initkeyboard: %r"); + screeninit(); + trayo.prev = trayo.next = &trayo; + worko.prev = worko.next = &worko; + runo.prev = runo.next = &runo; + simpleinit(); + drawtray(); + flushimage(display, 1); + + for(;;){ + Alt a[] = { + {mc->c, &mc->Mouse, CHANRCV}, + {kc->c, &r, CHANRCV}, + {nil, nil, CHANEND} + }; + + switch(alt(a)){ + case 0: mouse(); break; + case 1: key(r); break; + } + } +} diff --git a/sys/src/games/timmy/util.c b/sys/src/games/timmy/util.c new file mode 100644 index 000000000..82d285fe2 --- /dev/null +++ b/sys/src/games/timmy/util.c @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include +#include +#include "dat.h" +#include "fns.h" + +Vec +vecadd(Vec a, Vec b) +{ + return (Vec){a.x + b.x, a.y + b.y}; +} + +Vec +vecsub(Vec a, Vec b) +{ + return (Vec){a.x - b.x, a.y - b.y}; +} + +Vec +vecmul(Vec v, double s) +{ + return (Vec){v.x * s, v.y * s}; +} + +Vec +vecnorm(Vec v) +{ + double r; + + r = hypot(v.x, v.y); + if(r == 0) return (Vec){0, 0}; + v.x /= r; + v.y /= r; + return v; +} + +double +vecdist(Vec a, Vec b) +{ + return hypot(a.x - b.x, a.y - b.y); +} + +double +vecdot(Vec a, Vec b) +{ + return a.x * b.x + a.y * b.y; +} + +Vec +vecnormal(Vec n) +{ + Vec m; + + m.x = -n.y; + m.y = n.x; + return vecnorm(m); +}