/* render1.c * * By Byron C. Darrah * 4/28/94, Thursday * * render1 renders simple lists of polygons. render1 keeps an aggregate * list of all polygons submitted to it, and also keeps an index to this * list keyed by object ID numbers, so that it knows which polygons * belong to which objects. */ /* Here is the implementation of the renderer called render1. */ #include #include #include #include #include "render1.h" #include "display_list.h" typedef float rend_matrix_t[4][4]; rend_matrix_t camera_transformation; float perspective, default_perspective = DEFAULT_PERSPECTIVE; polygon_node_t ag_polygon_list; /* A dummy node to be the head of list */ /* Here is the structure for the nodes of the object ID index to the * aggregate list. */ typedef struct obj_ki_struct { struct obj_ki_struct *next; int obj_id; polygon_node_t *pnodes; /* array of polygon nodes. */ int npolygons; /* size of above array. */ } obj_node_t; obj_node_t obj_id_index; /* This list also has a dummy node for a head */ /*---------------------------------------------------------------------------*/ /*---------------------- INTERNAL FUNCTION PROTOTYPES -----------------------*/ /*---------------------------------------------------------------------------*/ void rend_agg_poly_insert(polygon_node_t *pnode); obj_node_t *rend_obj_lookup(int obj_id); void mtx_mult_4x4(rend_matrix_t f1, rend_matrix_t f2, rend_matrix_t product); void mtx_transform_pt(real_point_t *pos, rend_matrix_t m1, real_point_t *new_pos); void mtx_transform_pg(polygon_node_t *polygon_node); /*---------------------------------------------------------------------------*/ /*--------------------------- EXPORTED FUNCTIONS ----------------------------*/ /*---------------------------------------------------------------------------*/ /* This sets the camera matrix to a default value, initializes the * polygon lists, and the display. */ void rend_init(int *argc, char *argv[], float init_perspect) { real_point_t c_position = {0.0, 0.0, -20.0}; /* Camera positon */ real_point_t c_focus = {0.0, 0.0, 0.0}, /* Camera focus direction */ c_up = {0.0, 10.0, 0.0}; /* Camera up direction */ int i, color = 1; /* Scan for the -mono option in the argument list */ for(i = 0; i < *argc; i++) { if(color) { if(!strcmp(argv[i], "-mono")) { color = 0; (*argc)--; if(i < *argc) argv[i] = argv[i+1]; } } else argv[i] = argv[i+1]; } /* Initialize the display */ disp_init(argc, argv, color); ag_polygon_list.next = NULL; ag_polygon_list.prev = NULL; obj_id_index.next = NULL; obj_id_index.pnodes = NULL; obj_id_index.npolygons = 0; rend_set_camera(&c_position, &c_focus, &c_up); /* Initialize the perspective. If no perspective value was given, * set it to the default perspective. */ if (init_perspect != 0.0) default_perspective = init_perspect; /* Now work in a fudge-factor to cause points to be scaled according * to the display window size. */ if (disp_x_scale < disp_y_scale) perspective = default_perspective * disp_x_scale; else perspective = default_perspective * disp_y_scale; } /*---------------------------------------------------------------------------*/ void rend_end(void) { disp_end(); } /*---------------------------------------------------------------------------*/ /* Synchronize the scale of drawing operations with the size of the * actual output window, and redraw the output frame. */ void rend_setsize(void) { unsigned short width, height; disp_getsize(&width, &height); disp_setsize(width, height); /* Now work in a fudge-factor to cause points to be scaled according * to the display window size. */ if (disp_x_scale < disp_y_scale) perspective = default_perspective * disp_x_scale; else perspective = default_perspective * disp_y_scale; /* Now that the drawing size has been adjusted, redraw the output */ rend_transform_update_all(); rend_frame(); } /*---------------------------------------------------------------------------*/ /* This is used to set the camera matrix, which describes the position * and orientation of the POV. Whenever the camera matrix is changed, * the entire list of polygons must be recomputed. * * The position and orientation of the camera determines what gets displayed * (obviously). As a result, the camera's orientation is used to determine * a transformation which will be applied to all points to map them * to screen coordinates. For more information, see "Faster 3-D Drawing" * by Brian Carmichael, in The AmigaWorld Tech Journal, pg 20, January/February * 1992, or any decent 3-D rendering text. * * Whenever the camera is moved, the aggregate polygon list must be * recomputed (because the mapping of sim-space to screen-space is * changed). This can be done by calling rend_transform_update_all(). * This is an time-expensive operation, so it is good if the calling * application can remove from the aggregate list any objects that are going * to resubmit their polygons before the next frame-render, so these objects * will not have to be redundantly transformed. */ void rend_set_camera(real_point_t *position, real_point_t *focus, real_point_t *up) { rend_matrix_t m1, m2, m3; float cosAy, sinAy, cosAx, sinAx, cosAz, sinAz; float Dxz, Dyz, Dxy; real_point_t new_pos; /* let m1 be the transformation matrix for the translation, which moves * the camera's focus point to the origin. */ m1[0][0] = 1; m1[0][1] = m1[0][2] = m1[0][3] = 0; m1[1][0] = 0; m1[1][1] = 1; m1[1][2] = m1[1][3] = 0; m1[2][0] = m1[2][1] = 0; m1[2][2] = 1; m1[2][3] = 0; m1[3][0] = -position->x; m1[3][1] = -position->y; m1[3][2] = -position->z; m1[3][3] = 1; /* Compute the camera focus relative to the position. */ new_pos.x = focus->x - position->x; new_pos.y = focus->y - position->y; new_pos.z = focus->z - position->z; /* Now compute the cosine and sine of the y-rotation angle, Ay, * between the focus vector and the zy plane. */ Dxz = sqrt((double)(new_pos.x * new_pos.x + new_pos.z * new_pos.z)); if (Dxz != 0.0) { cosAy = new_pos.z / Dxz; sinAy = -new_pos.x / Dxz; } else /* Uh-oh - handle indeterminate case */ { cosAy = 1.0; sinAy = 0.0; } /* let m2 be the transformation matrix for rotating the focus point about * the y-axis onto the zy plane. */ m2[0][0] = cosAy; m2[0][1] = 0; m2[0][2] = -sinAy; m2[0][3] = 0; m2[1][0] = 0; m2[1][1] = 1; m2[1][2] = 0; m2[1][3] = 0; m2[2][0] = sinAy; m2[2][1] = 0; m2[2][2] = cosAy; m2[2][3] = 0; m2[3][0] = 0; m2[3][1] = 0; m2[3][2] = 0; m2[3][3] = 1; /* let m3 be the product of m1 and m2 */ mtx_mult_4x4(m1, m2, m3); /* Update the focus pos by multiplying it by m2. */ new_pos.x = new_pos.x * cosAy + new_pos.z * sinAy; new_pos.z = new_pos.x * -sinAy + new_pos.z * cosAy; /* Now compute the cosine and sine of the x-rotation angle, Ax */ Dyz = sqrt((double)(new_pos.y * new_pos.y + new_pos.z * new_pos.z)); cosAx = new_pos.z/Dyz; sinAx = new_pos.y/Dyz; /* let m2 be the transformation matrix for rotating the focus point about * the x-axis onto the +z-axis. */ m2[0][0] = 1; m2[0][1] = 0; m2[0][2] = 0; m2[0][3] = 0; m2[1][0] = 0; m2[1][1] = cosAx; m2[1][2] = sinAx; m2[1][3] = 0; m2[2][0] = 0; m2[2][1] = -sinAx; m2[2][2] = cosAx; m2[2][3] = 0; m2[3][0] = 0; m2[3][1] = 0; m2[3][2] = 0; m2[3][3] = 1; /* let m1 be the product of m3 and m2 */ mtx_mult_4x4(m3, m2, m1); /* Update the "up" point pos by multiplying it by m1 */ mtx_transform_pt(up, m1, &new_pos); /* Now compute the cosine and sine of the z-axis rotation angle, Az */ Dxy = sqrt((double)(new_pos.x * new_pos.x + new_pos.y * new_pos.y)); cosAz = new_pos.y/Dxy; sinAz = new_pos.x/Dxy; /* let m2 be the transformation matrix for rotating the up point about * the z-axis onto the zy-plane. */ m2[0][0] = cosAz; m2[0][1] = sinAz; m2[0][2] = 0; m2[0][3] = 0; m2[1][0] = -sinAz; m2[1][1] = cosAz; m2[1][2] = 0; m2[1][3] = 0; m2[2][0] = 0; m2[2][1] = 0; m2[2][2] = 1; m2[2][3] = 0; m2[3][0] = 0; m2[3][1] = 0; m2[3][2] = 0; m2[3][3] = 1; /* Now the final multiplication gives us the camera transformation: */ mtx_mult_4x4(m1, m2, camera_transformation); /* If you want to debug by printing out the camera matrix, uncomment this. */ /* fprintf(stderr, "---Camera Matrix---\n"); { int row, col; for(row = 0; row < 4; row++) { for(col = 0; col < 4; col++) fprintf(stderr, "%6.2f ", camera_transformation[row][col]); printf("\n"); } } fprintf(stderr, "-------------------\n"); */ } /*---------------------------------------------------------------------------*/ /* rend_register_obj is called by objects that wish to use the display. * An object that calls this function is "registered", and may submit * lists of polygons for display. * */ void rend_register_obj(int obj_id, int npolygons) { polygon_node_t *newlist; obj_node_t *newobj; if ((newlist = (polygon_node_t *)malloc(sizeof(polygon_node_t) * npolygons)) == NULL) { fprintf(stderr, "rend_register_obj: out of memory"); assert(0); } if ((newobj = (obj_node_t *)malloc(sizeof(obj_node_t))) == NULL) { fprintf(stderr, "rend_register_obj: out of memory"); assert(0); } /* Add the new object to the object ID index list */ newobj->next = obj_id_index.next; obj_id_index.next = newobj; /* Now fill in the data for this object node */ newobj->obj_id = obj_id; newobj->pnodes = newlist; newobj->npolygons = npolygons; } /*---------------------------------------------------------------------------*/ /* An object calls this function to submit an updatedlist of polygons * to the renderer. Render1 knows how many polygons to expect, from * when the object was registered by rend_register_obj. It transforms * the object's polygons to screen space, and adds them to the aggregate * list. Note: An object must be sure that it has removed it's polygons * before resubmitting them, or else the aggregate list will get messed up. */ int rend_submit_plist(int obj_id, polygon_t *polygons) { obj_node_t *obj_record; int i; polygon_node_t *pnode; if((obj_record = rend_obj_lookup(obj_id)) == NULL) { fprintf(stderr, "rend_submit_plist: unknown object %d.\n", obj_id); return (R1_OBJ_UNKNOWN); } /* Add the object's polygons */ for(i = 0; i < obj_record->npolygons; i++) { pnode = obj_record->pnodes+i; pnode->polygon = polygons+i; /* transform the polygons and add them to the aggregate list */ mtx_transform_pg(pnode); rend_agg_poly_insert(pnode); } return(R1_NORMAL); } /*---------------------------------------------------------------------------*/ /* remove an object's polygons from the aggregate display list */ int rend_rm_plist(int obj_id) { obj_node_t *obj_node; polygon_node_t *poly_array; int i; if ((obj_node = rend_obj_lookup(obj_id)) == NULL) return(R1_OBJ_UNKNOWN); poly_array = obj_node->pnodes; /* Since the polygon list is a doubly-linked list, polygons can be * removed from it by simply removing the pointers to them in the * previous and next nodes. The following loop does this. */ for (i = 0; i < obj_node->npolygons; i++) { if (poly_array[i].next) poly_array[i].next->prev = poly_array[i].prev; poly_array[i].prev->next = poly_array[i].next; poly_array[i].next = poly_array[i].prev = NULL; } return(R1_NORMAL); } /*---------------------------------------------------------------------------*/ /* Update the entire list of polygons -- * perform transformations on all polygons and rebuild the aggregate * polygon display list in z-sorted order. */ void rend_transform_update_all(void) { polygon_node_t *templist; polygon_node_t *curnode; /* Make a copy of the aggregate polygon list, then clear it */ templist = ag_polygon_list.next; ag_polygon_list.next = NULL; curnode = templist; while(curnode) { templist = curnode->next; mtx_transform_pg(curnode); /* Transform polygon to screen space. */ rend_agg_poly_insert(curnode); /* Insert the node to the agg. list. */ curnode = templist; /* Get the next node from the templist. */ } } /*---------------------------------------------------------------------------*/ /* Render a display frame -- * This outputs the aggregate polygon list to the display device. */ void rend_frame(void) { disp_polygons(ag_polygon_list.next); } /*---------------------------------------------------------------------------*/ /* Ring the audio bell. */ void rend_bell(void) { disp_bell(); } /*---------------------------------------------------------------------------*/ /* Freeze the image as is. Returns when either the specified interval has * expired, or a mouseclick has occurred in the image window. */ int rend_freeze(unsigned long interval) { int reason; while(1) { if ((reason = disp_freeze(interval)) == RESIZE) rend_setsize(); else return(reason); } } /*---------------------------------------------------------------------------*/ /*--------------------------- INTERNAL FUNCTIONS ----------------------------*/ /*---------------------------------------------------------------------------*/ /* Given a pointer to a polygon_node_t, insert the node into the aggregate * polygon display list. */ void rend_agg_poly_insert(polygon_node_t *pnode) { polygon_node_t *curnode = &ag_polygon_list; /* Find the node that pnode should be inserted after */ while(curnode->next && (curnode->next->k > pnode->k)) curnode = curnode->next; /* Insert the pnode into the aggregate list */ if(curnode->next) curnode->next->prev = pnode; pnode->next = curnode->next; curnode->next = pnode; pnode->prev = curnode; } /*---------------------------------------------------------------------------*/ /* Given an object id, look up the object's entry in the object list. * NULL is returned if the end of the list is reached before the * object id is found. */ obj_node_t *rend_obj_lookup(int obj_id) { obj_node_t *curnode = obj_id_index.next; for(;curnode && curnode->obj_id != obj_id; curnode = curnode->next) ; return(curnode); } /*---------------------------------------------------------------------------*/ /* mtx_mult_4x4 is a function which multiplies two 4x4 matrices and gives the * result. */ void mtx_mult_4x4(rend_matrix_t f1, rend_matrix_t f2, rend_matrix_t product) { int i, row, col; float sum; for (row = 0; row < 4; row++) for (col = 0; col < 4; col++) { sum = f1[row][0] * f2[0][col]; for(i = 1; i < 4; i++) sum += f1[row][i] * f2[i][col]; product[row][col] = sum; } } /*---------------------------------------------------------------------------*/ /* mtx_transform_pt transforms a point by the specified matrix. This is used * by other functions to map a point in simulation space to screen space. * * This routine is optimized based on the assumption that a point is a 1x4 * matrix with the first three elements equal to the x,y,z of the point, and * the last element always equal to one. */ void mtx_transform_pt(real_point_t *pos, rend_matrix_t m1, real_point_t *new_pos) { new_pos->x = pos->x * m1[0][0] + pos->y * m1[1][0] + pos->z * m1[2][0] + m1[3][0]; new_pos->y = pos->x * m1[0][1] + pos->y * m1[1][1] + pos->z * m1[2][1] + m1[3][1]; new_pos->z = pos->x * m1[0][2] + pos->y * m1[1][2] + pos->z * m1[2][2] + m1[3][2]; } /*---------------------------------------------------------------------------*/ /* Given a polygon, transform it to screen space */ void mtx_transform_pg(polygon_node_t *polygon_node) { int i; int clipped = 0; float scale; float d_sum = 0.0; polygon_t *polygon = polygon_node->polygon; for (i = polygon->npoints-1; i >= 0; i--) { real_point_t scrn_pt; int_point_t *int_pt = polygon->int_points+i; /* Tanslate and rotate each point in the polygon into screen space */ mtx_transform_pt(polygon->sim_points+i, camera_transformation, &scrn_pt); /* Perform perspective scaling adjustment on each point */ if (scrn_pt.z > 0.0) { scale = perspective / scrn_pt.z; int_pt->x = Gfx_X_Origin + (int)(scrn_pt.x * scale); int_pt->y = Gfx_Y_Origin - (int)(scrn_pt.y * scale); } /* Keep track of the z-sum */ if (scrn_pt.z > 0.0) { d_sum += (float) sqrt((double)(scrn_pt.x*scrn_pt.x + scrn_pt.y*scrn_pt.y + scrn_pt.z*scrn_pt.z)); } else /* Uh-oh: this point is behind the focus -- clip it */ { d_sum = 0.0; clipped = 1; break; } } /* Store the z-center of the polygon, which is used to determine where * the polygon will fit into the aggregate list. */ if (!clipped) polygon_node->k = d_sum/((float)polygon->npoints); else polygon_node->k = 0.0; } /*---------------------------------------------------------------------------*/