/* clip.c

   Win32 program to demonstrate z-sorted spans.

   Derived from the VC++ generic sample application.
   Tested with VC++ 2.0 running on Windows NT 3.5.

   Note: in this implementation, polygon faces must not be
   interpenetrating. Also, correct sorting is not guaranteed
   if two polygonal objects butt up against each other. In other
   words, each polygonal object must be made of a continuous,
   non-self-intersecting skin, and polygonal objects must not
   interpenetrate or touch in order for proper sorting to result.
   More complex, slower sorting is required to make those cases
   work reliably.
   
   Note: polygon processing could be considerably more efficient
   if polygons shared common edges and edges shared common vertices.
   Also, indirection to vertices could be used to avoid having to
   copy all the vertices during every clip test. Outcode-type
   testing could be used to determine completely clipped or
   unclipped polygons ahead of time, avoiding the need to clip and
   copy entirely for such polygons. Outcode-type tests work best in
   viewspace, with the frustum normalized so that the field of view
   is 90 degrees, so simple compares, rather than dot products, can
   be used to categorize points with respect to the frustum. See
   _Computer Graphics_, by Foley & van Dam, or _Procedural Elements
   of Computer Graphics_, by Rogers, for further information.
*/

#include <windows.h>   	// required for all Windows applications
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include "zsort.h" 		// specific to this program

#define INITIAL_DIB_WIDTH  	320		// initial dimensions of DIB
#define INITIAL_DIB_HEIGHT	240		//  into which we'll draw
#define MAX_POLY_VERTS      8       // assumes polygons have no more than
                                    //  four sides and are clipped a
                                    //  maximum of four times by frustum.
                                    //  Must be increased for more sides
                                    //  or more clip planes
#define MAX_SCREEN_HEIGHT   2048
#define MOVEMENT_SPEED      3.0
#define VMOVEMENT_SPEED     3.0
#define MAX_MOVEMENT_SPEED  30.0
#define PI                  3.141592
#define ROLL_SPEED          (PI/20.0)
#define PITCH_SPEED         (PI/20.0)
#define YAW_SPEED           (PI/20.0)
#define MAX_COORD           0x4000
#define NUM_FRUSTUM_PLANES  4
#define CLIP_PLANE_EPSILON  0.0001
#define MAX_SPANS           10000
#define MAX_SURFS           1000
#define MAX_EDGES           5000


typedef struct {
    double v[3];
} point_t;

typedef struct {
    double   x, y;
} point2D_t;

typedef struct {
    int x, y;
    int count;
    int color;
} span_t;

typedef struct {
    double  distance;
    point_t normal;
} plane_t;

typedef struct {
    int         color;
    int         numverts;
    point_t     verts[MAX_POLY_VERTS];
    plane_t     plane;
} polygon_t;

typedef struct surf_s {
    struct surf_s   *pnext, *pprev;
    int             color;
    int             visxstart;
    double          zinv00, zinvstepx, zinvstepy;
    int             state;
} surf_t;

typedef struct {
    int         numverts;
    point2D_t   verts[MAX_POLY_VERTS];
} polygon2D_t;

typedef struct convexobject_s {
    struct convexobject_s   *pnext;
    point_t                 center;
    int                     numpolys;
    polygon_t               *ppoly;
} convexobject_t;

typedef struct edge_s {
    int             x;
    int             xstep;
    int             leading;
    surf_t          *psurf;
    struct edge_s   *pnext, *pprev;
    struct edge_s   *pnextremove;
} edge_t;

BITMAPINFO *pbmiDIB;		// pointer to the BITMAPINFO
char *pDIB, *pDIBBase;		// pointers to DIB section we'll draw into
HBITMAP hDIBSection;        // handle of DIB section
HINSTANCE hInst;            // current instance
char szAppName[] = "Clip";  // The name of this application
char szTitle[]   = "3D clipping demo"; // The title bar text
HPALETTE hpalold, hpalDIB;
HWND hwndOutput;
int DIBWidth, DIBHeight;
int DIBPitch;
double  roll, pitch, yaw;
double  currentspeed;
point_t currentpos;
double  fieldofview, xcenter, ycenter;
double  xscreenscale, yscreenscale, maxscale;
double  maxscreenscaleinv;
int     numobjects;
double  speedscale = 1.0;
plane_t frustumplanes[NUM_FRUSTUM_PLANES];

double  mroll[3][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
double  mpitch[3][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
double  myaw[3][3] =  {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
point_t vpn, vright, vup;
point_t xaxis = {1, 0, 0};
point_t zaxis = {0, 0, 1};

polygon_t polys0[] = {
{15, 4, {{-10,10,-10}, {10,10,-10}, {10,-10,-10}, {-10,-10,-10}},
    {10, {0, 0, -1}}},
{14, 4, {{10,10,-10}, {10,10,10}, {10,-10,10}, {10,-10,-10}},
    {10, {1, 0, 0}}},
{13, 4, {{10,10,10}, {-10,10,10}, {-10,-10,10}, {10,-10,10}},
    {10, {0, 0, 1}}},
{12, 4, {{-10,10,10}, {-10,10,-10}, {-10,-10,-10}, {-10,-10,10}},
    {10, {-1, 0, 0}}},
{11, 4, {{-10,10,-10}, {-10,10,10}, {10,10,10}, {10,10,-10}},
    {10, {0, 1, 0}}},
{10, 4, {{-10,-10,-10}, {10,-10,-10}, {10,-10,10}, {-10,-10,10}},
    {10, {0, -1, 0}}},
};

polygon_t polys1[] = {
{1, 4, {{-200,0,-200}, {-200,0,200},
        {200,0,200}, {200,0,-200}}, {0, {0, 1, 0}}},
};

polygon_t polys2[] = {
{6, 4, {{0,10,0}, {20,10,0}, {10,10,-10}, {0,10,-10}},
    {10, {0, 1, 0}}},
{6, 4, {{-10,10,10}, {0,10,10}, {0,10,0}, {-10,10,0}},
    {10, {0, 1, 0}}},
{6, 4, {{0,10,0}, {0,10,-10}, {-10,10,-10}, {-10,10,0}},
    {10, {0, 1, 0}}},
{5, 4, {{0,-10,0}, {0,-10,-10}, {10,-10,-10}, {20,-10,0}},
    {10, {0, -1, 0}}},
{5, 4, {{-10,-10,10}, {-10,-10,0}, {0,-10,0}, {0,-10,10}},
    {10, {0, -1, 0}}},
{5, 4, {{-10,-10,0}, {-10,-10,-10}, {0,-10,-10}, {0,-10,0}},
    {10, {0, -1, 0}}},
{4, 4, {{-10,10,-10}, {10,10,-10}, {10,-10,-10}, {-10,-10,-10}},
    {10, {0, 0, -1}}},
{3, 4, {{10,10,-10}, {20,10,0}, {20,-10,0}, {10,-10,-10}},
    {14.14, {0.707, 0, -0.707}}},
{2, 4, {{20,10,0}, {0,10,0}, {0,-10,0}, {20,-10,0}},
    {0, {0, 0, 1}}},
{9, 4, {{0,10,0}, {0,10,10}, {0,-10,10}, {0,-10,0}},
    {0, {1, 0, 0}}},
{15, 4, {{0,10,10}, {-10,10,10}, {-10,-10,10}, {0,-10,10}},
    {10, {0, 0, 1}}},
{14, 4, {{-10,10,10}, {-10,10,-10}, {-10,-10,-10}, {-10,-10,10}},
    {10, {-1, 0, 0}}},
};

extern convexobject_t   objecthead;

convexobject_t objects[] = {
{&objects[1], {-50,0,70}, sizeof(polys2) / sizeof(polys2[0]), polys2},
{&objects[2], {0,20,70}, sizeof(polys0) / sizeof(polys0[0]), polys0},
{&objects[3], {50,0,70}, sizeof(polys0) / sizeof(polys0[0]), polys0},
{&objects[4], {-50,0,-70}, sizeof(polys2) / sizeof(polys2[0]), polys2},
{&objects[5], {0,20,-70}, sizeof(polys2) / sizeof(polys2[0]), polys2},
{&objects[6], {50,30,-70}, sizeof(polys0) / sizeof(polys0[0]), polys0},
{&objects[7], {-50,15,0}, sizeof(polys0) / sizeof(polys0[0]), polys0},
{&objects[8], {50,15,0}, sizeof(polys2) / sizeof(polys2[0]), polys2},
{&objects[9], {0,50,0}, sizeof(polys2) / sizeof(polys2[0]), polys2},
{&objects[10], {-100,100,115}, sizeof(polys2) / sizeof(polys2[0]), polys2},
{&objects[11], {-100,150,120}, sizeof(polys0) / sizeof(polys0[0]), polys0},
{&objects[12], {100,200,100}, sizeof(polys0) / sizeof(polys0[0]), polys0},
{&objects[13], {100,100,100}, sizeof(polys2) / sizeof(polys2[0]), polys2},
{&objecthead, {0,-20,0}, sizeof(polys1) / sizeof(polys1[0]), polys1},
};

// Head and tail for the object list
convexobject_t objecthead = {&objects[0]};

// Span, edge, and surface lists
span_t  spans[MAX_SPANS];
edge_t  edges[MAX_EDGES];
surf_t  surfs[MAX_SURFS];

// Bucket list of new edges to add on each scan line
edge_t  newedges[MAX_SCREEN_HEIGHT];

// Bucket list of edges to remove on each scan line
edge_t  *removeedges[MAX_SCREEN_HEIGHT];

// Head and tail for the active edge list
edge_t  edgehead;
edge_t  edgetail;

// Edge used as sentinel of new edge lists
edge_t  maxedge = {0x7FFFFFFF};

// Head/tail/sentinel/background surface of active surface stack
surf_t  surfstack;

// pointers to next available surface and edge
surf_t  *pavailsurf;
edge_t  *pavailedge;

int currentcolor;

void UpdateWorld(void);

/////////////////////////////////////////////////////////////////////
// WinMain
/////////////////////////////////////////////////////////////////////
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    MSG msg;
    HANDLE hAccelTable;

    if (!hPrevInstance) {       // Other instances of app running?
        if (!InitApplication(hInstance)) { // Initialize shared things
            return (FALSE);     // Exits if unable to initialize
        }
    }

    // Perform initializations that apply to a specific instance
    if (!InitInstance(hInstance, nCmdShow)) {
        return (FALSE);
    }

    hAccelTable = LoadAccelerators (hInstance, szAppName);

    // Acquire and dispatch messages until a WM_QUIT message is
    // received
    for (;;) {
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
			do {
	    		if (msg.message == WM_QUIT) {
					goto Done;
				}
	      		if (!TranslateAccelerator (msg.hwnd, hAccelTable,
	      		        &msg)) {
            		TranslateMessage(&msg);// xlates virt keycodes
       	        	DispatchMessage(&msg); // Dispatches msg to window
	       	   	}
			} while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE));
		}
        // Update the world
		UpdateWorld();
	}

Done:
    return (msg.wParam); // Returns the value from PostQuitMessage

    lpCmdLine; // This will prevent 'unused formal parameter' warnings
}

/////////////////////////////////////////////////////////////////////
// InitApplication
/////////////////////////////////////////////////////////////////////
BOOL InitApplication(HINSTANCE hInstance)
{
        WNDCLASS  wc;

        // Fill in window class structure with parameters that
        // describe the main window.
        wc.style         = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc   = (WNDPROC)WndProc;
        wc.cbClsExtra    = 0;
        wc.cbWndExtra    = 0;
        wc.hInstance     = hInstance;
        wc.hIcon         = LoadIcon (hInstance, szAppName);
        wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
        wc.lpszMenuName  = szAppName;
        wc.lpszClassName = szAppName;

        // Register the window class and return success/failure code.
        return (RegisterClass(&wc));
}

/////////////////////////////////////////////////////////////////////
// InitInstance
/////////////////////////////////////////////////////////////////////
BOOL InitInstance(
        HINSTANCE       hInstance,
        int             nCmdShow)
{
        HWND            hwnd; // Main window handle.
		HDC				hdc;
		INT				i, j, k;
		LOGPALETTE *	plogpal;
		PUSHORT 		pusTemp;
		HPALETTE		hpal;
		RECT			rctmp;
		int				screenx, screeny;

        // Save the instance handle in static variable, which will be
        // used in many subsequent calls from this application to
        // Windows
        hInst = hInstance; // Store inst handle in our global variable

        // Create a main window for this application instance
		DIBWidth = INITIAL_DIB_WIDTH;
		DIBHeight = INITIAL_DIB_HEIGHT;
	   	rctmp.left = 0;
		rctmp.top = 0;
		rctmp.right = DIBWidth;
		rctmp.bottom = DIBHeight;
   
	  	AdjustWindowRect(&rctmp, WS_OVERLAPPEDWINDOW, 1);

		screenx = GetSystemMetrics(SM_CXSCREEN);
		screeny = GetSystemMetrics(SM_CYSCREEN);

        hwnd = CreateWindow(
                szAppName,           // See RegisterClass() call.
                szTitle,             // Text for window title bar.
                WS_OVERLAPPEDWINDOW,// Window style.
                screenx - (rctmp.right - rctmp.left),
                screeny - (rctmp.bottom - rctmp.top),
				rctmp.right - rctmp.left,
				rctmp.bottom - rctmp.top,
                NULL,
                NULL,
                hInstance,
                NULL
        );

        // If window could not be created, return "failure"
        if (!hwnd) {
        	return (FALSE);
        }

		// Make the window visible and draw it
        ShowWindow(hwnd, nCmdShow); // Show the window
        UpdateWindow(hwnd);         // Sends WM_PAINT message

        hdc = GetDC(hwnd);

        // For 256-color mode, set up the palette for maximum speed
        // in copying to the screen. If not a 256-color mode, the
        // adapter isn't palettized, so we'll just use the default
        // palette. However, we will run a lot slower in this case
        // due to translation while copying
        if (GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) {
            // This is a 256-color palettized mode.
    		// Set up and realize our palette and the identity color
    		// table for the DIB (identity means that DIB color
            // indices and screen palette indices match exactly, so
            // GDI doesn't have to do any translation when copying
            // from DIB to screen. This helps performance a lot)
		    plogpal = malloc(sizeof(LOGPALETTE) +
	    	        256 * sizeof(PALETTEENTRY));

    		if (plogpal == NULL) {
		    	return(FALSE);
    		}

	    	// Take up all physical palette entries, to flush out
	    	// anything that's currently in the palette
	    	plogpal->palVersion = 0x300;
		    plogpal->palNumEntries = 236;
	    	for (i=0; i<236; i++) {
		    	plogpal->palPalEntry[i].peRed = i;
			    plogpal->palPalEntry[i].peGreen = 0;
    			plogpal->palPalEntry[i].peBlue = 0;
	    		plogpal->palPalEntry[i].peFlags =
		    	        PC_RESERVED | PC_NOCOLLAPSE;
    		}

	    	hpal = CreatePalette(plogpal);

    		if (hpal == 0) {
		    	return(FALSE);
    		}

		    hpalold = SelectPalette(hdc, hpal, FALSE);

    		if (hpalold == 0) {
		    	return(FALSE);
    		}

    		if (RealizePalette(hdc) != plogpal->palNumEntries) {
			    return(FALSE);
		    }

    		if (SelectPalette(hdc, hpalold, FALSE) == 0) {
		    	return(FALSE);
    		}

	    	if (!DeleteObject(hpal)) {
			    return(FALSE);
    		}

		    // Now set up the 6value-6value-6value RGB palette,
		    // followed by 20 gray levels, that we want to work with
		    // into the physical palette
	    	for (i=0; i<6; i++) {
			    for (j=0; j<6; j++) {
	    			for (k=0; k<6; k++) {
			    		plogpal->palPalEntry[i*36+j*6+k].peRed =
                                i*255/6;
					    plogpal->palPalEntry[i*36+j*6+k].peGreen =
					            j*255/6;
    					plogpal->palPalEntry[i*36+j*6+k].peBlue =
	   				            k*255/6;
	    				plogpal->palPalEntry[i*36+j*6+k].peFlags =
					        PC_RESERVED | PC_NOCOLLAPSE;
		    		}
    			}
	    	}

		    for (i=0; i<20; i++) {
    			plogpal->palPalEntry[i+216].peRed = i*255/20;
	    		plogpal->palPalEntry[i+216].peGreen = i*255/20;
			    plogpal->palPalEntry[i+216].peBlue = i*255/20;
    			plogpal->palPalEntry[i+216].peFlags =
	    		        PC_RESERVED | PC_NOCOLLAPSE;
		    }

    		hpal = CreatePalette(plogpal);

	    	if (hpal == 0) {
			    return(FALSE);
    		}

	    	if (SelectPalette(hdc, hpal, FALSE) == 0) {
			    return(FALSE);
    		}

	    	if (RealizePalette(hdc) != plogpal->palNumEntries) {
			    return(FALSE);
    		}

		    // Get back the 256 colors now in the physical palette,
		    // which are the 236 we just selected, plus the 20 static
		    // colors
    		if (GetSystemPaletteEntries(hdc, 0, 256,
    		        plogpal->palPalEntry) != 256) {
	    		return(FALSE);
		    }

    		for (i=10; i<246; i++) {
		    	plogpal->palPalEntry[i].peFlags =
			            PC_RESERVED | PC_NOCOLLAPSE;
    		}

	    	// Now create a logical palette that exactly matches the
		    // physical palette, and realize it. This is the palette
    		// into which the DIB pixel values will be indices
    		plogpal->palNumEntries = 256;

	    	hpalDIB = CreatePalette(plogpal);

    		if (hpalDIB == 0)
		    	return(FALSE);

	    	if (SelectPalette(hdc, hpalDIB, FALSE) == 0)
			    return(FALSE);

	    	DeleteObject(hpal);

    		if (RealizePalette(hdc) != plogpal->palNumEntries)
		    	return(FALSE);

	    	if (SelectPalette(hdc, hpalold, FALSE) == FALSE)
			    return(FALSE);

       		free(plogpal);
        }

		// Finally, set up the DIB section
		pbmiDIB = malloc(sizeof(BITMAPINFO) - 4 + 256*sizeof(USHORT));

		if (pbmiDIB == NULL)
			return(FALSE);


		pbmiDIB->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
		pbmiDIB->bmiHeader.biWidth = DIBWidth;
		pbmiDIB->bmiHeader.biHeight = DIBHeight;
		pbmiDIB->bmiHeader.biPlanes = 1;
		pbmiDIB->bmiHeader.biBitCount = 8;
		pbmiDIB->bmiHeader.biCompression = BI_RGB;
		pbmiDIB->bmiHeader.biSizeImage = 0;
		pbmiDIB->bmiHeader.biXPelsPerMeter = 0;
		pbmiDIB->bmiHeader.biYPelsPerMeter = 0;
		pbmiDIB->bmiHeader.biClrUsed = 256;
		pbmiDIB->bmiHeader.biClrImportant = 256;

		pusTemp = (PUSHORT) pbmiDIB->bmiColors;

		for (i=0; i<256; i++) {
			*pusTemp++ = i;
		}

        hDIBSection = CreateDIBSection (hdc, pbmiDIB, DIB_PAL_COLORS,
                          &pDIBBase, NULL, 0);

        if (!hDIBSection) {
            free(pbmiDIB);
            return(FALSE);
        }

        if (pbmiDIB->bmiHeader.biHeight > 0)
        {
            pDIB = (pDIBBase + (DIBHeight - 1) * DIBWidth);
            DIBPitch = -DIBWidth;   // bottom-up
        }
        else
        {
            pDIB = pDIBBase;
            DIBPitch = DIBWidth;    // top-down
        }

		// Clear the DIB
		memset(pDIBBase, 0, DIBWidth*DIBHeight);

		ReleaseDC(hwnd, hdc);

		hwndOutput = hwnd;

        // Set the initial location, direction, and speed
        roll = 0.0;
        pitch = 0.0;
        yaw = 0.0;
        currentspeed = 0.0;
        currentpos.v[0] = 0.0;
        currentpos.v[1] = 0.0;
        currentpos.v[2] = 0.0;
        fieldofview = 2.0;
        xscreenscale = DIBWidth / fieldofview;
        yscreenscale = DIBHeight / fieldofview;
        maxscale = max(xscreenscale, yscreenscale);
        maxscreenscaleinv = 1.0 / maxscale;
        xcenter = DIBWidth / 2.0 - 0.5;
        ycenter = DIBHeight / 2.0 - 0.5;

        numobjects = sizeof(objects) / sizeof(objects[0]);

        return (TRUE);              // We succeeded...
}

/////////////////////////////////////////////////////////////////////
// WndProc
/////////////////////////////////////////////////////////////////////
LRESULT CALLBACK WndProc(
                HWND hwnd,         // window handle
                UINT message,      // type of message
                WPARAM uParam,     // additional information
                LPARAM lParam)     // additional information
{
    int wmId, wmEvent;
	UINT fwSizeType;
	int oldDIBWidth, oldDIBHeight;
    HBITMAP holdDIBSection;
    HDC hdc;

    switch (message) {

    case WM_COMMAND:  // message: command from application menu

    	wmId    = LOWORD(uParam);
        wmEvent = HIWORD(uParam);

        switch (wmId) {

     	case IDM_EXIT:
        	DestroyWindow (hwnd);
            break;

        default:
        	return (DefWindowProc(hwnd, message, uParam, lParam));
        }
        break;

	case WM_KEYDOWN:
		switch (uParam) {

        case VK_DOWN:
            currentspeed -= MOVEMENT_SPEED * speedscale;
            if (currentspeed < -(MAX_MOVEMENT_SPEED * speedscale))
                currentspeed = -(MAX_MOVEMENT_SPEED * speedscale);
			break;

        case VK_UP:
            currentspeed += MOVEMENT_SPEED * speedscale;
            if (currentspeed > (MAX_MOVEMENT_SPEED * speedscale))
                currentspeed = (MAX_MOVEMENT_SPEED * speedscale);
			break;

        case 'N':
            roll += ROLL_SPEED * speedscale;
            if (roll >= (PI * 2))
                roll -= PI * 2;
            break;

        case 'M':
            roll -= ROLL_SPEED * speedscale;
            if (roll < 0)
                roll += PI * 2;
            break;

        case 'A':
            pitch -= PITCH_SPEED * speedscale;
            if (pitch < 0)
                pitch += PI * 2;
            break;

        case 'Z':
            pitch += PITCH_SPEED * speedscale;
            if (pitch >= (PI * 2))
                pitch -= PI * 2;
            break;

        case 'D':
            currentpos.v[1] += VMOVEMENT_SPEED;
            break;

        case 'C':
            currentpos.v[1] -= VMOVEMENT_SPEED;
            break;

        case VK_LEFT:
            yaw -= YAW_SPEED * speedscale;
            if (yaw < 0)
                yaw += PI * 2;
			break;

        case VK_RIGHT:
            yaw += YAW_SPEED * speedscale;
            if (yaw >= (PI * 2))
                yaw -= PI * 2;
			break;

		default:
			break;
		}
		return(0);

	case WM_KEYUP:
		switch (uParam) {

		case VK_SUBTRACT:
            fieldofview *= 0.9;
            xscreenscale = DIBWidth / fieldofview;
            yscreenscale = DIBHeight / fieldofview;
            maxscale = max(xscreenscale, yscreenscale);
			break;

		case VK_ADD:
            fieldofview *= 1.1;
            xscreenscale = DIBWidth / fieldofview;
            yscreenscale = DIBHeight / fieldofview;
            maxscale = max(xscreenscale, yscreenscale);
			break;

        case 'F':
            speedscale *= 1.1;
            break;

        case 'S':
            speedscale *= 0.9;
            break;

		default:
			break;
		}
		return(0);

	case WM_SIZE:	// window size changed
		fwSizeType = uParam;
		if (fwSizeType != SIZE_MINIMIZED) {
            // Skip when this is called before the first DIB
            // section is created
            if (hDIBSection == 0)
                break;

			oldDIBWidth = DIBWidth;
			oldDIBHeight = DIBHeight;

			DIBWidth = LOWORD(lParam);
			DIBWidth = (DIBWidth + 3) & ~3;
			DIBHeight = HIWORD(lParam);

            if ((DIBHeight < 10) || (DIBWidth < 10))
            {
            // Keep the DIB section big enough so we don't start
            // drawing outside the DIB (the window can get smaller,
            // but we don't really care, and GDI will clip the
            // blts for us)
    			DIBWidth = oldDIBWidth;
    			DIBHeight = oldDIBHeight;
            }

			// Resize the DIB section to the new size
            holdDIBSection = hDIBSection;
			pbmiDIB->bmiHeader.biWidth = DIBWidth;
			pbmiDIB->bmiHeader.biHeight = DIBHeight;

            hdc = GetDC(hwnd);
            hDIBSection = CreateDIBSection (hdc, pbmiDIB,
                    DIB_PAL_COLORS, &pDIBBase, NULL, 0);

            if (hDIBSection) {
                // Success
                DeleteObject(holdDIBSection);

                if (pbmiDIB->bmiHeader.biHeight > 0)
                {
                    pDIB = (pDIBBase + (DIBHeight - 1) * DIBWidth);
                    DIBPitch = -DIBWidth;   // bottom-up
                }
                else
                {
                    pDIB = pDIBBase;
                    DIBPitch = DIBWidth;    // top-down
                }

                xscreenscale = DIBWidth / fieldofview;
                yscreenscale = DIBHeight / fieldofview;
                maxscale = max(xscreenscale, yscreenscale);
                xcenter = DIBWidth / 2.0 - 0.5;
                ycenter = DIBHeight / 2.0 - 0.5;
            } else {
                // Failed, just use old size
    			pbmiDIB->bmiHeader.biWidth = oldDIBWidth;
	    		pbmiDIB->bmiHeader.biHeight = oldDIBHeight;
    			DIBWidth = oldDIBWidth;
    			DIBHeight = oldDIBHeight;
            }

			// Clear the DIB
			memset(pDIBBase, 0, DIBWidth*DIBHeight);
		}
		break;

	case WM_DESTROY:  // message: window being destroyed
		free(pbmiDIB);
        DeleteObject(hDIBSection);
		DeleteObject(hpalold);
                        
        PostQuitMessage(0);
        break;

    default:          // Passes it on if unproccessed
		return (DefWindowProc(hwnd, message, uParam, lParam));
    }
    return (0);
}

/////////////////////////////////////////////////////////////////////
// 3-D dot product.
/////////////////////////////////////////////////////////////////////
double DotProduct(point_t *vec1, point_t *vec2)
{
    return vec1->v[0] * vec2->v[0] +
           vec1->v[1] * vec2->v[1] +
           vec1->v[2] * vec2->v[2];
}

/////////////////////////////////////////////////////////////////////
// 3-D cross product.
/////////////////////////////////////////////////////////////////////
void CrossProduct(point_t *in1, point_t *in2, point_t *out)
{
    out->v[0] = in1->v[1] * in2->v[2] - in1->v[2] * in2->v[1];
    out->v[1] = in1->v[2] * in2->v[0] - in1->v[0] * in2->v[2];
    out->v[2] = in1->v[0] * in2->v[1] - in1->v[1] * in2->v[0];
}

/////////////////////////////////////////////////////////////////////
// Concatenate two 3x3 matrices.
/////////////////////////////////////////////////////////////////////
void MConcat(double in1[3][3], double in2[3][3], double out[3][3])
{
    int     i, j;

    for (i=0 ; i<3 ; i++)
    {
        for (j=0 ; j<3 ; j++)
        {
            out[i][j] = in1[i][0] * in2[0][j] +
                        in1[i][1] * in2[1][j] +
                        in1[i][2] * in2[2][j];
        }
    }
}

/////////////////////////////////////////////////////////////////////
// Project viewspace polygon vertices into screen coordinates.
// Note that the y axis goes up in worldspace and viewspace, but
// goes down in screenspace.
/////////////////////////////////////////////////////////////////////
void ProjectPolygon (polygon_t *ppoly, polygon2D_t *ppoly2D)
{
    int     i;
    double  zrecip;

    for (i=0 ; i<ppoly->numverts ; i++)
    {
        zrecip = 1.0 / ppoly->verts[i].v[2];
        ppoly2D->verts[i].x =
                ppoly->verts[i].v[0] * zrecip * maxscale + xcenter;
        ppoly2D->verts[i].y = ycenter -
                (ppoly->verts[i].v[1] * zrecip * maxscale);
    }

    ppoly2D->numverts = ppoly->numverts;
}

/////////////////////////////////////////////////////////////////////
// Move the view position and set the world->view transform.
/////////////////////////////////////////////////////////////////////
void UpdateViewPos()
{
    int     i;
    point_t motionvec;
    double  s, c, mtemp1[3][3], mtemp2[3][3];

    // Move in the view direction, across the x-y plane, as if
    // walking. This approach moves slower when looking up or
    // down at more of an angle
    motionvec.v[0] = DotProduct(&vpn, &xaxis);
    motionvec.v[1] = 0.0;
    motionvec.v[2] = DotProduct(&vpn, &zaxis);

    for (i=0 ; i<3 ; i++)
    {
        currentpos.v[i] += motionvec.v[i] * currentspeed;
        if (currentpos.v[i] > MAX_COORD)
            currentpos.v[i] = MAX_COORD;
        if (currentpos.v[i] < -MAX_COORD)
            currentpos.v[i] = -MAX_COORD;
    }

    // Set up the world-to-view rotation.
    // Note: much of the work done in concatenating these matrices
    // can be factored out, since it contributes nothing to the
    // final result; multiply the three matrices together on paper
    // to generate a minimum equation for each of the 9 final elements
    s = sin(roll);
    c = cos(roll);
    mroll[0][0] = c;
    mroll[0][1] = s;
    mroll[1][0] = -s;
    mroll[1][1] = c;

    s = sin(pitch);
    c = cos(pitch);
    mpitch[1][1] = c;
    mpitch[1][2] = s;
    mpitch[2][1] = -s;
    mpitch[2][2] = c;

    s = sin(yaw);
    c = cos(yaw);
    myaw[0][0] = c;
    myaw[0][2] = -s;
    myaw[2][0] = s;
    myaw[2][2] = c;

    MConcat(mroll, myaw, mtemp1);
    MConcat(mpitch, mtemp1, mtemp2);

    // Break out the rotation matrix into vright, vup, and vpn.
    // We could work directly with the matrix; breaking it out
    // into three vectors is just to make things clearer
    for (i=0 ; i<3 ; i++)
    {
        vright.v[i] = mtemp2[0][i];
        vup.v[i] = mtemp2[1][i];
        vpn.v[i] = mtemp2[2][i];
    }

    // Simulate crude friction
    if (currentspeed > (MOVEMENT_SPEED * speedscale / 2.0))
        currentspeed -= MOVEMENT_SPEED * speedscale / 2.0;
    else if (currentspeed < -(MOVEMENT_SPEED * speedscale / 2.0))
        currentspeed += MOVEMENT_SPEED * speedscale / 2.0;
    else
        currentspeed = 0.0;
}

/////////////////////////////////////////////////////////////////////
// Rotate a vector from viewspace to worldspace.
/////////////////////////////////////////////////////////////////////
void BackRotateVector(point_t *pin, point_t *pout)
{
    int     i;

    // Rotate into the world orientation
    for (i=0 ; i<3 ; i++)
    {
        pout->v[i] = pin->v[0] * vright.v[i] +
                     pin->v[1] * vup.v[i] +
                     pin->v[2] * vpn.v[i];
    }
}

/////////////////////////////////////////////////////////////////////
// Transform a point from worldspace to viewspace.
/////////////////////////////////////////////////////////////////////
void TransformPoint(point_t *pin, point_t *pout)
{
    int     i;
    point_t tvert;

    // Translate into a viewpoint-relative coordinate
    for (i=0 ; i<3 ; i++)
    {
        tvert.v[i] = pin->v[i] - currentpos.v[i];
    }

    // Rotate into the view orientation
    pout->v[0] = DotProduct(&tvert, &vright);
    pout->v[1] = DotProduct(&tvert, &vup);
    pout->v[2] = DotProduct(&tvert, &vpn);
}

/////////////////////////////////////////////////////////////////////
// Transform a polygon from worldspace to viewspace.
/////////////////////////////////////////////////////////////////////
void TransformPolygon(polygon_t *pinpoly, polygon_t *poutpoly)
{
    int     i;

    for (i=0 ; i<pinpoly->numverts ; i++)
    {
        TransformPoint(&pinpoly->verts[i], &poutpoly->verts[i]);
    }

    poutpoly->numverts = pinpoly->numverts;
}

/////////////////////////////////////////////////////////////////////
// Returns true if polygon faces the viewpoint, assuming a clockwise
// winding of vertices as seen from the front.
/////////////////////////////////////////////////////////////////////
int PolyFacesViewer(polygon_t *ppoly, plane_t *pplane)
{
    int     i;
    point_t viewvec;

    for (i=0 ; i<3 ; i++)
    {
        viewvec.v[i] = ppoly->verts[0].v[i] - currentpos.v[i];
    }

    // Use an epsilon here so we don't get polygons tilted so
    // sharply that the gradients are unusable or invalid
    if (DotProduct (&viewvec, &pplane->normal) < -0.01)
        return 1;
    else
        return 0;
}

/////////////////////////////////////////////////////////////////////
// Set up a clip plane with the specified normal.
/////////////////////////////////////////////////////////////////////
void SetWorldspaceClipPlane(point_t *normal, plane_t *plane)
{

    // Rotate the plane normal into worldspace
    BackRotateVector(normal, &plane->normal);

    plane->distance = DotProduct(&currentpos, &plane->normal) +
            CLIP_PLANE_EPSILON;
}

/////////////////////////////////////////////////////////////////////
// Set up the planes of the frustum, in worldspace coordinates.
/////////////////////////////////////////////////////////////////////
void SetUpFrustum(void)
{
    double  angle, s, c;
    point_t normal;

    angle = atan(2.0 / fieldofview * maxscale / xscreenscale);
    s = sin(angle);
    c = cos(angle);

    // Left clip plane
    normal.v[0] = s;
    normal.v[1] = 0;
    normal.v[2] = c;
    SetWorldspaceClipPlane(&normal, &frustumplanes[0]);

    // Right clip plane
    normal.v[0] = -s;
    SetWorldspaceClipPlane(&normal, &frustumplanes[1]);

    angle = atan(2.0 / fieldofview * maxscale / yscreenscale);
    s = sin(angle);
    c = cos(angle);

    // Bottom clip plane
    normal.v[0] = 0;
    normal.v[1] = s;
    normal.v[2] = c;
    SetWorldspaceClipPlane(&normal, &frustumplanes[2]);

    // Top clip plane
    normal.v[1] = -s;
    SetWorldspaceClipPlane(&normal, &frustumplanes[3]);
}

/////////////////////////////////////////////////////////////////////
// Clip a polygon to a plane.
/////////////////////////////////////////////////////////////////////
int ClipToPlane(polygon_t *pin, plane_t *pplane, polygon_t *pout)
{
    int     i, j, nextvert, curin, nextin;
    double  curdot, nextdot, scale;
    point_t *pinvert, *poutvert;

    pinvert = pin->verts;
    poutvert = pout->verts;

    curdot = DotProduct(pinvert, &pplane->normal);
    curin = (curdot >= pplane->distance);

    for (i=0 ; i<pin->numverts ; i++)
    {
        nextvert = (i + 1) % pin->numverts;

        // Keep the current vertex if it's inside the plane
        if (curin)
            *poutvert++ = *pinvert;

        nextdot = DotProduct(&pin->verts[nextvert], &pplane->normal);
        nextin = (nextdot >= pplane->distance);

        // Add a clipped vertex if one end of the current edge is
        // inside the plane and the other is outside
        if (curin != nextin)
        {
            scale = (pplane->distance - curdot) /
                    (nextdot - curdot);
            for (j=0 ; j<3 ; j++)
            {
                poutvert->v[j] = pinvert->v[j] +
                    ((pin->verts[nextvert].v[j] - pinvert->v[j]) *
                     scale);
            }
            poutvert++;
        }

        curdot = nextdot;
        curin = nextin;
        pinvert++;
    }

    pout->numverts = poutvert - pout->verts;
    if (pout->numverts < 3)
        return 0;

    return 1;
}

/////////////////////////////////////////////////////////////////////
// Clip a polygon to the frustum.
/////////////////////////////////////////////////////////////////////
int ClipToFrustum(polygon_t *pin, polygon_t *pout)
{
    int         i, curpoly;
    polygon_t   tpoly[2], *ppoly;

    curpoly = 0;
    ppoly = pin;

    for (i=0 ; i<(NUM_FRUSTUM_PLANES-1); i++)
    {
        if (!ClipToPlane(ppoly,
                         &frustumplanes[i],
                         &tpoly[curpoly]))
        {
            return 0;
        }
        ppoly = &tpoly[curpoly];
        curpoly ^= 1;
    }

    return ClipToPlane(ppoly,
                       &frustumplanes[NUM_FRUSTUM_PLANES-1],
                       pout);
}

/////////////////////////////////////////////////////////////////////
// Add the polygon's edges to the global edge table.
/////////////////////////////////////////////////////////////////////
void AddPolygonEdges (plane_t *plane, polygon2D_t *screenpoly)
{
    double  distinv, deltax, deltay, slope;
    int     i, nextvert, numverts, temp, topy, bottomy, height;
    edge_t  *pedge;

    numverts = screenpoly->numverts;

    // Clamp the polygon's vertices just in case some very near
    // points have wandered out of range due to floating-point
    // imprecision
    for (i=0 ; i<numverts ; i++)
    {
        if (screenpoly->verts[i].x < -0.5)
            screenpoly->verts[i].x = -0.5;
        if (screenpoly->verts[i].x > ((double)DIBWidth - 0.5))
            screenpoly->verts[i].x = (double)DIBWidth - 0.5;
        if (screenpoly->verts[i].y < -0.5)
            screenpoly->verts[i].y = -0.5;
        if (screenpoly->verts[i].y > ((double)DIBHeight - 0.5))
            screenpoly->verts[i].y = (double)DIBHeight - 0.5;
                }

    // Add each edge in turn
    for (i=0 ; i<numverts ; i++)
    {
        nextvert = i + 1;
        if (nextvert >= numverts)
            nextvert = 0;

        topy = (int)ceil(screenpoly->verts[i].y);
        bottomy = (int)ceil(screenpoly->verts[nextvert].y);
        height = bottomy - topy;
        if (height == 0)
            continue;       // doesn't cross any scan lines
        if (height < 0)
        {
            // Leading edge
            temp = topy;
            topy = bottomy;
            bottomy = temp;

            pavailedge->leading = 1;

            deltax = screenpoly->verts[i].x -
                     screenpoly->verts[nextvert].x;
            deltay = screenpoly->verts[i].y -
                     screenpoly->verts[nextvert].y;
            slope = deltax / deltay;

            // Edge coordinates are in 16.16 fixed point
            pavailedge->xstep = (int)(slope * (float)0x10000);
            pavailedge->x = (int)((screenpoly->verts[nextvert].x +
                ((float)topy - screenpoly->verts[nextvert].y) *
                slope) * (float)0x10000);
        }
        else
        {
            // Trailing edge
            pavailedge->leading = 0;

            deltax = screenpoly->verts[nextvert].x -
                     screenpoly->verts[i].x;
            deltay = screenpoly->verts[nextvert].y -
                     screenpoly->verts[i].y;
            slope = deltax / deltay;

            // Edge coordinates are in 16.16 fixed point
            pavailedge->xstep = (int)(slope * (float)0x10000);
            pavailedge->x = (int)((screenpoly->verts[i].x +
                ((float)topy - screenpoly->verts[i].y) * slope) *
                (float)0x10000);
        }

        // Put the edge on the list to be added on top scan
        pedge = &newedges[topy];
        while (pedge->pnext->x < pavailedge->x)
            pedge = pedge->pnext;
        pavailedge->pnext = pedge->pnext;
        pedge->pnext = pavailedge;

        // Put the edge on the list to be removed after final scan
        pavailedge->pnextremove = removeedges[bottomy - 1];
        removeedges[bottomy - 1] = pavailedge;

        // Associate the edge with the surface we'll create for
        // this polygon
        pavailedge->psurf = pavailsurf;

        // Make sure we don't overflow the edge array
        if (pavailedge < &edges[MAX_EDGES])
            pavailedge++;
    }

    // Create the surface, so we'll know how to sort and draw from
    // the edges
    pavailsurf->state = 0;
    pavailsurf->color = currentcolor;

    // Set up the 1/z gradients from the polygon, calculating the
    // base value at screen coordinate 0,0 so we can use screen
    // coordinates directly when calculating 1/z from the gradients
    distinv = 1.0 / plane->distance;
    pavailsurf->zinvstepx = plane->normal.v[0] * distinv *
            maxscreenscaleinv * (fieldofview / 2.0);
    pavailsurf->zinvstepy = -plane->normal.v[1] * distinv *
            maxscreenscaleinv * (fieldofview / 2.0);
    pavailsurf->zinv00 = plane->normal.v[2] * distinv -
            xcenter * pavailsurf->zinvstepx -
            ycenter * pavailsurf->zinvstepy;

    // Make sure we don't overflow the surface array
    if (pavailsurf < &surfs[MAX_SURFS])
        pavailsurf++;
}

/////////////////////////////////////////////////////////////////////
// Scan all the edges in the global edge table into spans.
/////////////////////////////////////////////////////////////////////
void ScanEdges (void)
{
    int     x, y;
    double  fx, fy, zinv, zinv2;
    edge_t  *pedge, *pedge2, *ptemp;
    span_t  *pspan;
    surf_t  *psurf, *psurf2;

    pspan = spans;

    // Set up the active edge list as initially empty, containing
    // only the sentinels (which are also the background fill). Most
    // of these fields could be set up just once at start-up
    edgehead.pnext = &edgetail;
    edgehead.pprev = NULL;
    edgehead.x = -0xFFFF;           // left edge of screen
    edgehead.leading = 1;
    edgehead.psurf = &surfstack;

    edgetail.pnext = NULL;          // mark edge of list
    edgetail.pprev = &edgehead;
    edgetail.x = DIBWidth << 16;    // right edge of screen
    edgetail.leading = 0;
    edgetail.psurf = &surfstack;

    // The background surface is the entire stack initially, and
    // is infinitely far away, so everything sorts in front of it.
    // This could be set just once at start-up
    surfstack.pnext = surfstack.pprev = &surfstack;
    surfstack.color = 0;
    surfstack.zinv00 = -999999.0;
    surfstack.zinvstepx = surfstack.zinvstepy = 0.0;

    for (y=0 ; y<DIBHeight ; y++)
    {
        fy = (double)y;

        // Sort in any edges that start on this scan
        pedge = newedges[y].pnext;
        pedge2 = &edgehead;
        while (pedge != &maxedge)
        {
            while (pedge->x > pedge2->pnext->x)
                pedge2 = pedge2->pnext;

            ptemp = pedge->pnext;
            pedge->pnext = pedge2->pnext;
            pedge->pprev = pedge2;
            pedge2->pnext->pprev = pedge;
            pedge2->pnext = pedge;

            pedge2 = pedge;
            pedge = ptemp;
        }

        // Scan out the active edges into spans

        // Start out with the left background edge already inserted,
        // and the surface stack containing only the background
        surfstack.state = 1;
        surfstack.visxstart = 0;

        for (pedge=edgehead.pnext ; pedge ; pedge=pedge->pnext)
        {
            psurf = pedge->psurf;

            if (pedge->leading)
            {
                // It's a leading edge. Figure out where it is
                // relative to the current surfaces and insert in
                // the surface stack; if it's on top, emit the span
                // for the current top.
                // First, make sure the edges don't cross
                if (++psurf->state == 1)
                {
                    fx = (double)pedge->x * (1.0 / (double)0x10000);
                    // Calculate the surface's 1/z value at this pixel
                    zinv = psurf->zinv00 + psurf->zinvstepx * fx +
                            psurf->zinvstepy * fy;

                    // See if that makes it a new top surface
                    psurf2 = surfstack.pnext;
                    zinv2 = psurf2->zinv00 + psurf2->zinvstepx * fx +
                            psurf2->zinvstepy * fy;
                    if (zinv >= zinv2)
                    {
                        // It's a new top surface
                        // emit the span for the current top
                        x = (pedge->x + 0xFFFF) >> 16;
                        pspan->count = x - psurf2->visxstart;
                        if (pspan->count > 0)
                        {
                            pspan->y = y;
                            pspan->x = psurf2->visxstart;
                            pspan->color = psurf2->color;

                            // Make sure we don't overflow
                            // the span array
                            if (pspan < &spans[MAX_SPANS])
                                pspan++;
                        }

                        psurf->visxstart = x;

                        // Add the edge to the stack
                        psurf->pnext = psurf2;
                        psurf2->pprev = psurf;
                        surfstack.pnext = psurf;
                        psurf->pprev = &surfstack;
                    }
                    else
                    {
                        // Not a new top; sort into the surface stack.
                        // Guaranteed to terminate due to sentinel
                        // background surface
                        do
                        {
                            psurf2 = psurf2->pnext;
                            zinv2 = psurf2->zinv00 +
                                    psurf2->zinvstepx * fx +
                                    psurf2->zinvstepy * fy;
                        } while (zinv < zinv2);

                        // Insert the surface into the stack
                        psurf->pnext = psurf2;
                        psurf->pprev = psurf2->pprev;
                        psurf2->pprev->pnext = psurf;
                        psurf2->pprev = psurf;
                    }
                }
            }
            else
            {
                // It's a trailing edge; if this was the top surface,
                // emit the span and remove it.
                // First, make sure the edges didn't cross
                if (--psurf->state == 0)
                {
                    if (surfstack.pnext == psurf)
                    {
                        // It's on top, emit the span
                        x = ((pedge->x + 0xFFFF) >> 16);
                        pspan->count = x - psurf->visxstart;
                        if (pspan->count > 0)
                        {
                            pspan->y = y;
                            pspan->x = psurf->visxstart;
                            pspan->color = psurf->color;

                            // Make sure we don't overflow
                            // the span array
                            if (pspan < &spans[MAX_SPANS])
                                pspan++;
                        }

                        psurf->pnext->visxstart = x;
                    }

                    // Remove the surface from the stack
                    psurf->pnext->pprev = psurf->pprev;
                    psurf->pprev->pnext = psurf->pnext;
                }
            }
        }

        // Remove edges that are done
        pedge = removeedges[y];
        while (pedge)
        {
            pedge->pprev->pnext = pedge->pnext;
            pedge->pnext->pprev = pedge->pprev;
            pedge = pedge->pnextremove;
        }

        // Step the remaining edges one scan line, and re-sort
        for (pedge=edgehead.pnext ; pedge != &edgetail ; )
        {
            ptemp = pedge->pnext;

            // Step the edge
            pedge->x += pedge->xstep;

            // Move the edge back to the proper sorted location,
            // if necessary
            while (pedge->x < pedge->pprev->x)
            {
                pedge2 = pedge->pprev;
                pedge2->pnext = pedge->pnext;
                pedge->pnext->pprev = pedge2;
                pedge2->pprev->pnext = pedge;
                pedge->pprev = pedge2->pprev;
                pedge->pnext = pedge2;
                pedge2->pprev = pedge;
            }

            pedge = ptemp;
        }
    }

    pspan->x = -1;  // mark the end of the list
}

/////////////////////////////////////////////////////////////////////
// Draw all the spans that were scanned out.
/////////////////////////////////////////////////////////////////////
void DrawSpans (void)
{
    span_t  *pspan;

    for (pspan=spans ; pspan->x != -1 ; pspan++)
    {
        memset (pDIB + (DIBPitch * pspan->y) + pspan->x,
                pspan->color,
                pspan->count);
    }
}

/////////////////////////////////////////////////////////////////////
// Clear the lists of edges to add and remove on each scan line.
/////////////////////////////////////////////////////////////////////
void ClearEdgeLists(void)
{
    int i;

    for (i=0 ; i<DIBHeight ; i++)
    {
        newedges[i].pnext = &maxedge;
        removeedges[i] = NULL;
    }
}

/////////////////////////////////////////////////////////////////////
// Render the current state of the world to the screen.
/////////////////////////////////////////////////////////////////////
void UpdateWorld()
{
	HPALETTE        holdpal;
    HDC             hdcScreen, hdcDIBSection;
    HBITMAP         holdbitmap;
    polygon2D_t     screenpoly;
    polygon_t       *ppoly, tpoly0, tpoly1, tpoly2;
    convexobject_t  *pobject;
    int             i, j, k;
    plane_t         plane;
    point_t         tnormal;

    UpdateViewPos();
    SetUpFrustum();
    ClearEdgeLists();
    pavailsurf = surfs;
    pavailedge = edges;


    // Draw all visible faces in all objects
    pobject = objecthead.pnext;

    while (pobject != &objecthead)
    {
        ppoly = pobject->ppoly;

        for (i=0 ; i<pobject->numpolys ; i++)
        {
            // Move the polygon relative to the object center
            tpoly0.numverts = ppoly[i].numverts;
            for (j=0 ; j<tpoly0.numverts ; j++)
            {
                for (k=0 ; k<3 ; k++)
                    tpoly0.verts[j].v[k] = ppoly[i].verts[j].v[k] +
                            pobject->center.v[k];
            }

            if (PolyFacesViewer(&tpoly0, &ppoly[i].plane))
            {
                if (ClipToFrustum(&tpoly0, &tpoly1))
                {
                    currentcolor = ppoly[i].color;
                    TransformPolygon (&tpoly1, &tpoly2);
                    ProjectPolygon (&tpoly2, &screenpoly);

                    // Move the polygon's plane into viewspace
                    // First move it into worldspace (object relative)
                    tnormal = ppoly[i].plane.normal;
                    plane.distance = ppoly[i].plane.distance +
                        DotProduct (&pobject->center, &tnormal);
                    // Now transform it into viewspace
                    // Determine the distance from the viewpont
                    plane.distance -=
                          DotProduct (&currentpos, &tnormal);
                    // Rotate the normal into view orientation
                    plane.normal.v[0] =
                            DotProduct (&tnormal, &vright);
                    plane.normal.v[1] =
                            DotProduct (&tnormal, &vup);
                    plane.normal.v[2] =
                            DotProduct (&tnormal, &vpn);

                    AddPolygonEdges (&plane, &screenpoly);
                }
            }
        }

        pobject = pobject->pnext;
    }

    ScanEdges ();
    DrawSpans ();

	// We've drawn the frame; copy it to the screen
	hdcScreen = GetDC(hwndOutput);
	holdpal = SelectPalette(hdcScreen, hpalDIB, FALSE);
	RealizePalette(hdcScreen);

    hdcDIBSection = CreateCompatibleDC(hdcScreen);
    holdbitmap = SelectObject(hdcDIBSection, hDIBSection);

    BitBlt(hdcScreen, 0, 0, DIBWidth, DIBHeight, hdcDIBSection,
           0, 0, SRCCOPY);

	SelectPalette(hdcScreen, holdpal, FALSE);
	ReleaseDC(hwndOutput, hdcScreen);
    SelectObject(hdcDIBSection, holdbitmap);
    DeleteDC(hdcDIBSection);
}
