www.pudn.com > raiders3d_2.rar > t3dlib5.cpp


// T3DLIB5.CPP 
 
#define STANDALONE 0 
 
// INCLUDES /////////////////////////////////////////////////// 
 
#define DEBUG_ON 
 
#define WIN32_LEAN_AND_MEAN   
 
#if (STANDALONE==1) 
#define INITGUID       // you need this or DXGUID.LIB 
#endif 
 
#include    // include important windows stuff 
#include   
#include  
#include  
#include  // include important C/C++ stuff 
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  
 
#include       // needed for defs in T3DLIB1.H  
#include "T3DLIB1.H"    // T3DLIB4 is based on some defs in this  
#include "T3DLIB4.H" 
#include "T3DLIB5.H" 
 
// CLASSES //////////////////////////////////////////////////// 
 
// PROTOTYPES ///////////////////////////////////////////////// 
 
// GLOBALS //////////////////////////////////////////////////// 
 
#if (STANDALONE==1) 
HWND main_window_handle           = NULL; // save the window handle 
HINSTANCE main_instance           = NULL; // save the instance 
char buffer[256];                         // used to print text 
#endif 
 
// FUNCTIONS ////////////////////////////////////////////////// 
 
char *Get_Line_PLG(char *buffer, int maxlength, FILE *fp) 
{ 
// this little helper function simply read past comments  
// and blank lines in a PLG file and always returns full  
// lines with something on them on NULL if the file is empty 
 
int index = 0;  // general index 
int length = 0; // general length 
 
// enter into parsing loop 
while(1) 
     { 
     // read the next line 
     if (!fgets(buffer, maxlength, fp)) 
     return(NULL); 
 
    // kill the whitespace 
    for (length = strlen(buffer), index = 0; isspace(buffer[index]); index++); 
  
    // test if this was a blank line or a comment 
    if (index >= length || buffer[index]=='#')  
       continue; 
    
    // at this point we have a good line 
    return(&buffer[index]); 
    } // end while 
 
} // end Get_Line_PLG 
 
/////////////////////////////////////////////////////////////// 
 
float Compute_OBJECT4DV1_Radius(OBJECT4DV1_PTR obj) 
{ 
// this function computes the average and maximum radius for  
// sent object and opdates the object data 
 
// reset incase there's any residue 
obj->avg_radius = 0; 
obj->max_radius = 0; 
 
// loop thru and compute radius 
for (int vertex = 0; vertex < obj->num_vertices; vertex++) 
    { 
    // update the average and maximum radius 
    float dist_to_vertex =  
          sqrt(obj->vlist_local[vertex].x*obj->vlist_local[vertex].x + 
               obj->vlist_local[vertex].y*obj->vlist_local[vertex].y + 
               obj->vlist_local[vertex].z*obj->vlist_local[vertex].z); 
     
    // accumulate total radius 
    obj->avg_radius+=dist_to_vertex; 
 
    // update maximum radius    
    if (dist_to_vertex > obj->max_radius) 
       obj->max_radius = dist_to_vertex;  
  
    } // end for vertex 
 
// finallize average radius computation 
obj->avg_radius/=obj->num_vertices; 
 
// return max radius 
return(obj->max_radius); 
 
} // end Compute_OBJECT4DV1_Radius 
 
/////////////////////////////////////////////////////////////// 
 
int Load_OBJECT4DV1_PLG(OBJECT4DV1_PTR obj, // pointer to object 
                    char *filename,     // filename of plg file 
                    VECTOR4D_PTR scale,     // initial scaling factors 
                    VECTOR4D_PTR pos,       // initial position 
                    VECTOR4D_PTR rot)       // initial rotations 
{ 
// this function loads a plg object in off disk, additionally 
// it allows the caller to scale, position, and rotate the object 
// to save extra calls later for non-dynamic objects 
 
FILE *fp;          // file pointer 
char buffer[256];  // working buffer 
 
char *token_string;  // pointer to actual token text, ready for parsing 
 
// file format review, note types at end of each description 
// # this is a comment 
 
// # object descriptor 
// object_name_string num_verts_int num_polys_int 
 
// # vertex list 
// x0_float y0_float z0_float 
// x1_float y1_float z1_float 
// x2_float y2_float z2_float 
// . 
// . 
// xn_float yn_float zn_float 
// 
// # polygon list 
// surface_description_ushort num_verts_int v0_index_int v1_index_int ..  vn_index_int 
// . 
// . 
// surface_description_ushort num_verts_int v0_index_int v1_index_int ..  vn_index_int 
 
// lets keep it simple and assume one element per line 
// hence we have to find the object descriptor, read it in, then the 
// vertex list and read it in, and finally the polygon list -- simple :) 
 
// Step 1: clear out the object and initialize it a bit 
memset(obj, 0, sizeof(OBJECT4DV1)); 
 
// set state of object to active and visible 
obj->state = OBJECT4DV1_STATE_ACTIVE | OBJECT4DV1_STATE_VISIBLE; 
 
// set position of object 
obj->world_pos.x = pos->x; 
obj->world_pos.y = pos->y; 
obj->world_pos.z = pos->z; 
obj->world_pos.w = pos->w; 
 
// Step 2: open the file for reading 
if (!(fp = fopen(filename, "r"))) 
   { 
   Write_Error("Couldn't open PLG file %s.", filename); 
   return(0); 
   } // end if 
 
// Step 3: get the first token string which should be the object descriptor 
if (!(token_string = Get_Line_PLG(buffer, 255, fp))) 
   { 
   Write_Error("PLG file error with file %s (object descriptor invalid).",filename); 
   return(0); 
   } // end if 
 
Write_Error("Object Descriptor: %s", token_string); 
 
// parse out the info object 
sscanf(token_string, "%s %d %d", obj->name, &obj->num_vertices, &obj->num_polys); 
 
// Step 4: load the vertex list 
for (int vertex = 0; vertex < obj->num_vertices; vertex++) 
    { 
    // get the next vertex 
    if (!(token_string = Get_Line_PLG(buffer, 255, fp))) 
          { 
          Write_Error("PLG file error with file %s (vertex list invalid).",filename); 
          return(0); 
          } // end if 
 
    // parse out vertex 
    sscanf(token_string, "%f %f %f", &obj->vlist_local[vertex].x,  
                                     &obj->vlist_local[vertex].y,  
                                     &obj->vlist_local[vertex].z); 
    obj->vlist_local[vertex].w = 1;     
 
    // scale vertices 
    obj->vlist_local[vertex].x*=scale->x; 
    obj->vlist_local[vertex].y*=scale->y; 
    obj->vlist_local[vertex].z*=scale->z; 
 
    Write_Error("\nVertex %d = %f, %f, %f, %f", vertex, 
                                           obj->vlist_local[vertex].x,  
                                           obj->vlist_local[vertex].y,  
                                           obj->vlist_local[vertex].z, 
                                           obj->vlist_local[vertex].w); 
 
    } // end for vertex 
 
// compute average and max radius 
Compute_OBJECT4DV1_Radius(obj); 
 
Write_Error("\nObject average radius = %f, max radius = %f",  
            obj->avg_radius, obj->max_radius); 
 
int poly_surface_desc = 0; // PLG/PLX surface descriptor 
int poly_num_verts    = 0; // number of vertices for current poly (always 3) 
char tmp_string[8];        // temp string to hold surface descriptor in and 
                           // test if it need to be converted from hex 
 
// Step 5: load the polygon list 
for (int poly=0; poly < obj->num_polys; poly++) 
    { 
    // get the next polygon descriptor 
    if (!(token_string = Get_Line_PLG(buffer, 255, fp))) 
        { 
        Write_Error("PLG file error with file %s (polygon descriptor invalid).",filename); 
        return(0); 
        } // end if 
    
    Write_Error("\nPolygon %d:", poly); 
 
    // each vertex list MUST have 3 vertices since we made this a rule that all models 
    // must be constructed of triangles 
    // read in surface descriptor, number of vertices, and vertex list 
    sscanf(token_string, "%s %d %d %d %d", tmp_string, 
                                           &poly_num_verts, // should always be 3  
                                           &obj->plist[poly].vert[0], 
                                           &obj->plist[poly].vert[1], 
                                           &obj->plist[poly].vert[2]); 
     
 
    // since we are allowing the surface descriptor to be in hex format 
    // with a leading "0x" we need to test for it 
    if (tmp_string[0] == '0' && toupper(tmp_string[1]) == 'X') 
       sscanf(tmp_string,"%x", &poly_surface_desc); 
    else 
       poly_surface_desc = atoi(tmp_string); 
     
    // point polygon vertex list to object's vertex list 
    // note that this is redundant since the polylist is contained 
    // within the object in this case and its up to the user to select 
    // whether the local or transformed vertex list is used when building up 
    // polygon geometry, might be a better idea to set to NULL in the context 
    // of polygons that are part of an object 
    obj->plist[poly].vlist = obj->vlist_local;  
 
    Write_Error("\nSurface Desc = 0x%.4x, num_verts = %d, vert_indices [%d, %d, %d]",  
                                                         poly_surface_desc,  
                                                         poly_num_verts,  
                                                         obj->plist[poly].vert[0], 
                                                         obj->plist[poly].vert[1], 
                                                         obj->plist[poly].vert[2]); 
 
    // now we that we have the vertex list and we have entered the polygon 
    // vertex index data into the polygon itself, now let's analyze the surface 
    // descriptor and set the fields for the polygon based on the description 
 
    // extract out each field of data from the surface descriptor 
    // first let's get the single/double sided stuff out of the way 
    if ((poly_surface_desc & PLX_2SIDED_FLAG)) 
       { 
       SET_BIT(obj->plist[poly].attr, POLY4DV1_ATTR_2SIDED); 
       Write_Error("\n2 sided."); 
       } // end if 
    else 
       { 
       // one sided 
       Write_Error("\n1 sided."); 
       } // end else 
 
    // now let's set the color type and color 
    if ((poly_surface_desc & PLX_COLOR_MODE_RGB_FLAG))  
       { 
       // this is an RGB 4.4.4 surface 
       SET_BIT(obj->plist[poly].attr,POLY4DV1_ATTR_RGB16); 
  
       // now extract color and copy into polygon color 
       // field in proper 16-bit format  
       // 0x0RGB is the format, 4 bits per pixel  
       int red   = ((poly_surface_desc & 0x0f00) >> 8); 
       int green = ((poly_surface_desc & 0x00f0) >> 4); 
       int blue  = (poly_surface_desc & 0x000f); 
 
       // although the data is always in 4.4.4 format, the graphics card 
       // is either 5.5.5 or 5.6.5, but our virtual color system translates 
       // 8.8.8 into 5.5.5 or 5.6.5 for us, but we have to first scale all 
       // these 4.4.4 values into 8.8.8 
       obj->plist[poly].color = RGB16Bit(red*16, green*16, blue*16); 
       Write_Error("\nRGB color = [%d, %d, %d]", red, green, blue); 
       } // end if 
    else 
       { 
       // this is an 8-bit color indexed surface 
       SET_BIT(obj->plist[poly].attr,POLY4DV1_ATTR_8BITCOLOR); 
 
       // and simple extract the last 8 bits and that's the color index 
       obj->plist[poly].color = (poly_surface_desc & 0x00ff); 
        
       Write_Error("\n8-bit color index = %d", obj->plist[poly].color); 
 
       } // end else 
 
   // handle shading mode 
   int shade_mode = (poly_surface_desc & PLX_SHADE_MODE_MASK); 
 
   // set polygon shading mode 
   switch(shade_mode) 
         { 
         case PLX_SHADE_MODE_PURE_FLAG: { 
         SET_BIT(obj->plist[poly].attr, POLY4DV1_ATTR_SHADE_MODE_PURE); 
         Write_Error("\nShade mode = pure"); 
         } break; 
 
         case PLX_SHADE_MODE_FLAT_FLAG: { 
         SET_BIT(obj->plist[poly].attr, POLY4DV1_ATTR_SHADE_MODE_FLAT); 
         Write_Error("\nShade mode = flat"); 
 
         } break; 
 
         case PLX_SHADE_MODE_GOURAUD_FLAG: { 
         SET_BIT(obj->plist[poly].attr, POLY4DV1_ATTR_SHADE_MODE_GOURAUD); 
         Write_Error("\nShade mode = gouraud"); 
         } break; 
 
         case PLX_SHADE_MODE_PHONG_FLAG: { 
         SET_BIT(obj->plist[poly].attr, POLY4DV1_ATTR_SHADE_MODE_PHONG); 
         Write_Error("\nShade mode = phong"); 
         } break; 
 
         default: break; 
         } // end switch 
 
    // finally set the polygon to active 
    obj->plist[poly].state = POLY4DV1_STATE_ACTIVE;     
 
    } // end for poly 
 
// close the file 
fclose(fp); 
 
// return success 
return(1); 
 
} // end Load_OBJECT4DV1_PLG 
 
////////////////////////////////////////////////////////////// 
 
void Translate_OBJECT4DV1(OBJECT4DV1_PTR obj, VECTOR4D_PTR vt)  
{ 
// NOTE: Not matrix based 
// this function translates an object without matrices, 
// simply updates the world_pos 
VECTOR4D_Add(&obj->world_pos, vt, &obj->world_pos); 
 
} // end Translate_OBJECT4DV1 
 
///////////////////////////////////////////////////////////// 
 
void Scale_OBJECT4DV1(OBJECT4DV1_PTR obj, VECTOR4D_PTR vs) 
{ 
// NOTE: Not matrix based 
// this function scales and object without matrices  
// modifies the object's local vertex list  
// additionally the radii is updated for the object 
 
// for each vertex in the mesh scale the local coordinates by 
// vs on a componentwise basis, that is, sx, sy, sz 
for (int vertex=0; vertex < obj->num_vertices; vertex++) 
    { 
    obj->vlist_local[vertex].x*=vs->x; 
    obj->vlist_local[vertex].y*=vs->y; 
    obj->vlist_local[vertex].z*=vs->z; 
    // leave w unchanged, always equal to 1 
 
    } // end for vertex 
 
// now since the object is scaled we have to do something with  
// the radii calculation, but we don't know how the scaling 
// factors relate to the original major axis of the object, 
// therefore for scaling factors all ==1 we will simple multiply 
// which is correct, but for scaling factors not equal to 1, we 
// must take the largest scaling factor and use it to scale the 
// radii with since it's the worst case scenario of the new max and 
// average radii 
 
// find max scaling factor 
float scale = MAX(vs->x, vs->y); 
scale = MAX(scale, vs->z); 
 
// now scale 
obj->max_radius*=scale; 
obj->avg_radius*=scale; 
 
} // end Scale_OBJECT4DV1 
 
///////////////////////////////////////////////////////////// 
 
void Build_XYZ_Rotation_MATRIX4X4(float theta_x, // euler angles 
                                  float theta_y,  
                                  float theta_z, 
                                  MATRIX4X4_PTR mrot) // output  
{ 
// this helper function takes a set if euler angles and computes 
// a rotation matrix from them, usefull for object and camera 
// work, also  we will do a little testing in the function to determine 
// the rotations that need to be performed, since there's no 
// reason to perform extra matrix multiplies if the angles are 
// zero! 
 
MATRIX4X4 mx, my, mz, mtmp;       // working matrices 
float sin_theta=0, cos_theta=0;   // used to initialize matrices 
int rot_seq = 0;                  // 1 for x, 2 for y, 4 for z 
 
// step 0: fill in with identity matrix 
MAT_IDENTITY_4X4(mrot); 
 
// step 1: based on zero and non-zero rotation angles, determine 
// rotation sequence 
if (fabs(theta_x) > EPSILON_E5) // x 
   rot_seq = rot_seq | 1; 
 
if (fabs(theta_y) > EPSILON_E5) // y 
   rot_seq = rot_seq | 2; 
 
if (fabs(theta_z) > EPSILON_E5) // z 
   rot_seq = rot_seq | 4; 
 
// now case on sequence 
switch(rot_seq) 
      { 
      case 0: // no rotation 
      { 
      // what a waste! 
      return; 
      } break; 
 
      case 1: // x rotation 
      { 
      // compute the sine and cosine of the angle 
      cos_theta = Fast_Cos(theta_x); 
      sin_theta = Fast_Sin(theta_x); 
 
      // set the matrix up  
      Mat_Init_4X4(&mx, 1,    0,          0,         0, 
                        0,    cos_theta,  sin_theta, 0, 
                        0,   -sin_theta, cos_theta, 0, 
                        0,    0,          0,         1); 
 
      // that's it, copy to output matrix 
      MAT_COPY_4X4(&mx, mrot); 
      return; 
 
      } break; 
 
      case 2: // y rotation 
      { 
      // compute the sine and cosine of the angle 
      cos_theta = Fast_Cos(theta_y); 
      sin_theta = Fast_Sin(theta_y); 
 
      // set the matrix up  
      Mat_Init_4X4(&my,cos_theta, 0, -sin_theta, 0,   
                       0,         1,  0,         0, 
                       sin_theta, 0, cos_theta,  0, 
                       0,         0, 0,          1); 
 
 
      // that's it, copy to output matrix 
      MAT_COPY_4X4(&my, mrot); 
      return; 
 
      } break; 
 
      case 3: // xy rotation 
      { 
      // compute the sine and cosine of the angle for x 
      cos_theta = Fast_Cos(theta_x); 
      sin_theta = Fast_Sin(theta_x); 
 
      // set the matrix up  
      Mat_Init_4X4(&mx, 1,    0,          0,         0, 
                        0,    cos_theta,  sin_theta, 0, 
                        0,   -sin_theta, cos_theta, 0, 
                        0,    0,          0,         1); 
 
      // compute the sine and cosine of the angle for y 
      cos_theta = Fast_Cos(theta_y); 
      sin_theta = Fast_Sin(theta_y); 
 
      // set the matrix up  
      Mat_Init_4X4(&my,cos_theta, 0, -sin_theta, 0,   
                       0,         1,  0,         0, 
                       sin_theta, 0, cos_theta,  0, 
                       0,         0, 0,          1); 
 
      // concatenate matrices  
      Mat_Mul_4X4(&mx, &my, mrot); 
      return; 
 
      } break; 
 
      case 4: // z rotation 
      { 
      // compute the sine and cosine of the angle 
      cos_theta = Fast_Cos(theta_z); 
      sin_theta = Fast_Sin(theta_z); 
 
      // set the matrix up  
      Mat_Init_4X4(&mz, cos_theta, sin_theta, 0, 0,   
                       -sin_theta, cos_theta, 0, 0, 
                        0,         0,         1, 0, 
                        0,         0,         0, 1); 
 
 
      // that's it, copy to output matrix 
      MAT_COPY_4X4(&mz, mrot); 
      return; 
 
      } break; 
 
      case 5: // xz rotation 
      { 
      // compute the sine and cosine of the angle x 
      cos_theta = Fast_Cos(theta_x); 
      sin_theta = Fast_Sin(theta_x); 
 
      // set the matrix up  
      Mat_Init_4X4(&mx, 1,    0,          0,         0, 
                        0,    cos_theta,  sin_theta, 0, 
                        0,   -sin_theta, cos_theta, 0, 
                        0,    0,          0,         1); 
 
      // compute the sine and cosine of the angle z 
      cos_theta = Fast_Cos(theta_z); 
      sin_theta = Fast_Sin(theta_z); 
 
      // set the matrix up  
      Mat_Init_4X4(&mz, cos_theta, sin_theta, 0, 0,   
                       -sin_theta, cos_theta, 0, 0, 
                        0,         0,         1, 0, 
                        0,         0,         0, 1); 
 
      // concatenate matrices  
      Mat_Mul_4X4(&mx, &mz, mrot); 
      return; 
 
      } break; 
 
      case 6: // yz rotation 
      { 
      // compute the sine and cosine of the angle y 
      cos_theta = Fast_Cos(theta_y); 
      sin_theta = Fast_Sin(theta_y); 
 
      // set the matrix up  
      Mat_Init_4X4(&my,cos_theta, 0, -sin_theta, 0,   
                       0,         1,  0,         0, 
                       sin_theta, 0, cos_theta,  0, 
                       0,         0, 0,          1); 
 
      // compute the sine and cosine of the angle z 
      cos_theta = Fast_Cos(theta_z); 
      sin_theta = Fast_Sin(theta_z); 
 
      // set the matrix up  
      Mat_Init_4X4(&mz, cos_theta, sin_theta, 0, 0,   
                       -sin_theta, cos_theta, 0, 0, 
                        0,         0,         1, 0, 
                        0,         0,         0, 1); 
 
      // concatenate matrices  
      Mat_Mul_4X4(&my, &mz, mrot); 
      return; 
 
      } break; 
 
      case 7: // xyz rotation 
      { 
      // compute the sine and cosine of the angle x 
      cos_theta = Fast_Cos(theta_x); 
      sin_theta = Fast_Sin(theta_x); 
 
      // set the matrix up  
      Mat_Init_4X4(&mx, 1,    0,         0,         0, 
                        0,    cos_theta, sin_theta, 0, 
                        0,   -sin_theta, cos_theta, 0, 
                        0,    0,         0,         1); 
 
      // compute the sine and cosine of the angle y 
      cos_theta = Fast_Cos(theta_y); 
      sin_theta = Fast_Sin(theta_y); 
 
      // set the matrix up  
      Mat_Init_4X4(&my,cos_theta, 0, -sin_theta, 0,   
                       0,         1,  0,         0, 
                       sin_theta, 0,  cos_theta,  0, 
                       0,         0,  0,          1); 
 
      // compute the sine and cosine of the angle z 
      cos_theta = Fast_Cos(theta_z); 
      sin_theta = Fast_Sin(theta_z); 
 
      // set the matrix up  
      Mat_Init_4X4(&mz, cos_theta, sin_theta, 0, 0,   
                       -sin_theta, cos_theta, 0, 0, 
                        0,         0,         1, 0, 
                        0,         0,         0, 1); 
 
      // concatenate matrices, watch order! 
      Mat_Mul_4X4(&mx, &my, &mtmp); 
      Mat_Mul_4X4(&mtmp, &mz, mrot); 
 
      } break; 
 
      default: break; 
       
      } // end switch 
 
} // end Build_XYZ_Rotation_MATRIX4X4                                     
  
/////////////////////////////////////////////////////////// 
 
void Build_Model_To_World_MATRIX4X4(VECTOR4D_PTR vpos, MATRIX4X4_PTR m) 
{ 
// this function builds up a general local to world  
// transformation matrix that is really nothing more than a translation 
// of the origin by the amount specified in vpos 
 
Mat_Init_4X4(m, 1,       0,       0,       0,  
                0,       1,       0,       0,  
                0,       0,       1,       0, 
                vpos->x, vpos->y, vpos->z, 1 ); 
 
} // end Build_Model_To_World_MATRIX4X4 
 
////////////////////////////////////////////////////////// 
 
void Build_Camera_To_Perspective_MATRIX4X4(CAM4DV1_PTR cam, MATRIX4X4_PTR m) 
{ 
// this function builds up a camera to perspective transformation 
// matrix, in most cases the camera would have a 2x2 normalized 
// view plane with a 90 degree FOV, since the point of the having 
// this matrix must be to also have a perspective to screen (viewport) 
// matrix that scales the normalized coordinates, also the matrix 
// assumes that you are working in 4D homogenous coordinates and at  
// some point there will be a 4D->3D conversion, it might be immediately 
// after this transform is applied to vertices, or after the perspective 
// to screen transform 
 
Mat_Init_4X4(m, cam->view_dist, 0,                                0, 0,  
                0,              cam->view_dist*cam->aspect_ratio, 0, 0,  
                0,              0,                                1, 1, 
                0,              0,                                0, 0); 
 
} // end Build_Camera_To_Perspective_MATRIX4X4 
 
/////////////////////////////////////////////////////////// 
 
void Build_Perspective_To_Screen_4D_MATRIX4X4(CAM4DV1_PTR cam, MATRIX4X4_PTR m) 
{ 
// this function builds up a perspective to screen transformation 
// matrix, the function assumes that you want to perform the 
// transform in homogeneous coordinates and at raster time there will be  
// a 4D->3D homogenous conversion and of course only the x,y points 
// will be considered for the 2D rendering, thus you would use this 
// function's matrix is your perspective coordinates were still  
// in homgeneous form whene this matrix was applied, additionally 
// the point of this matrix to to scale and translate the perspective 
// coordinates to screen coordinates, thus the matrix is built up 
// assuming that the perspective coordinates are in normalized form for 
// a (2x2)/aspect_ratio viewplane, that is, x: -1 to 1, y:-1/aspect_ratio to 1/aspect_ratio 
 
float alpha = (0.5*cam->viewport_width-0.5); 
float beta  = (0.5*cam->viewport_height-0.5); 
 
Mat_Init_4X4(m, alpha,   0,     0,    0,  
                0,      -beta,  0,    0,  
                alpha,   beta,  1,    0, 
                0,       0,     0,    1); 
 
} // end Build_Perspective_To_Screen_4D_MATRIX4X4() 
 
////////////////////////////////////////////////////////// 
 
void Build_Perspective_To_Screen_MATRIX4X4(CAM4DV1_PTR cam, MATRIX4X4_PTR m) 
{ 
// this function builds up a perspective to screen transformation 
// matrix, the function assumes that you want to perform the 
// transform in 2D/3D coordinates, that is, you have already converted 
// the perspective coordinates from homogenous 4D to 3D before applying 
// this matrix, additionally 
// the point of this matrix to to scale and translate the perspective 
// coordinates to screen coordinates, thus the matrix is built up 
// assuming that the perspective coordinates are in normalized form for 
// a 2x2 viewplane, that is, x: -1 to 1, y:-1 to 1  
// the only difference between this function and the version that 
// assumes the coordinates are still in homogenous format is the 
// last column doesn't force w=z, in fact the z, and w results 
// are irrelevent since we assume that BEFORE this matrix is applied 
// all points are already converted from 4D->3D 
 
float alpha = (0.5*cam->viewport_width-0.5); 
float beta  = (0.5*cam->viewport_height-0.5); 
 
Mat_Init_4X4(m, alpha,   0,     0,    0,  
                0,      -beta,  0,    0,  
                alpha,   beta,  1,    0, 
                0,       0,     0,    1); 
 
} // end Build_Perspective_To_Screen_MATRIX4X4() 
 
/////////////////////////////////////////////////////////// 
 
void Build_Camera_To_Screen_MATRIX4X4(CAM4DV1_PTR cam, MATRIX4X4_PTR m) 
{ 
// this function creates a single matrix that performs the 
// entire camera->perspective->screen transform, the only 
// important thing is that the camera must be created with 
// a viewplane specified to be the size of the viewport 
// furthermore, after this transform is applied the the vertex 
// must be converted from 4D homogeneous to 3D, technically 
// the z is irrelevant since the data would be used for the 
// screen, but still the division by w is needed no matter 
// what 
 
float alpha = (0.5*cam->viewport_width-0.5); 
float beta  = (0.5*cam->viewport_height-0.5); 
 
Mat_Init_4X4(m, cam->view_dist,    0,               0,    0,  
                0,                -cam->view_dist,  0,    0,  
                alpha,             beta,            1,    1, 
                0,                 0,               0,    0); 
 
} // end Build_Camera_To_Screen_MATRIX4X4() 
 
/////////////////////////////////////////////////////////// 
 
void Transform_OBJECT4DV1(OBJECT4DV1_PTR obj, // object to transform 
                          MATRIX4X4_PTR mt,   // transformation matrix 
                          int coord_select,   // selects coords to transform 
                          int transform_basis) // flags if vector orientation 
                                               // should be transformed too 
{ 
// this function simply transforms all of the vertices in the local or trans 
// array by the sent matrix 
 
// what coordinates should be transformed? 
switch(coord_select) 
      { 
      case TRANSFORM_LOCAL_ONLY: 
      { 
      // transform each local/model vertex of the object mesh in place 
      for (int vertex=0; vertex < obj->num_vertices; vertex++) 
          { 
          POINT4D presult; // hold result of each transformation 
 
          // transform point 
          Mat_Mul_VECTOR4D_4X4(&obj->vlist_local[vertex], mt, &presult); 
 
          // store result back 
          VECTOR4D_COPY(&obj->vlist_local[vertex], &presult);  
          } // end for index 
      } break; 
  
      case TRANSFORM_TRANS_ONLY: 
      { 
      // transform each "transformed" vertex of the object mesh in place 
      // remember, the idea of the vlist_trans[] array is to accumulate 
      // transformations 
      for (int vertex=0; vertex < obj->num_vertices; vertex++) 
          { 
          POINT4D presult; // hold result of each transformation 
 
          // transform point 
          Mat_Mul_VECTOR4D_4X4(&obj->vlist_trans[vertex], mt, &presult); 
 
          // store result back 
          VECTOR4D_COPY(&obj->vlist_trans[vertex], &presult);  
          } // end for index 
 
      } break; 
 
      case TRANSFORM_LOCAL_TO_TRANS: 
      { 
      // transform each local/model vertex of the object mesh and store result 
      // in "transformed" vertex list 
      for (int vertex=0; vertex < obj->num_vertices; vertex++) 
          { 
          POINT4D presult; // hold result of each transformation 
 
          // transform point 
          Mat_Mul_VECTOR4D_4X4(&obj->vlist_local[vertex], mt, &obj->vlist_trans[vertex]); 
 
          } // end for index 
      } break; 
 
      default: break; 
 
} // end switch 
 
// finally, test if transform should be applied to orientation basis 
// hopefully this is a rotation, otherwise the basis will get corrupted 
if (transform_basis) 
   { 
   // now rotate orientation basis for object 
   VECTOR4D vresult; // use to rotate each orientation vector axis 
 
   // rotate ux of basis 
   Mat_Mul_VECTOR4D_4X4(&obj->ux, mt, &vresult); 
   VECTOR4D_COPY(&obj->ux, &vresult);  
 
   // rotate uy of basis 
   Mat_Mul_VECTOR4D_4X4(&obj->uy, mt, &vresult); 
   VECTOR4D_COPY(&obj->uy, &vresult);  
 
   // rotate uz of basis 
   Mat_Mul_VECTOR4D_4X4(&obj->uz, mt, &vresult); 
   VECTOR4D_COPY(&obj->uz, &vresult);  
   } // end if 
 
} // end Transform_OBJECT4DV1 
                                    
/////////////////////////////////////////////////////////// 
 
void Rotate_XYZ_OBJECT4DV1(OBJECT4DV1_PTR obj, // object to rotate 
                          float theta_x,      // euler angles 
                          float theta_y,  
                          float theta_z) 
{ 
// this function rotates and object parallel to the 
// XYZ axes in that order or a subset thereof, without 
// matrices (at least externally sent) 
// modifies the object's local vertex list  
// additionally it rotates the unit directional vectors 
// that track the objects orientation, also note that each 
// time this function is called it calls the rotation generation 
// function, this is wastefull if a number of object are being rotated 
// by the same matrix, therefore, if that's the case, then generate the 
// rotation matrix, store it, and call the general Transform_OBJECT4DV1() 
// with the matrix 
  
MATRIX4X4 mrot; // used to store generated rotation matrix 
 
// generate rotation matrix, no way to avoid rotation with a matrix 
// too much math to do manually! 
Build_XYZ_Rotation_MATRIX4X4(theta_x, theta_y, theta_z, &mrot); 
 
// now simply rotate each point of the mesh in local/model coordinates 
for (int vertex=0; vertex < obj->num_vertices; vertex++) 
    { 
    POINT4D presult; // hold result of each transformation 
 
    // transform point 
    Mat_Mul_VECTOR4D_4X4(&obj->vlist_local[vertex], &mrot, &presult); 
 
    // store result back 
    VECTOR4D_COPY(&obj->vlist_local[vertex], &presult);  
 
    } // end for index 
 
// now rotate orientation basis for object 
VECTOR4D vresult; // use to rotate each orientation vector axis 
 
// rotate ux of basis 
Mat_Mul_VECTOR4D_4X4(&obj->ux, &mrot, &vresult); 
VECTOR4D_COPY(&obj->ux, &vresult);  
 
// rotate uy of basis 
Mat_Mul_VECTOR4D_4X4(&obj->uy, &mrot, &vresult); 
VECTOR4D_COPY(&obj->uy, &vresult);  
 
// rotate uz of basis 
Mat_Mul_VECTOR4D_4X4(&obj->uz, &mrot, &vresult); 
VECTOR4D_COPY(&obj->uz, &vresult);  
 
} // end Rotate_XYZ_OBJECT4DV1 
 
//////////////////////////////////////////////////////////// 
 
void Model_To_World_OBJECT4DV1(OBJECT4DV1_PTR obj, int coord_select) 
{ 
// NOTE: Not matrix based 
// this function converts the local model coordinates of the 
// sent object into world coordinates, the results are stored 
// in the transformed vertex list (vlist_trans) within the object 
 
// interate thru vertex list and transform all the model/local  
// coords to world coords by translating the vertex list by 
// the amount world_pos and storing the results in vlist_trans[] 
 
if (coord_select == TRANSFORM_LOCAL_TO_TRANS) 
{ 
for (int vertex=0; vertex < obj->num_vertices; vertex++) 
    { 
    // translate vertex 
    VECTOR4D_Add(&obj->vlist_local[vertex], &obj->world_pos, &obj->vlist_trans[vertex]); 
    } // end for vertex 
} // end if local 
else 
{ // TRANSFORM_TRANS_ONLY 
for (int vertex=0; vertex < obj->num_vertices; vertex++) 
    { 
    // translate vertex 
    VECTOR4D_Add(&obj->vlist_trans[vertex], &obj->world_pos, &obj->vlist_trans[vertex]); 
    } // end for vertex 
} // end else trans 
 
} // end Model_To_World_OBJECT4DV1 
 
//////////////////////////////////////////////////////////// 
 
int Cull_OBJECT4DV1(OBJECT4DV1_PTR obj,  // object to cull 
                    CAM4DV1_PTR cam,     // camera to cull relative to 
                     int cull_flags)     // clipping planes to consider 
{ 
// NOTE: is matrix based 
// this function culls an entire object from the viewing 
// frustrum by using the sent camera information and object 
// the cull_flags determine what axes culling should take place 
// x, y, z or all which is controlled by ORing the flags 
// together 
// if the object is culled its state is modified thats all 
// this function assumes that both the camera and the object 
// are valid! 
 
// step 1: transform the center of the object's bounding 
// sphere into camera space 
 
POINT4D sphere_pos; // hold result of transforming center of bounding sphere 
 
// transform point 
Mat_Mul_VECTOR4D_4X4(&obj->world_pos, &cam->mcam, &sphere_pos); 
 
// step 2:  based on culling flags remove the object 
if (cull_flags & CULL_OBJECT_Z_PLANE) 
{ 
// cull only based on z clipping planes 
 
// test far plane 
if ( ((sphere_pos.z - obj->max_radius) > cam->far_clip_z) || 
     ((sphere_pos.z + obj->max_radius) < cam->near_clip_z) ) 
   {  
   SET_BIT(obj->state, OBJECT4DV1_STATE_CULLED); 
   return(1); 
   } // end if 
 
} // end if 
 
if (cull_flags & CULL_OBJECT_X_PLANE) 
{ 
// cull only based on x clipping planes 
// we could use plane equations, but simple similar triangles 
// is easier since this is really a 2D problem 
// if the view volume is 90 degrees the the problem is trivial 
// buts lets assume its not 
 
// test the the right and left clipping planes against the leftmost and rightmost 
// points of the bounding sphere 
float z_test = (0.5)*cam->viewplane_width*sphere_pos.z/cam->view_dist; 
 
if ( ((sphere_pos.x-obj->max_radius) > z_test)  || // right side 
     ((sphere_pos.x+obj->max_radius) < -z_test) )  // left side, note sign change 
   {  
   SET_BIT(obj->state, OBJECT4DV1_STATE_CULLED); 
   return(1); 
   } // end if 
} // end if 
 
if (cull_flags & CULL_OBJECT_Y_PLANE) 
{ 
// cull only based on y clipping planes 
// we could use plane equations, but simple similar triangles 
// is easier since this is really a 2D problem 
// if the view volume is 90 degrees the the problem is trivial 
// buts lets assume its not 
 
// test the the top and bottom clipping planes against the bottommost and topmost 
// points of the bounding sphere 
float z_test = (0.5)*cam->viewplane_height*sphere_pos.z/cam->view_dist; 
 
if ( ((sphere_pos.y-obj->max_radius) > z_test)  || // top side 
     ((sphere_pos.y+obj->max_radius) < -z_test) )  // bottom side, note sign change 
   {  
   SET_BIT(obj->state, OBJECT4DV1_STATE_CULLED); 
   return(1); 
   } // end if 
 
} // end if 
 
// return failure to cull 
return(0); 
 
} // end Cull_OBJECT4DV1 
 
//////////////////////////////////////////////////////////// 
 
void Remove_Backfaces_OBJECT4DV1(OBJECT4DV1_PTR obj, CAM4DV1_PTR cam) 
{ 
// NOTE: this is not a matrix based function 
// this function removes the backfaces from an object's 
// polygon mesh, the function does this based on the vertex 
// data in vlist_trans along with the camera position (only) 
// note that only the backface state is set in each polygon 
 
// test if the object is culled 
if (obj->state & OBJECT4DV1_STATE_CULLED) 
   return; 
 
// process each poly in mesh 
for (int poly=0; poly < obj->num_polys; poly++) 
    { 
    // acquire polygon 
    POLY4DV1_PTR curr_poly = &obj->plist[poly]; 
 
    // is this polygon valid? 
    // test this polygon if and only if it's not clipped, not culled, 
    // active, and visible and not 2 sided. Note we test for backface in the event that 
    // a previous call might have already determined this, so why work 
    // harder! 
    if (!(curr_poly->state & POLY4DV1_STATE_ACTIVE) || 
         (curr_poly->state & POLY4DV1_STATE_CLIPPED ) || 
         (curr_poly->attr  & POLY4DV1_ATTR_2SIDED)    || 
         (curr_poly->state & POLY4DV1_STATE_BACKFACE) ) 
       continue; // move onto next poly 
     
    // extract vertex indices into master list, rember the polygons are  
    // NOT self contained, but based on the vertex list stored in the object 
    // itself 
    int vindex_0 = curr_poly->vert[0]; 
    int vindex_1 = curr_poly->vert[1]; 
    int vindex_2 = curr_poly->vert[2]; 
     
    // we will use the transformed polygon vertex list since the backface removal 
    // only makes sense at the world coord stage further of the pipeline  
 
    // we need to compute the normal of this polygon face, and recall 
    // that the vertices are in cw order, u = p0->p1, v=p0->p2, n=uxv 
    VECTOR4D u, v, n; 
  
    // build u, v 
    VECTOR4D_Build(&obj->vlist_trans[ vindex_0 ], &obj->vlist_trans[ vindex_1 ], &u); 
    VECTOR4D_Build(&obj->vlist_trans[ vindex_0 ], &obj->vlist_trans[ vindex_2 ], &v); 
 
    // compute cross product 
    VECTOR4D_Cross(&u, &v, &n); 
 
    // now create eye vector to viewpoint 
    VECTOR4D view; 
    VECTOR4D_Build(&obj->vlist_trans[ vindex_0 ], &cam->pos, &view);  
 
    // and finally, compute the dot product 
    float dp = VECTOR4D_Dot(&n, &view); 
 
    // if the sign is > 0 then visible, 0 = scathing, < 0 invisible 
    if (dp <= 0.0 ) 
       SET_BIT(curr_poly->state, POLY4DV1_STATE_BACKFACE); 
 
    } // end for poly 
 
} // end Remove_Backfaces_OBJECT4DV1 
 
//////////////////////////////////////////////////////////// 
 
void Remove_Backfaces_RENDERLIST4DV1(RENDERLIST4DV1_PTR rend_list, CAM4DV1_PTR cam) 
{ 
// NOTE: this is not a matrix based function 
// this function removes the backfaces from polygon list 
// the function does this based on the polygon list data 
// tvlist along with the camera position (only) 
// note that only the backface state is set in each polygon 
 
for (int poly = 0; poly < rend_list->num_polys; poly++) 
    { 
    // acquire current polygon 
    POLYF4DV1_PTR curr_poly = rend_list->poly_ptrs[poly]; 
 
    // is this polygon valid? 
    // test this polygon if and only if it's not clipped, not culled, 
    // active, and visible and not 2 sided. Note we test for backface in the event that 
    // a previous call might have already determined this, so why work 
    // harder! 
    if ((curr_poly==NULL) || !(curr_poly->state & POLY4DV1_STATE_ACTIVE) || 
        (curr_poly->state & POLY4DV1_STATE_CLIPPED ) ||  
        (curr_poly->attr  & POLY4DV1_ATTR_2SIDED)    || 
        (curr_poly->state & POLY4DV1_STATE_BACKFACE) ) 
        continue; // move onto next poly 
     
        // we need to compute the normal of this polygon face, and recall 
        // that the vertices are in cw order, u = p0->p1, v=p0->p2, n=uxv 
        VECTOR4D u, v, n; 
  
        // build u, v 
        VECTOR4D_Build(&curr_poly->tvlist[0], &curr_poly->tvlist[1], &u); 
        VECTOR4D_Build(&curr_poly->tvlist[0], &curr_poly->tvlist[2], &v); 
 
        // compute cross product 
        VECTOR4D_Cross(&u, &v, &n); 
 
        // now create eye vector to viewpoint 
        VECTOR4D view; 
        VECTOR4D_Build(&curr_poly->tvlist[0], &cam->pos, &view);  
 
        // and finally, compute the dot product 
        float dp = VECTOR4D_Dot(&n, &view); 
 
        // if the sign is > 0 then visible, 0 = scathing, < 0 invisible 
        if (dp <= 0.0 ) 
            SET_BIT(curr_poly->state, POLY4DV1_STATE_BACKFACE); 
 
         } // end for poly 
 
} // end Remove_Backfaces_RENDERLIST4DV1 
 
//////////////////////////////////////////////////////////// 
 
void World_To_Camera_OBJECT4DV1(OBJECT4DV1_PTR obj, CAM4DV1_PTR cam) 
{ 
// NOTE: this is a matrix based function 
// this function transforms the world coordinates of an object 
// into camera coordinates, based on the sent camera matrix 
// but it totally disregards the polygons themselves, 
// it only works on the vertices in the vlist_trans[] list 
// this is one way to do it, you might instead transform 
// the global list of polygons in the render list since you  
// are guaranteed that those polys represent geometry that  
// has passed thru backfaces culling (if any) 
 
// transform each vertex in the object to camera coordinates 
// assumes the object has already been transformed to world 
// coordinates and the result is in vlist_trans[] 
for (int vertex = 0; vertex < obj->num_vertices; vertex++) 
    { 
    // transform the vertex by the mcam matrix within the camera 
    // it better be valid! 
    POINT4D presult; // hold result of each transformation 
 
    // transform point 
    Mat_Mul_VECTOR4D_4X4(&obj->vlist_trans[vertex], &cam->mcam, &presult); 
 
    // store result back 
    VECTOR4D_COPY(&obj->vlist_trans[vertex], &presult);  
    } // end for vertex 
 
} // end World_To_Camera_OBJECT4DV1 
 
//////////////////////////////////////////////////////////// 
 
void Camera_To_Perspective_OBJECT4DV1(OBJECT4DV1_PTR obj, CAM4DV1_PTR cam) 
{ 
// NOTE: this is not a matrix based function 
// this function transforms the camera coordinates of an object 
// into perspective coordinates, based on the  
// sent camera object, but it totally disregards the polygons themselves, 
// it only works on the vertices in the vlist_trans[] list 
// this is one way to do it, you might instead transform 
// the global list of polygons in the render list since you  
// are guaranteed that those polys represent geometry that  
// has passed thru backfaces culling (if any) 
// finally this function is really for experimental reasons only 
// you would probably never let an object stay intact this far down 
// the pipeline, since it's probably that there's only a single polygon 
// that is visible! But this function has to transform the whole mesh! 
 
// transform each vertex in the object to perspective coordinates 
// assumes the object has already been transformed to camera 
// coordinates and the result is in vlist_trans[] 
for (int vertex = 0; vertex < obj->num_vertices; vertex++) 
    { 
    float z = obj->vlist_trans[vertex].z; 
 
    // transform the vertex by the view parameters in the camera 
    obj->vlist_trans[vertex].x = cam->view_dist*obj->vlist_trans[vertex].x/z; 
    obj->vlist_trans[vertex].y = cam->view_dist*obj->vlist_trans[vertex].y*cam->aspect_ratio/z; 
    // z = z, so no change 
 
    // not that we are NOT dividing by the homogenous w coordinate since 
    // we are not using a matrix operation for this version of the function  
   
    } // end for vertex 
 
} // end Camera_To_Perspective_OBJECT4DV1 
 
////////////////////////////////////////////////////////////// 
 
void Camera_To_Perspective_Screen_OBJECT4DV1(OBJECT4DV1_PTR obj, CAM4DV1_PTR cam) 
{ 
// NOTE: this is not a matrix based function 
// this function transforms the camera coordinates of an object 
// into Screen scaled perspective coordinates, based on the  
// sent camera object, that is, view_dist_h and view_dist_v  
// should be set to cause the desired (width X height) 
// projection of the vertices, but the function totally  
// disregards the polygons themselves, 
// it only works on the vertices in the vlist_trans[] list 
// this is one way to do it, you might instead transform 
// the global list of polygons in the render list since you  
// are guaranteed that those polys represent geometry that  
// has passed thru backfaces culling (if any) 
// finally this function is really for experimental reasons only 
// you would probably never let an object stay intact this far down 
// the pipeline, since it's probably that there's only a single polygon 
// that is visible! But this function has to transform the whole mesh! 
// finally, the function also inverts the y axis, so the coordinates 
// generated from this function ARE screen coordinates and ready for 
// rendering 
 
float alpha = (0.5*cam->viewport_width-0.5); 
float beta  = (0.5*cam->viewport_height-0.5); 
 
// transform each vertex in the object to perspective screen coordinates 
// assumes the object has already been transformed to camera 
// coordinates and the result is in vlist_trans[] 
for (int vertex = 0; vertex < obj->num_vertices; vertex++) 
    { 
    float z = obj->vlist_trans[vertex].z; 
 
    // transform the vertex by the view parameters in the camera 
    obj->vlist_trans[vertex].x = cam->view_dist*obj->vlist_trans[vertex].x/z; 
    obj->vlist_trans[vertex].y = cam->view_dist*obj->vlist_trans[vertex].y/z; 
    // z = z, so no change 
 
    // not that we are NOT dividing by the homogenous w coordinate since 
    // we are not using a matrix operation for this version of the function  
 
    // now the coordinates are in the range x:(-viewport_width/2 to viewport_width/2) 
    // and y:(-viewport_height/2 to viewport_height/2), thus we need a translation and 
    // since the y-axis is inverted, we need to invert y to complete the screen  
    // transform: 
    obj->vlist_trans[vertex].x =  obj->vlist_trans[vertex].x + alpha; 
    obj->vlist_trans[vertex].y = -obj->vlist_trans[vertex].y + beta; 
 
    } // end for vertex 
 
} // end Camera_To_Perspective_Screen_OBJECT4DV1 
 
////////////////////////////////////////////////////////////// 
 
void Perspective_To_Screen_OBJECT4DV1(OBJECT4DV1_PTR obj, CAM4DV1_PTR cam) 
{ 
// NOTE: this is not a matrix based function 
// this function transforms the perspective coordinates of an object 
// into screen coordinates, based on the sent viewport info 
// but it totally disregards the polygons themselves, 
// it only works on the vertices in the vlist_trans[] list 
// this is one way to do it, you might instead transform 
// the global list of polygons in the render list since you  
// are guaranteed that those polys represent geometry that  
// has passed thru backfaces culling (if any) 
// finally this function is really for experimental reasons only 
// you would probably never let an object stay intact this far down 
// the pipeline, since it's probably that there's only a single polygon 
// that is visible! But this function has to transform the whole mesh! 
// this function would be called after a perspective 
// projection was performed on the object 
 
// transform each vertex in the object to screen coordinates 
// assumes the object has already been transformed to perspective 
// coordinates and the result is in vlist_trans[] 
 
float alpha = (0.5*cam->viewport_width-0.5); 
float beta  = (0.5*cam->viewport_height-0.5); 
 
for (int vertex = 0; vertex < obj->num_vertices; vertex++) 
    { 
    // assumes the vertex is in perspective normalized coords from -1 to 1 
    // on each axis, simple scale them to viewport and invert y axis and project 
    // to screen 
 
    // transform the vertex by the view parameters in the camera 
    obj->vlist_trans[vertex].x = alpha + alpha*obj->vlist_trans[vertex].x; 
    obj->vlist_trans[vertex].y = beta  - beta *obj->vlist_trans[vertex].y; 
   
    } // end for vertex 
 
} // end Perspective_To_Screen_OBJECT4DV1 
 
///////////////////////////////////////////////////////////// 
 
void Convert_From_Homogeneous4D_OBJECT4DV1(OBJECT4DV1_PTR obj) 
{ 
// this function convertes all vertices in the transformed 
// vertex list from 4D homogeneous coordinates to normal 3D coordinates 
// by dividing each x,y,z component by w 
 
for (int vertex = 0; vertex < obj->num_vertices; vertex++) 
    { 
    // convert to non-homogenous coords 
    VECTOR4D_DIV_BY_W(&obj->vlist_trans[vertex]);      
    } // end for vertex 
 
} // end Convert_From_Homogeneous4D_OBJECT4DV1 
 
///////////////////////////////////////////////////////////// 
 
void Transform_RENDERLIST4DV1(RENDERLIST4DV1_PTR rend_list, // render list to transform 
                              MATRIX4X4_PTR mt,   // transformation matrix 
                              int coord_select)   // selects coords to transform 
{ 
// this function simply transforms all of the polygons vertices in the local or trans 
// array of the render list by the sent matrix 
 
// what coordinates should be transformed? 
switch(coord_select) 
      { 
      case TRANSFORM_LOCAL_ONLY: 
      { 
      for (int poly = 0; poly < rend_list->num_polys; poly++) 
          { 
          // acquire current polygon 
          POLYF4DV1_PTR curr_poly = rend_list->poly_ptrs[poly]; 
 
          // is this polygon valid? 
          // transform this polygon if and only if it's not clipped, not culled, 
          // active, and visible, note however the concept of "backface" is  
          // irrelevant in a wire frame engine though 
          if ((curr_poly==NULL) || !(curr_poly->state & POLY4DV1_STATE_ACTIVE) || 
              (curr_poly->state & POLY4DV1_STATE_CLIPPED ) || 
              (curr_poly->state & POLY4DV1_STATE_BACKFACE) ) 
             continue; // move onto next poly 
 
          // all good, let's transform  
          for (int vertex = 0; vertex < 3; vertex++) 
              { 
              // transform the vertex by mt 
              POINT4D presult; // hold result of each transformation 
 
              // transform point 
              Mat_Mul_VECTOR4D_4X4(&curr_poly->vlist[vertex], mt, &presult); 
 
              // store result back 
              VECTOR4D_COPY(&curr_poly->vlist[vertex], &presult);  
              } // end for vertex 
  
          } // end for poly 
 
      } break; 
  
      case TRANSFORM_TRANS_ONLY: 
      { 
      // transform each "transformed" vertex of the render list 
      // remember, the idea of the tvlist[] array is to accumulate 
      // transformations 
      for (int poly = 0; poly < rend_list->num_polys; poly++) 
          { 
          // acquire current polygon 
          POLYF4DV1_PTR curr_poly = rend_list->poly_ptrs[poly]; 
 
          // is this polygon valid? 
          // transform this polygon if and only if it's not clipped, not culled, 
          // active, and visible, note however the concept of "backface" is  
          // irrelevant in a wire frame engine though 
          if ((curr_poly==NULL) || !(curr_poly->state & POLY4DV1_STATE_ACTIVE) || 
              (curr_poly->state & POLY4DV1_STATE_CLIPPED ) || 
              (curr_poly->state & POLY4DV1_STATE_BACKFACE) ) 
             continue; // move onto next poly 
 
          // all good, let's transform  
          for (int vertex = 0; vertex < 3; vertex++) 
              { 
              // transform the vertex by mt 
              POINT4D presult; // hold result of each transformation 
 
              // transform point 
              Mat_Mul_VECTOR4D_4X4(&curr_poly->tvlist[vertex], mt, &presult); 
 
              // store result back 
              VECTOR4D_COPY(&curr_poly->tvlist[vertex], &presult);  
              } // end for vertex 
  
          } // end for poly 
 
      } break; 
 
      case TRANSFORM_LOCAL_TO_TRANS: 
      { 
      // transform each local/model vertex of the render list and store result 
      // in "transformed" vertex list 
      for (int poly = 0; poly < rend_list->num_polys; poly++) 
          { 
          // acquire current polygon 
          POLYF4DV1_PTR curr_poly = rend_list->poly_ptrs[poly]; 
 
          // is this polygon valid? 
          // transform this polygon if and only if it's not clipped, not culled, 
          // active, and visible, note however the concept of "backface" is  
          // irrelevant in a wire frame engine though 
          if ((curr_poly==NULL) || !(curr_poly->state & POLY4DV1_STATE_ACTIVE) || 
              (curr_poly->state & POLY4DV1_STATE_CLIPPED ) || 
              (curr_poly->state & POLY4DV1_STATE_BACKFACE) ) 
             continue; // move onto next poly 
 
          // all good, let's transform  
          for (int vertex = 0; vertex < 3; vertex++) 
              { 
              // transform the vertex by mt 
              Mat_Mul_VECTOR4D_4X4(&curr_poly->vlist[vertex], mt, &curr_poly->tvlist[vertex]); 
              } // end for vertex 
  
          } // end for poly 
 
      } break; 
 
      default: break; 
 
} // end switch 
 
} // end Transform_RENDERLIST4DV1 
 
///////////////////////////////////////////////////////////////////////// 
 
void Model_To_World_RENDERLIST4DV1(RENDERLIST4DV1_PTR rend_list,  
                                  POINT4D_PTR world_pos,  
                                  int coord_select) 
{ 
// NOTE: Not matrix based 
// this function converts the local model coordinates of the 
// sent render list into world coordinates, the results are stored 
// in the transformed vertex list (tvlist) within the renderlist 
 
// interate thru vertex list and transform all the model/local  
// coords to world coords by translating the vertex list by 
// the amount world_pos and storing the results in tvlist[] 
// is this polygon valid? 
 
if (coord_select == TRANSFORM_LOCAL_TO_TRANS) 
   { 
   for (int poly = 0; poly < rend_list->num_polys; poly++) 
       { 
       // acquire current polygon 
       POLYF4DV1_PTR curr_poly = rend_list->poly_ptrs[poly]; 
 
       // transform this polygon if and only if it's not clipped, not culled, 
       // active, and visible, note however the concept of "backface" is  
       // irrelevant in a wire frame engine though 
       if ((curr_poly==NULL) || !(curr_poly->state & POLY4DV1_STATE_ACTIVE) || 
           (curr_poly->state & POLY4DV1_STATE_CLIPPED ) || 
           (curr_poly->state & POLY4DV1_STATE_BACKFACE) ) 
          continue; // move onto next poly 
 
   // all good, let's transform  
   for (int vertex = 0; vertex < 3; vertex++) 
       { 
       // translate vertex 
       VECTOR4D_Add(&curr_poly->vlist[vertex], world_pos, &curr_poly->tvlist[vertex]); 
       } // end for vertex 
 
   } // end for poly 
} // end if local 
else // TRANSFORM_TRANS_ONLY 
{ 
for (int poly = 0; poly < rend_list->num_polys; poly++) 
    { 
    // acquire current polygon 
    POLYF4DV1_PTR curr_poly = rend_list->poly_ptrs[poly]; 
 
   // transform this polygon if and only if it's not clipped, not culled, 
   // active, and visible, note however the concept of "backface" is  
   // irrelevant in a wire frame engine though 
   if ((curr_poly==NULL) || !(curr_poly->state & POLY4DV1_STATE_ACTIVE) || 
       (curr_poly->state & POLY4DV1_STATE_CLIPPED ) || 
       (curr_poly->state & POLY4DV1_STATE_BACKFACE) ) 
        continue; // move onto next poly 
 
   for (int vertex = 0; vertex < 3; vertex++) 
       { 
       // translate vertex 
       VECTOR4D_Add(&curr_poly->tvlist[vertex], world_pos, &curr_poly->tvlist[vertex]); 
       } // end for vertex 
 
    } // end for poly 
 
} // end else 
 
} // end Model_To_World_RENDERLIST4DV1 
 
//////////////////////////////////////////////////////////// 
 
void Convert_From_Homogeneous4D_RENDERLIST4DV1(RENDERLIST4DV1_PTR rend_list) 
{ 
// this function convertes all valid polygons vertices in the transformed 
// vertex list from 4D homogeneous coordinates to normal 3D coordinates 
// by dividing each x,y,z component by w 
 
for (int poly = 0; poly < rend_list->num_polys; poly++) 
{ 
// acquire current polygon 
POLYF4DV1_PTR curr_poly = rend_list->poly_ptrs[poly]; 
 
// is this polygon valid? 
// transform this polygon if and only if it's not clipped, not culled, 
// active, and visible, note however the concept of "backface" is  
// irrelevant in a wire frame engine though 
if ((curr_poly==NULL) || !(curr_poly->state & POLY4DV1_STATE_ACTIVE) || 
     (curr_poly->state & POLY4DV1_STATE_CLIPPED ) || 
     (curr_poly->state & POLY4DV1_STATE_BACKFACE) ) 
       continue; // move onto next poly 
 
// all good, let's transform  
for (int vertex = 0; vertex < 3; vertex++) 
    { 
    // convert to non-homogenous coords 
    VECTOR4D_DIV_BY_W(&curr_poly->tvlist[vertex]);  
    } // end for vertex 
 
} // end for poly 
 
} // end Convert_From_Homogeneous4D_RENDERLIST4DV1 
 
///////////////////////////////////////////////////////////////////////// 
 
void World_To_Camera_RENDERLIST4DV1(RENDERLIST4DV1_PTR rend_list,  
                                 CAM4DV1_PTR cam) 
{ 
// NOTE: this is a matrix based function 
// this function transforms each polygon in the global render list 
// to camera coordinates based on the sent camera transform matrix 
// you would use this function instead of the object based function 
// if you decided earlier in the pipeline to turn each object into  
// a list of polygons and then add them to the global render list 
// the conversion of an object into polygons probably would have 
// happened after object culling, local transforms, local to world 
// and backface culling, so the minimum number of polygons from 
// each object are in the list, note that the function assumes 
// that at LEAST the local to world transform has been called 
// and the polygon data is in the transformed list tvlist of 
// the POLYF4DV1 object 
 
// transform each polygon in the render list into camera coordinates 
// assumes the render list has already been transformed to world 
// coordinates and the result is in tvlist[] of each polygon object 
 
for (int poly = 0; poly < rend_list->num_polys; poly++) 
{ 
// acquire current polygon 
POLYF4DV1_PTR curr_poly = rend_list->poly_ptrs[poly]; 
 
// is this polygon valid? 
// transform this polygon if and only if it's not clipped, not culled, 
// active, and visible, note however the concept of "backface" is  
// irrelevant in a wire frame engine though 
if ((curr_poly==NULL) || !(curr_poly->state & POLY4DV1_STATE_ACTIVE) || 
     (curr_poly->state & POLY4DV1_STATE_CLIPPED ) || 
     (curr_poly->state & POLY4DV1_STATE_BACKFACE) ) 
       continue; // move onto next poly 
 
// all good, let's transform  
for (int vertex = 0; vertex < 3; vertex++) 
    { 
    // transform the vertex by the mcam matrix within the camera 
    // it better be valid! 
    POINT4D presult; // hold result of each transformation 
 
    // transform point 
    Mat_Mul_VECTOR4D_4X4(&curr_poly->tvlist[vertex], &cam->mcam, &presult); 
 
    // store result back 
    VECTOR4D_COPY(&curr_poly->tvlist[vertex], &presult);  
    } // end for vertex 
 
} // end for poly 
 
} // end World_To_Camera_RENDERLIST4DV1 
 
/////////////////////////////////////////////////////////////// 
 
void Camera_To_Perspective_RENDERLIST4DV1(RENDERLIST4DV1_PTR rend_list,  
                                               CAM4DV1_PTR cam) 
{ 
// NOTE: this is not a matrix based function 
// this function transforms each polygon in the global render list 
// into perspective coordinates, based on the  
// sent camera object,  
// you would use this function instead of the object based function 
// if you decided earlier in the pipeline to turn each object into  
// a list of polygons and then add them to the global render list 
 
// transform each polygon in the render list into camera coordinates 
// assumes the render list has already been transformed to world 
// coordinates and the result is in tvlist[] of each polygon object 
 
for (int poly = 0; poly < rend_list->num_polys; poly++) 
{ 
// acquire current polygon 
POLYF4DV1_PTR curr_poly = rend_list->poly_ptrs[poly]; 
 
// is this polygon valid? 
// transform this polygon if and only if it's not clipped, not culled, 
// active, and visible, note however the concept of "backface" is  
// irrelevant in a wire frame engine though 
if ((curr_poly==NULL) || !(curr_poly->state & POLY4DV1_STATE_ACTIVE) || 
     (curr_poly->state & POLY4DV1_STATE_CLIPPED ) || 
     (curr_poly->state & POLY4DV1_STATE_BACKFACE) ) 
       continue; // move onto next poly 
 
// all good, let's transform  
for (int vertex = 0; vertex < 3; vertex++) 
    { 
    float z = curr_poly->tvlist[vertex].z; 
 
    // transform the vertex by the view parameters in the camera 
    curr_poly->tvlist[vertex].x = cam->view_dist*curr_poly->tvlist[vertex].x/z; 
    curr_poly->tvlist[vertex].y = cam->view_dist*curr_poly->tvlist[vertex].y*cam->aspect_ratio/z; 
    // z = z, so no change 
 
    // not that we are NOT dividing by the homogenous w coordinate since 
    // we are not using a matrix operation for this version of the function  
 
    } // end for vertex 
 
} // end for poly 
 
} // end Camera_To_Perspective_RENDERLIST4DV1 
 
//////////////////////////////////////////////////////////////// 
 
void Camera_To_Perspective_Screen_RENDERLIST4DV1(RENDERLIST4DV1_PTR rend_list,  
                                                 CAM4DV1_PTR cam) 
{ 
// NOTE: this is not a matrix based function 
// this function transforms the camera coordinates of an object 
// into Screen scaled perspective coordinates, based on the  
// sent camera object, that is, view_dist_h and view_dist_v  
// should be set to cause the desired (viewport_width X viewport_height) 
// it only works on the vertices in the tvlist[] list 
// finally, the function also inverts the y axis, so the coordinates 
// generated from this function ARE screen coordinates and ready for 
// rendering 
 
// transform each polygon in the render list to perspective screen  
// coordinates assumes the render list has already been transformed  
// to camera coordinates and the result is in tvlist[] 
for (int poly = 0; poly < rend_list->num_polys; poly++) 
{ 
// acquire current polygon 
POLYF4DV1_PTR curr_poly = rend_list->poly_ptrs[poly]; 
 
// is this polygon valid? 
// transform this polygon if and only if it's not clipped, not culled, 
// active, and visible, note however the concept of "backface" is  
// irrelevant in a wire frame engine though 
if ((curr_poly==NULL) || !(curr_poly->state & POLY4DV1_STATE_ACTIVE) || 
     (curr_poly->state & POLY4DV1_STATE_CLIPPED ) || 
     (curr_poly->state & POLY4DV1_STATE_BACKFACE) ) 
       continue; // move onto next poly 
 
float alpha = (0.5*cam->viewport_width-0.5); 
float beta  = (0.5*cam->viewport_height-0.5); 
 
// all good, let's transform  
for (int vertex = 0; vertex < 3; vertex++) 
    { 
    float z = curr_poly->tvlist[vertex].z; 
 
    // transform the vertex by the view parameters in the camera 
    curr_poly->tvlist[vertex].x = cam->view_dist*curr_poly->tvlist[vertex].x/z; 
    curr_poly->tvlist[vertex].y = cam->view_dist*curr_poly->tvlist[vertex].y/z; 
    // z = z, so no change 
 
    // not that we are NOT dividing by the homogenous w coordinate since 
    // we are not using a matrix operation for this version of the function  
 
    // now the coordinates are in the range x:(-viewport_width/2 to viewport_width/2) 
    // and y:(-viewport_height/2 to viewport_height/2), thus we need a translation and 
    // since the y-axis is inverted, we need to invert y to complete the screen  
    // transform: 
    curr_poly->tvlist[vertex].x =  curr_poly->tvlist[vertex].x + alpha;  
    curr_poly->tvlist[vertex].y = -curr_poly->tvlist[vertex].y + beta; 
 
    } // end for vertex 
 
} // end for poly 
 
} // end Camera_To_Perspective_Screen_RENDERLIST4DV1 
 
////////////////////////////////////////////////////////////// 
 
void Perspective_To_Screen_RENDERLIST4DV1(RENDERLIST4DV1_PTR rend_list,  
                                               CAM4DV1_PTR cam) 
{ 
// NOTE: this is not a matrix based function 
// this function transforms the perspective coordinates of the render 
// list into screen coordinates, based on the sent viewport in the camera 
// assuming that the viewplane coordinates were normalized 
// you would use this function instead of the object based function 
// if you decided earlier in the pipeline to turn each object into  
// a list of polygons and then add them to the global render list 
// you would only call this function if you previously performed 
// a normalized perspective transform 
 
// transform each polygon in the render list from perspective to screen  
// coordinates assumes the render list has already been transformed  
// to normalized perspective coordinates and the result is in tvlist[] 
for (int poly = 0; poly < rend_list->num_polys; poly++) 
{ 
// acquire current polygon 
POLYF4DV1_PTR curr_poly = rend_list->poly_ptrs[poly]; 
 
// is this polygon valid? 
// transform this polygon if and only if it's not clipped, not culled, 
// active, and visible, note however the concept of "backface" is  
// irrelevant in a wire frame engine though 
if ((curr_poly==NULL) || !(curr_poly->state & POLY4DV1_STATE_ACTIVE) || 
     (curr_poly->state & POLY4DV1_STATE_CLIPPED ) || 
     (curr_poly->state & POLY4DV1_STATE_BACKFACE) ) 
       continue; // move onto next poly 
 
float alpha = (0.5*cam->viewport_width-0.5); 
float beta  = (0.5*cam->viewport_height-0.5); 
 
// all good, let's transform  
for (int vertex = 0; vertex < 3; vertex++) 
    { 
    // the vertex is in perspective normalized coords from -1 to 1 
    // on each axis, simple scale them and invert y axis and project 
    // to screen 
 
    // transform the vertex by the view parameters in the camera 
    curr_poly->tvlist[vertex].x = alpha + alpha*curr_poly->tvlist[vertex].x; 
    curr_poly->tvlist[vertex].y = beta  - beta *curr_poly->tvlist[vertex].y; 
    } // end for vertex 
 
} // end for poly 
 
} // end Perspective_To_Screen_RENDERLIST4DV1 
 
/////////////////////////////////////////////////////////////// 
 
void Reset_RENDERLIST4DV1(RENDERLIST4DV1_PTR rend_list) 
{ 
// this function intializes and resets the sent render list and 
// redies it for polygons/faces to be inserted into it 
// note that the render list in this version is composed 
// of an array FACE4DV1 pointer objects, you call this 
// function each frame 
 
// since we are tracking the number of polys in the 
// list via num_polys we can set it to 0 
// but later we will want a more robust scheme if 
// we generalize the linked list more and disconnect 
// it from the polygon pointer list 
rend_list->num_polys = 0; // that was hard! 
 
}  // Reset_RENDERLIST4DV1 
 
//////////////////////////////////////////////////////////////// 
 
void Reset_OBJECT4DV1(OBJECT4DV1_PTR obj) 
{ 
// this function resets the sent object and redies it for  
// transformations, basically just resets the culled, clipped and 
// backface flags, but here's where you would add stuff 
// to ready any object for the pipeline 
// the object is valid, let's rip it apart polygon by polygon 
 
// reset object's culled flag 
RESET_BIT(obj->state, OBJECT4DV1_STATE_CULLED); 
 
// now the clipped and backface flags for the polygons  
for (int poly = 0; poly < obj->num_polys; poly++) 
    { 
    // acquire polygon 
    POLY4DV1_PTR curr_poly = &obj->plist[poly]; 
     
    // first is this polygon even visible? 
    if (!(curr_poly->state & POLY4DV1_STATE_ACTIVE)) 
       continue; // move onto next poly 
 
    // reset clipped and backface flags 
    RESET_BIT(curr_poly->state, POLY4DV1_STATE_CLIPPED); 
    RESET_BIT(curr_poly->state, POLY4DV1_STATE_BACKFACE); 
 
    } // end for poly 
 
} // end Reset_OBJECT4DV1 
 
/////////////////////////////////////////////////////////////// 
 
int Insert_POLY4DV1_RENDERLIST4DV1(RENDERLIST4DV1_PTR rend_list,  
                                   POLY4DV1_PTR poly) 
{ 
// converts the sent POLY4DV1 into a FACE4DV1 and inserts it 
// into the render list 
 
// step 0: are we full? 
if (rend_list->num_polys >= RENDERLIST4DV1_MAX_POLYS) 
   return(0); 
 
// step 1: copy polygon into next opening in polygon render list 
 
// point pointer to polygon structure 
rend_list->poly_ptrs[rend_list->num_polys] = &rend_list->poly_data[rend_list->num_polys]; 
 
// copy fields 
rend_list->poly_data[rend_list->num_polys].state = poly->state; 
rend_list->poly_data[rend_list->num_polys].attr  = poly->attr; 
rend_list->poly_data[rend_list->num_polys].color = poly->color; 
 
// now copy vertices, be careful! later put a loop, but for now 
// know there are 3 vertices always! 
VECTOR4D_COPY(&rend_list->poly_data[rend_list->num_polys].tvlist[0], 
              &poly->vlist[poly->vert[0]]); 
 
VECTOR4D_COPY(&rend_list->poly_data[rend_list->num_polys].tvlist[1], 
              &poly->vlist[poly->vert[1]]); 
 
VECTOR4D_COPY(&rend_list->poly_data[rend_list->num_polys].tvlist[2], 
              &poly->vlist[poly->vert[2]]); 
 
// and copy into local vertices too 
VECTOR4D_COPY(&rend_list->poly_data[rend_list->num_polys].vlist[0], 
              &poly->vlist[poly->vert[0]]); 
 
VECTOR4D_COPY(&rend_list->poly_data[rend_list->num_polys].vlist[1], 
              &poly->vlist[poly->vert[1]]); 
 
VECTOR4D_COPY(&rend_list->poly_data[rend_list->num_polys].vlist[2], 
              &poly->vlist[poly->vert[2]]); 
 
// now the polygon is loaded into the next free array position, but 
// we need to fix up the links 
 
// test if this is the first entry 
if (rend_list->num_polys == 0) 
   { 
   // set pointers to null, could loop them around though to self 
   rend_list->poly_data[0].next = NULL; 
   rend_list->poly_data[0].prev = NULL; 
   } // end if 
else 
   { 
   // first set this node to point to previous node and next node (null) 
   rend_list->poly_data[rend_list->num_polys].next = NULL; 
   rend_list->poly_data[rend_list->num_polys].prev =  
         &rend_list->poly_data[rend_list->num_polys-1]; 
 
   // now set previous node to point to this node 
   rend_list->poly_data[rend_list->num_polys-1].next =  
          &rend_list->poly_data[rend_list->num_polys]; 
   } // end else 
 
// increment number of polys in list 
rend_list->num_polys++; 
 
// return successful insertion 
return(1); 
 
} // end Insert_POLY4DV1_RENDERLIST4DV1 
 
////////////////////////////////////////////////////////////// 
 
int Insert_POLYF4DV1_RENDERLIST4DV1(RENDERLIST4DV1_PTR rend_list,  
                                     POLYF4DV1_PTR poly) 
{ 
// inserts the sent polyface POLYF4DV1 into the render list 
 
// step 0: are we full? 
if (rend_list->num_polys >= RENDERLIST4DV1_MAX_POLYS) 
   return(0); 
 
// step 1: copy polygon into next opening in polygon render list 
 
// point pointer to polygon structure 
rend_list->poly_ptrs[rend_list->num_polys] = &rend_list->poly_data[rend_list->num_polys]; 
 
// copy face right into array, thats it 
memcpy((void *)&rend_list->poly_data[rend_list->num_polys],(void *)poly, sizeof(POLYF4DV1)); 
 
// now the polygon is loaded into the next free array position, but 
// we need to fix up the links 
// test if this is the first entry 
if (rend_list->num_polys == 0) 
   { 
   // set pointers to null, could loop them around though to self 
   rend_list->poly_data[0].next = NULL; 
   rend_list->poly_data[0].prev = NULL; 
   } // end if 
else 
   { 
   // first set this node to point to previous node and next node (null) 
   rend_list->poly_data[rend_list->num_polys].next = NULL; 
   rend_list->poly_data[rend_list->num_polys].prev =  
         &rend_list->poly_data[rend_list->num_polys-1]; 
 
   // now set previous node to point to this node 
   rend_list->poly_data[rend_list->num_polys-1].next =  
          &rend_list->poly_data[rend_list->num_polys]; 
   } // end else 
 
// increment number of polys in list 
rend_list->num_polys++; 
 
// return successful insertion 
return(1); 
 
} // end Insert_POLYF4DV1_RENDERLIST4DV1 
 
////////////////////////////////////////////////////////////// 
 
int Insert_OBJECT4DV1_RENDERLIST4DV1(RENDERLIST4DV1_PTR rend_list,  
                                      OBJECT4DV1_PTR obj, 
                                      int insert_local) 
{ 
// converts the entire object into a face list and then inserts 
// the visible, active, non-clipped, non-culled polygons into 
// the render list, also note the flag insert_local control  
// whether or not the vlist_local or vlist_trans vertex list 
// is used, thus you can insert an object "raw" totally untranformed 
// if you set insert_local to 1, default is 0, that is you would 
// only insert an object after at least the local to world transform 
 
// is this objective inactive or culled or invisible? 
if (!(obj->state & OBJECT4DV1_STATE_ACTIVE) || 
     (obj->state & OBJECT4DV1_STATE_CULLED) || 
     !(obj->state & OBJECT4DV1_STATE_VISIBLE)) 
   return(0);  
 
// the object is valid, let's rip it apart polygon by polygon 
for (int poly = 0; poly < obj->num_polys; poly++) 
    { 
    // acquire polygon 
    POLY4DV1_PTR curr_poly = &obj->plist[poly]; 
 
    // first is this polygon even visible? 
    if (!(curr_poly->state & POLY4DV1_STATE_ACTIVE) || 
         (curr_poly->state & POLY4DV1_STATE_CLIPPED ) || 
         (curr_poly->state & POLY4DV1_STATE_BACKFACE) ) 
    continue; // move onto next poly 
 
    // override vertex list polygon refers to 
    // the case that you want the local coords used 
    // first save old pointer 
    POINT4D_PTR vlist_old = curr_poly->vlist; 
 
    if (insert_local) 
       curr_poly->vlist = obj->vlist_local; 
    else 
       curr_poly->vlist = obj->vlist_trans; 
 
    // now insert this polygon 
    if (!Insert_POLY4DV1_RENDERLIST4DV1(rend_list, curr_poly)) 
       { 
       // fix vertex list pointer 
       curr_poly->vlist = vlist_old; 
    
       // the whole object didn't fit! 
       return(0); 
       } // end if 
 
    // fix vertex list pointer 
    curr_poly->vlist = vlist_old; 
 
    } // end for 
 
// return success 
return(1); 
 
} // end Insert_OBJECT4DV1_RENDERLIST4DV1 
 
////////////////////////////////////////////////////////////// 
 
void Draw_OBJECT4DV1_Wire(OBJECT4DV1_PTR obj,  
                          UCHAR *video_buffer, int lpitch) 
                      
{ 
// this function renders an object to the screen in wireframe,  
// 8 bit mode, it has no regard at all about hidden surface removal,  
// etc. the function only exists as an easy way to render an object  
// without converting it into polygons, the function assumes all  
// coordinates are screen coordinates, but will perform 2D clipping 
 
// iterate thru the poly list of the object and simply draw 
// each polygon 
for (int poly=0; poly < obj->num_polys; poly++) 
    { 
    // render this polygon if and only if it's not clipped, not culled, 
    // active, and visible, note however the concecpt of "backface" is  
    // irrelevant in a wire frame engine though 
    if (!(obj->plist[poly].state & POLY4DV1_STATE_ACTIVE) || 
         (obj->plist[poly].state & POLY4DV1_STATE_CLIPPED ) || 
         (obj->plist[poly].state & POLY4DV1_STATE_BACKFACE) ) 
       continue; // move onto next poly 
     
    // extract vertex indices into master list, rember the polygons are  
    // NOT self contained, but based on the vertex list stored in the object 
    // itself 
    int vindex_0 = obj->plist[poly].vert[0]; 
    int vindex_1 = obj->plist[poly].vert[1]; 
    int vindex_2 = obj->plist[poly].vert[2]; 
     
    // draw the lines now 
    Draw_Clip_Line(obj->vlist_trans[ vindex_0 ].x, obj->vlist_trans[ vindex_0 ].y,  
                obj->vlist_trans[ vindex_1 ].x, obj->vlist_trans[ vindex_1 ].y,  
                obj->plist[poly].color, 
                video_buffer, lpitch); 
 
    Draw_Clip_Line(obj->vlist_trans[ vindex_1 ].x, obj->vlist_trans[ vindex_1 ].y,  
                obj->vlist_trans[ vindex_2 ].x, obj->vlist_trans[ vindex_2 ].y,  
                obj->plist[poly].color, 
                video_buffer, lpitch); 
 
    Draw_Clip_Line(obj->vlist_trans[ vindex_2 ].x, obj->vlist_trans[ vindex_2 ].y,  
                obj->vlist_trans[ vindex_0 ].x, obj->vlist_trans[ vindex_0 ].y,  
                obj->plist[poly].color, 
                video_buffer, lpitch); 
 
// track rendering stats 
#ifdef DEBUG_ON 
debug_polys_rendered_per_frame++; 
#endif 
 
    } // end for poly 
 
} // end Draw_OBJECT4DV1_Wire 
 
/////////////////////////////////////////////////////////////// 
 
void Draw_RENDERLIST4DV1_Wire(RENDERLIST4DV1_PTR rend_list,  
                              UCHAR *video_buffer, int lpitch) 
{ 
// this function "executes" the render list or in other words 
// draws all the faces in the list in wire frame 8bit mode 
// note there is no need to sort wire frame polygons, but  
// later we will need to, so hidden surfaces stay hidden 
// also, we leave it to the function to determine the bitdepth 
// and call the correct rasterizer 
 
// at this point, all we have is a list of polygons and it's time 
// to draw them 
for (int poly=0; poly < rend_list->num_polys; poly++) 
    { 
    // render this polygon if and only if it's not clipped, not culled, 
    // active, and visible, note however the concecpt of "backface" is  
    // irrelevant in a wire frame engine though 
    if (!(rend_list->poly_ptrs[poly]->state & POLY4DV1_STATE_ACTIVE) || 
         (rend_list->poly_ptrs[poly]->state & POLY4DV1_STATE_CLIPPED ) || 
         (rend_list->poly_ptrs[poly]->state & POLY4DV1_STATE_BACKFACE) ) 
       continue; // move onto next poly 
 
    // draw the triangle edge one, note that clipping was already set up 
    // by 2D initialization, so line clipper will clip all polys out 
    // of the 2D screen/window boundary 
    Draw_Clip_Line(rend_list->poly_ptrs[poly]->tvlist[0].x,  
                rend_list->poly_ptrs[poly]->tvlist[0].y, 
                rend_list->poly_ptrs[poly]->tvlist[1].x,  
                rend_list->poly_ptrs[poly]->tvlist[1].y, 
                rend_list->poly_ptrs[poly]->color, 
                video_buffer, lpitch); 
 
    Draw_Clip_Line(rend_list->poly_ptrs[poly]->tvlist[1].x,  
                rend_list->poly_ptrs[poly]->tvlist[1].y, 
                rend_list->poly_ptrs[poly]->tvlist[2].x,  
                rend_list->poly_ptrs[poly]->tvlist[2].y, 
                rend_list->poly_ptrs[poly]->color, 
                video_buffer, lpitch); 
 
    Draw_Clip_Line(rend_list->poly_ptrs[poly]->tvlist[2].x,  
                rend_list->poly_ptrs[poly]->tvlist[2].y, 
                rend_list->poly_ptrs[poly]->tvlist[0].x,  
                rend_list->poly_ptrs[poly]->tvlist[0].y, 
                rend_list->poly_ptrs[poly]->color, 
                video_buffer, lpitch); 
// track rendering stats 
#ifdef DEBUG_ON 
debug_polys_rendered_per_frame++; 
#endif 
 
    } // end for poly 
 
} // end Draw_RENDERLIST4DV1_Wire 
 
///////////////////////////////////////////////////////////// 
 
void Draw_OBJECT4DV1_Wire16(OBJECT4DV1_PTR obj,  
                            UCHAR *video_buffer, int lpitch) 
                      
{ 
// this function renders an object to the screen in wireframe,  
// 16 bit mode, it has no regard at all about hidden surface removal,  
// etc. the function only exists as an easy way to render an object  
// without converting it into polygons, the function assumes all  
// coordinates are screen coordinates, but will perform 2D clipping 
 
// iterate thru the poly list of the object and simply draw 
// each polygon 
for (int poly=0; poly < obj->num_polys; poly++) 
    { 
    // render this polygon if and only if it's not clipped, not culled, 
    // active, and visible, note however the concecpt of "backface" is  
    // irrelevant in a wire frame engine though 
    if (!(obj->plist[poly].state & POLY4DV1_STATE_ACTIVE) || 
         (obj->plist[poly].state & POLY4DV1_STATE_CLIPPED ) || 
         (obj->plist[poly].state & POLY4DV1_STATE_BACKFACE) ) 
       continue; // move onto next poly 
     
    // extract vertex indices into master list, rember the polygons are  
    // NOT self contained, but based on the vertex list stored in the object 
    // itself 
    int vindex_0 = obj->plist[poly].vert[0]; 
    int vindex_1 = obj->plist[poly].vert[1]; 
    int vindex_2 = obj->plist[poly].vert[2]; 
     
    // draw the lines now 
    Draw_Clip_Line16(obj->vlist_trans[ vindex_0 ].x, obj->vlist_trans[ vindex_0 ].y,  
                obj->vlist_trans[ vindex_1 ].x, obj->vlist_trans[ vindex_1 ].y,  
                obj->plist[poly].color, 
                video_buffer, lpitch); 
 
    Draw_Clip_Line16(obj->vlist_trans[ vindex_1 ].x, obj->vlist_trans[ vindex_1 ].y,  
                obj->vlist_trans[ vindex_2 ].x, obj->vlist_trans[ vindex_2 ].y,  
                obj->plist[poly].color, 
                video_buffer, lpitch); 
 
    Draw_Clip_Line16(obj->vlist_trans[ vindex_2 ].x, obj->vlist_trans[ vindex_2 ].y,  
                obj->vlist_trans[ vindex_0 ].x, obj->vlist_trans[ vindex_0 ].y,  
                obj->plist[poly].color, 
                video_buffer, lpitch); 
 
// track rendering stats 
#ifdef DEBUG_ON 
debug_polys_rendered_per_frame++; 
#endif 
 
 
    } // end for poly 
 
} // end Draw_OBJECT4DV1_Wire16 
 
/////////////////////////////////////////////////////////////// 
 
void Draw_RENDERLIST4DV1_Wire16(RENDERLIST4DV1_PTR rend_list,  
                                UCHAR *video_buffer, int lpitch) 
{ 
// this function "executes" the render list or in other words 
// draws all the faces in the list in wire frame 16bit mode 
// note there is no need to sort wire frame polygons, but  
// later we will need to, so hidden surfaces stay hidden 
// also, we leave it to the function to determine the bitdepth 
// and call the correct rasterizer 
 
// at this point, all we have is a list of polygons and it's time 
// to draw them 
for (int poly=0; poly < rend_list->num_polys; poly++) 
    { 
    // render this polygon if and only if it's not clipped, not culled, 
    // active, and visible, note however the concecpt of "backface" is  
    // irrelevant in a wire frame engine though 
    if (!(rend_list->poly_ptrs[poly]->state & POLY4DV1_STATE_ACTIVE) || 
         (rend_list->poly_ptrs[poly]->state & POLY4DV1_STATE_CLIPPED ) || 
         (rend_list->poly_ptrs[poly]->state & POLY4DV1_STATE_BACKFACE) ) 
       continue; // move onto next poly 
 
    // draw the triangle edge one, note that clipping was already set up 
    // by 2D initialization, so line clipper will clip all polys out 
    // of the 2D screen/window boundary 
    Draw_Clip_Line16(rend_list->poly_ptrs[poly]->tvlist[0].x,  
                rend_list->poly_ptrs[poly]->tvlist[0].y, 
                rend_list->poly_ptrs[poly]->tvlist[1].x,  
                rend_list->poly_ptrs[poly]->tvlist[1].y, 
                rend_list->poly_ptrs[poly]->color, 
                video_buffer, lpitch); 
 
    Draw_Clip_Line16(rend_list->poly_ptrs[poly]->tvlist[1].x,  
                rend_list->poly_ptrs[poly]->tvlist[1].y, 
                rend_list->poly_ptrs[poly]->tvlist[2].x,  
                rend_list->poly_ptrs[poly]->tvlist[2].y, 
                rend_list->poly_ptrs[poly]->color, 
                video_buffer, lpitch); 
 
    Draw_Clip_Line16(rend_list->poly_ptrs[poly]->tvlist[2].x,  
                rend_list->poly_ptrs[poly]->tvlist[2].y, 
                rend_list->poly_ptrs[poly]->tvlist[0].x,  
                rend_list->poly_ptrs[poly]->tvlist[0].y, 
                rend_list->poly_ptrs[poly]->color, 
                video_buffer, lpitch); 
 
// track rendering stats 
#ifdef DEBUG_ON 
debug_polys_rendered_per_frame++; 
#endif 
 
    } // end for poly 
 
} // end Draw_RENDERLIST4DV1_Wire 
 
///////////////////////////////////////////////////////////// 
 
void Build_CAM4DV1_Matrix_Euler(CAM4DV1_PTR cam, int cam_rot_seq) 
{ 
// this creates a camera matrix based on Euler angles  
// and stores it in the sent camera object 
// if you recall from chapter 6 to create the camera matrix 
// we need to create a transformation matrix that looks like: 
 
// Mcam = mt(-1) * my(-1) * mx(-1) * mz(-1) 
// that is the inverse of the camera translation matrix mutilplied 
// by the inverses of yxz, in that order, however, the order of 
// the rotation matrices is really up to you, so we aren't going 
// to force any order, thus its programmable based on the value 
// of cam_rot_seq which can be any value CAM_ROT_SEQ_XYZ where  
// XYZ can be in any order, YXZ, ZXY, etc. 
 
MATRIX4X4 mt_inv,  // inverse camera translation matrix 
          mx_inv,  // inverse camera x axis rotation matrix 
          my_inv,  // inverse camera y axis rotation matrix 
          mz_inv,  // inverse camera z axis rotation matrix 
          mrot,    // concatenated inverse rotation matrices 
          mtmp;    // temporary working matrix 
 
 
// step 1: create the inverse translation matrix for the camera 
// position 
Mat_Init_4X4(&mt_inv, 1,    0,     0,     0, 
                      0,    1,     0,     0, 
                      0,    0,     1,     0, 
                      -cam->pos.x, -cam->pos.y, -cam->pos.z, 1); 
 
// step 2: create the inverse rotation sequence for the camera 
// rember either the transpose of the normal rotation matrix or 
// plugging negative values into each of the rotations will result 
// in an inverse matrix 
 
// first compute all 3 rotation matrices 
 
// extract out euler angles 
float theta_x = cam->dir.x; 
float theta_y = cam->dir.y; 
float theta_z = cam->dir.z; 
 
// compute the sine and cosine of the angle x 
float cos_theta = Fast_Cos(theta_x);  // no change since cos(-x) = cos(x) 
float sin_theta = -Fast_Sin(theta_x); // sin(-x) = -sin(x) 
 
// set the matrix up  
Mat_Init_4X4(&mx_inv, 1,    0,         0,         0, 
                      0,    cos_theta, sin_theta, 0, 
                      0,   -sin_theta, cos_theta, 0, 
                      0,    0,         0,         1); 
 
// compute the sine and cosine of the angle y 
cos_theta = Fast_Cos(theta_y);  // no change since cos(-x) = cos(x) 
sin_theta = -Fast_Sin(theta_y); // sin(-x) = -sin(x) 
 
// set the matrix up  
Mat_Init_4X4(&my_inv,cos_theta, 0, -sin_theta, 0,   
                     0,         1,  0,         0, 
                     sin_theta, 0,  cos_theta,  0, 
                     0,         0,  0,          1); 
 
// compute the sine and cosine of the angle z 
cos_theta = Fast_Cos(theta_z);  // no change since cos(-x) = cos(x) 
sin_theta = -Fast_Sin(theta_z); // sin(-x) = -sin(x) 
 
// set the matrix up  
Mat_Init_4X4(&mz_inv, cos_theta, sin_theta, 0, 0,   
                     -sin_theta, cos_theta, 0, 0, 
                      0,         0,         1, 0, 
                      0,         0,         0, 1); 
 
// now compute inverse camera rotation sequence 
switch(cam_rot_seq) 
      { 
      case CAM_ROT_SEQ_XYZ: 
      { 
      Mat_Mul_4X4(&mx_inv, &my_inv, &mtmp); 
      Mat_Mul_4X4(&mtmp, &mz_inv, &mrot); 
      } break; 
 
      case CAM_ROT_SEQ_YXZ: 
      { 
      Mat_Mul_4X4(&my_inv, &mx_inv, &mtmp); 
      Mat_Mul_4X4(&mtmp, &mz_inv, &mrot); 
      } break; 
 
      case CAM_ROT_SEQ_XZY: 
      { 
      Mat_Mul_4X4(&mx_inv, &mz_inv, &mtmp); 
      Mat_Mul_4X4(&mtmp, &my_inv, &mrot); 
      } break; 
 
      case CAM_ROT_SEQ_YZX: 
      { 
      Mat_Mul_4X4(&my_inv, &mz_inv, &mtmp); 
      Mat_Mul_4X4(&mtmp, &mx_inv, &mrot); 
      } break; 
 
      case CAM_ROT_SEQ_ZYX: 
      { 
      Mat_Mul_4X4(&mz_inv, &my_inv, &mtmp); 
      Mat_Mul_4X4(&mtmp, &mx_inv, &mrot); 
      } break; 
 
      case CAM_ROT_SEQ_ZXY: 
      { 
      Mat_Mul_4X4(&mz_inv, &mx_inv, &mtmp); 
      Mat_Mul_4X4(&mtmp, &my_inv, &mrot); 
 
      } break; 
 
      default: break; 
      } // end switch 
 
// now mrot holds the concatenated product of inverse rotation matrices 
// multiply the inverse translation matrix against it and store in the  
// camera objects' camera transform matrix we are done! 
Mat_Mul_4X4(&mt_inv, &mrot, &cam->mcam); 
 
} // end Build_CAM4DV1_Matrix_Euler 
 
///////////////////////////////////////////////////////////// 
 
void Build_CAM4DV1_Matrix_UVN(CAM4DV1_PTR cam, int mode) 
{ 
// this creates a camera matrix based on a look at vector n, 
// look up vector v, and a look right (or left) u 
// and stores it in the sent camera object, all values are 
// extracted out of the camera object itself 
// mode selects how uvn is computed 
// UVN_MODE_SIMPLE - low level simple model, use the target and view reference point 
// UVN_MODE_SPHERICAL - spherical mode, the x,y components will be used as the 
//     elevation and heading of the view vector respectively 
//     along with the view reference point as the position 
//     as usual 
 
MATRIX4X4 mt_inv,  // inverse camera translation matrix 
          mt_uvn,  // the final uvn matrix 
          mtmp;    // temporary working matrix 
 
// step 1: create the inverse translation matrix for the camera 
// position 
Mat_Init_4X4(&mt_inv, 1,    0,     0,     0, 
                      0,    1,     0,     0, 
                      0,    0,     1,     0, 
                      -cam->pos.x, -cam->pos.y, -cam->pos.z, 1); 
 
 
// step 2: determine how the target point will be computed 
if (mode == UVN_MODE_SPHERICAL) 
   { 
   // use spherical construction 
   // target needs to be recomputed 
 
   // extract elevation and heading  
   float phi   = cam->dir.x; // elevation 
   float theta = cam->dir.y; // heading 
 
   // compute trig functions once 
   float sin_phi = Fast_Sin(phi); 
   float cos_phi = Fast_Cos(phi); 
 
   float sin_theta = Fast_Sin(theta); 
   float cos_theta = Fast_Cos(theta); 
 
   // now compute the target point on a unit sphere x,y,z 
   cam->target.x = -1*sin_phi*sin_theta; 
   cam->target.y =  1*cos_phi; 
   cam->target.z =  1*sin_phi*cos_theta; 
   } // end else 
 
// at this point, we have the view reference point, the target and that's 
// all we need to recompute u,v,n 
// Step 1: n =  
VECTOR4D_Build(&cam->pos, &cam->target, &cam->n); 
 
// Step 2: Let v = <0,1,0> 
VECTOR4D_INITXYZ(&cam->v,0,1,0); 
 
// Step 3: u = (v x n) 
VECTOR4D_Cross(&cam->v,&cam->n,&cam->u); 
 
// Step 4: v = (n x u) 
VECTOR4D_Cross(&cam->n,&cam->u,&cam->v); 
 
// Step 5: normalize all vectors 
VECTOR4D_Normalize(&cam->u); 
VECTOR4D_Normalize(&cam->v); 
VECTOR4D_Normalize(&cam->n); 
 
 
// build the UVN matrix by placing u,v,n as the columns of the matrix 
Mat_Init_4X4(&mt_uvn, cam->u.x,    cam->v.x,     cam->n.x,     0, 
                      cam->u.y,    cam->v.y,     cam->n.y,     0, 
                      cam->u.z,    cam->v.z,     cam->n.z,     0, 
                      0,           0,            0,            1); 
 
// now multiply the translation matrix and the uvn matrix and store in the  
// final camera matrix mcam 
Mat_Mul_4X4(&mt_inv, &mt_uvn, &cam->mcam); 
 
} // end Build_CAM4DV1_Matrix_UVN 
 
///////////////////////////////////////////////////////////// 
 
void Init_CAM4DV1(CAM4DV1_PTR cam,       // the camera object 
                  int cam_attr,          // attributes 
                  POINT4D_PTR cam_pos,   // initial camera position 
                  VECTOR4D_PTR cam_dir,  // initial camera angles 
                  POINT4D_PTR cam_target, // UVN target 
                  float near_clip_z,     // near and far clipping planes 
                  float far_clip_z, 
                  float fov,             // field of view in degrees 
                  float viewport_width,  // size of final screen viewport 
                  float viewport_height) 
{ 
// this function initializes the camera object cam, the function 
// doesn't do a lot of error checking or sanity checking since  
// I want to allow you to create projections as you wish, also  
// I tried to minimize the number of parameters the functions needs 
 
// first set up parms that are no brainers 
cam->attr = cam_attr;              // camera attributes 
 
VECTOR4D_COPY(&cam->pos, cam_pos); // positions 
VECTOR4D_COPY(&cam->dir, cam_dir); // direction vector or angles for 
                                   // euler camera 
// for UVN camera 
VECTOR4D_INITXYZ(&cam->u, 1,0,0);  // set to +x 
VECTOR4D_INITXYZ(&cam->v, 0,1,0);  // set to +y 
VECTOR4D_INITXYZ(&cam->n, 0,0,1);  // set to +z         
 
if (cam_target!=NULL) 
   VECTOR4D_COPY(&cam->target, cam_target); // UVN target 
else 
   VECTOR4D_ZERO(&cam->target); 
 
cam->near_clip_z = near_clip_z;     // near z=constant clipping plane 
cam->far_clip_z  = far_clip_z;      // far z=constant clipping plane 
 
cam->viewport_width  = viewport_width;   // dimensions of viewport 
cam->viewport_height = viewport_height; 
 
cam->viewport_center_x = (viewport_width-1)/2; // center of viewport 
cam->viewport_center_y = (viewport_height-1)/2; 
 
cam->aspect_ratio = (float)viewport_width/(float)viewport_height; 
 
// set all camera matrices to identity matrix 
MAT_IDENTITY_4X4(&cam->mcam); 
MAT_IDENTITY_4X4(&cam->mper); 
MAT_IDENTITY_4X4(&cam->mscr); 
 
// set independent vars 
cam->fov              = fov; 
 
// set the viewplane dimensions up, they will be 2 x (2/ar) 
cam->viewplane_width  = 2.0; 
cam->viewplane_height = 2.0/cam->aspect_ratio; 
 
// now we know fov and we know the viewplane dimensions plug into formula and 
// solve for view distance parameters 
float tan_fov_div2 = tan(DEG_TO_RAD(fov/2)); 
 
cam->view_dist = (0.5)*(cam->viewplane_width)*tan_fov_div2; 
 
// test for 90 fov first since it's easy :) 
if (fov == 90.0) 
    { 
      // set up the clipping planes -- easy for 90 degrees! 
      POINT3D pt_origin; // point on the plane 
      VECTOR3D_INITXYZ(&pt_origin,0,0,0); 
 
      VECTOR3D vn; // normal to plane 
 
      // right clipping plane  
      VECTOR3D_INITXYZ(&vn,1,0,-1); // x=z plane 
      PLANE3D_Init(&cam->rt_clip_plane, &pt_origin,  &vn, 1); 
 
      // left clipping plane 
      VECTOR3D_INITXYZ(&vn,-1,0,-1); // -x=z plane 
      PLANE3D_Init(&cam->lt_clip_plane, &pt_origin,  &vn, 1); 
 
      // top clipping plane 
      VECTOR3D_INITXYZ(&vn,0,1,-1); // y=z plane 
      PLANE3D_Init(&cam->tp_clip_plane, &pt_origin,  &vn, 1); 
 
      // bottom clipping plane 
      VECTOR3D_INITXYZ(&vn,0,-1,-1); // -y=z plane 
      PLANE3D_Init(&cam->bt_clip_plane, &pt_origin,  &vn, 1); 
    } // end if d=1 
else  
    { 
      // now compute clipping planes yuck! 
      POINT3D pt_origin; // point on the plane 
      VECTOR3D_INITXYZ(&pt_origin,0,0,0); 
 
      VECTOR3D vn; // normal to plane 
 
      // since we don't have a 90 fov, computing the normals 
      // are a bit tricky, there are a number of geometric constructions 
      // that solve the problem, but I'm going to solve for the 
      // vectors that represent the 2D projections of the frustrum planes 
      // on the x-z and y-z planes and then find perpendiculars to them 
  
      // right clipping plane, check the math on graph paper  
      VECTOR3D_INITXYZ(&vn,cam->view_dist,0,-cam->viewplane_width/2.0);  
      PLANE3D_Init(&cam->rt_clip_plane, &pt_origin,  &vn, 1); 
 
      // left clipping plane, we can simply reflect the right normal about 
      // the z axis since the planes are symetric about the z axis 
      // thus invert x only 
      VECTOR3D_INITXYZ(&vn,-cam->view_dist,0,-cam->viewplane_width/2.0);  
      PLANE3D_Init(&cam->lt_clip_plane, &pt_origin,  &vn, 1); 
 
      // top clipping plane, same construction 
      VECTOR3D_INITXYZ(&vn,0,cam->view_dist,-cam->viewplane_width/2.0);  
      PLANE3D_Init(&cam->tp_clip_plane, &pt_origin,  &vn, 1); 
 
      // bottom clipping plane, same inversion 
      VECTOR3D_INITXYZ(&vn,0,-cam->view_dist,-cam->viewplane_width/2.0);  
      PLANE3D_Init(&cam->bt_clip_plane, &pt_origin,  &vn, 1); 
    } // end else 
 
} // end Init_CAM4DV1 
 
// MAIN ////////////////////////////////////////////////////// 
 
#if (STANDALONE==1) 
void main() 
{ 
 
static VECTOR4D vscale={1,1,1,1}, vpos = {0,0,0,1}, vrot = {0,0,0,1}; 
static OBJECT4DV1 obj; 
static CAM4DV1 cam; 
static RENDERLIST4DV1 rend_list; 
static POLYF4DV1 poly1; 
 
 
Open_Error_File("ERROR.TXT", stdout); 
 
// set function pointer to something 
RGB16Bit = RGB16Bit565; 
 
// initialize math engine 
Build_Sin_Cos_Tables(); 
 
// initialize the renderlist 
Init_RENDERLIST4DV1(&rend_list); 
 
// initialize single polygon 
poly1.state  = POLY4DV1_STATE_ACTIVE; 
poly1.attr   =  0;  
poly1.color = RGB16Bit(255,255,255); 
   
poly1.vlist[0].x = 0; 
poly1.vlist[0].y = 50; 
poly1.vlist[0].z = 100; 
poly1.vlist[0].w = 1; 
 
poly1.vlist[1].x = 50; 
poly1.vlist[1].y = 0; 
poly1.vlist[1].z = 100; 
poly1.vlist[1].w = 1; 
 
poly1.vlist[2].x = -50; 
poly1.vlist[2].y = 50; 
poly1.vlist[2].z = 100; 
poly1.vlist[2].w = 1; 
 
poly1.tvlist[0].x = 0; 
poly1.tvlist[0].y = 50; 
poly1.tvlist[0].z = 100; 
poly1.tvlist[0].w = 1; 
 
poly1.tvlist[1].x = 50; 
poly1.tvlist[1].y = 0; 
poly1.tvlist[1].z = 100; 
poly1.tvlist[1].w = 1; 
 
poly1.tvlist[2].x = -50; 
poly1.tvlist[2].y = 50; 
poly1.tvlist[2].z = 100; 
poly1.tvlist[2].w = 1; 
 
poly1.next = poly1.prev = NULL; 
 
// insert the polygon in the renderlist 
Insert_POLYF4DV1_RENDERLIST4DV1(&rend_list, &poly1); 
 
Write_Error("\nPolygon Initial"); 
VECTOR4D_Print(&rend_list.poly_ptrs[0]->tvlist[0], "v0"); 
VECTOR4D_Print(&rend_list.poly_ptrs[0]->tvlist[1], "v1"); 
VECTOR4D_Print(&rend_list.poly_ptrs[0]->tvlist[2], "v2"); 
 
 
// initialize camera  
static POINT4D cam_pos = {0,0,-500}; 
static VECTOR4D cam_dir = {0,0,0}; 
 
Init_CAM4DV1(&cam,      // the camera object 
             &cam_pos,  // initial camera position 
             &cam_dir,  // initial camera angles 
             10.0,      // near and far clipping planes 
             1000.0, 
             90.0,      // field of view in degrees 
             1.0,       // viewing distance 
             0.0,       // size of viewplane 
             0.0, 
             640,   // size of final screen viewport 
             480, 
             ((float)640/(float)480)); 
 
// generate camera matrix 
Build_CAM4DV1_Matrix_Euler(&cam, CAM_ROT_SEQ_XYZ); 
 
// apply world to camera transformation 
Camera_To_Perspective_Norm_RENDERLIST4DV1(&rend_list, &cam); 
 
Write_Error("\nPolygon Perspective"); 
VECTOR4D_Print(&rend_list.poly_ptrs[0]->tvlist[0], "v0"); 
VECTOR4D_Print(&rend_list.poly_ptrs[0]->tvlist[1], "v1"); 
VECTOR4D_Print(&rend_list.poly_ptrs[0]->tvlist[2], "v2"); 
 
// apply screen transform 
Perspective_Norm_To_Screen_RENDERLIST4DV1(&rend_list, &cam); 
 
Write_Error("\nPolygon Screen"); 
VECTOR4D_Print(&rend_list.poly_ptrs[0]->tvlist[0], "v0"); 
VECTOR4D_Print(&rend_list.poly_ptrs[0]->tvlist[1], "v1"); 
VECTOR4D_Print(&rend_list.poly_ptrs[0]->tvlist[2], "v2"); 
 
 
//Load_OBJECT4DV1_PLG(&obj, "test.plg",vscale, vpos, vrot); 
 
Close_Error_File(); 
   
} // end main 
#endif