diff options
Diffstat (limited to 'appl/wm/bounce.b')
| -rw-r--r-- | appl/wm/bounce.b | 356 |
1 files changed, 356 insertions, 0 deletions
diff --git a/appl/wm/bounce.b b/appl/wm/bounce.b new file mode 100644 index 00000000..7d6cdd1b --- /dev/null +++ b/appl/wm/bounce.b @@ -0,0 +1,356 @@ +implement Bounce; + +# bouncing balls demo. it uses tk and multiple processes to animate a +# number of balls bouncing around the screen. each ball has its own +# process; CPU time is doled out fairly to each process by using +# a central monitor loop. + +include "sys.m"; + sys: Sys; +include "draw.m"; + draw: Draw; + Point, Rect: import draw; +include "tk.m"; + tk: Tk; +include "tkclient.m"; + tkclient: Tkclient; +include "math.m"; + math: Math; +include "rand.m"; + +Bounce: module { + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +BALLSIZE: con 4; +ZERO: con 1e-6; +π: con Math->Pi; + +Line: adt { + p1, p2: Point; +}; + +Realpoint: adt { + x, y: real; +}; + +gamecmds := array[] of { +"canvas .c", +"bind .c <ButtonRelease-1> {send cmd 0 %x %y}", +"bind .c <ButtonRelease-2> {send cmd 0 %x %y}", +"bind .c <Button-1> {send cmd 1 %x %y}", +"bind .c <Button-2> {send cmd 2 %x %y}", +"frame .f", +"button .f.left -bitmap small_color_left.bit -bd 0 -command {send cmd k -1}", +"button .f.right -bitmap small_color_right.bit -bd 0 -command {send cmd k 1}", +"label .f.l -text {8 balls}", +"pack .f.left .f.right -side left", +"pack .f.l -side left", +"pack .f -fill x", +"pack .c -fill both -expand 1", +}; + +randch: chan of int; +lines: list of (int, Line); +lineid := 0; +lineversion := 0; + +addline(win: ref Tk->Toplevel, v: Line) +{ + lines = (++lineid, v) :: lines; + cmd(win, ".c create line " + pt2s(v.p1) + " " + pt2s(v.p2) + " -width 3 -fill black" + + " -tags l" + string lineid); + lineversion++; +} + +nomod(s: string) +{ + sys->fprint(sys->fildes(2), "bounce: cannot load %s: %r\n", s); + raise "fail:bad module"; +} + +init(ctxt: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + draw = load Draw Draw->PATH; + math = load Math Math->PATH; + tk = load Tk Tk->PATH; + tkclient = load Tkclient Tkclient->PATH; + if (tkclient == nil) + nomod(Tkclient->PATH); + tkclient->init(); + nballs := 8; + if (argv != nil && tl argv != nil) + nballs = int hd tl argv; + if (nballs < 0) { + sys->fprint(sys->fildes(2), "usage: bounce [nballs]\n"); + raise "fail:usage"; + } + sys->pctl(Sys->NEWPGRP, nil); + if(ctxt == nil) + ctxt = tkclient->makedrawcontext(); + (win, wmctl) := tkclient->toplevel(ctxt, nil, "Bounce", 0); + cmdch := chan of string; + tk->namechan(win, cmdch, "cmd"); + for (i := 0; i < len gamecmds; i++) + cmd(win, gamecmds[i]); + cmd(win, ".c configure -width 400 -height 400"); + cmd(win, "pack propagate . 0"); + cmd(win, ".f.l configure -text '" + string nballs + " balls"); + tkclient->onscreen(win, nil); + tkclient->startinput(win, "kbd"::"ptr"::nil); + + mch := chan of (int, Point); + randch = chan of int; + spawn randgenproc(randch); + csz := Point(int cmd(win, ".c cget -actwidth"), int cmd(win, ".c cget -actheight")); + + # add edges of window + addline(win, ((-1, -1), (csz.x, -1))); + addline(win, ((csz.x, -1), csz)); + addline(win, (csz, (-1, csz.y))); + addline(win, ((-1, csz.y), (-1, -1))); + + spawn makelinesproc(win, mch); + mkball := chan of (int, Realpoint, Realpoint); + spawn monitor(win, mkball); + for (i = 0; i < nballs; i++) + mkball <-= (1, randpoint(csz), makeunit(randpoint(csz))); + for (;;) alt { + s := <-win.ctxt.kbd => + tk->keyboard(win, s); + s := <-win.ctxt.ptr => + tk->pointer(win, *s); + s := <-win.ctxt.ctl or + s = <-win.wreq or + s = <-wmctl => + tkclient->wmctl(win, s); + c := <-cmdch => + (nil, toks) := sys->tokenize(c, " "); + if (hd toks != "k") { + mch <-= (int hd toks, Point(int hd tl toks, int hd tl tl toks)); + continue; + } + n := nballs + int hd tl toks; + if (n < 0) + n = 0; + dn := 1; + if (n < nballs) + dn = -1; + for (; nballs != n; nballs += dn) + mkball <-= (dn, randpoint(csz), makeunit(randpoint(csz))); + cmd(win, ".f.l configure -text '" + string nballs + " balls"); + cmd(win, "update"); + } +} + +randpoint(size: Point): Realpoint +{ + return (randreal(size.x), randreal(size.y)); +} + +# return randomish real number between 1 and x-1 +randreal(x: int): real +{ + return real (<-randch % ((x - 1) * 100)) / 100.0 + 1.0; +} + +# make sure cpu time is handed to all ball processes fairly +# by passing a "token" around to each process in turn. +# each process does its work when it *hasn't* got its +# token but it can't go through two iterations without +# waiting its turn. +# +# new processes can be created and destroyed by +# sending on mkball. processes are arranged in a stack-like +# order: new processes are added to the top of the stack, and +# processes are destroyed from the top of the stack downwards. +monitor(win: ref Tk->Toplevel, mkball: chan of (int, Realpoint, Realpoint)) +{ + procl := proc := chan of int :: nil; + spawn nullproc(hd proc); # always there to avoid deadlock when no balls. + hd proc <-= 1; # hand token to dummy proc + for (;;) { + procc := hd proc; + alt { + (n, p, v) := <-mkball => + if (n > 0) { # start new ball proc going. + procl = chan of int :: procl; + spawn animproc(hd procl, win, p, v); + } else if (tl procl != nil) { # stop a ball proc. + <-hd proc; # get token. + hd procl <-= 0; # stop proc. + proc = procl = tl procl; # remove proc. + hd proc <-= 1; # hand out token. + } + <-procc => # got token. + if ((proc = tl proc) == nil) + proc = procl; + hd proc <-= 1; # hand token to next process. + } + } +} + +nullproc(c: chan of int) +{ + for (;;) + c <-= <-c; +} + +# animate one ball. initial position and unit-velocity are +# given by p and v. +animproc(c: chan of int, win: ref Tk->Toplevel, p, v: Realpoint) +{ + speed := 0.1 + real (<-randch % 40) / 100.0; + ballid := cmd(win, sys->sprint(".c create oval 0 0 1 1 -fill #%.6x", <-randch & 16rffffff)); + hitlineid := -1; + smallcount := 0; + version := lineversion; +loop: for (;;) { + hitline: Line; + hitp: Realpoint; + + dist := 1000000.0; + oldid := hitlineid; + for (l := lines; l != nil; l = tl l) { + (id, line) := hd l; + (ok, hp, hdist) := intersect(p, v, line); + if (ok && hdist < dist && id != oldid && (smallcount < 10 || hdist > 1.5)) { + (hitp, hitline, hitlineid, dist) = (hp, line, id, hdist); + } + } + if (dist > 10000.0) { + sys->print("no intersection!\n"); +# sys->print("p: [%f, %f], v: [%f, %f]\n", p.x, p.y, v.x, v.y); +# for (l := lines; l != nil; l = tl l) { +# (id, line) := hd l; +# (ok, hp, hdist) := intersect(p, v, line); +# sys->print("line: [%d %d]->[%d %d] -> %d, [%f, %f], %f\n", line.p1.x, line.p1.y, line.p2.x, line.p2.y, +# ok, hp.x, hp.y, hdist); +# } + cmd(win, ".c delete " + ballid + ";update"); + while (c <-= <-c) + ; + exit; + } + if (dist < 0.0001) + smallcount++; + else + smallcount = 0; + bouncev := boing(v, hitline); + t0 := sys->millisec(); + dt := int (dist / speed); + t := 0; + do { + s := real t * speed; + currp := Realpoint(p.x + s * v.x, p.y + s * v.y); + bp := Point(int currp.x, int currp.y); + cmd(win, ".c coords " + ballid + " " + + string (bp.x-BALLSIZE)+" "+string (bp.y-BALLSIZE)+" "+ + string (bp.x+BALLSIZE)+" "+string (bp.y+BALLSIZE)); + cmd(win, "update"); + if (lineversion > version) { + (p, hitlineid, version) = (currp, oldid, lineversion); + continue loop; + } + # pass the token back to the monitor. + if (<-c == 0) { + cmd(win, ".c delete " + ballid + ";update"); + exit; + } + c <-= 1; + t = sys->millisec() - t0; + } while (t < dt); + p = hitp; + v = bouncev; + } +} + +# thread-safe access to the Rand module +randgenproc(ch: chan of int) +{ + rand := load Rand Rand->PATH; + for (;;) + ch <-= rand->rand(16r7fffffff); +} + +makelinesproc(win: ref Tk->Toplevel, mch: chan of (int, Point)) +{ + for (;;) { + (down, p1) := <-mch; + addline(win, (p1, p1)); + (id, nil) := hd lines; + p2 := p1; + do { + (down, p2) = <-mch; + cmd(win, ".c coords l" + string id + " " + pt2s(p1) + " " + pt2s(p2)); + cmd(win, "update"); + lines = (id, (p1, p2)) :: tl lines; + lineversion++; + if (down > 1) { + dp := p2.sub(p1); + if (dp.x*dp.x + dp.y*dp.y > 5) { + p1 = p2; + addline(win, (p2, p2)); + (id, nil) = hd lines; + } + } + } while (down); + } +} + +# make a vector of unit-length, parallel to v. +makeunit(v: Realpoint): Realpoint +{ + mag := math->sqrt(v.x * v.x + v.y * v.y); + return (v.x / mag, v.y / mag); +} + +# bounce ball travelling in direction av off line b. +# return the new unit vector. +boing(av: Realpoint, b: Line): Realpoint +{ + f := b.p2.sub(b.p1); + d := math->atan2(real f.y, real f.x) * 2.0 - math->atan2(av.y, av.x); + return (math->cos(d), math->sin(d)); +} + +# compute the intersection of lines a and b. +# b is assumed to be fixed, and a is indefinitely long +# but doesn't extend backwards from its starting point. +# a is defined by the starting point p and the unit vector v. +intersect(p, v: Realpoint, b: Line): (int, Realpoint, real) +{ + w := Realpoint(real (b.p2.x - b.p1.x), real (b.p2.y - b.p1.y)); + det := w.x * v.y - v.x * w.y; + if (det > -ZERO && det < ZERO) + return (0, (0.0, 0.0), 0.0); + + y21 := real b.p1.y - p.y; + x21 := real b.p1.x - p.x; + s := (w.x * y21 - w.y * x21) / det; + if (s < 0.0) + return (0, (0.0, 0.0), 0.0); + + hp := Realpoint(p.x+v.x*s, p.y+v.y*s); + if (b.p1.x > b.p2.x) + (b.p1.x, b.p2.x) = (b.p2.x, b.p1.x); + if (b.p1.y > b.p2.y) + (b.p1.y, b.p2.y) = (b.p2.y, b.p1.y); + + return (int hp.x >= b.p1.x && int hp.x <= b.p2.x + && int hp.y >= b.p1.y && int hp.y <= int b.p2.y, hp, s); +} + +cmd(top: ref Tk->Toplevel, s: string): string +{ + e := tk->cmd(top, s); + if (e != nil && e[0] == '!') + sys->print("tk error %s on '%s'\n", e, s); + return e; +} + +pt2s(p: Point): string +{ + return string p.x + " " + string p.y; +} |
