//------------------------------------------------------------------- // filldemo.cpp // // This prototype demonstrates an application of vector algebra // to the problem of hidden line removal in wireframe models of // convex objects, and also the use of a polygon-fill algorithm // to provide solid coloring of such a model's visible facets. // // NOTE: This program utilizes the 'bitblit' technique (instead // of 'page-flipping') to achieve its smooth animation: drawing // operations are performed upon an offscreen region of display // memory, then rapidly copied onto the visible region of vram. // // compile using: $ g++ filldemo.cpp int86.cpp -o filldemo // // programmer: ALLAN CRUSE // date begun: 17 OCT 2003 // completion: 20 OCT 2003 // revised on: 08 NOV 2005 -- replaced svgalib with 'int86()' //------------------------------------------------------------------- #include // for printf(), perror() #include // for open(), fcntl() #include // for read(), lseek() #include // for exit() #include // for mmap() #include // for sqrt() #include // for strncpy() #include // for tcsetattr() #include "int86.h" // for init8086(), int86() #define MAXVERT 50 #define MAXEDGE 50 #define MAXFACE 50 typedef struct { float x, y, z; } float3_t; typedef int edge_t[ 2 ]; typedef struct { int numsides; int poly[ 10 ]; } face_t; typedef struct { float3_t vrp, vpn, vup; } camera_t; typedef struct { int numverts; float3_t vert[ MAXVERT ]; int numedges; edge_t edge[ MAXEDGE ]; int numfaces; face_t face[ MAXFACE ]; int facecolor[ MAXFACE ]; } model_t; unsigned char *vram = (unsigned char*)0xA0000000; const int vesa_mode = 0x4101, hres = 640, vres = 480; struct vm86plus_struct vm; float scaleX = 100.0; float scaleY = 100.0; float transX = 320.0; float transY = 240.0; void copy_page( int src, int dst ) { unsigned char *vram = (unsigned char*)0xA0000000; int pagesize = hres * vres; long *srcp = (long*)&vram[ src * pagesize ]; long *dstp = (long*)&vram[ dst * pagesize ]; for (int i = 0; i < pagesize/4; i++) dstp[ i ] = srcp[ i ]; } double dot_product( float3_t a, float3_t b ) { return a.x * b.x + a.y * b.y + a.z * b.z; } void normalize( float3_t &v ) { double norm = sqrt( dot_product( v, v ) ); v.x /= norm; v.y /= norm; v.z /= norm; } void cross_product( float3_t u, float3_t v, float3_t &w ) { w.x = u.y * v.z - u.z * v.y; w.y = u.z * v.x - u.x * v.z; w.z = u.x * v.y - u.y * v.x; } void draw_rectangle( int x, int y, int h, int v, int color ) { unsigned char *dstn = vram + hres*vres; int minx = 0, miny = 0, maxx = hres-1, maxy = vres-1; do { vram[ y*hres + x ] = color; ++x; } while ( x < maxx ); do { vram[ y*hres + x ] = color; ++y; } while ( y < maxy ); do { vram[ y*hres + x ] = color; --x; } while ( x > minx ); do { vram[ y*hres + x ] = color; --y; } while ( y > miny ); } void fill_rectangle( int x, int y, int h, int v, int color ) { int minx = x, miny = y, maxx = x+h-1, maxy = y+v-1; for (y = miny; y <= maxy; y++) for (x = minx; x <= maxx; x++) vram[ y*hres + x ] = color; } void draw_pixel( int x, int y, int color ) { if (( x < 0 )||( x >= hres )) return; if (( y < 0 )||( y >= vres )) return; vram[ y*hres + x ] = color; } void draw_line( int x1, int y1, int x2, int y2, int color ) { int deltax = x2 - x1; int deltay = y2 - y1; int xinc = ( deltax < 0 ) ? -1 : 1; int yinc = ( deltay < 0 ) ? -1 : 1; if ( deltax < 0 ) deltax = - deltax; if ( deltay < 0 ) deltay = - deltay; if (( deltax == 0 )&&( deltay == 0 )) { draw_pixel( x1, y1, color ); return; } if ( deltay <= deltax ) // odd octant { int errorterm = - deltax; do { draw_pixel( x1, y1, color ); errorterm += 2*deltay; if ( errorterm >= 0 ) { y1 += yinc; errorterm -= 2*deltax; } x1 += xinc; } while ( x1 != x2 ); } else { int errorterm = - deltay; do { draw_pixel( x1, y1, color ); errorterm += 2*deltax; if ( errorterm >= 0 ) { x1 += xinc; errorterm -= 2*deltay; } y1 += yinc; } while ( y1 != y2 ); } } int face_is_visible( model_t model, float3_t eye, int facenum ) { int i = model.face[ facenum ].poly[0]; int j = model.face[ facenum ].poly[1]; int k = model.face[ facenum ].poly[2]; float3_t vert0 = model.vert[ i ]; float3_t vert1 = model.vert[ j ]; float3_t vert2 = model.vert[ k ]; float3_t side1; side1.x = vert1.x - vert0.x; side1.y = vert1.y - vert0.y; side1.z = vert1.z - vert0.z; float3_t side2; side2.x = vert2.x - vert1.x; side2.y = vert2.y - vert1.y; side2.z = vert2.z - vert1.z; float3_t fpn; cross_product( side1, side2, fpn ); float3_t ray; ray.x = eye.x - vert1.x; ray.y = eye.y - vert1.y; ray.z = eye.z - vert1.z; double dot = dot_product( ray, fpn ); return ( dot < 0.0 ) ? 0 : 1; } typedef struct node { int x; struct node *next; } node_t; typedef node_t * nodeptr_t; #define VRES 480 #define MAXNODE (VRES * MAXVERT) nodeptr_t NIL = (nodeptr_t)(-1); nodeptr_t bucket[ VRES ]; node_t nodepool[ MAXNODE ]; int nodes_used = 0; void insert_pixel( int x, int y ) { if (( y < 0 )||( y > vres )) return; if ( x < 0 ) x = 0; if ( x >= hres ) x = hres-1; node_t *newnodep = &nodepool[ nodes_used++ ]; newnodep->x = x; newnodep->next = bucket[ y ]; bucket[ y ] = newnodep; // insure nodes are sorted in order of increasing x node_t *p = bucket[ y ]; node_t *q = p->next; while ( q != NIL ) { if ( p->x > q->x ) { int temp; temp = p->x; p->x = q->x; q->x = temp; } p = q; q = q->next; } } void insert_line_segment( float3_t p, float3_t q ) { int x1 = (int)p.x; int y1 = (int)p.y; int x2 = (int)q.x; int y2 = (int)q.y; int deltax = x2 - x1, deltay = y2 - y1; int xinc = ( deltax < 0 ) ? -1 : 1; int yinc = ( deltay < 0 ) ? -1 : 1; if ( deltax < 0 ) deltax = - deltax; if ( deltay < 0 ) deltay = - deltay; insert_pixel( x1, y1 ); if ( deltay == 0 ) return; int errorterm = - deltay; while ( y1 != y2 ) { errorterm += 2*deltax; while ( errorterm >= 0 ) { x1 += xinc; errorterm -= 2*deltay; } y1 += yinc; insert_pixel( x1, y1 ); } } void fill_polygon( model_t view, int facenum ) { // initialize the bucket-list for (int i = 0; i < VRES; i++) bucket[ i ] = NIL; nodes_used = 0; // scan-convert the polygon int numsides = view.face[ facenum ].numsides; for (int i = 0; i < numsides; i++) { int j = ( i+1 )%numsides; int p = view.face[ facenum ].poly[ i ]; int q = view.face[ facenum ].poly[ j ]; float3_t vp = view.vert[ p ]; float3_t vq = view.vert[ q ]; insert_line_segment( vp, vq ); } // extra insertions for the special polygon corners for (int i = 0; i < numsides; i++) { int j = ( i+1 )%numsides; int k = ( i+2 )%numsides; int p = view.face[ facenum ].poly[ i ]; int q = view.face[ facenum ].poly[ j ]; int r = view.face[ facenum ].poly[ k ]; float3_t vp = view.vert[ p ]; float3_t vq = view.vert[ q ]; float3_t vr = view.vert[ r ]; if ( (( vp.y < vq.y )&&( vq.y < vr.y )) ||(( vp.y > vq.y )&&( vq.y > vr.y )) ) { int x = (int)vq.x; int y = (int)vq.y; insert_pixel( x, y ); } } // fill the polygon with the face-color int color = view.facecolor[ facenum ]; for (int y = 0; y < vres; y++) { node_t *p = bucket[ y ]; node_t *q = ( p != NIL ) ? q = p->next : NIL; while ( q != NIL ) { int x1 = p->x; int x2 = q->x; while ( x1 <= x2 ) { draw_pixel( x1, y, color ); ++x1; } p = q->next; q = ( p != NIL ) ? q = p->next : NIL; } } } int get_model_data( char *filename, model_t &model ) { FILE *fp = fopen( filename, "ra" ); if ( fp == NULL ) { perror( "fopen" ); return -1; } if ( fscanf( fp, "%d", &model.numverts ) != 1 ) return -1; for (int i = 0; i < model.numverts; i++) { if ( fscanf( fp, "%f", &model.vert[i].x ) != 1 ) return -1; if ( fscanf( fp, "%f", &model.vert[i].y ) != 1 ) return -1; if ( fscanf( fp, "%f", &model.vert[i].z ) != 1 ) return -1; } if ( fscanf( fp, "%d", &model.numedges ) != 1 ) return -1; for (int i = 0; i < model.numedges; i++) { if ( fscanf( fp, "%d", &model.edge[i][0] ) != 1 ) return -1; if ( fscanf( fp, "%d", &model.edge[i][1] ) != 1 ) return -1; } if ( fscanf( fp, "%d", &model.numfaces ) != 1 ) return -1; for (int i = 0; i < model.numfaces; i++) { if ( fscanf( fp, "%d", &model.face[i].numsides ) != 1 ) return -1; for (int j = 0; j < model.face[i].numsides; j++) if ( fscanf( fp, "%d", &model.face[i].poly[j] ) != 1 ) return -1; if ( fscanf( fp, "%d", &model.facecolor[i] ) != 1 ) return -1; } fclose( fp ); return 0; } void draw_model( model_t model, camera_t camera, float3_t eye ) { // setup the view coordinates float3_t n = camera.vpn; normalize( n ); float3_t v = camera.vup; normalize( v ); float3_t u; cross_product( v, n, u ); // transform points of the model to view-coordinates model_t view = model; for (int i = 0; i < model.numverts; i++) { float3_t p = model.vert[i]; p.x -= camera.vrp.x; p.y -= camera.vrp.y; p.z -= camera.vrp.z; view.vert[i].x = dot_product( u, p ); view.vert[i].y = dot_product( v, p ); view.vert[i].z = dot_product( n, p ); } // distance from eye to viewplane float3_t c = camera.vrp; c.x -= eye.x; c.y -= eye.y; c.z -= eye.z; double eyedist = sqrt( c.x * c.x + c.y * c.y + c.z * c.z ); // perform the perspective projection for (int i = 0; i < model.numverts; i++) { float3_t vertex = view.vert[i]; double hittime = 1.0 / ( 1.0 - vertex.z / eyedist ); view.vert[i].x *= hittime; view.vert[i].y *= hittime; view.vert[i].z = 0.0; } // perform scaling and translation for (int i = 0; i < model.numverts; i++) { view.vert[i].x *= scaleX; view.vert[i].y *= scaleY; view.vert[i].x += transX; view.vert[i].y += transY; } // clear the screen-window fill_rectangle( 1, 1, hres-2, vres-2, 0 ); // draw the wireframe model for (int i = 0; i < view.numfaces; i++) if ( face_is_visible( model, eye, i ) ) { fill_polygon( view, i ); int numsides = view.face[i].numsides; for (int j = 0; j < numsides; j++) { int k = (j + 1) % numsides; int p = view.face[i].poly[j]; int q = view.face[i].poly[k]; float3_t vp = view.vert[p]; float3_t vq = view.vert[q]; int x0 = (int)vp.x; int y0 = (int)vp.y; int x1 = (int)vq.x; int y1 = (int)vq.y; int color = view.facecolor[i] | 8; draw_line( x0, y0, x1, y1, color ); } } } int main( int argc, char **argv ) { // setup filename of the wireframe data-file char filename[ 64 ] = "plato.dat"; // default if ( argc > 1 ) strncpy( filename, argv[1], 63 ); // read the data-file (and display a summary report) model_t model; if ( get_model_data( filename, model ) < 0 ) { perror( "get_model_data" ); exit(1); } system( "clear" ); printf( "\nfilename: \'%s\' ", filename ); printf( "\nnumverts=%d ", model.numverts ); printf( "\nnumedges=%d ", model.numedges ); printf( "\nnumfaces=%d ", model.numfaces ); printf( "\n" ); getchar(); // setup video memory-mapping int fd = open( "/dev/vram", O_RDWR ); if ( fd < 0 ) { perror( "/dev/vram" ); return -1; } int size = lseek( fd, 0, SEEK_END ); int prot = PROT_READ | PROT_WRITE; int flag = MAP_FIXED | MAP_SHARED; if ( mmap( (void*)vram, size, prot, flag, fd, 0 ) == MAP_FAILED ) { perror( "mmap" ); return -1; } // enter graphics mode init8086(); vm.regs.eax = 0x4F02; vm.regs.ebx = vesa_mode; int86( 0x10, vm ); // draw screen border int color = 15; draw_rectangle( 0, 0, hres, vres, color ); getchar(); // transfer border-image to offscreen page copy_page( 0, 1 ); // change vram-pointer to the offscreen page vram += hres*vres; // enable raw keyboard input struct termios otty; tcgetattr( 0, &otty ); struct termios tty = otty; tty.c_lflag &= ~( ICANON | ECHO ); tty.c_cc[ VMIN ] = 0; tty.c_cc[ VTIME ] = 0; tcsetattr( 0, TCSAFLUSH, &tty ); // setup the camera and eye-position float3_t vrp = { 0.0, 0.0, 0.0 }; float3_t vpn = { 0.0, 0.0, 1.0 }; float3_t vup = { 0.0, -1.0, 0.0 }; float3_t eye = { 0.0, 0.0, 8.0 }; double t = 0.0; for(;;) { // recompute the eye-position eye.x = 8.0 * sin( t ); eye.z = 8.0 * cos( t ); t += 0.03; // recompute the view-plane normal vpn.x = eye.x; vpn.z = eye.z; // adjust the camera and draw the model camera_t camera = { vrp, vpn, vup }; draw_model( model, camera, eye ); // bitblit: move offscreen page to visible page copy_page( 1, 0 ); // check for termination keypress if ( getchar() == '\e' ) break; } // leave graphics mode vm.regs.eax = 0x0003; int86( 0x10, vm ); tcsetattr( 0, TCSAFLUSH, &otty ); }