/*% cyntax % && cc -go # % -lpicfile -lfb
* cpr -- cheezy polygon renderer.
* syntax:
* { push transformation stack
* } pop transformation stack
* v fov near far ex ey ez lx ly lz ux uy uz Set viewing parameters
* Q r i j k Quaternion rotate
* R angle axis rotate
* T dx dy dz translate
* S sx sy sz scale
* M m00 m01 m02 m03 m10 m11 ... m33 transformation matrix
* t x0 y0 z0 x1 y1 z1 x2 y2 z2 c0 c1 Render a triangle
* p c0 c1 [x y z]*; Render a polygon
* c num r g b a Set a color table entry
* l x y z Set light source direction
* b r g b a Clear the background
* h c0 c1 nx ny x0 y0 x1 y1 file Render a height field
* s c0 c1 x y z r Render a sphere
*/
#include <u.h>
#include <libc.h>
#include <libg.h>
#include <stdio.h>
#include <fb.h>
#include <geometry.h>
void depthclip(void);
void geode(Point3 *a, Point3 *b, Point3 *c, Point3 cen, double r, int c1, int c2, int n);
int getnormal(void);
void input(FILE *ifd);
void insert(struct edge *e, struct edge **bp);
double len(Point3 p);
void main(int argc, char *argv[]);
char *mkchan(void);
void normalize(Point3 *v);
void outline(PICFILE *f, unsigned char *p);
void polyren(int c, int other);
void readbg(FILE *f);
void readcolor(FILE *f);
void readcomment(FILE *f);
void readheight(FILE *f);
void readlight(FILE *f);
void readmat(FILE *f);
void readpoly(FILE *f);
void readpop(FILE *f);
void readpush(FILE *f);
void readquat(FILE *f);
void readrot(FILE *f);
void readscale(FILE *f);
void readsphere(FILE *f);
void readtran(FILE *f);
void readtri(FILE *f);
void readview(FILE *f);
void span(int z, double x0, double y0, double x1, double y1);
#define F_UNCOVERED HUGE
/*
* Screen characteristics
*/
double aspect; /* aspect ratio of whole picture */
int nz=480;
int nx=640;
typedef struct color{
unsigned char r, g, b, a;
}Color;
struct pix{
Color rgba;
float y;
}**pix;
Color rgba;
#define NCOLOR 500
Color color[NCOLOR];
Point3 light={-.6, .48, .64, 1};
Point3 eye;
Point3 yclip; /* actually a plane */
#define NEDGE 2048
Point3 vert[NEDGE];
Point3 *evert;
struct edge{
Point3 p, q;
double x, y, dx, dy;
struct edge *next;
}edge[NEDGE];
struct edge *eedge;
Point3 normal;
int npoly=1;
struct edge **buck;
struct edge **b0, **b1;
#define R 0x01
#define G 0x02
#define B 0x04
#define A 0x08
#define Z 0xf0
int chan;
Space *mx; /* modeling space */
Space *wx; /* world space */
void main(int argc, char *argv[]){
struct pix *pp;
int i, z;
PICFILE *ofd;
FILE *ifd;
argc=getflags(argc, argv, "a:1[aspect]c:1[rgbaz]w:2[nx nz]");
if(argc<=0) usage("file ...");
if(flag['w']){
nx=atoi(flag['w'][0]);
nz=atoi(flag['w'][1]);
}
if(flag['a']) aspect=atof(flag['a'][0]);
else aspect=(double)nz/nx;
buck=malloc((nz+1)*sizeof(struct edge *));
b0=&buck[0];
b1=&buck[nz];
if(buck==0){
NoSpace:
fprint(2, "%s: can't malloc\n", argv[0]);
exits("no space");
}
pix=malloc((nz+1)*sizeof(struct pix *)); /* pix[nz] is end of array */
if(pix==0) goto NoSpace;
pix[0]=malloc(nx*nz*sizeof(struct pix));
if(pix[0]==0) goto NoSpace;
for(z=1;z<=nz;z++) pix[z]=pix[z-1]+nx;
if((ofd=picopen_w("OUT", "runcode", 0, 0, nx, nz, mkchan(), 0, 0))==0){
perror(argv[0]);
exits("create output");
}
for(pp=pix[0];pp!=pix[nz];pp++)
pp->y=F_UNCOVERED;
for(z=0;z!=256;z++){
color[z].r=color[z].g=color[z].b=z;
color[z].a=255;
}
for(z=0;z!=12;z++){
color[z+256].r=color[z+256].g=color[z+256].b=pow(255., z/11.);
color[z+256].a=255;
}
for(z=0;z!=20;z++){
color[z+268].r=color[z+268].g=color[z+268].b=pow(255., z/19.);
color[z+268].a=255;
}
mx=pushmat(0);
if(argc==1) input(stdin);
else for(i=1;i!=argc;i++){
ifd=fopen(argv[i], "r");
if(ifd==0){
perror(argv[i]);
exits("open input");
}
input(ifd);
fclose(ifd);
}
for(z=0;z!=nz;z++)
outline(ofd, (unsigned char *)&pix[z][0]);
exits("");
}
void input(FILE *ifd){
int c;
while((c=getc(ifd))!=EOF) switch(c){
case ' ':
case '\t':
case '\n':
break;
default:
fprintf(stderr, "Bad input `%c", c);
while((c=getc(ifd))!='\n' && c!=EOF)
putc(c, stderr);
fprintf(stderr, "'\n");
break;
case '{': readpush(ifd); break;
case '}': readpop(ifd); break;
case 'v': readview(ifd); break;
case 'Q': readquat(ifd); break;
case 'R': readrot(ifd); break;
case 'T': readtran(ifd); break;
case 'S': readscale(ifd); break;
case 'M': readmat(ifd); break;
case 'c': readcolor(ifd); break;
case 'l': readlight(ifd); break;
case 'b': readbg(ifd); break;
case 't': readtri(ifd); break;
case 'p': readpoly(ifd); break;
case 'h': readheight(ifd); break;
case 's': readsphere(ifd); break;
case '#': readcomment(ifd); break;
}
}
char *mkchan(void){
if(!flag['c'] || strcmp(flag['c'][0], "rgb")==0){
chan=R|G|B;
return "rgb";
}
if(strcmp(flag['c'][0], "rgba")==0){
chan=R|G|B|A;
return "rgba";
}
if(strcmp(flag['c'][0], "rgbaz")==0){
chan=R|G|B|A|Z;
return sizeof(float)==4?"rgbaz...":"rgbaz.......";
}
fprintf(stderr, "Bad channels %s\n", flag['c'][0]);
exits("bad chan");
return 0; /* for cyntax */
}
void readcomment(FILE *f){
register c;
do
c=getc(f);
while(c!='\n' && c!=EOF);
}
void readbg(FILE *f){
int r, g, b, a;
Color c;
register struct pix *p;
if(fscanf(f, "%d%d%d%d", &r, &g, &b, &a)!=4){
fprintf(stderr, "cpr: bad background\n");
exits("bad bg");
}
c.r=r;
c.g=g;
c.b=b;
c.a=a;
for(p=pix[0];p!=pix[nz];p++) p->rgba=c;
}
void readcolor(FILE *f){
int n, r, g, b, a;
if(fscanf(f, "%d%d%d%d%d", &n, &r, &g, &b, &a)!=5 || n<0 || NCOLOR<=n){
fprintf(stderr, "cpr: Bad color\n");
exits("bad color");
}
color[n].r=r;
color[n].g=g;
color[n].b=b;
color[n].a=a;
}
double len(Point3 p){
return sqrt(p.x*p.x+p.y*p.y+p.z*p.z);
}
void readlight(FILE *f){
double l;
if(fscanf(f, "%lf%lf%lf", &light.x, &light.y, &light.z)!=3){
fprintf(stderr, "cpr: Bad light\n");
exits("bad light");
}
l=len(light);
if(l!=0){
light.x/=l;
light.y/=l;
light.z/=l;
}
}
void readview(FILE *f){
double fov, near, far;
Point3 e, v, u;
double l;
e.w=1;
v.w=1;
u.w=1;
if(fscanf(f, "%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf\n",
&fov, &near, &far,
&e.x, &e.y, &e.z, &v.x, &v.y, &v.z, &u.x, &u.y, &u.z)!=12){
fprintf(stderr, "cpr: Bad view spec\n");
exits("bad view");
}
/* viewport transformation, then perspective, then viewing direction */
while(mx) mx=popmat(mx);
mx=pushmat(0);
move(mx, .5*nx, 0., .5*nz);
scale(mx, .5*nx, 1., -.5*aspect*nz);
persp(mx, fov, near, far);
look(mx, e, v, u);
wx=mx;
mx=pushmat(mx);
eye=e;
yclip=sub3(v, e);
l=near/sqrt(yclip.x*yclip.x+yclip.y*yclip.y+yclip.z*yclip.z);
yclip.w=-((e.x+l*yclip.x)*yclip.x+(e.y+l*yclip.y)*yclip.y+(e.z+l*yclip.z)*yclip.z);
}
void readpush(FILE *f){
mx=pushmat(mx);
}
void readpop(FILE *f){
mx=popmat(mx);
}
void readquat(FILE *f){
Quaternion q;
if(fscanf(f, "%lf%lf%lf%lf", &q.r, &q.i, &q.j, &q.k)!=4
|| q.r==0 && q.i==0 && q.j==0 && q.k==0){
fprintf(stderr, "bad rotate\n");
exits("bad rot");
}
q=qunit(q);
qrot(mx, q);
}
void readrot(FILE *f){
double theta;
int axis;
if(fscanf(f, "%lf%d", &theta, &axis)!=2 || axis<0 || 2<axis){
fprintf(stderr, "bad rotate\n");
exits("bad rot");
}
rot(mx, theta, axis);
}
void readscale(FILE *f){
double sx, sy, sz;
if(fscanf(f, "%lf%lf%lf", &sx, &sy, &sz)!=3){
fprintf(stderr, "bad scale\n");
exits("bad scale");
}
scale(mx, sx, sy, sz);
}
void readtran(FILE *f){
double tx, ty, tz;
if(fscanf(f, "%lf%lf%lf", &tx, &ty, &tz)!=3){
fprintf(stderr, "bad translate\n");
exits("bad translate");
}
move(mx, tx, ty, tz);
}
void readmat(FILE *f){
Matrix m;
int i, j;
for(i=0;i!=4;i++) for(j=0;j!=4;j++) if(fscanf(f, "%lf", &m[i][j])!=1){
fprintf(stderr, "bad matrix\n");
exits("bad matrix");
}
xform(mx, m);
}
/*
* Get the polygon's normal vector. The normal is guaranteed pointing
* towards the viewer. The return value is 0 if the polygon passes its
* vertices clockwise from the viewer's point of view and 1 if counterclockwise.
*/
int getnormal(void){
register struct edge *p;
Point3 a, b;
double l;
normal.x=normal.y=normal.z=0;
normal.w=1;
a.x=edge[1].p.x-edge[0].p.x;
a.y=edge[1].p.y-edge[0].p.y;
a.z=edge[1].p.z-edge[0].p.z;
for(p=edge+2;p!=eedge;p++){
b.x=p->p.x-edge[0].p.x;
b.y=p->p.y-edge[0].p.y;
b.z=p->p.z-edge[0].p.z;
normal.x+=a.y*b.z-b.y*a.z;
normal.y+=b.x*a.z-a.x*b.z;
normal.z+=a.x*b.y-a.y*b.x;
a.x=b.x;
a.y=b.y;
a.z=b.z;
}
l=len(normal);
if(l==0) return 0; /* what should we do here? */
normal.x/=l;
normal.y/=l;
normal.z/=l;
if(normal.x*(eye.x-edge[0].p.x)
+normal.y*(eye.y-edge[0].p.y)
+normal.z*(eye.z-edge[0].p.z)>=0){
normal.x=-normal.x;
normal.y=-normal.y;
normal.z=-normal.z;
return 1;
}
return 0;
}
void readpoly(FILE *f){
Point3 in;
int c, c0, c1;
if(fscanf(f, "%d%d", &c0, &c1)!=2){
Bad:
fprintf(stderr, "cpr: bad polygon %d\n", npoly);
exits("bad polygon");
}
in.x=in.y=in.z=0;
in.w=1.;
for(evert=vert;evert!=&vert[NEDGE];evert++){
do
c=getc(f);
while(c==' ' || c=='\t' || c=='\n');
if(c==';' || c==EOF) break;
ungetc(c, f);
if(fscanf(f, "%lf%lf%lf", &in.x, &in.y, &in.z)!=3)
goto Bad;
*evert=in;
}
if(evert<vert+3)
return;
polyren(c0, c1);
}
void readtri(FILE *f){
Point3 in;
int c0, c1;
in.w=1.;
for(evert=vert;evert!=&vert[3];evert++){
if(fscanf(f, "%lf%lf%lf", &in.x, &in.y, &in.z)!=3)
goto Bad;
*evert=in;
}
if(fscanf(f, "%d%d", &c0, &c1)!=2){
Bad:
fprintf(stderr, "cpr: bad triangle\n");
exits("bad triangle");
}
polyren(c0, c1);
}
void readheight(FILE *f){
char name[512];
FILE *data;
float flt, f0, f1, *save;
int nx, ny, x, y, c1, c2;
double x0, y0, x1, y1, dx, dy;
if(fscanf(f, "%d%d%d%d%lf%lf%lf%lf%s", &c1, &c2, &nx, &ny, &x0, &y0, &x1, &y1, name)!=9){
fprintf(stderr, "ncpr: bad sampled surface\n");
exits("bad height-field");
}
if(nx<=0 || ny<=0){
fprintf(stderr, "ncpr: non-positive sample dimensions\n");
exits("bad height-field");
}
dx=(x1-x0)/nx;
dy=(y1-y0)/ny;
data=fopen(name, "r");
if(data==0){
perror(name);
exits("no data file");
}
save=(float *)malloc(nx*sizeof(float));
if(save==0){
fprintf(stderr, "Out of space\n");
exits("no space");
}
for(x=0;x!=nx;x++){
if(fread((char *)&flt, sizeof flt, 1, data)!=1){
Eof:
fclose(data);
return;
}
save[x]=flt;
}
for(y=1;y!=ny;y++){
if(fread((char *)&flt, sizeof flt, 1, data)!=1) goto Eof;
f0=flt;
for(x=1;x!=nx;x++){
if(fread((char *)&flt, sizeof flt, 1, data)!=1) goto Eof;
f1=flt;
vert[0].x=x0+x*dx;
vert[0].z=f1;
vert[0].y=y0+y*dy;
vert[0].w=1.;
vert[1].x=x0+(x-1)*dx;
vert[1].z=f0;
vert[1].y=y0+y*dy;
vert[1].w=1.;
vert[2].x=x0+(x-1)*dx;
vert[2].z=save[x-1];
vert[2].y=y0+(y-1)*dy;
vert[2].w=1.;
evert=vert+3;
polyren(c1, c2);
vert[0].x=x0+x*dx;
vert[0].z=f1;
vert[0].y=y0+y*dy;
vert[0].w=1.;
vert[1].x=x0+(x-1)*dx;
vert[1].z=save[x-1];
vert[1].y=y0+(y-1)*dy;
vert[1].w=1.;
vert[2].x=x0+x*dx;
vert[2].z=save[x];
vert[2].y=y0+(y-1)*dy;
vert[2].w=1.;
evert=vert+3;
polyren(c1, c2);
save[x-1]=f0;
f0=f1;
}
save[nx-1]=f0;
}
fclose(data);
free((char *)save);
}
/*
* sphere primitive -- really a period 8 geodesic dome
*/
Point3 sphvert[]={ /* icosahedron vertices */
0.0000, 0.0000, 1.0, 1.0, 0.0000, 0.8660, 0.5, 1.0, 0.5090, 0.7006,-0.5, 1.0,
0.8236, 0.2676, 0.5, 1.0, 0.8236,-0.2676,-0.5, 1.0, 0.5090,-0.7006, 0.5, 1.0,
0.0000,-0.8660,-0.5, 1.0, -0.5090,-0.7006, 0.5, 1.0, -0.8236,-0.2676,-0.5, 1.0,
-0.8236, 0.2676, 0.5, 1.0, -0.5090, 0.7006,-0.5, 1.0, 0.0000, 0.0000,-1.0, 1.0
};
Point3 *sphface[][3]={ /* icosahedron faces */
&sphvert[ 0],&sphvert[1],&sphvert[ 3], &sphvert[ 0],&sphvert[ 3],&sphvert[5],
&sphvert[ 0],&sphvert[5],&sphvert[ 7], &sphvert[ 0],&sphvert[ 7],&sphvert[9],
&sphvert[ 0],&sphvert[9],&sphvert[ 1], &sphvert[ 1],&sphvert[ 2],&sphvert[3],
&sphvert[ 2],&sphvert[3],&sphvert[ 4], &sphvert[ 3],&sphvert[ 4],&sphvert[5],
&sphvert[ 4],&sphvert[5],&sphvert[ 6], &sphvert[ 5],&sphvert[ 6],&sphvert[7],
&sphvert[ 6],&sphvert[7],&sphvert[ 8], &sphvert[ 7],&sphvert[ 8],&sphvert[9],
&sphvert[ 8],&sphvert[9],&sphvert[10], &sphvert[ 9],&sphvert[10],&sphvert[1],
&sphvert[10],&sphvert[1],&sphvert[ 2], &sphvert[11],&sphvert[ 2],&sphvert[4],
&sphvert[11],&sphvert[4],&sphvert[ 6], &sphvert[11],&sphvert[ 6],&sphvert[8],
&sphvert[11],&sphvert[8],&sphvert[10], &sphvert[11],&sphvert[10],&sphvert[2],
};
void readsphere(FILE *f){
Point3 cen;
double rad;
int c1, c2, i;
if(fscanf(f, "%d%d%lf%lf%lf%lf", &c1, &c2, &cen.x, &cen.y, &cen.z, &rad)!=6){
fprintf(stderr, "ncpr: bad sphere\n");
exits("bad sphere");
}
for(i=0;i!=sizeof sphface/sizeof sphface[0];i++)
geode(sphface[i][0], sphface[i][1], sphface[i][2], cen, rad, c1, c2, 3);
}
void geode(Point3 *a, Point3 *b, Point3 *c, Point3 cen, double r, int c1, int c2, int n){
Point3 pa, pb, pc, p;
if(n==0){
vert[0].x=cen.x+a->x*r;
vert[0].y=cen.y+a->y*r;
vert[0].z=cen.z+a->z*r;
vert[0].w=1;
vert[1].x=cen.x+b->x*r;
vert[1].y=cen.y+b->y*r;
vert[1].z=cen.z+b->z*r;
vert[1].w=1;
vert[2].x=cen.x+c->x*r;
vert[2].y=cen.y+c->y*r;
vert[2].z=cen.z+c->z*r;
vert[2].w=1;
evert=&vert[3];
polyren(c1, c2);
return;
}
pa.x=b->x+c->x;
pa.y=b->y+c->y;
pa.z=b->z+c->z;
normalize(&pa);
pb.x=a->x+c->x;
pb.y=a->y+c->y;
pb.z=a->z+c->z;
normalize(&pb);
pc.x=b->x+a->x;
pc.y=b->y+a->y;
pc.z=b->z+a->z;
normalize(&pc);
geode(&pc, &pb, a, cen, r, c1, c2, n-1);
geode(c, &pb, &pa, cen, r, c1, c2, n-1);
geode(&pc, b, &pa, cen, r, c1, c2, n-1);
geode(&pa, &pb, &pc, cen, r, c1, c2, n-1);
}
void normalize(Point3 *v){
double l=sqrt(v->x*v->x+v->y*v->y+v->z*v->z);
if(l!=0.){
v->x/=l;
v->y/=l;
v->z/=l;
}
}
void insert(struct edge *e, struct edge **bp){
while(*bp && (*bp)->x<e->x)
bp=&((*bp)->next);
e->next=*bp;
*bp=e;
}
void depthclip(void){
Point3 q, *v;
struct edge *e;
int pin, qin;
double pd, qd, alpha;
q=evert[-1];
qd=q.x*yclip.x+q.y*yclip.y+q.z*yclip.z+yclip.w;
qin=qd>=0;
eedge=edge;
for(v=vert;v!=evert;v++){
pd=v->x*yclip.x+v->y*yclip.y+v->z*yclip.z+yclip.w;
pin=pd>=0;
if(pin^qin){
alpha=pd/(pd-qd);
eedge->p.x=v->x+alpha*(q.x-v->x);
eedge->p.y=v->y+alpha*(q.y-v->y);
eedge->p.z=v->z+alpha*(q.z-v->z);
eedge->p.w=1;
eedge++;
}
if(pin) eedge++->p=*v;
q=*v;
qd=pd;
qin=pin;
}
}
void polyren(int c, int other){
register struct edge *e, *f, *g, **b;
register z;
Point3 p, in, xin, n, *v;
double t;
Color col;
++npoly;
for(v=vert;v!=evert;v++) *v=xformpoint(*v, wx, mx);
depthclip();
if(eedge<edge+3) return;
if(getnormal()) c=other;
for(e=edge;e!=eedge;e++){
e->p=xformpointd(e->p, 0, wx);
if(e==edge)
eedge[-1].q=e->p;
else
e[-1].q=e->p;
}
for(b=b0;b<=b1;b++)
*b=0;
for(e=edge;e!=eedge;e++){
if(e->p.z>e->q.z){ p=e->p; e->p=e->q; e->q=p; }
if(e->p.z==e->q.z || nz<=e->p.z || e->q.z<=0)
continue;
if(e->p.z<0)
z=0;
else
z=ceil(e->p.z);
if(z<e->q.z){
t=(z-e->p.z)/(e->q.z-e->p.z);
e->x=e->p.x+t*(e->q.x-e->p.x);
e->y=e->p.y+t*(e->q.y-e->p.y);
t=1/(e->q.z-e->p.z);
e->dx=t*(e->q.x-e->p.x);
e->dy=t*(e->q.y-e->p.y);
insert(e, &buck[z]);
}
}
if(c<0){
c=-c;
t=(normal.x*light.x+normal.y*light.y+normal.z*light.z+1)/2;
rgba.r=(color[c].r&255)*t;
rgba.g=(color[c].g&255)*t;
rgba.b=(color[c].b&255)*t;
rgba.a=color[c].a;
}
else if(c>=300){
c-=300;
rgba.r=c&0xFF;
rgba.g=(c>>8)&0xFF;
rgba.b=(c>>16)&0xFF;
rgba.a=255;
}
else
rgba=color[c];
for(b=b0,z=0;b!=b1;b++,z++) if(*b){
for(e=*b;e && (f=e->next);e=g){
g=f->next;
span(z, e->x, e->y, f->x, f->y);
if(z+1<e->q.z){
e->x+=e->dx;
e->y+=e->dy;
insert(e, b+1);
}
if(z+1<f->q.z){
f->x+=f->dx;
f->y+=f->dy;
insert(f, b+1);
}
}
}
}
void span(int z, double x0, double y0, double x1, double y1){
register struct pix *pp;
register x, ix0, ix1;
double dy, y;
if(x0==x1)
return;
ix0=ceil(x0);
if(ix0<0)
ix0=0;
ix1=floor(x1);
if(nx<=ix1)
ix1=nx-1;
dy=(y1-y0)/(x1-x0);
for(x=ix0,y=y0+(ix0-x0)*dy,pp=&pix[z][ix0];x<=ix1;x++,y+=dy,pp++){
if(y<pp->y){
pp->rgba=rgba;
pp->y=y;
}
}
}
void outline(PICFILE *f, unsigned char *p){
register unsigned char *q;
static unsigned char *obuf=0;
register x;
if(obuf==0){
obuf=malloc(nx*8);
if(obuf==0){
fprint(2, "cpr: out of space\n");
exits("no mem");
}
}
if(chan==(R|G|B|A|Z) && sizeof(float)==4){
picwrite(f, (char *)p);
return;
}
q=obuf;
for(x=0;x!=nx;x++){
if(chan&R) *q++=*p++; else p++;
if(chan&G) *q++=*p++; else p++;
if(chan&B) *q++=*p++; else p++;
if(chan&A) *q++=*p++; else p++;
if(chan&Z){
switch(sizeof(float)){
default:
fprintf(stderr,
"cpr: sizeof(float) is %d! rewrite outline!\n",
sizeof(float));
exits("bad size!");
case 8: *q++=*p++; *q++=*p++; *q++=*p++; *q++=*p++;
case 4: *q++=*p++; *q++=*p++; *q++=*p++; *q++=*p++;
}
}
else
p+=sizeof(float);
}
picwrite(f, (char *)obuf);
}