#include #include #include #include #include #include #include #include #include <9p.h> #include "libobj/obj.h" #include "libgraphics/graphics.h" typedef struct Dirtab Dirtab; typedef struct Client Client; struct Dirtab { char *name; uint perm; }; struct Client { ulong slot; int inuse; Camera *cam; }; enum { CMviewport, CMmove, CMprojection, CMfov, CMclip, CMshoot, }; Cmdtab cmds[] = { CMviewport, "viewport", 3, /* viewport $width $height */ CMmove, "move", 5, /* move $subject $x $y $z */ CMprojection, "projection", 2, /* projection [persp|ortho] */ CMfov, "fov", 2, /* fov $angle */ CMclip, "clip", 3, /* clip [near|far] $dz */ CMshoot, "shoot", 1, /* shoot */ }; enum { Qroot, Qnew, Qn, Qctl, Qframe, Qscene, }; #define QPATH(type, slot) (((slot)<<8)|(type)) #define QTYPE(path) ((path)&0xFF) #define SLOT(path) ((path)>>8) char Ebotch[] = "9P protocol botch"; char Enotfound[] = "file does not exist"; char Enotdir[] = "not a directory"; char Eperm[] = "permission denied"; char Enoscene[] = "no scene"; Dirtab dirtab[] = { [Qroot] "/", DMDIR|0555, [Qnew] "new", 0666, [Qn] nil, DMDIR|0555, [Qctl] "ctl", 0666, [Qframe] "frame", 0444, [Qscene] "scene", 0666, }; char *jefe = "Pablo R. Picasso"; Memsubfont *memfont; Renderer *renderer; Client *clients; ulong nclients; static LightSource light = {{0,100,100,1}, {1,1,1,1}, LIGHT_POINT}; static Point3 vshader(VSparams *sp) { Client *c; Point3 pos, lightdir; double intens; c = &clients[0]; /* TODO figure out a way to address the correct client */ pos = model2world(sp->su->entity, sp->v->p); lightdir = normvec3(subpt3(light.p, pos)); intens = fmax(0, dotvec3(sp->v->n, lightdir)); addvattr(sp->v, "intensity", VANumber, &intens); if(sp->v->mtl != nil){ sp->v->c.r = sp->v->mtl->Kd.r; sp->v->c.g = sp->v->mtl->Kd.g; sp->v->c.b = sp->v->mtl->Kd.b; sp->v->c.a = 1; } return world2clip(c->cam, pos); } static Color fshader(FSparams *sp) { Color tc, c; if(sp->v.mtl != nil && sp->v.mtl->map_Kd != nil && sp->v.uv.w != 0) tc = texture(sp->v.mtl->map_Kd, sp->v.uv, neartexsampler); else if(sp->su->entity->mdl->tex != nil && sp->v.uv.w != 0) tc = texture(sp->su->entity->mdl->tex, sp->v.uv, neartexsampler); else tc = Pt3(1,1,1,1); c.a = fclamp(sp->v.c.a*tc.a, 0, 1); c.b = fclamp(sp->v.c.b*tc.b, 0, 1); c.g = fclamp(sp->v.c.g*tc.g, 0, 1); c.r = fclamp(sp->v.c.r*tc.r, 0, 1); return c; } Shadertab auxshaders = {"ident", vshader, fshader}; static int mode2perm(int m) { static int perms[4] = {4, 2, 6, 1}; return perms[m&OMASK]; } static long strwidth(char *s) { long cw, nc, n; Rune r; /* * memfont = defont = vga. so we can safely assume all * the glyphs have the same width. */ nc = 0; cw = memfont->info->width; while(*s){ nc += n = chartorune(&r, s); s += n; } return cw*nc; } static ulong newclient(void) { Client *c; int i; for(i = 0; i < nclients; i++) if(!clients[i].inuse) return i; if(nclients%16 == 0) clients = erealloc9p(clients, (nclients+16)*sizeof(*clients)); c = &clients[nclients++]; c->slot = c-clients; c->inuse = 1; c->cam = emalloc9p(sizeof *c->cam); c->cam->vp = mkviewport(Rect(0,0,320,200)); c->cam->fov = 40*DEG; c->cam->clip.n = 0.01; c->cam->clip.f = 1000; c->cam->projtype = PERSPECTIVE; c->cam->rctl = renderer; c->cam->s = newscene(nil); placecamera(c->cam, Pt3(0,0,100,1), Pt3(0,0,0,1), Vec3(0,1,0)); reloadcamera(c->cam); return c->slot; } static Client * getclient(ulong slot) { if(slot >= nclients) return nil; return &clients[slot]; } static void closeclient(Client *c) { c->inuse = 0; } static void fillstat(Dir *d, uvlong path) { Dirtab *t; t = &dirtab[QTYPE(path)]; if(t->name != nil) d->name = estrdup9p(t->name); else{ d->name = smprint("%llud", SLOT(path)); if(d->name == nil) sysfatal("smprint: %r"); } d->uid = estrdup9p(jefe); d->gid = estrdup9p(jefe); d->muid = nil; d->atime = d->mtime = time(0); d->length = 0; d->mode = t->perm; d->qid = (Qid){path, 0, t->perm>>24}; } static int rootgen(int i, Dir *d, void*) { if(++i >= Qn+nclients) return -1; fillstat(d, i < Qn? i: QPATH(Qn, i-Qn)); return 0; } static int clientgen(int i, Dir *d, void *aux) { Client *c; c = aux; i += Qn+1; if(i < nelem(dirtab)){ fillstat(d, QPATH(i, c->slot)); return 0; } return -1; } static int readimg(Memimage *i, char *t, Rectangle r, int offset, int n) { int ww, oo, y, m; uchar *tt; ww = bytesperline(r, i->depth); r.min.y += offset/ww; if(r.min.y >= r.max.y) return 0; y = r.min.y + (n + ww-1)/ww; if(y < r.max.y) r.max.y = y; m = ww * Dy(r); oo = offset % ww; if(oo == 0 && n >= m) return unloadmemimage(i, r, (uchar*)t, n); if((tt = malloc(m)) == nil) return -1; m = unloadmemimage(i, r, tt, m) - oo; if(m > 0){ if(n < m) m = n; memmove(t, tt + oo, m); } free(tt); return m; } void fsattach(Req *r) { if(r->ifcall.aname && r->ifcall.aname[0]){ respond(r, "invalid attach specifier"); return; } r->fid->qid = (Qid){Qroot, 0, QTDIR}; r->ofcall.qid = r->fid->qid; respond(r, nil); } char * fswalk1(Fid *f, char *name, Qid *qid) { char buf[32]; uvlong path; ulong n; int i; path = f->qid.path; switch(QTYPE(path)){ case Qroot: if(strcmp(name, "..") == 0){ *qid = f->qid; return nil; } for(i = 1; i <= Qn; i++){ if(i == Qn){ n = strtoul(name, nil, 10); snprint(buf, sizeof buf, "%lud", n); if(n < nclients && strcmp(buf, name) == 0){ *qid = (Qid){QPATH(Qn, n), 0, dirtab[Qn].perm>>24}; f->qid = *qid; return nil; } break; } if(strcmp(name, dirtab[i].name) == 0){ *qid = (Qid){QPATH(i, SLOT(path)), 0, dirtab[i].perm>>24}; f->qid = *qid; return nil; } } return Enotfound; case Qn: if(strcmp(name, "..") == 0){ *qid = (Qid){Qroot, 0, QTDIR}; return nil; } for(i = Qn+1; i < nelem(dirtab); i++) if(strcmp(name, dirtab[i].name) == 0){ *qid = (Qid){QPATH(i, SLOT(path)), 0, dirtab[i].perm>>24}; f->qid = *qid; return nil; } return Enotfound; default: return Enotdir; } } void fsopen(Req *r) { Dirtab *t; uvlong path; int perm, want; path = r->fid->qid.path; if(QTYPE(path) >= nelem(dirtab)){ respond(r, Ebotch); return; } t = &dirtab[QTYPE(path)]; perm = t->perm; if(strcmp(r->fid->uid, jefe) == 0) perm >>= 6; if((r->ifcall.mode & (OTRUNC|OCEXEC|ORCLOSE)) != 0) goto deny; want = mode2perm(r->ifcall.mode); if((want & perm) != want){ deny: respond(r, Eperm); return; } if(QTYPE(path) == Qnew){ path = QPATH(Qctl, newclient()); r->fid->qid.path = path; r->ofcall.qid.path = path; } respond(r, nil); } void fsread(Req *r) { Client *c; Framebuf *fb; Memimage *i; char buf[1024], cbuf[30], *t; uvlong path; ulong off, cnt; int n; path = r->fid->qid.path; off = r->ifcall.offset; cnt = r->ifcall.count; c = &clients[SLOT(path)]; switch(QTYPE(path)){ default: respond(r, "bug in fsread"); break; case Qroot: dirread9p(r, rootgen, nil); respond(r, nil); break; case Qn: dirread9p(r, clientgen, &clients[SLOT(path)]); respond(r, nil); break; case Qctl: snprint(buf, sizeof buf, "%llud", SLOT(path)); readstr(r, buf); respond(r, nil); break; case Qframe: fb = c->cam->vp->getfb(c->cam->vp); i = fb->cb; if(off < 5*12){ n = snprint(buf, sizeof buf, "%11s %11d %11d %11d %11d ", chantostr(cbuf, i->chan), i->r.min.x, i->r.min.y, i->r.max.x, i->r.max.y); t = estrdup9p(buf); if(off > n){ off = n; cnt = 0; } if(off+cnt > n) cnt = n-off; r->ofcall.data = t+off; r->ofcall.count = cnt; respond(r, nil); free(t); break; } off -= 5*12; n = -1; t = malloc(cnt); if(t != nil){ r->ofcall.data = t; n = readimg(i, t, i->r, off, cnt); } if(n < 0){ buf[0] = 0; errstr(buf, sizeof buf); respond(r, buf); }else{ r->ofcall.count = n; respond(r, nil); } free(t); break; case Qscene: // snprint(buf, sizeof buf, "%s\n", Enoscene); n = snprint(buf, sizeof buf, "viewport %R\n", c->cam->vp->getfb(c->cam->vp)->r); n += snprint(buf+n, sizeof(buf)-n, "pos %V\n", c->cam->p); n += snprint(buf+n, sizeof(buf)-n, "fov %g°\n", c->cam->fov/DEG); n += snprint(buf+n, sizeof(buf)-n, "clip [%g %g]\n", c->cam->clip.n, c->cam->clip.f); snprint(buf+n, sizeof(buf)-n, "proj %s\n", c->cam->projtype == PERSPECTIVE? "persp": "ortho"); readstr(r, buf); respond(r, nil); break; } } void fswrite(Req *r) { Client *c; Model *model; Entity *ent; Framebuf *fb; Cmdbuf *cb; Cmdtab *ct; Point pt; char *msg, *f[4]; uvlong path; ulong cnt; int nf, w, h; double clipn, clipf; path = r->fid->qid.path; cnt = r->ifcall.count; c = &clients[SLOT(path)]; switch(QTYPE(path)){ default: respond(r, "bug in fswrite"); break; case Qctl: msg = emalloc9p(cnt+1); memmove(msg, r->ifcall.data, cnt); msg[cnt] = 0; cb = parsecmd(msg, strlen(msg)); ct = lookupcmd(cb, cmds, nelem(cmds)); if(ct == nil) goto nocmd; switch(ct->index){ case CMviewport: w = strtoul(cb->f[1], nil, 10); h = strtoul(cb->f[2], nil, 10); rmviewport(c->cam->vp); c->cam->vp = mkviewport(Rect(0,0,w,h)); break; case CMmove: if(strcmp(cb->f[1], "camera") == 0){ c->cam->p.x = strtod(cb->f[2], nil); c->cam->p.y = strtod(cb->f[3], nil); c->cam->p.z = strtod(cb->f[4], nil); } break; case CMprojection: if(strcmp(cb->f[1], "persp") == 0) c->cam->projtype = PERSPECTIVE; else if(strcmp(cb->f[1], "ortho") == 0) c->cam->projtype = ORTHOGRAPHIC; reloadcamera(c->cam); break; case CMfov: c->cam->fov = strtod(cb->f[1], nil); if(utfrune(cb->f[1], L'°') != nil) c->cam->fov *= DEG; c->cam->fov = fclamp(c->cam->fov, 1*DEG, 180*DEG); reloadcamera(c->cam); break; case CMclip: clipn = c->cam->clip.n; clipf = c->cam->clip.f; if(strcmp(cb->f[1], "near") == 0) clipn = strtod(cb->f[2], nil); else if(strcmp(cb->f[1], "far") == 0) clipf = strtod(cb->f[2], nil); if(clipn >= clipf) break; c->cam->clip.n = clipn; c->cam->clip.f = clipf; reloadcamera(c->cam); break; case CMshoot: if(c->cam->s->nents < 1){ fb = c->cam->vp->getfb(c->cam->vp); pt = Pt(Dx(fb->r)/2-strwidth(Enoscene)/2,Dy(fb->r)/2-memfont->height/2); memimagedraw(fb->cb, fb->r, memwhite, ZP, nil, ZP, S); memimagestring(fb->cb, pt, memblack, ZP, memfont, Enoscene); break; } shootcamera(c->cam, &auxshaders); break; } nocmd: free(cb); free(msg); r->ofcall.count = cnt; respond(r, nil); break; case Qscene: msg = emalloc9p(cnt+1); memmove(msg, r->ifcall.data, cnt); msg[cnt] = 0; nf = tokenize(msg, f, nelem(f)); if(nf != 1) goto noscene; fprint(2, "loading obj from %s\n", msg); /* TODO load an actual scene (format tbd) */ model = newmodel(); if((model->obj = objparse(f[0])) == nil){ delmodel(model); goto noscene; } refreshmodel(model); ent = newentity(model); c->cam->s->addent(c->cam->s, ent); noscene: free(msg); r->ofcall.count = cnt; respond(r, nil); break; } } void fsstat(Req *r) { fillstat(&r->d, r->fid->qid.path); respond(r, nil); } void fsdestroyfid(Fid *f) { uvlong path; path = f->qid.path; if(f->omode != -1 && QTYPE(path) >= Qn) closeclient(&clients[SLOT(path)]); } void fsending(Srv*) { threadexitsall(nil); } Srv fs = { .attach = fsattach, .walk1 = fswalk1, .open = fsopen, .read = fsread, .write = fswrite, .stat = fsstat, .destroyfid = fsdestroyfid, .end = fsending, }; void usage(void) { fprint(2, "usage: %s [-D] [-s srvname] [-m mtpt]\n", argv0); exits("usage"); } void threadmain(int argc, char *argv[]) { char *srvname, *mtpt; srvname = "render"; mtpt = "/mnt/render"; GEOMfmtinstall(); ARGBEGIN{ case 'D': chatty9p++; break; case 's': srvname = EARGF(usage()); break; case 'm': mtpt = EARGF(usage()); break; default: usage(); }ARGEND if(argc != 0) usage(); jefe = getuser(); if(memimageinit() != 0) sysfatal("memimageinit: %r"); if((renderer = initgraphics()) == nil) sysfatal("initgraphics: %r"); memfont = getmemdefont(); threadpostmountsrv(&fs, srvname, mtpt, MREPL|MCREATE); threadexits(nil); }