/* 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 <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <assert.h>
#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;
}
/*---------------------------------------------------------------------------*/
syntax highlighted by Code2HTML, v. 0.9.1