/* ------------------------------------------------------------------ Black Nebula File : Clip.c Programmer: Colin Adams Date: 23/4/91 Last Modified : 10/6/91 ------------------------------------------------------------------ */ #include "3d.h" #include #define BOUNDARY_COUNT 4 #define MAX_LIMIT 500 extern short U_rot, W_rot, View_Angle; extern short num_active[]; static point s[BOUNDARY_COUNT], first_point[BOUNDARY_COUNT]; static short new_edge[BOUNDARY_COUNT]; static polygon *clip_poly; static object *sort_obj; /* global for sort */ static point clip_point; static int Depth[TOTAL_MAX_OBJECTS]; /* array to sort in */ static int xp[3], yp[3], zp[3]; enum { TOP, RIGHT, BOTTOM, LEFT }; /* ------------------------------------------------------------------ Fast Square Root Code for IEEE floating point numbers from Graphics Gems, Academic Press 1990 Supplied by Peter Stephenson ------------------------------------------------------------------ */ static short sqrttab[0x100]; void build_table(void) { unsigned short i; float f; unsigned int *fi = (int *) &f; for(i=0; i<= 0x7f; i++) { /* build a float with the bit pattern i as mantissa and an exponent of 0, stored as 127 */ *fi = (i << 16) | ( 127 << 23); f = sqrt(f); /* take the square root then strip the first 7 bits of the mantissa into the table */ sqrttab[i] = (*fi & 0x7fffff) >> 16; /* repeat the process, this time with an exponent of 1, stored as 128 */ *fi = (i << 16) | (128 << 23); f = sqrt(f); sqrttab[i+0x80] = (*fi & 0x7fffff) >> 16; } } float fsqrt(float n) { unsigned int *num = (int *) &n; short e; if(n==0) return 0; e = (*num >> 23) - 127; *num &= 0x7fffff; if(e & 0x01) *num |= 0x800000; e >>= 1; *num = ((sqrttab[*num >> 16]) << 16) | ((e + 127) << 23); return n; } /* ------------------------------------------------------------------ Clipping Routines The clipping algorithm is Sutherland and Hodgeman's. The description of the algorithm is in Computer Graphics by D. Hearn & M. Baker. ------------------------------------------------------------------ */ short inside(point *p, short edge) /* returns true if the point is inside the given edge, false if not */ { switch(edge) { case TOP: /* top line */ if(p->y >= Y_MIN) return 1; return 0; case RIGHT: /* right hand side */ if(p->x <= X_MAX) return 1; return 0; case BOTTOM: /* bottom line */ if(p->y <= Y_MAX) return 1; return 0; case LEFT: /* left hand side */ if(p->x >= X_MIN) return 1; return 0; } } short cross(point *p, point *p2, short edge) /* returns true if the 2 points cross the given edge */ { /* This first bit is a slight hack to make points generated a mile apart when on the other side of the eye, not appear! May or may not be fixed. 2/6/91 It is now very unlikely to be fixed, as I don't think it can be!! But it seems to work... */ if(abs(p->x) > MAX_LIMIT || abs(p2->x) > MAX_LIMIT || abs(p->y) > MAX_LIMIT || abs(p2->y) > MAX_LIMIT) return 0; /* end of horrible speed reducing hack */ switch(edge) { case TOP: /* top edge */ if(p->y > Y_MIN) { if(p2->y <= Y_MIN) return 1; } else if(p2->y > Y_MIN) return 1; return 0; case RIGHT: /* right hand edge */ if(p->x > X_MAX) { if(p2->x <= X_MAX) return 1; } else if(p2->x > X_MAX) return 1; return 0; case BOTTOM: /* bottom edge */ if(p->y > Y_MAX) { if(p2->y <= Y_MAX) return 1; } else if(p2->y > Y_MAX) return 1; return 0; case LEFT: /* left hand edge */ if(p->x > X_MIN) { if(p2->x <= X_MIN) return 1; } else if(p2->x > X_MIN) return 1; return 0; } } void find_intersection(point *p1, point *p2, short edge, point *i) /* Find intersection point of line, uses fixed point maths. This routine is heavily optimized, requiring only 1 integer division and 1 integer multiplication to find the intersection!!! If you know a faster method please tell me! */ { register int mnumer, mdenom; /* get gradient */ mnumer = (p2->y - p1->y); mdenom = (p2->x - p1->x); switch(edge) { case TOP: { register int j = Y_MIN - p1->y; i->y = Y_MIN; i->x = p1->x + (mnumer ? (j * mdenom) / mnumer : 0); return; } case RIGHT: { register int j = (X_MAX - p1->x); i->x = X_MAX; i->y = p1->y + (mdenom ? (mnumer*j)/mdenom : 0); return; } case BOTTOM: { register int j = Y_MAX - p1->y; i->y = Y_MAX; i->x = p1->x + (mnumer ? (j * mdenom) / mnumer : 0); return; } case LEFT: { register int j = (X_MIN - p1->x); i->x = X_MIN; i->y = p1->y + (mdenom ? (mnumer*j)/mdenom : 0); return; } } } void clip_this(point *p, short edge) /* recursively checks a point against all the boundary edges */ { point i; if(new_edge[edge]) { first_point[edge].x = p->x; first_point[edge].y = p->y; new_edge[edge] = 0; } else if(cross(p, &s[edge], edge)) { find_intersection(p, &s[edge], edge, &i); if(edge < 3) clip_this(&i, edge+1); else { clip_poly->clip_num++; clip_poly->clip_x[clip_poly->clip_num] = i.x; clip_poly->clip_y[clip_poly->clip_num] = i.y; } } s[edge].x = p->x; s[edge].y = p->y; if(inside(p, edge)) { if(edge < 3) clip_this(p, edge+1); else { clip_poly->clip_num++; clip_poly->clip_x[clip_poly->clip_num] = p->x; clip_poly->clip_y[clip_poly->clip_num] = p->y; } } } void clip_closer(void) /* Closing routine. For each window edge, clips the line connecting the last saved vertex and the first_point processed against the edge */ { point i; register short edge; for(edge=0; edgeclip_num++; clip_poly->clip_x[clip_poly->clip_num] = i.x; clip_poly->clip_y[clip_poly->clip_num] = i.y; } } } } void polygon_clip(polygon *poly) /* clips the polygon sent against the boundaries */ { register int k; clip_poly = poly; /* use a global for speed */ clip_poly->clip_num = -1; /* no points in output yet */ for(k=0; k<4; k++) new_edge[k] = 1; for(k=0; knumpoints; k++) { clip_point.x = clip_poly->x[k]; clip_point.y = clip_poly->y[k]; clip_this(&clip_point, 0); } clip_closer(); /* close polygon */ clip_poly->clip_num++; } /* ------------------------------------------------------------------ Hidden Surface Routines Determines the order to draw objects, and polygons within an object ------------------------------------------------------------------ */ int SpinPolyCentre(object *obj, polygon *poly) /* Spin a point and calculate it's depth from the eye. The point is the centre point of a polygon, and is used for hidden surface removal. This routine should be pretty quick, but if you know a faster algorithm, I'd love to know it! */ { register int x,y,z; register int xrot, yrot, zrot; xrot = obj->rot_x; yrot = obj->rot_y; zrot = obj->rot_z; x = poly->centre.x; y = poly->centre.y; z = poly->centre.z; /* rotate a point around the z axis */ if(zrot) { register int temp, temp2, temp3; temp = x; temp2 = costable[zrot]; temp3 = sintable[zrot]; x = ((temp2*temp)>>16) - ((temp3*y)>>16); y = ((temp3*temp)>>16) + ((temp2*y)>>16); } /* rotate a point around the x axis */ if(xrot) { register int temp, temp2, temp3; temp = y; temp2 = costable[xrot]; temp3 = sintable[xrot]; y = ((temp2*temp)>>16) - ((temp3*z)>>16); z = ((temp3*temp)>>16) + ((temp2*z)>>16); } /* rotate a point around the y axis */ if(yrot) { register int temp, temp2, temp3; temp = z; temp2 = costable[yrot]; temp3 = sintable[yrot]; z = ((temp2*temp)>>16) - ((temp3*x)>>16); x = ((temp3*temp)>>16) + ((temp2*x)>>16); } /* calculate depths */ x = ex - (x + obj->trans_x); y = ey - (y + obj->trans_y); z = ez - (z + obj->trans_z); return (x*x + y*y + z*z); } void qsort_poly(int *left, int *right, int al, int ar) /* Quicksort for polygon depths within an object. This can be written faster. */ { register int *p = left, *q = right, x = *(left+(right-left)/2), w; register int tar = ar, tal = al; do { while(*p>x) { p++; tal++; } while(*qq) break; w = *p; *p = *q; *q = w; { register polygon *temp; temp = sort_obj->draworder[tar]; sort_obj->draworder[tar] = sort_obj->draworder[tal]; sort_obj->draworder[tal] = temp; } p++; q--; tal++; tar--; } while (p<=q); if(leftpoly; register short count = 0; register char oneobj = 0; if(!obj->poly->next) oneobj = 1; while(pg) { obj->draworder[count] = pg; /* NB. as an optimization, the actual distance from the eye is not stored but the square of it is, so the magnitude is relevant! */ /* Check for back faces, this is the only way to make my hidden surface code work properly... I really didn't want to add this, but doesn't seem to slow down the code much as it doesn't draw/erase polygons which are back faces. Unfortunately it still sorts faces which aren't visible, should be fixed! */ if(pg->numpoints>2 && !oneobj) { register int i, A, B, C, D; for(i=0; i<3; i++) { xp[i] = manipulate_x[pg->p[i]]; yp[i] = manipulate_y[pg->p[i]]; zp[i] = manipulate_z[pg->p[i]]; } /* horrible plane equation calculations */ A = yp[0]*(zp[1]-zp[2]) + yp[1]*(zp[2]-zp[0]) + yp[2]*(zp[0]-zp[1]); B = zp[0]*(xp[1]-xp[2]) + zp[1]*(xp[2]-xp[0]) + zp[2]*(xp[0]-xp[1]); C = xp[0]*(yp[1]-yp[2]) + xp[1]*(yp[2]-yp[0]) + xp[2]*(yp[0]-yp[1]); D = - xp[0]*(yp[1]*zp[2] - yp[2]*zp[1]) - xp[1]*(yp[2]*zp[0] - yp[0]*zp[2]) - xp[2]*(yp[0]*zp[1] - yp[1]*zp[0]); if((ex*A + ey*B + ez*C + D)<0) { pg->back_face = 1; pg->clip_num = 0; } else { pg->back_face = 0; } } else pg->back_face = 0; Depth[count++] = pg->back_face ? 0 : SpinPolyCentre(obj, pg); pg = (polygon *) pg->next; } obj->draworder[count] = NULL; /* terminate list */ sort_obj = obj; if(count) qsort_poly(Depth, &Depth[count-1], 0, count-1); } /* Object sorting routines */ double rad(double degrees) /* converts degrees to radians */ { double one8 = 180; return 3.141592654/(one8/degrees); } int GetAngle(int *h, short px, short py, short pz) { register int o, angle, tempint; register char quad; int temp; float depth, temp2; int d; tempint = ex-px; depth = tempint * tempint; tempint = ez - pz; depth += tempint * tempint; tempint = ey - py; *h = depth + tempint * tempint; if(px>ex) { o = px - ex; if(pz>ez) quad = 4; else { if(pz==ez) return 0; quad = 1; } } else { if(px==ex) { if(pzez) quad = 3; else { if(pz==ez) return 180; quad = 2; } } temp = o * 4096; temp2 = fsqrt(depth); d = temp/temp2; if(d>4096) d = 4096; /* to fix small errors which make d > 4096 */ angle = anti_sin[d]; switch(quad) { case 1: angle = 90 - angle; break; case 2: angle = 90 + angle; break; case 3: angle = 180 + (90-angle); break; case 4: angle += 270; break; } return angle; } int SpinObjCentre(object *obj) { register int x,y,z; register short angle; int distance; x = obj->trans_x + obj->centre_x; y = obj->trans_y + obj->centre_y; z = obj->trans_z + obj->centre_z; angle = GetAngle(&distance, x, y, z); /* tries to eliminate objects which aren't in the field of view */ if(View_Angle>=45) { if(View_Angle<=315) { if(angle>View_Angle+45 || angledrawme = 0; } else { if(angleView_Angle-315) obj->drawme = 0; } } else { if(angle>View_Angle+45 && angledrawme = 0; } /* check if I hit something */ if(distance<(obj->radius*5+500) && obj->typei_am_dying = 1; /* kill enemy too! */ obj->explode = 1; am_i_dead = 1; } return distance; } void qsort_depth(int *left, int *right, int al, int ar) /* quicksort objects into drawing order */ { register int *p = left, *q = right, x = *(left+(right-left)/2), w; register int tar = ar, tal = al; do { while(*p>x) { p++; tal++; } while(*qq) break; w = *p; *p = *q; *q = w; { register object *temp; temp = active_list[tar]; active_list[tar] = active_list[tal]; active_list[tal] = temp; } p++; q--; tal++; tar--; } while (p<=q); if(lefti_am_dying) { if(active_list[i]->explode && active_list[i]->i_am_dying==1) CreateExplosion(active_list[i]); if(active_list[i]->i_am_dying++>2) { if(active_list[i]->type==EXPLOSION) { for(j=0; jtype][j]) { obj_free[active_list[i]->type][j] = 0; break; } } } num_active[active_list[i]->type]--; for(j=i; jdrawme = 0; break; } } if(idrawme) PrepareObject(active_list[i]); } } for(i=0; i