/*****************************************************************************
*   A Windows NT driver.						     *
*									     *
* Written by:  Gershon Elber				Ver 0.1, June 1993.  *
*****************************************************************************/

#include <stdio.h>
#include <windows.h>
#include <process.h>
#include "irit_sm.h"
#include "genmat.h"
#include "iritprsr.h"
#include "allocate.h"
#include "attribut.h"
#include "ip_cnvrt.h"
#include "cagd_lib.h"
#include "iritgrap.h"
#include "irit_soc.h"

#define APP_CLASS		"wnddrvs"
#define APP_TITLE		"Irit Display Device - Window NT"
#define APP_MENU		1000
#define APP_VIEW_CLASS		"IritViewClass"
#define APP_TRANS_CLASS		"IritTransClass"

#define DEFAULT_TRANS_WIDTH	200
#define DEFAULT_TRANS_HEIGHT	500
#define DEFAULT_VIEW_WIDTH	400
#define DEFAULT_VIEW_HEIGHT	400
                                   
#define ARGCV_LINE_LEN	1000
#define ARGCV_MAX_WORDS	100

#define WNT_MAP_X_COORD(x) ((int) (ViewWidth2 + x * ViewWidth2))
#define WNT_MAP_Y_COORD(y) ((int) (ViewHeight2 - y * ViewHeight2))

#define SET_MENU_FLAGS(Bool)  (MF_STRING | (Bool ? MF_CHECKED : 0))

/* Interactive menu setup structure: */
#define INTERACT_NUM_OF_STRINGS		3
#define INTERACT_NUM_OF_SUB_WNDWS	15
#define INTERACT_SUB_WINDOW_WIDTH  0.8		 /* Relative to window size. */
#define INTERACT_SUB_WINDOW_HEIGHT 0.04

typedef struct InteractString {
    RealType X, Y;
    int Color;
    char *Str;
} InteractString;
typedef struct InteractSubWindow {
    RealType X, Y;					   /* Center points. */
    int Color;
    IGGraphicEventType Event;
    int TextInside; /* If TRUE, Str will be in window, otherwise left to it. */
    char *Str;
} InteractSubWindow;
typedef struct InteractWindowStruct {	 /* The interactive menu structures. */
    /* Rotate, Translate, Scale strings: */
    InteractString Strings[INTERACT_NUM_OF_STRINGS];
    InteractSubWindow SubWindows[INTERACT_NUM_OF_SUB_WNDWS];
} InteractWindowStruct;

/* Interactive mode menu set up structure is define below: */
static InteractWindowStruct InteractMenu =
{
  {
    { 0.5, 0.76, IG_IRIT_RED,   "Rotate" },
    { 0.5, 0.56, IG_IRIT_GREEN, "Translate" },
    { 0.5, 0.36, IG_IRIT_CYAN,  "Scale" },
  },
  {
    { 0.5, 0.92, IG_IRIT_YELLOW, IG_EVENT_SCR_OBJ_TGL,    TRUE,  "Screen Coords." },
    { 0.5, 0.84, IG_IRIT_BLUE,   IG_EVENT_PERS_ORTHO_TGL, TRUE,  "Perspective" },
    { 0.5, 0.79, IG_IRIT_BLUE,   IG_EVENT_PERS_ORTHO_Z,   FALSE, "Z" },
    { 0.5, 0.69, IG_IRIT_RED,    IG_EVENT_ROTATE_X,       FALSE, "X" },  /* Rot */
    { 0.5, 0.64, IG_IRIT_RED,    IG_EVENT_ROTATE_Y,       FALSE, "Y" },
    { 0.5, 0.59, IG_IRIT_RED,    IG_EVENT_ROTATE_Z,       FALSE, "Z" },
    { 0.5, 0.49, IG_IRIT_GREEN,  IG_EVENT_TRANSLATE_X,    FALSE, "X" },/* Trans */
    { 0.5, 0.44, IG_IRIT_GREEN,  IG_EVENT_TRANSLATE_Y,    FALSE, "Y" },
    { 0.5, 0.39, IG_IRIT_GREEN,  IG_EVENT_TRANSLATE_Z,    FALSE, "Z" },
    { 0.5, 0.29, IG_IRIT_CYAN,   IG_EVENT_SCALE,	  FALSE, "" }, /* Scale */
    { 0.5, 0.21, IG_IRIT_YELLOW, IG_EVENT_DEPTH_CUE,      TRUE,  "Depth Cue" },
    { 0.5, 0.16, IG_IRIT_YELLOW, IG_EVENT_SAVE_MATRIX,    TRUE,  "Save Matrix" },
    { 0.5, 0.12, IG_IRIT_YELLOW, IG_EVENT_PUSH_MATRIX,    TRUE,  "Push Matrix" },
    { 0.5, 0.08, IG_IRIT_YELLOW, IG_EVENT_POP_MATRIX,     TRUE,  "Pop Matrix" },
    { 0.5, 0.03, IG_IRIT_WHITE,  IG_EVENT_QUIT,	          TRUE,  "Quit" },
  }
};

/* Colors to be used for viewed object (see also iritgrap.h):		   */
/* This colors are adjusted so as to give Windows NT better chance at      */
/* selecting these colors as solid.					   */
static short Colors[IG_MAX_COLOR + 1][3] =
{
    { 0,   0,   0   },  /* 0. BLACK */
    { 0,   0,   255 },  /* 1. BLUE */
    { 0,   255, 0   },  /* 2. GREEN */
    { 0,   255, 255 },  /* 3. CYAN */
    { 255, 0,   0   },  /* 4. RED */
    { 255, 0,   255 },  /* 5. MAGENTA */
    { 127, 127, 0   },  /* 6. BROWN */
    { 127, 127, 127 },  /* 7. LIGHTGREY */
    { 63,  63,  63  },  /* 8. DARKGRAY */
    { 63,  63,  255 },  /* 9. LIGHTBLUE */
    { 63,  255, 63  },  /* 10. LIGHTGREEN */
    { 63,  255, 255 },  /* 11. LIGHTCYAN */
    { 255, 63,  63  },  /* 12. LIGHTRED */
    { 255, 63,  255 },  /* 13. LIGHTMAGENTA */
    { 255, 255, 63  },  /* 14. YELLOW */
    { 255, 255, 255 }   /* 15. WHITE */
};

static int
    CurrentXPosition = 0,
    CurrentYPosition = 0;
static unsigned int
    TransWidth = DEFAULT_TRANS_WIDTH,
    TransHeight = DEFAULT_TRANS_HEIGHT,
    TransWidth2 = DEFAULT_TRANS_WIDTH / 2,
    TransHeight2 = DEFAULT_TRANS_HEIGHT / 2,
    ViewWidth = DEFAULT_VIEW_WIDTH,
    ViewHeight = DEFAULT_VIEW_HEIGHT,
    ViewWidth2 = DEFAULT_VIEW_WIDTH / 2,
    ViewHeight2 = DEFAULT_VIEW_HEIGHT / 2;

static HBRUSH BackGroundBrush;
static COLORREF BackGroundColor, CrntColorLowIntensity, CrntColorHighIntensity,
    ColorsLowIntensity[IG_MAX_COLOR + 1], ColorsHighIntensity[IG_MAX_COLOR + 1];
static HDC CurrenthDC = 0;
static HPEN CurrenthPen = 0;
static HWND hWndView, hWndTrans;
static HMENU GlblStateMenu = 0;

static void GetArgCV(CHAR *Str, int *argc, CHAR ***argv);
static void GetInputFromSocket(void *Data);
static LONG APIENTRY WndProc(HWND hWndFrame, UINT wMsg, WPARAM wParam,
								LONG lParam);
static LONG APIENTRY ViewWndProc(HWND hWndFrame, UINT wMsg, WPARAM wParam,
								LONG lParam);
static LONG APIENTRY TransWndProc(HWND hWndFrame, UINT wMsg, WPARAM wParam,
								LONG lParam);
static IGGraphicEventType GetGraphicEvent(RealType *ChangeFactor, int X, int Y);
static void RedrawViewWindow(HWND hWnd);
static void RedrawTransformationWindow(HWND hWnd);
static void SetColorIndex(int c);
static void SetColorIndex2(int color, int width);
static void SetColorRGB(int Color[3]);
static void DrawTextLocal(char *Str, int PosX, int PosY);

/****************************************************************************
* Process the command line into a form understandable by K&R definition.    *
****************************************************************************/
static void GetArgCV(CHAR *Str, int *argc, CHAR ***argv)
{
    static CHAR CommandLine[ARGCV_LINE_LEN];
    static CHAR *Argv[ARGCV_MAX_WORDS];
    int Argc;
    CHAR *p;

    strncpy(CommandLine, Str, ARGCV_LINE_LEN - 1);
    CommandLine[ARGCV_LINE_LEN] = 0;

    for (Argc = 1, Argv[0] = "wntdrvs", p = strtok(CommandLine, " \t\n\r");
         p != NULL && Argc < ARGCV_MAX_WORDS - 1;
         p = strtok(NULL, " \t\n\r"))
	Argv[Argc++] = p;

    Argv[Argc] = NULL;

    *argc = Argc;
    *argv = Argv;
}

/****************************************************************************
* Pop up all windows, read input and display.				    *
****************************************************************************/
int PASCAL WinMain(HANDLE hInst, HANDLE hPrevInst, LPSTR lpszLine, int nShow)
{
    int argc;
    char **argv;
    MSG msg;
    HWND hWndFrame;

    GetArgCV(lpszLine, &argc, &argv);
    IGConfigureGlobals("wntdrvs", argc, argv);

    BackGroundColor = RGB(IGGlblBackGroundColor[0],
		          IGGlblBackGroundColor[1],
			  IGGlblBackGroundColor[2]);
    BackGroundBrush = CreateSolidBrush(BackGroundColor);

    if (!hPrevInst) {
	WNDCLASS wndClass;
	int Registered = TRUE;

	/* Set up common defaults. */
        wndClass.style         = CS_HREDRAW | CS_VREDRAW;
	wndClass.cbClsExtra    = 0;
	wndClass.cbWndExtra    = 0;
	wndClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
	wndClass.hInstance     = hInst;

	/* Register top level main window. */
	wndClass.lpfnWndProc   = WndProc;
	wndClass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
	wndClass.hbrBackground = GetStockObject(BLACK_BRUSH);
	wndClass.lpszMenuName  = MAKEINTRESOURCE(APP_MENU);
	wndClass.lpszClassName = APP_CLASS;
	if (!RegisterClass(&wndClass))
	    return 1;

	/* Register viewing window. */
	wndClass.lpfnWndProc   = ViewWndProc;
	wndClass.hIcon         = NULL;
	wndClass.hbrBackground = CreateSolidBrush(BackGroundColor);
	wndClass.lpszClassName = APP_VIEW_CLASS;
	if (!RegisterClass(&wndClass))
	    return 1;

	/* Register transformation window. */
	wndClass.lpfnWndProc   = TransWndProc;
	wndClass.lpszClassName = APP_TRANS_CLASS;
	if (!RegisterClass(&wndClass))
	    return 1;
    }

    if (!IGGlblStandAlone)
	SocClientCreateSocket();

    msg.wParam = 1;
    if (hWndFrame = CreateWindow(APP_CLASS,
				 APP_TITLE,
				 WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
				 CW_USEDEFAULT,
				 CW_USEDEFAULT,
				 CW_USEDEFAULT,
				 CW_USEDEFAULT,            
				 NULL,
				 NULL,
				 hInst,
				 NULL)) {
	HDC hDC;

	if (hDC = GetDC(hWndFrame)) {
	    int i;

	    for (i = 0; i <= IG_MAX_COLOR; i++) {
	        ColorsHighIntensity[i] = GetNearestColor(hDC,
				RGB(Colors[i][0], Colors[i][1], Colors[i][2]));
	        ColorsLowIntensity[i] = GetNearestColor(hDC,
		    RGB(Colors[i][0] / 2, Colors[i][1] / 2, Colors[i][2] / 2));
	    }
	    ReleaseDC(hWndFrame, hDC);
	}

        ShowWindow(hWndFrame, nShow);
        UpdateWindow(hWndFrame);

	/* Set up the input socket as a secondary threat, if we are to wait  */
	/* for input from input socket.					     */
	if (!IGGlblStandAlone)
	    _beginthread(GetInputFromSocket, 0, NULL);

        while (GetMessage(&msg, NULL, 0, 0)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    UnregisterClass(APP_CLASS, hInst);
    UnregisterClass(APP_VIEW_CLASS, hInst);
    UnregisterClass(APP_TRANS_CLASS, hInst);

    return msg.wParam;
}

/*****************************************************************************
* Construct global pop up menu.						     *
*****************************************************************************/
void IGCreateStateMenu(void)
{
    if (GlblStateMenu) 
	DestroyMenu(GlblStateMenu);

    GlblStateMenu = CreatePopupMenu();

    AppendMenu(GlblStateMenu, MF_STRING, IG_STATE_MORE_SENSITIVE,
	       "More Sensitive");
    AppendMenu(GlblStateMenu, MF_STRING, IG_STATE_LESS_SENSITIVE,
	       "Less Sensitive");
    AppendMenu(GlblStateMenu,
	       SET_MENU_FLAGS(IGGlblTransformMode == IG_TRANS_SCREEN),
	       IG_STATE_SCR_OBJ_TGL, "Screen trans.");
    AppendMenu(GlblStateMenu,
	       SET_MENU_FLAGS(IGGlblViewMode == IG_VIEW_PERSPECTIVE),
	       IG_STATE_PERS_ORTHO_TGL, "Perspective");
    AppendMenu(GlblStateMenu, SET_MENU_FLAGS(IGGlblDepthCue),
	       IG_STATE_DEPTH_CUE, "Depth Cue");
    AppendMenu(GlblStateMenu, SET_MENU_FLAGS(IGGlblDrawInternal),
	       IG_STATE_DRAW_INTERNAL, "Draw Internal Edges");
    AppendMenu(GlblStateMenu, SET_MENU_FLAGS(IGGlblDrawVNormal),
	       IG_STATE_DRAW_VNORMAL, "Draw Vrtx Normals");
    AppendMenu(GlblStateMenu, SET_MENU_FLAGS(IGGlblDrawPNormal),
	       IG_STATE_DRAW_PNORMAL, "Draw Poly Normals");
    AppendMenu(GlblStateMenu, SET_MENU_FLAGS(IGGlblDrawSurfaceMesh),
	       IG_STATE_DRAW_SRF_MESH, "Draw Surface Mesh");
    AppendMenu(GlblStateMenu, SET_MENU_FLAGS(IGGlblDrawSurfacePoly),
	       IG_STATE_DRAW_SRF_POLY, "Surface Polygons");
    AppendMenu(GlblStateMenu, MF_STRING, IG_STATE_FOUR_PER_FLAT,
	       IGGlblFourPerFlat ? "Four Per Flat" : "Two Per Flat");
    AppendMenu(GlblStateMenu, MF_STRING, IG_STATE_MORE_ISOLINES,
	       "More Isolines");
    AppendMenu(GlblStateMenu, MF_STRING, IG_STATE_LESS_ISOLINES,
	       "Less Isolines");
    AppendMenu(GlblStateMenu, MF_STRING, IG_STATE_FINER_APPROX,
	       "Finer approximation");
    AppendMenu(GlblStateMenu, MF_STRING, IG_STATE_COARSER_APPROX,
	       "Coarser approximation");
    AppendMenu(GlblStateMenu, MF_STRING, IG_STATE_LONGER_VECTORS,
	       "Longer Vectors");
    AppendMenu(GlblStateMenu, MF_STRING, IG_STATE_SHORTER_VECTORS,
	       "Shorter Vectors");
    AppendMenu(GlblStateMenu, MF_STRING, IG_STATE_WIDER_LINES,
	       "Wider Lines");
    AppendMenu(GlblStateMenu, MF_STRING, IG_STATE_NARROW_LINES,
	       "Thinner Lines");
}

/*****************************************************************************
* A secondary threat that waits for input from socket.			     *
*****************************************************************************/
static void GetInputFromSocket(void *Data)
{
    printf("Waiting for input from socket\n");

    while (TRUE) {
	if (IGReadObjectsFromSocket(IGGlblViewMode, &IGGlblDisplayList))
	    InvalidateRect(hWndView, NULL, TRUE);
    }
}

/****************************************************************************
* Handle events of top level main window.				    *
****************************************************************************/
static LONG APIENTRY WndProc(HWND hWndFrame, UINT wMsg, WPARAM wParam,
								LONG lParam)
{
    static RECT rect;
    HDC hDC;
    TEXTMETRIC tm;

    switch (wMsg) {
        case WM_CREATE:
            if (hDC = GetDC(hWndFrame)) {
		SelectObject(hDC, GetStockObject(SYSTEM_FIXED_FONT));
		GetTextMetrics(hDC, &tm);
		ReleaseDC(hWndFrame, hDC);
	    }

	    GetClientRect(hWndFrame, &rect);

	    hWndView = CreateWindow(APP_VIEW_CLASS,
				    "v",
				    WS_CHILD | WS_BORDER | WS_VISIBLE,
				    0,
				    0,
				    ViewWidth = (rect.right * 3) / 4 - 2,
				    ViewHeight = rect.bottom,
				    hWndFrame,
				    0,
				    ((LPCREATESTRUCT) lParam)->hInstance,
				    NULL);
	    ViewWidth2 = ViewWidth / 2;
	    ViewHeight2 = ViewHeight / 2;

	    hWndTrans = CreateWindow(APP_TRANS_CLASS,
				     "t",
				     WS_CHILD | WS_BORDER | WS_VISIBLE,
				     (rect.right * 3) / 4,
				     0,
				     TransWidth = rect.right / 4,
				     TransHeight = rect.bottom,
				     hWndFrame,
				     0,
				     ((LPCREATESTRUCT) lParam)->hInstance,
				     NULL);
	    TransWidth2 = TransWidth / 2;
	    TransHeight2 = TransHeight / 2;
 	    return 0;

	case WM_SIZE:
	    rect.left   = 0;
	    rect.top    = 0;
	    rect.right  = LOWORD(lParam);
	    rect.bottom = HIWORD(lParam);

	    SetWindowPos(hWndView,
			 HWND_TOP,
			 0,
			 0,
			 ViewWidth = (rect.right * 3) / 4 - 2,
			 ViewHeight = rect.bottom,
			 0);
	    ViewWidth2 = ViewWidth / 2;
	    ViewHeight2 = ViewHeight / 2;

	    SetWindowPos(hWndTrans,
			 HWND_TOP,
			 (rect.right * 3) / 4,
			 0,
			 TransWidth = rect.right / 4 - 1,
			 TransHeight = rect.bottom - 1,
			 0);
	    TransWidth2 = TransWidth / 2;
	    TransHeight2 = TransHeight / 2;
	    return 0;

	case WM_DESTROY:
	    PostQuitMessage(0);
	    return 0;

        default:
	    return DefWindowProc(hWndFrame, wMsg, wParam, lParam);
    }
    return 0;
}

/****************************************************************************
* Call backfunction for viewing window.					    *
****************************************************************************/
static LONG APIENTRY ViewWndProc(HWND hWndFrame, UINT wMsg, WPARAM wParam,
								LONG lParam)
{
    switch (wMsg) {
	case WM_PAINT:
	    RedrawViewWindow(hWndFrame);
	    return 0;

	case WM_DESTROY:
	    PostQuitMessage(0);
	    return 0;

	case WM_LBUTTONDOWN:
	case WM_RBUTTONDOWN:
	    IGGlblAbortKeyPressed = TRUE;
	    break;

	default:
	    return DefWindowProc(hWndFrame, wMsg, wParam, lParam);
    }
    return 0;
}

/****************************************************************************
* Call backfunction for transformation window.				    *
****************************************************************************/
static LONG APIENTRY TransWndProc(HWND hWndFrame, UINT wMsg, WPARAM wParam,
								LONG lParam)
{
    static IGGraphicEventType
	LastEvent = IG_EVENT_NONE;
    static BOOL
	bLeftBtnDown = FALSE;
    static int
	iMouseX = 0,
	iMouseY = 0;
    int iNewMouseX, iNewMouseY;
    IGGraphicEventType Event;
    RealType ChangeFactor;
    POINT Point;

    switch (wMsg) {
	case WM_PAINT:
	    RedrawTransformationWindow(hWndFrame);
	    return 0;

	case WM_RBUTTONDOWN:
	    if (!GlblStateMenu)
		IGCreateStateMenu();

	    Point.x = LOWORD(lParam);
	    Point.y = HIWORD(lParam);
	    ClientToScreen (hWndFrame, &Point);

	    /* Activate the pop up menu. Events goes to WM_COMMAND here. */
	    TrackPopupMenu(GlblStateMenu,
			   TPM_CENTERALIGN | TPM_RIGHTBUTTON,
			   Point.x, Point.y, 0, hWndFrame, NULL);
	    return 0;

	case WM_LBUTTONDOWN:
	    /* Save fact that button is pressed. Might be a drag operation.*/
	    bLeftBtnDown = TRUE;
	    iMouseX = LOWORD(lParam);
	    iMouseY = HIWORD(lParam);

	    if ((Event = GetGraphicEvent(&ChangeFactor,
					 iMouseX, iMouseY)) != IG_EVENT_NONE) {
		if (Event == IG_EVENT_QUIT) {
		    exit(0);		   /* Not the nicest ways to quit. */
		}
		else {
		    if (IGProcessEvent(Event,
				       ChangeFactor * IGGlblChangeFactor))
		        InvalidateRect(hWndView, NULL, TRUE);

		    /* Save the event in case drag operation is performed. */
		    LastEvent = Event;
		}
	    }
	    return 0;

	case WM_LBUTTONUP:
	    /* Done with mouse click or drag operation. */
	    bLeftBtnDown = FALSE;
	    return 0;

	case WM_MOUSEMOVE:
	    if (bLeftBtnDown && IG_IS_DRAG_EVENT(LastEvent)) {
		iNewMouseX = LOWORD(lParam);
		iNewMouseY = HIWORD(lParam);
		ChangeFactor = (iNewMouseX - iMouseX) /
						     ((RealType) TransWidth);
		iMouseX = iNewMouseX;
		if (IGProcessEvent(LastEvent,
				   ChangeFactor * IGGlblChangeFactor))
		    InvalidateRect(hWndView, NULL, TRUE);
	    }
	    return 0;

	case WM_DESTROY:
	    PostQuitMessage(0);
	    return 0;

	case WM_COMMAND:
	    /* Comamnds from the popup menu. */
	    if (IGHandleState(wParam, TRUE))
		InvalidateRect(hWndView, NULL, TRUE);
	    return 0;

	default:
	    return DefWindowProc(hWndFrame, wMsg, wParam, lParam);
    }
    return 0;
}

/******************************************************************************
* Get an XY location on the transformation window and computes the evnet.     *
******************************************************************************/
static IGGraphicEventType GetGraphicEvent(RealType *ChangeFactor, int X, int Y)
{
    int i;
    IGGraphicEventType
	RetVal = IG_EVENT_NONE;
    RealType XPos, YPos;

    XPos = ((RealType) X) / TransWidth;
    YPos = 1.0 - ((RealType) Y) / TransHeight;

    /* Make sure we are in bound in the X direction. */
    if (XPos < (1.0 - INTERACT_SUB_WINDOW_WIDTH) / 2.0 ||
        XPos > 1.0 - (1.0 - INTERACT_SUB_WINDOW_WIDTH) / 2.0)
	return IG_EVENT_NONE;

    /* Now search the sub window the event occured in. */
    for (i = 0; i < INTERACT_NUM_OF_SUB_WNDWS; i++) {
        if (InteractMenu.SubWindows[i].Y <= YPos &&
	    InteractMenu.SubWindows[i].Y + INTERACT_SUB_WINDOW_HEIGHT >=
								      YPos) {
	    RetVal = InteractMenu.SubWindows[i].Event;
	    break;
	}
    }

    /* Take care of special cases in which the window should be updated. */
    switch (RetVal) {
	case IG_EVENT_SCR_OBJ_TGL:
	    IGGlblTransformMode = IGGlblTransformMode == IG_TRANS_OBJECT ?
							 IG_TRANS_SCREEN :
							 IG_TRANS_OBJECT;
	    InvalidateRect(hWndTrans, NULL, TRUE);
	    IGCreateStateMenu();
	    break;
	case IG_EVENT_PERS_ORTHO_TGL:
	    IGGlblViewMode = IGGlblViewMode == IG_VIEW_PERSPECTIVE ?
					       IG_VIEW_ORTHOGRAPHIC :
					       IG_VIEW_PERSPECTIVE;
	    InvalidateRect(hWndTrans, NULL, TRUE);
	    IGCreateStateMenu();
	    break;
	case IG_EVENT_DEPTH_CUE:
	    IGGlblDepthCue = !IGGlblDepthCue;
	    InvalidateRect(hWndTrans, NULL, TRUE);
	    IGCreateStateMenu();
	    break;
    }

    *ChangeFactor = (((RealType) X) - TransWidth2) / TransWidth2;

    return RetVal;
}

/*****************************************************************************
* Redraw the viewing window.						     *
*****************************************************************************/
static void RedrawViewWindow(HWND hWnd)
{
    PAINTSTRUCT ps;

    switch (IGGlblViewMode) {		 /* Update the current view. */
	case IG_VIEW_ORTHOGRAPHIC:
	    GEN_COPY(IGGlblCrntViewMat, IritPrsrViewMat, sizeof(MatrixType));
	    break;
	case IG_VIEW_PERSPECTIVE:
	    MatMultTwo4by4(IGGlblCrntViewMat, IritPrsrViewMat,
							IritPrsrPrspMat);
	    break;
    }

    if (CurrenthDC = BeginPaint(hWnd, &ps)) {
	RECT rect;
	IPObjectStruct *PObj;

	GetClientRect(hWnd, &rect);
	FillRect(CurrenthDC, &rect, BackGroundBrush);

	/* Slim chance that a race will oocur if the following test and set */
	/* is done simultanuously. Should use semaphors probably.	    */
	while (IGGlblDisplayListIsUsed)
	    IritSleep(10);
	IGGlblDisplayListIsUsed = TRUE;

	for (PObj = IGGlblDisplayList, IGGlblAbortKeyPressed = FALSE;
	     PObj != NULL && !IGGlblAbortKeyPressed;
	     PObj = PObj -> Pnext)
	    IGDrawObject(PObj);

	IGGlblDisplayListIsUsed = FALSE;

	if (CurrenthPen)
            DeleteObject(SelectObject(CurrenthDC, CurrenthPen));
	EndPaint(hWnd, &ps);
	CurrenthDC = 0;
	CurrenthPen = 0;
    }
}

/*****************************************************************************
* Redraw the transformation window.					     *
*****************************************************************************/
static void RedrawTransformationWindow(HWND hWnd)
{
    int i;
    long SubTransPosX, SubTransPosY, SubTransWidth, SubTransHeight;
    RECT rect;
    PAINTSTRUCT ps;

    /* Make sure the menu is consistent with internatal data. */
    InteractMenu.SubWindows[0].Str =
	IGGlblTransformMode == IG_TRANS_OBJECT ? "Object Coords."
					       : "Screen Coords.";
    InteractMenu.SubWindows[1].Str =
	IGGlblViewMode == IG_VIEW_PERSPECTIVE ? "Perspective" : "Orthographic";
    InteractMenu.SubWindows[10].Str =
	IGGlblDepthCue ? "Depth Cue" : "No Depth Cue";

    SubTransWidth = (int) (TransWidth * INTERACT_SUB_WINDOW_WIDTH);
    SubTransHeight = (int) (TransHeight * INTERACT_SUB_WINDOW_HEIGHT);
    SubTransPosX = (TransWidth - SubTransWidth) / 2;

    if (CurrenthDC = BeginPaint(hWnd, &ps)) {
	GetClientRect(hWnd, &rect);
	FillRect(CurrenthDC, &rect, BackGroundBrush);

	SetBkMode(CurrenthDC, TRANSPARENT);
	SetTextAlign(CurrenthDC, TA_CENTER | VTA_CENTER);

	for (i = 0; i < INTERACT_NUM_OF_SUB_WNDWS; i++) {
	    SetColorIndex2(InteractMenu.SubWindows[i].Color, 2);
	    SetTextColor(CurrenthDC, CrntColorHighIntensity);

	    SubTransPosY = (int) (TransHeight *
					(1.0 - InteractMenu.SubWindows[i].Y));

	    MoveToEx(CurrenthDC, SubTransPosX, SubTransPosY, NULL);
	    LineTo(CurrenthDC, SubTransPosX + SubTransWidth, SubTransPosY);
	    LineTo(CurrenthDC,
		   SubTransPosX + SubTransWidth, SubTransPosY - SubTransHeight);
	    LineTo(CurrenthDC, SubTransPosX, SubTransPosY - SubTransHeight);
	    LineTo(CurrenthDC, SubTransPosX, SubTransPosY);
	    if (InteractMenu.SubWindows[i].TextInside) {
	        DrawTextLocal(InteractMenu.SubWindows[i].Str,
			      TransWidth / 2,
			      SubTransPosY - SubTransHeight);
	    }
	    else {
		DrawTextLocal(InteractMenu.SubWindows[i].Str,
			      (TransWidth - SubTransWidth) / 3,
			      SubTransPosY - SubTransHeight);
		MoveToEx(CurrenthDC,
			 SubTransPosX + SubTransWidth / 2, SubTransPosY, NULL);
		LineTo(CurrenthDC,
		       SubTransPosX + SubTransWidth / 2,
		       SubTransPosY - SubTransHeight);
	    }
	}

	for (i = 0; i < INTERACT_NUM_OF_STRINGS; i++) {
	    SetColorIndex2(InteractMenu.Strings[i].Color, 2);
	    SetTextColor(CurrenthDC, CrntColorHighIntensity);

	    DrawTextLocal(InteractMenu.Strings[i].Str,
		  (int) (InteractMenu.Strings[i].X * TransWidth),
		  (int) ((1.0 - InteractMenu.Strings[i].Y) * TransHeight
						- SubTransHeight / 2));
	}

	if (CurrenthPen)
            DeleteObject(SelectObject(CurrenthDC, CurrenthPen));
	EndPaint(hWnd, &ps);
	CurrenthDC = 0;
	CurrenthPen = 0;
    }
}

/****************************************************************************
* Routine to move in 2D normalized (-1..1) view space.			    *
****************************************************************************/
void IGMoveTo2D(RealType X, RealType Y)
{
     int x, y;

    MoveToEx(CurrenthDC, x = WNT_MAP_X_COORD(X), y = WNT_MAP_Y_COORD(Y), NULL);
}

/****************************************************************************
* Routine to draw in 2D normalized (-1..1) view space.			    *
****************************************************************************/
void IGLineTo2D(RealType X, RealType Y)
{
     int x, y;

    LineTo(CurrenthDC, x = WNT_MAP_X_COORD(X), y = WNT_MAP_Y_COORD(Y));
}

/****************************************************************************
* Routine to set the intensity of a color (high or low).		    *
****************************************************************************/
void IGSetColorIntensity(int High)
{
    if (!CurrenthDC)
	return;
    if (CurrenthPen)
        DeleteObject(SelectObject(CurrenthDC, CurrenthPen));
    CurrenthPen = SelectObject(CurrenthDC, CreatePen(PS_SOLID, IGGlblLineWidth,
		      High ? CrntColorHighIntensity : CrntColorLowIntensity));

    IGGlblIntensityHighState = High;
}

/****************************************************************************
* Routine to set the color according to the given object's color.	    *
****************************************************************************/
void IGSetColorObj(IPObjectStruct *PObj)
{
    int c, Color[3];

    if (AttrGetObjectRGBColor(PObj, &Color[0], &Color[1], &Color[2])) {
	SetColorRGB(Color);
    }
    else if ((c = AttrGetObjectColor(PObj)) != IP_ATTR_NO_COLOR) {
	SetColorIndex(c);
    }
    else {
	/* Use white as default color: */
	SetColorIndex(IG_IRIT_WHITE);
    }
}

/****************************************************************************
* Routine to set the color according to the given color index.		    *
****************************************************************************/
static void SetColorIndex(int color)
{
    SetColorIndex2(color, IGGlblLineWidth);
}

/****************************************************************************
* Routine to set the color according to the given color index.		    *
****************************************************************************/
static void SetColorIndex2(int color, int width)
{
    if (color > IG_MAX_COLOR)
	color = IG_IRIT_WHITE;

    CrntColorHighIntensity = ColorsHighIntensity[color];
    CrntColorLowIntensity = ColorsLowIntensity[color];

    if (!CurrenthDC)
	return;
    if (CurrenthPen)
        DeleteObject(SelectObject(CurrenthDC, CurrenthPen));
    CurrenthPen = SelectObject(CurrenthDC, CreatePen(PS_SOLID, width,
						     CrntColorHighIntensity));

    IGGlblIntensityHighState = TRUE;
}

/****************************************************************************
* Routine to set the color according to the given RGB values.		    *
****************************************************************************/
static void SetColorRGB(int Color[3])
{
    CrntColorHighIntensity = GetNearestColor(CurrenthDC,
					     RGB(Color[0], Color[1], Color[2]));
    CrntColorLowIntensity = GetNearestColor(CurrenthDC,
				 RGB(Color[0] / 2, Color[1] / 2, Color[2] / 2));


    if (!CurrenthDC)
	return;
    if (CurrenthPen)
        DeleteObject(SelectObject(CurrenthDC, CurrenthPen));

    CurrenthPen = SelectObject(CurrenthDC, CreatePen(PS_SOLID, 1,
						     CrntColorHighIntensity));

    IGGlblIntensityHighState = TRUE;
}

/******************************************************************************
* Handle the event of a pop up window.					      *
******************************************************************************/
int IGHandleState(int State, int Refresh)
{
    int UpdateView = TRUE;

    switch (State) {
	case IG_STATE_WIDER_LINES:
	    IGGlblLineWidth *= 2;
	    break;
	case IG_STATE_NARROW_LINES:
	    IGGlblLineWidth /= 2;
	    if (IGGlblLineWidth < 1)
		IGGlblLineWidth = 1;
	    break;
        case IG_STATE_SCR_OBJ_TGL:
	case IG_STATE_PERS_ORTHO_TGL:
	case IG_STATE_DEPTH_CUE:
	    InvalidateRect(hWndTrans, NULL, TRUE);
	default:
	    UpdateView = IGDefaultStateHandler(State, Refresh);
	    break;
    }

    IGCreateStateMenu();

    return UpdateView;
}

/******************************************************************************
* Draw text centered at the given position.				      *
******************************************************************************/
static void DrawTextLocal(char *Str, int PosX, int PosY)
{
    TextOut(CurrenthDC, PosX, PosY, Str, strlen(Str));
}

/*****************************************************************************
* Routine to make some sound.						     *
*****************************************************************************/
void IGIritBeep(void)
{
    Beep(1000, 100);
}
