ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/wm/polyhedra.b/
implement WmPolyhedra;
include "sys.m";
sys: Sys;
include "draw.m";
draw: Draw;
Point, Rect, Pointer, Image, Screen, Display: import draw;
include "tk.m";
tk: Tk;
Toplevel: import tk;
include "tkclient.m";
tkclient: Tkclient;
include "bufio.m";
bufio: Bufio;
Iobuf: import bufio;
include "math.m";
math: Math;
sin, cos, tan, sqrt: import math;
include "rand.m";
rand: Rand;
include "daytime.m";
daytime: Daytime;
include "math/polyhedra.m";
polyhedra: Polyhedra;
Polyhedron: import Polyhedra;
scanpolyhedra, getpolyhedron: import polyhedra;
include "math/polyfill.m";
polyfill: Polyfill;
initzbuf, clearzbuf, fillpoly: import polyfill;
include "smenu.m";
smenu: Smenu;
Scrollmenu: import smenu;
WmPolyhedra : module
{
init : fn(nil : ref Draw->Context, argv : list of string);
};
WIDTH, HEIGHT: con 400;
mainwin: ref Toplevel;
Disp, black, white, opaque: ref Image;
Dispr: Rect;
pinit := 40;
init(ctxt : ref Draw->Context, argv : list of string)
{
sys = load Sys Sys->PATH;
draw = load Draw Draw->PATH;
tk = load Tk Tk->PATH;
tkclient = load Tkclient Tkclient->PATH;
bufio = load Bufio Bufio->PATH;
math = load Math Math->PATH;
rand = load Rand Rand->PATH;
daytime = load Daytime Daytime->PATH;
polyhedra = load Polyhedra Polyhedra->PATH;
polyfill = load Polyfill Polyfill->PATH;
smenu = load Smenu Smenu->PATH;
rand->init(daytime->now());
daytime = nil;
polyfill->init();
√2 = sqrt(2.0);
√3 = sqrt(3.0);
cursor := "";
tkclient->init();
if(ctxt == nil){
ctxt = tkclient->makedrawcontext();
# sys->fprint(sys->fildes(2), "wm not running\n");
# exit;
}
argv = tl argv;
while(argv != nil){
case hd argv{
"-p" =>
argv = tl argv;
if(argv != nil)
pinit = int hd argv;
"-r" =>
pinit = -1;
"-c" =>
argv = tl argv;
if(argv != nil)
cursor = hd argv;
}
if(argv != nil)
argv = tl argv;
}
(win, wmcmd) := tkclient->toplevel(ctxt, "", "Polyhedra", Tkclient->Resize | Tkclient->Hide);
mainwin = win;
sys->pctl(Sys->NEWPGRP, nil);
cmdch := chan of string;
tk->namechan(win, cmdch, "cmd");
for(i := 0; i < len win_config; i++)
cmd(win, win_config[i]);
if(cursor != nil)
cmd(win, "cursor -bitmap " + cursor);
tkclient->onscreen(win, nil);
tkclient->startinput(win, "kbd"::"ptr"::nil);
fittoscreen(win);
pid := -1;
sync := chan of int;
chanθ := chan of real;
geo := newgeom();
setimage(win, geo);
cmd(win, "update");
display := win.image.display;
white = display.color(Draw->White);
black = display.color(Draw->Black);
opaque = display.opaque;
shade = array[NSHADES] of ref Image;
for(i = 0; i < NSHADES; i++){
# v := (255*i)/(NSHADES-1); # NSHADES=17
v := (192*i)/(NSHADES-1)+32; # NSHADES=13
# v := (128*i)/(NSHADES-1)+64; # NSHADES=9
shade[i] = display.rgb(v, v, v);
# shade[i] = rgba(display, v, v, v, 16r7f);
}
(geo.npolyhedra, geo.polyhedra, geo.b) = scanpolyhedra("/lib/polyhedra.all");
if(geo.npolyhedra == 0){
sys->fprint(sys->fildes(2), "cannot open polyhedra database\n");
exit;
}
yieldc := chan of int;
# spawn yieldproc(yieldc);
# ypid := <- yieldc;
initgeom(geo);
sm := array[2] of ref Scrollmenu;
sm[0] = scrollmenu(win, ".f.menu", geo.polyhedra, geo.npolyhedra, 0);
sm[1] = scrollmenu(win, ".f.menud", geo.polyhedra, geo.npolyhedra, 1);
# createmenu(win, geo.polyhedra);
spawn drawpolyhedron(geo, sync, chanθ, yieldc);
pid = <- sync;
newproc := 0;
for(;;){
alt{
c := <-win.ctxt.kbd =>
tk->keyboard(win, c);
c := <-win.ctxt.ptr =>
tk->pointer(win, *c);
c := <-win.ctxt.ctl or
c = <-win.wreq =>
tkclient->wmctl(win, c);
c := <- wmcmd =>
case c{
"exit" =>
exits(pid, sm);
* =>
sync <-= 0;
tkclient->wmctl(win, c);
if(c[0] == '!'){
if(setimage(win, geo) <= 0)
exits(pid, sm);
}
sync <-= 1;
}
c := <- cmdch =>
(nil, toks) := sys->tokenize(c, " ");
case hd toks{
"prev" =>
geo.curpolyhedron = geo.curpolyhedron.prv;
getpoly(geo, -1);
newproc = 1;
"next" =>
geo.curpolyhedron = geo.curpolyhedron.nxt;
getpoly(geo, 1);
newproc = 1;
"dual" =>
geo.dual = !geo.dual;
newproc = 1;
"edges" =>
edges = !edges;
"faces" =>
faces = !faces;
"clear" =>
clear = !clear;
"slow" =>
if(geo.θ > ε){
if(geo.θ < 2.)
chanθ <-= geo.θ/2.;
else
chanθ <-= geo.θ-1.;
}
"fast" =>
if(geo.θ < 45.){
if(geo.θ < 1.)
chanθ <-= 2.*geo.θ;
else
chanθ <-= geo.θ+1.;
}
"axis" =>
setaxis(geo);
initmatrix(geo);
newproc = 1;
"menu" =>
x := int cmd(win, ".p cget actx");
y := int cmd(win, ".p cget acty");
w := int cmd(win, ".p cget -actwidth");
h := int cmd(win, ".p cget -actheight");
sm[geo.dual].post(x+w/8, y+h/8, cmdch, "");
# cmd(win, ".f.menu post " + x + " " + y);
* =>
i = int hd toks;
fp := geo.polyhedra;
for(p := fp; p != nil; p = p.nxt){
if(p.indx == i){
geo.curpolyhedron = p;
getpoly(geo, 1);
newproc = 1;
break;
}
if(p.nxt == fp)
break;
}
}
}
if(newproc){
sync <-= 0; # stop it first
kill(pid);
spawn drawpolyhedron(geo, sync, chanθ, yieldc);
pid = <- sync;
newproc = 0;
}
}
}
setimage(win: ref Toplevel, geo: ref Geom): int
{
panelw := int tk->cmd(win, ".p cget -actwidth");
panelh := int tk->cmd(win, ".p cget -actheight");
if(panelw < 3)
panelw = 3;
if(panelh < 3)
panelh = 3;
Dispr = Rect((0,0), (panelw, panelh));
Disp = win.image.display.newimage(Dispr, win.image.chans, 0, Draw->Black);
if(Disp == nil){
sys->fprint(sys->fildes(2), "not enough image memory\n");
return 0;
}
tk->putimage(win, ".p", Disp, nil);
if(Dispr.dx() > Dispr.dy())
h := Dispr.dy();
else
h = Dispr.dx();
rr: Rect = ((0, 0), (h, h));
corner := ((Dispr.min.x+Dispr.max.x-rr.max.x)/2, (Dispr.min.y+Dispr.max.y-rr.max.y)/2);
geo.r = (rr.min.add(corner), rr.max.add(corner));
geo.h = h;
geo.sx = real ((3*h)/8);
geo.sy = - real ((3*h)/8);
geo.tx = h/2+geo.r.min.x;
geo.ty = h/2+geo.r.min.y;
geo.zstate = initzbuf(geo.r);
return 1;
}
# yieldcpu(c: chan of int)
# {
# c <-= 1;
# <-c;
# }
# yieldproc(c: chan of int)
# {
# c <-= sys->pctl(0, nil);
# for (;;) {
# <-c;
# c <-= 1;
# }
# }
π: con Math->Pi;
√2, √3: real;
∞: con 1<<30;
ε: con 0.001;
Axis: adt{
λ, μ, ν: int;
};
Vector: adt{
x, y, z: real;
};
Geom: adt{
h: int; # length, breadth of r below
r: Rect; # area on screen to update
sx, sy: real; # x, y scale
tx, ty: int; # x, y translation
θ: real; # angle of rotation
TM: array of array of real; # rotation matrix
axis: Axis; # direction cosines of rotation
view: Vector;
light: Vector;
npolyhedra: int;
polyhedra: ref Polyhedron;
curpolyhedron: ref Polyhedron;
b: ref Iobuf; # of polyhedra file
dual: int;
zstate: ref Polyfill->Zstate;
};
NSHADES: con 13; # odd
shade: array of ref Image;
clear, faces: int = 1;
edges: int = 0;
setview(geo: ref Geom)
{
geo.view = (0.0, 0.0, 1.0);
geo.light = (0.0, -1.0, 0.0);
}
map(v: Vector, geo: ref Geom): Point
{
return (int (geo.sx*v.x)+geo.tx, int (geo.sy*v.y)+geo.ty);
}
minus(v1: Vector): Vector
{
return (-v1.x, -v1.y, -v1.z);
}
add(v1, v2: Vector): Vector
{
return (v1.x+v2.x, v1.y+v2.y, v1.z+v2.z);
}
sub(v1, v2: Vector): Vector
{
return (v1.x-v2.x, v1.y-v2.y, v1.z-v2.z);
}
mul(v1: Vector, l: real): Vector
{
return (l*v1.x, l*v1.y, l*v1.z);
}
div(v1: Vector, l: real): Vector
{
return (v1.x/l, v1.y/l, v1.z/l);
}
normalize(v1: Vector): Vector
{
return div(v1, sqrt(dot(v1, v1)));
}
dot(v1, v2: Vector): real
{
return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;
}
cross(v1, v2: Vector): Vector
{
return (v1.y*v2.z-v2.y*v1.z, v1.z*v2.x-v2.z*v1.x, v1.x*v2.y-v2.x*v1.y);
}
drawpolyhedron(geo: ref Geom, sync: chan of int, chanθ: chan of real, yieldc: chan of int)
{
s: string;
sync <-= sys->pctl(0, nil);
p := geo.curpolyhedron;
if(!geo.dual || p.anti){
s = p.name;
s += " (" + string p.indx + ")";
puts(s);
drawpolyhedron0(p.V, p.F, p.concave, p.allf || p.anti, p.v, p.f, p.fv, p.inc, geo, sync, chanθ, yieldc);
}
else{
s = p.dname;
s += " (" + string p.indx + ")";
puts(s);
drawpolyhedron0(p.F, p.V, p.concave, p.anti, p.f, p.v, p.vf, 0.0, geo, sync, chanθ, yieldc);
}
}
drawpolyhedron0(V, F, concave, allf: int, v, f: array of Vector, fv: array of array of int, inc: real, geo: ref Geom, sync: chan of int, chanθ: chan of real, yieldc: chan of int)
{
norm : array of array of Vector;
newn, oldn : array of Vector;
yieldc = nil; # not used now
θ := geo.θ;
totθ := 0.;
if(θ != 0.)
n := int ((360.+θ/2.)/θ);
else
n = ∞;
p := n;
t := 0;
vec := array[2] of array of Vector;
vec[0] = array[V] of Vector;
vec[1] = array[V] of Vector;
if(concave){
norm = array[2] of array of Vector;
norm[0] = array[F] of Vector;
norm[1] = array[F] of Vector;
}
Disp.draw(geo.r, black, opaque, (0, 0));
reveal(geo.r);
for(i := 0; ; i = (i+1)%p){
alt{
<- sync =>
<- sync;
θ = <- chanθ =>
geo.θ = θ;
initmatrix(geo);
if(θ != 0.){
n = int ((360.+θ/2.)/θ);
p = int ((360.-totθ+θ/2.)/θ);
}
else
n = p = ∞;
if(p == 0)
i = 0;
else
i = 1;
* =>
# yieldcpu(yieldc);
sys->sleep(0);
}
if(concave)
clearzbuf(geo.zstate);
new := vec[t];
old := vec[!t];
if(concave){
newn = norm[t];
oldn = norm[!t];
}
t = !t;
if(i == 0){
for(j := 0; j < V; j++)
new[j] = v[j];
if(concave){
for(j = 0; j < F; j++)
newn[j] = f[j];
}
setview(geo);
totθ = 0.;
p = n;
}
else{
for(j := 0; j < V; j++)
new[j] = mulm(geo.TM, old[j]);
if(concave){
for(j = 0; j < F; j++)
newn[j] = mulm(geo.TM, oldn[j]);
}
else{
geo.view = mulmi(geo.TM, geo.view);
geo.light = mulmi(geo.TM, geo.light);
}
totθ += θ;
}
if(clear)
Disp.draw(geo.r, black, opaque, (0, 0));
for(j := 0; j < F; j++){
if(concave){
if(allf || dot(geo.view, newn[j]) < 0.0)
polyfilla(fv[j], new, newn[j], dot(geo.light, newn[j]), geo, concave, inc);
}
else{
if(dot(geo.view, f[j]) < 0.0)
polyfilla(fv[j], new, f[j], dot(geo.light, f[j]), geo, concave, 0.0);
}
}
reveal(geo.r);
}
}
ZSCALE: con real (1<<20);
LIMIT: con real (1<<11);
polyfilla(fv: array of int, v: array of Vector, f: Vector, ill: real, geo: ref Geom, concave: int, inc: real)
{
dc, dx, dy: int;
d := 0.0;
n := fv[0];
ap := array[n+1] of Point;
for(j := 0; j < n; j++){
vtx := v[fv[j+1]];
# vtx = add(vtx, mul(f, 0.1)); # interesting effects with -/larger factors
ap[j] = map(vtx, geo);
d += dot(f, vtx);
}
ap[n] = ap[0];
d /= real n;
if(concave){
if(fv[n+1] != 1)
d += inc;
if(f.z > -ε && f.z < ε)
return;
α := geo.sx;
β := real geo.tx;
γ := geo.sy;
δ := real geo.ty;
c := f.z;
a := -f.x/(c*α);
if(a <= -LIMIT || a >= LIMIT)
return;
b := -f.y/(c*γ);
if(b <= -LIMIT || b >= LIMIT)
return;
d = d/c-β*a-δ*b;
if(d <= -LIMIT || d >= LIMIT)
return;
dx = int (a*ZSCALE);
dy = int (b*ZSCALE);
dc = int (d*ZSCALE);
}
edge := white;
face := shade[int ((real ((NSHADES-1)/2))*(1.0-ill))];
if(concave){
if(!faces)
face = black;
if(!edges)
edge = nil;
fillpoly(Disp, ap, ~0, face, (0, 0), geo.zstate, dc, dx, dy);
}
else{
if(faces)
Disp.fillpoly(ap, ~0, face, (0, 0));
if(edges)
Disp.poly(ap, Draw->Endsquare, Draw->Endsquare, 0, edge, (0, 0));
}
}
getpoly(geo: ref Geom, dir: int)
{
p := geo.curpolyhedron;
if(0){
while(p.anti){
if(dir > 0)
p = p.nxt;
else
p = p.prv;
}
}
geo.curpolyhedron = p;
getpolyhedron(p, geo.b);
}
degtorad(α: real): real
{
return α*π/180.0;
}
initmatrix(geo: ref Geom)
{
TM := geo.TM;
φ := degtorad(geo.θ);
sinθ := sin(φ);
cosθ := cos(φ);
(l, m, n) := normalize((real geo.axis.λ, real geo.axis.μ, real geo.axis.ν));
f := 1.0-cosθ;
TM[1][1] = (1.0-l*l)*cosθ + l*l;
TM[1][2] = l*m*f-n*sinθ;
TM[1][3] = l*n*f+m*sinθ;
TM[2][1] = l*m*f+n*sinθ;
TM[2][2] = (1.0-m*m)*cosθ + m*m;
TM[2][3] = m*n*f-l*sinθ;
TM[3][1] = l*n*f-m*sinθ;
TM[3][2] = m*n*f+l*sinθ;
TM[3][3] = (1.0-n*n)*cosθ + n*n;
}
mulm(TM: array of array of real, v: Vector): Vector
{
x := v.x;
y := v.y;
z := v.z;
v.x = TM[1][1]*x + TM[1][2]*y + TM[1][3]*z;
v.y = TM[2][1]*x + TM[2][2]*y + TM[2][3]*z;
v.z = TM[3][1]*x + TM[3][2]*y + TM[3][3]*z;
return v;
}
mulmi(TM: array of array of real, v: Vector): Vector
{
x := v.x;
y := v.y;
z := v.z;
v.x = TM[1][1]*x + TM[2][1]*y + TM[3][1]*z;
v.y = TM[1][2]*x + TM[2][2]*y + TM[3][2]*z;
v.z = TM[1][3]*x + TM[2][3]*y + TM[3][3]*z;
return v;
}
reveal(r: Rect)
{
cmd := sys->sprint(".p dirty %d %d %d %d", r.min.x, r.min.y, r.max.x, r.max.y);
tk->cmd(mainwin, cmd);
tk->cmd(mainwin, "update");
}
newgeom(): ref Geom
{
geo := ref Geom;
TM := array[4] of array of real;
for(i := 0; i < 4; i++)
TM[i] = array[4] of real;
geo.θ = 10.;
geo.TM = TM;
geo.axis = (1, 1, 1);
geo.view = (1., 1., 1.);
geo.light = (1., 1., 1.);
geo.dual = 0;
return geo;
}
setaxis(geo: ref Geom)
{
oaxis := geo.axis;
# while(geo.axis == Axis (0, 0, 0) || geo.axis = oaxis) not allowed
while((geo.axis.λ == 0 && geo.axis.μ == 0 && geo.axis.ν == 0) || (geo.axis.λ == oaxis.λ && geo.axis.μ == oaxis.μ && geo.axis.ν == oaxis.ν))
geo.axis = (rand->rand(5) - 2, rand->rand(5) - 2, rand->rand(5) - 2);
}
initgeom(geo: ref Geom)
{
if(pinit < 0)
pn := rand->rand(geo.npolyhedra);
else
pn = pinit;
for(p := geo.polyhedra; --pn >= 0; p = p.nxt)
;
geo.curpolyhedron = p;
getpoly(geo, 1);
setaxis(geo);
geo.θ = real (rand->rand(5)+1);
geo.dual = 0;
initmatrix(geo);
setview(geo);
Disp.draw(geo.r, black, opaque, (0, 0));
reveal(geo.r);
}
kill(pid: int): int
{
fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
if(fd == nil)
return -1;
if(sys->write(fd, array of byte "kill", 4) != 4)
return -1;
return 0;
}
exits(pid: int, sm: array of ref Scrollmenu)
{
if(pid != -1)
kill(pid);
# kill(ypid);
sm[0].destroy();
sm[1].destroy();
exit;
}
cmd(top: ref Toplevel, s: string): string
{
e := tk->cmd(top, s);
if (e != nil && e[0] == '!')
sys->fprint(sys->fildes(2), "polyhedra: tk error on '%s': %s\n", s, e);
return e;
}
puts(s: string)
{
cmd(mainwin, ".f1.txt configure -text {" + s + "}");
cmd(mainwin, "update");
}
MENUMAX: con 10;
scrollmenu(top: ref Tk->Toplevel, mname: string, p: ref Polyhedron, n: int, dual: int): ref Scrollmenu
{
labs := array[n] of string;
i := 0;
for(q := p; q != nil && i < n; q = q.nxt){
if(dual)
name := q.dname;
else
name = q.name;
labs[i++] = string q.indx + " " + name;
}
sm := Scrollmenu.new(top, mname, labs, MENUMAX, (n-MENUMAX)/2);
cmd(top, mname + " configure -borderwidth 3");
return sm;
}
createmenu(top: ref Tk->Toplevel, p: ref Polyhedron)
{
mn := ".f.menu";
cmd(top, "menu " + mn);
i := j := 0;
for(q := p ; q != nil; q = q.nxt){
cmd(top, mn + " add command -label {" + string q.indx + " " + q.name + "} -command {send cmd " + string q.indx + "}");
if(q.nxt == p)
break;
i++;
j++;
if(j == MENUMAX && q.nxt != nil){
cmd(top, mn + " add cascade -label MORE -menu " + mn + ".menu");
mn += ".menu";
cmd(top, "menu " + mn);
j = 0;
}
}
}
fittoscreen(win: ref Tk->Toplevel)
{
Point: import draw;
if (win.image == nil || win.image.screen == nil)
return;
r := win.image.screen.image.r;
scrsize := Point((r.max.x - r.min.x), (r.max.y - r.min.y));
bd := int cmd(win, ". cget -bd");
winsize := Point(int cmd(win, ". cget -actwidth") + bd * 2, int cmd(win, ". cget -actheight") + bd * 2);
if (winsize.x > scrsize.x)
cmd(win, ". configure -width " + string (scrsize.x - bd * 2));
if (winsize.y > scrsize.y)
cmd(win, ". configure -height " + string (scrsize.y - bd * 2));
actr: Rect;
actr.min = Point(int cmd(win, ". cget -actx"), int cmd(win, ". cget -acty"));
actr.max = actr.min.add((int cmd(win, ". cget -actwidth") + bd*2,
int cmd(win, ". cget -actheight") + bd*2));
(dx, dy) := (actr.dx(), actr.dy());
if (actr.max.x > r.max.x)
(actr.min.x, actr.max.x) = (r.max.x - dx, r.max.x);
if (actr.max.y > r.max.y)
(actr.min.y, actr.max.y) = (r.max.y - dy, r.max.y);
if (actr.min.x < r.min.x)
(actr.min.x, actr.max.x) = (r.min.x, r.min.x + dx);
if (actr.min.y < r.min.y)
(actr.min.y, actr.max.y) = (r.min.y, r.min.y + dy);
cmd(win, ". configure -x " + string actr.min.x + " -y " + string actr.min.y);
}
win_config := array[] of {
"frame .f",
"button .f.prev -text {prev} -command {send cmd prev}",
"button .f.next -text {next} -command {send cmd next}",
"checkbutton .f.dual -text {dual} -command {send cmd dual} -variable dual",
".f.dual deselect",
"pack .f.prev -side left",
"pack .f.next -side right",
"pack .f.dual -side top",
"frame .f0",
"checkbutton .f0.edges -text {edges} -command {send cmd edges} -variable edges",
".f0.edges deselect",
"checkbutton .f0.faces -text {faces} -command {send cmd faces} -variable faces",
".f0.faces select",
"checkbutton .f0.clear -text {clear} -command {send cmd clear} -variable clear",
".f0.clear select",
"pack .f0.edges -side left",
"pack .f0.faces -side right",
"pack .f0.clear -side top",
"frame .f2",
"button .f2.slow -text {slow} -command {send cmd slow}",
"button .f2.fast -text {fast} -command {send cmd fast}",
"button .f2.axis -text {axis} -command {send cmd axis}",
"pack .f2.slow -side left",
"pack .f2.fast -side right",
"pack .f2.axis -side top",
"frame .f1",
"label .f1.txt -text { } -width " + string WIDTH,
"pack .f1.txt -side top -fill x",
"frame .f3",
"button .f3.menu -text {menu} -command {send cmd menu}",
"pack .f3.menu -side left",
"frame .pbd -bd 3",
"panel .p -width " + string WIDTH + " -height " + string HEIGHT,
"pack .f -side top -fill x",
"pack .f0 -side top -fill x",
"pack .f2 -side top -fill x",
"pack .f1 -side top -fill x",
"pack .f3 -side top -fill x",
"pack .p -in .pbd -fill both -expand 1",
"pack .pbd -side bottom -fill both -expand 1",
"pack propagate . 0",
};
rgba(d: ref Display, r: int, g: int, b: int, α: int): ref Image
{
c := draw->setalpha((r<<24)|(g<<16)|(b<<8), α);
return d.newimage(((0, 0), (1, 1)), d.image.chans, 1, c);
}