shithub: riscv

Download patch

ref: 4821c261c4cea895ddcbd7c70bc71103c9a2763b
parent: baf20a548b903bb76f94045645f9b679383bb728
author: aiju <devnull@localhost>
date: Thu Jun 23 17:12:06 EDT 2016

added games/timmy

--- /dev/null
+++ b/sys/man/1/timmy
@@ -1,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).
--- /dev/null
+++ b/sys/src/games/timmy/dat.h
@@ -1,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
--- /dev/null
+++ b/sys/src/games/timmy/fns.h
@@ -1,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 *);
--- /dev/null
+++ b/sys/src/games/timmy/mkfile
@@ -1,0 +1,14 @@
+</$objtype/mkfile
+
+BIN=/$objtype/bin/games
+TARG=timmy
+OFILES=\
+	timmy.$O\
+	simple.$O\
+	poly.$O\
+	util.$O\
+	phys.$O\
+
+HFILES=dat.h fns.h
+
+</sys/src/cmd/mkone
--- /dev/null
+++ b/sys/src/games/timmy/phys.c
@@ -1,0 +1,219 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#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);
+	}
+}
--- /dev/null
+++ b/sys/src/games/timmy/poly.c
@@ -1,0 +1,323 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#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);
+}
--- /dev/null
+++ b/sys/src/games/timmy/simple.c
@@ -1,0 +1,107 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#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);
+}
--- /dev/null
+++ b/sys/src/games/timmy/timmy.c
@@ -1,0 +1,328 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <cursor.h>
+#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;
+		}
+	}
+}
--- /dev/null
+++ b/sys/src/games/timmy/util.c
@@ -1,0 +1,60 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+#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);
+}