/*****************************************************************************
*   An SGI 4D driver using GL (experimental adaptive isocurve renderer.).    *
*									     *
* Written by:  Gershon Elber				Ver 0.1, June 1993.  *
*****************************************************************************/

#include <gl/gl.h>
#include <gl/device.h>

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <ctype.h>
#include "irit_sm.h"
#include "genmat.h"
#include "config.h"
#include "iritprsr.h"
#include "allocate.h"
#include "attribut.h"
#include "ip_cnvrt.h"
#include "cagd_lib.h"
#include "symb_lib.h"
#include "iritgrap.h"

/* Interactive menu setup structure: */
#define INTERACT_NUM_OF_STRINGS		4
#define INTERACT_NUM_OF_SUB_WNDWS	16

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;

#define INTERACT_SUB_WINDOW_WIDTH  0.8		 /* Relative to window size. */
#define INTERACT_SUB_WINDOW_HEIGHT 0.04

static int
    GlblStateMenu = 0,
    GlblRuledSrfApprox = FALSE;

static long
    TransWinID = 0,
    TransWinWidth = 100,
    TransWinWidth2 = 50,
    TransWinHeight = 100,
    TransWinLow = 0,
    TransWinLeft = 0,
    ViewWinID = 0,
    ViewWinWidth = 100,
    ViewWinWidth2 = 50,
    ViewWinHeight = 100,
    ViewWinHeight2 = 50,
    ViewWinLow = 0,
    ViewWinLeft = 0;

static RealType
    GlblAdapIsoEps = 0.05,
    GlblRuledSrfEps = 0.02;

/* Interactive mode menu set up structure is define below: */
static InteractWindowStruct InteractMenu = {
    { { 0.5, 0.81, IG_IRIT_RED,		"Rotate" },
      { 0.5, 0.65, IG_IRIT_GREEN,	"Translate" },
      { 0.5, 0.49, IG_IRIT_CYAN,	"Scale" },
      { 0.5, 0.41, IG_IRIT_LIGHTGREEN,	"Clip Plane" },
    },
    { { 0.5, 0.93, IG_IRIT_YELLOW, IG_EVENT_SCR_OBJ_TGL,	TRUE,  "Screen Coords." },
      { 0.5, 0.87, IG_IRIT_BLUE,   IG_EVENT_PERS_ORTHO_TGL,     TRUE,  "Perspective" },
      { 0.5, 0.83, IG_IRIT_BLUE,   IG_EVENT_PERS_ORTHO_Z,	FALSE, "Z" },
      { 0.5, 0.75, IG_IRIT_RED,    IG_EVENT_ROTATE_X,		FALSE, "X" }, /* Rot */
      { 0.5, 0.71, IG_IRIT_RED,    IG_EVENT_ROTATE_Y,		FALSE, "Y" },
      { 0.5, 0.67, IG_IRIT_RED,    IG_EVENT_ROTATE_Z,		FALSE, "Z" },
      { 0.5, 0.59, IG_IRIT_GREEN,  IG_EVENT_TRANSLATE_X,	FALSE, "X" }, /* Trans */
      { 0.5, 0.55, IG_IRIT_GREEN,  IG_EVENT_TRANSLATE_Y,	FALSE, "Y" },
      { 0.5, 0.51, IG_IRIT_GREEN,  IG_EVENT_TRANSLATE_Z,	FALSE, "Z" },
      { 0.5, 0.43, IG_IRIT_CYAN,   IG_EVENT_SCALE,		FALSE, "" },  /* Scale */
      { 0.5, 0.35, IG_IRIT_LIGHTGREEN, IG_EVENT_NEAR_CLIP,	FALSE,  "" },
      { 0.5, 0.31, IG_IRIT_LIGHTGREEN, IG_EVENT_FAR_CLIP,	FALSE,  "" },

      { 0.5, 0.20, IG_IRIT_YELLOW, IG_EVENT_SAVE_MATRIX,	TRUE,  "Save Matrix" },
      { 0.5, 0.13, IG_IRIT_YELLOW, IG_EVENT_PUSH_MATRIX,	TRUE,  "Push Matrix" },
      { 0.5, 0.09, IG_IRIT_YELLOW, IG_EVENT_POP_MATRIX,		TRUE,  "Pop Matrix" },
      { 0.5, 0.03, IG_IRIT_WHITE,  IG_EVENT_QUIT,		TRUE,  "Quit" },
    }
};

static short Colors[IG_MAX_COLOR + 1][3] =
{
    { 0,   0,   0   },  /* 0. BLACK */
    { 0,   0,   170 },  /* 1. BLUE */
    { 0,   170, 0   },  /* 2. GREEN */
    { 0,   170, 170 },  /* 3. CYAN */
    { 170, 0,   0   },  /* 4. RED */
    { 170, 0,   170 },  /* 5. MAGENTA */
    { 170, 170, 0   },  /* 6. BROWN */
    { 170, 170, 170 },  /* 7. LIGHTGREY */
    { 85,  85,  85  },  /* 8. DARKGRAY */
    { 85,  85,  255 },  /* 9. LIGHTBLUE */
    { 85,  255, 85  },  /* 10. LIGHTGREEN */
    { 85,  255, 255 },  /* 11. LIGHTCYAN */
    { 255, 85,  85  },  /* 12. LIGHTRED */
    { 255, 85,  255 },  /* 13. LIGHTMAGENTA */
    { 255, 255, 85  },  /* 14. YELLOW */
    { 255, 255, 255 }   /* 15. WHITE */
};

static void IGDrawPolyNormal(IPObjectStruct *PObj, int HasNormals);
static IPPolygonStruct *IritSurface2AdapIso(CagdSrfStruct *Srf,
					    CagdSrfDirType Dir,
					    RealType Eps,
					    int SamplesPerCurve);
static void SetColorIndex(int c);
static void SetColorRGB(int Color[3]);
static void ClearViewArea(void);
static void SetTransformWindow(void);
static void RedrawTransformWindow(void);
static void SetViewWindow(void);
static IGGraphicEventType GetGraphicEvent(RealType *ChangeFactor,
					  int BlockForEvent);
static void DrawText(char *Str, long PosX, long PosY);

/*****************************************************************************
* DESCRIPTION:                                                               M
* Main module of xgladap - Adaptive isocurve freeform surface rendering      M
*			   graphics driver of IRIT.             	     M
*                                                                            *
* PARAMETERS:                                                                M
*   argc, argv:  Command line.                                               M
*                                                                            *
* RETURN VALUE:                                                              M
*   int:         Exit code.                                                  M
*                                                                            *
* KEYWORDS:                                                                  M
*   main                                                                     M
*****************************************************************************/
void main(int argc, char **argv)
{
    RealType ChangeFactor;
    IGGraphicEventType Event;

    IGConfigureGlobals("xgladap", argc, argv);

    SetViewWindow();
    IGRedrawViewWindow();
    SetTransformWindow();
    RedrawTransformWindow();

    qdevice(LEFTMOUSE);
    qdevice(MIDDLEMOUSE);
    qdevice(RIGHTMOUSE);

    /* The default drawing window is the view window. */
    winset(ViewWinID);

    setbell(1);						   /* Make it short. */

    IGCreateStateMenu();

    while ((Event = GetGraphicEvent(&ChangeFactor, TRUE)) != IG_EVENT_QUIT) {
	if (IGProcessEvent(Event, ChangeFactor * IGGlblChangeFactor))
	    IGRedrawViewWindow();
    }
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Optionally construct a state pop up menu for the driver, if has one.       M
*                                                                            *
* PARAMETERS:                                                                M
*   None                                                                     *
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   IGCreateStateMenu                                                        M
*****************************************************************************/
void IGCreateStateMenu(void)
{
    if (GlblStateMenu) 
        freepup(GlblStateMenu);

    GlblStateMenu = newpup();

    addtopup(GlblStateMenu, " Set Up %t", 0);
    addtopup(GlblStateMenu, "Oops!%l", 0);
    addtopup(GlblStateMenu, "More Sensitive", 0);
    addtopup(GlblStateMenu, "Less Sensitive%l", 0);
    addtopup(GlblStateMenu, IGGlblTransformMode == IG_TRANS_SCREEN ?
				"Screen Trans." : "Object Trans", 0);
    addtopup(GlblStateMenu, IGGlblViewMode == IG_VIEW_PERSPECTIVE ?
				"Perspective" : "Orthographic", 0);
    addtopup(GlblStateMenu,
	     IGGlblDepthCue ? "Depth Cue" : "No Depth Cue", 0);
    addtopup(GlblStateMenu,
	     IGGlblCacheGeom ? "Cache Geom" : "No Geom Cache", 0);
    addtopup(GlblStateMenu,
	     IGGlblDrawSolid ? "Draw Solid" : "Draw Wireframe", 0);
    addtopup(GlblStateMenu,
	     IGGlblBackFaceCull ? "Cull Back Face" : "No Cull Back Face", 0);
    addtopup(GlblStateMenu,
	     IGGlblDoDoubleBuffer ? "Double Buffer" : "Single Buffer", 0);
    addtopup(GlblStateMenu,
	     IGGlblAntiAliasing ? "Anti Aliasing" : "No Anti Aliasing", 0);
    addtopup(GlblStateMenu,
	     IGGlblDrawInternal ? "Draw Internal Edges" : "No Internal Edges", 0);
    addtopup(GlblStateMenu,
	     IGGlblDrawVNormal ? "Draw Vrtx Normals" :  "No Vrtx Normals", 0);
    addtopup(GlblStateMenu,
	     IGGlblDrawPNormal ? "Draw Poly Normals" :  "No Poly Normals", 0);
    addtopup(GlblStateMenu,
	     IGGlblDrawSurfaceMesh ? "Draw Surface Mesh" : "No Surface Mesh", 0);
    addtopup(GlblStateMenu,
	     IGGlblDrawSurfacePoly ? "Surface Polygons" : "Surface Isolines", 0);
    addtopup(GlblStateMenu,
	     IGGlblFourPerFlat ? "Four Per Flat%l" : "Two Per Flat%l", 0);
    addtopup(GlblStateMenu, "More Isolines", 0);
    addtopup(GlblStateMenu, "Less Isolines%l", 0);
    addtopup(GlblStateMenu, "Finer Approx.", 0);
    addtopup(GlblStateMenu, "Coarser Approx.%l", 0);
    addtopup(GlblStateMenu, "Longer Vectors", 0);
    addtopup(GlblStateMenu, "Shorter Vectors%l", 0);
    addtopup(GlblStateMenu, "Wider Lines", 0);
    addtopup(GlblStateMenu, "Narrow Lines%l", 0);
    addtopup(GlblStateMenu, "Front View", 0);
    addtopup(GlblStateMenu, "Side View", 0);
    addtopup(GlblStateMenu, "Top View", 0);
    addtopup(GlblStateMenu, "Isometry View%l", 0);
    addtopup(GlblStateMenu, "Clear View Area%l", 0);
    addtopup(GlblStateMenu, "Animation%l", 0);

    addtopup(GlblStateMenu, "Finer Adap. Iso.", 0);
    addtopup(GlblStateMenu, "Coarser Adap. Iso.", 0);
    addtopup(GlblStateMenu, "Finer Ruled Srf.", 0);
    addtopup(GlblStateMenu, "Coarser Ruled Srf.", 0);
    addtopup(GlblStateMenu, 
	     GlblRuledSrfApprox ? "Direct Adap. Iso." : "Ruled Adap. Iso.", 0);
    addtopup(GlblStateMenu, "Adap. Iso. Dir.", 0);
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Draw a single Point/Vector object using current modes and transformations. M
*                                                                            *
* PARAMETERS:                                                                M
*   PObj:     A point/vector object to draw.                                 M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   IGDrawPtVec                                                              M
*****************************************************************************/
void IGDrawPtVec(IPObjectStruct *PObj)
{
    static PointType
	Zero = { 0.0, 0.0, 0.0 };
    int i;
    PointType Ends[6];
    RealType
	*Pt = PObj -> U.Pt;

    for (i = 0; i < 6; i++)
	PT_COPY(Ends[i], Pt);

    Ends[0][0] -= IG_POINT_WIDTH;
    Ends[1][0] += IG_POINT_WIDTH;
    Ends[2][1] -= IG_POINT_WIDTH;
    Ends[3][1] += IG_POINT_WIDTH;
    Ends[4][2] -= IG_POINT_WIDTH;
    Ends[5][2] += IG_POINT_WIDTH;

    for (i = 0; i < 6; i += 2) {
	bgnline();
	v3d(Ends[i]);
	v3d(Ends[i+1]);
	endline();
    }

    if (IP_IS_VEC_OBJ(PObj)) {
	bgnline();
	v3d(Pt);
	v3d(Zero);
	endline();
    }
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Draw a single Poly object using current modes and transformations.	     M
*                                                                            *
* PARAMETERS:                                                                M
*   PObj:     A poly object to draw.                                         M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   IGDrawPoly                                                               M
*****************************************************************************/
void IGDrawPoly(IPObjectStruct *PObj)
{
    IGDrawPolyNormal(PObj, FALSE);
}

/*****************************************************************************
* DESCRIPTION:                                                               *
* Draw a single Poly object using current modes and transformations.	     *
*   This function allows polylines to have normals so we can actually draw   *
* the adaptive isolines to render a surface.				     *
*                                                                            *
* PARAMETERS:                                                                *
*   PObj:         A polygon or a polyline to draw.                           *
*   HasNormals:   Do we have normal information included?                    *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*****************************************************************************/
static void IGDrawPolyNormal(IPObjectStruct *PObj, int HasNormals)
{
    IPVertexStruct *V;
    IPPolygonStruct
	*Pl = PObj -> U.Pl;

    if (IP_IS_POLYLINE_OBJ(PObj)) {
	for (; Pl != NULL; Pl = Pl -> Pnext) {
	    bgnline();
	    for (V = Pl -> PVertex; V != NULL; V = V -> Pnext) {
		if (HasNormals) {
		    float n[3];

		    n[0] = V -> Normal[0];
		    n[1] = V -> Normal[1];
		    n[2] = V -> Normal[2];
		    n3f(n);
		}
	        v3d(V -> Coord);
	    }
	    endline();
	}
    }
    else if (IP_IS_POINTLIST_OBJ(PObj)) {
	for (; Pl != NULL; Pl = Pl -> Pnext) {
	    for (V = Pl -> PVertex; V != NULL; V = V -> Pnext) {
		int i;
		PointType Ends[6];
		RealType
		    *Pt = V -> Coord;

		for (i = 0; i < 6; i++)
		    PT_COPY(Ends[i], Pt);

		Ends[0][0] -= IG_POINT_WIDTH;
		Ends[1][0] += IG_POINT_WIDTH;
		Ends[2][1] -= IG_POINT_WIDTH;
		Ends[3][1] += IG_POINT_WIDTH;
		Ends[4][2] -= IG_POINT_WIDTH;
		Ends[5][2] += IG_POINT_WIDTH;

		for (i = 0; i < 6; i += 2) {
		    bgnline();
		    v3d(Ends[i]);
		    v3d(Ends[i+1]);
		    endline();
		}
	    }
	}
    }
    else if (IP_IS_POLYGON_OBJ(PObj)) {
	int i, j,
	    NumOfVertices = 0;
	PointType PNormal, VNormal;

	for (; Pl != NULL; Pl = Pl -> Pnext) {
	    if (IGGlblBackFaceCull) {
	        RealType P1[3], P2[3];

		MatMultVecby4by4(P1, Pl -> PVertex -> Coord,
				 IritPrsrViewMat);
		PT_ADD(PNormal, Pl -> PVertex -> Coord, Pl -> Plane);
		MatMultVecby4by4(P2, PNormal, IritPrsrViewMat);
		if (P2[2] - P1[2] > 0.0)
		    continue;
	    }

	    if (IGGlblDrawPNormal) {
		NumOfVertices = 0;
		PNormal[0] = PNormal[1] = PNormal[2] = 0.0;
	    }

	    if (IGGlblDrawSolid) {
		bgnpolygon();
		for (V = Pl -> PVertex; V != NULL; V = V -> Pnext) {
		    float n[3];

		    n[0] = V -> Normal[0];
		    n[1] = V -> Normal[1];
		    n[2] = V -> Normal[2];
		    n3f(n);
		    v3d(V -> Coord);

		    if (IGGlblDrawPNormal) {
			for (j = 0; j < 3; j++)
			    PNormal[j] += V -> Coord[j];
			NumOfVertices++;
		    }
		}
		endpolygon();
	    }
	    else {
		bgnline();
		for (V = Pl -> PVertex; V != NULL; V = V -> Pnext) {
		    v3d(V -> Coord);
		    if (IP_IS_INTERNAL_VRTX(V) && !IGGlblDrawInternal) {
			endline();
			bgnline();
		    }

		    if (IGGlblDrawPNormal) {
			for (j = 0; j < 3; j++)
			    PNormal[j] += V -> Coord[j];
			NumOfVertices++;
		    }
		}
		v3d(Pl -> PVertex -> Coord);
		endline();
	    }

	    if (IGGlblDrawPNormal && IP_HAS_PLANE_POLY(Pl)) {
		bgnline();
		for (i = 0; i < 3; i++)
		    PNormal[i] /= NumOfVertices;
		v3d(PNormal);
		for (i = 0; i < 3; i++)
		    PNormal[i] += Pl -> Plane[i] * IGGlblNormalLen;
		v3d(PNormal);
		endline();
	    }

	    if (IGGlblDrawVNormal) {
		for (V = Pl -> PVertex; V != NULL; V = V -> Pnext) {
		    if (IP_HAS_NORMAL_VRTX(V)) {
			for (j = 0; j < 3; j++)
			    VNormal[j] = V -> Coord[j] +
				         V -> Normal[j] * IGGlblNormalLen;
			bgnline();
			v3d(V ->Coord);
			v3d(VNormal);
			endline();
		    }
		}
	    }
	}
    }
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Draw a single Surface object using current modes and transformations.	     M
*   Surface must be with either E3 or P3 point type and must be a NURB srf.  M
*   Piecewise linear approximation is cashed under "_isoline" and "_ctlmesh" M
* attributes of PObj. Adaptive isocurves are saved under "_adap_iso" and     M
* polygons under "_polygons.".						     M
*                                                                            *
* PARAMETERS:                                                                M
*   PObj:     A surface object to draw.                                      M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   IGDrawSurface                                                            M
*****************************************************************************/
void IGDrawSurface(IPObjectStruct *PObj)
{
    IPObjectStruct *PObjPolylines, *PObjCtlMesh, *PObjPolygons;
    IPPolygonStruct *PPolylines, *PCtlMesh, *PPolygons, *PPolyTemp;

    if ((PObjPolylines = AttrGetObjectObjAttrib(PObj, "_isoline")) == NULL &&
	IGGlblNumOfIsolines > 0) {
	CagdSrfStruct *Srf,
	    *Srfs = PObj -> U.Srfs;

	PObjPolylines = IPAllocObject("", IP_OBJ_POLY, NULL);
	PObjPolylines -> Attrs = AttrCopyAttributes(PObj -> Attrs);
	IP_SET_POLYLINE_OBJ(PObjPolylines);
	for (Srf = Srfs; Srf != NULL; Srf = Srf -> Pnext) {
	    int NumOfIso[2];

	    NumOfIso[0] = -IGGlblNumOfIsolines;
	    NumOfIso[1] = -IGGlblNumOfIsolines;
	    PPolylines = IritSurface2Polylines(Srf, NumOfIso,
					       IGGlblSamplesPerCurve,
					       IGGlblPolylineOptiApprox);

	    if (PPolylines != NULL) {
		for (PPolyTemp = PPolylines;
		     PPolyTemp -> Pnext;
		     PPolyTemp = PPolyTemp -> Pnext);
		PPolyTemp -> Pnext = PObjPolylines -> U.Pl;
		PObjPolylines -> U.Pl = PPolylines;
	    }
	}
	AttrSetObjectObjAttrib(PObj, "_isoline", PObjPolylines, FALSE);
    }

    if (IGGlblDrawSolid) {
	if (IGGlblDrawSurfacePoly) {
	    if ((PObjPolygons = AttrGetObjectObjAttrib(PObj, "_polygons"))
								    == NULL) {
		CagdSrfStruct *Srf,
		    *Srfs = PObj -> U.Srfs;

		PObjPolygons = IPAllocObject("", IP_OBJ_POLY, NULL);
		PObjPolygons -> Attrs = AttrCopyAttributes(PObj -> Attrs);
		IP_SET_POLYGON_OBJ(PObjPolygons);

		for (Srf = Srfs; Srf != NULL; Srf = Srf -> Pnext) {
		    PPolygons = IritSurface2Polygons(Srf, IGGlblFourPerFlat,
						     IGGlblFineNess, FALSE,
						     IGGlblPolygonOptiApprox);

		    if (PPolygons != NULL) {
			if (PPolygons) {
			    for (PPolyTemp = PPolygons;
				 PPolyTemp -> Pnext;
				 PPolyTemp = PPolyTemp -> Pnext);
			    PPolyTemp -> Pnext = PObjPolygons -> U.Pl;
			    PObjPolygons -> U.Pl = PPolygons;
			}
		    }
		}
		AttrSetObjectObjAttrib(PObj, "_polygons", PObjPolygons, FALSE);
	    }

	    IGDrawPolyNormal(PObjPolygons, TRUE);
	}
	else {
	    if ((PObjPolylines = AttrGetObjectObjAttrib(PObj, "_adap_iso"))
								    == NULL) {
		CagdSrfStruct *Srf,
		    *Srfs = PObj -> U.Srfs;

		PObjPolylines = IPAllocObject("", IP_OBJ_POLY, NULL);
		PObjPolylines -> Attrs = AttrCopyAttributes(PObj -> Attrs);
		IP_SET_POLYLINE_OBJ(PObjPolylines);

		for (Srf = Srfs; Srf != NULL; Srf = Srf -> Pnext) {
		    PPolylines = IritSurface2AdapIso(Srf, IGGlblAdapIsoDir,
						     GlblAdapIsoEps,
						     IGGlblSamplesPerCurve);

		    if (PPolylines) {
			for (PPolyTemp = PPolylines;
			     PPolyTemp -> Pnext;
			     PPolyTemp = PPolyTemp -> Pnext);
			PPolyTemp -> Pnext = PObjPolylines -> U.Pl;
			PObjPolylines -> U.Pl = PPolylines;
		    }
		}
		AttrSetObjectObjAttrib(PObj, "_adap_iso", PObjPolylines, FALSE);
	    }

	    IGDrawPolyNormal(PObjPolylines, TRUE);
	}
    }
    else
        IGDrawPolyNormal(PObjPolylines, FALSE);

    if (IGGlblDrawSurfaceMesh) {
	if ((PObjPolylines = AttrGetObjectObjAttrib(PObj, "_ctlmesh"))
								== NULL) {
	    CagdSrfStruct *Srf,
		*Srfs = PObj -> U.Srfs;

	    PObjCtlMesh = IPAllocObject("", IP_OBJ_POLY, NULL);
	    PObjCtlMesh -> Attrs = AttrCopyAttributes(PObj -> Attrs);
	    IP_SET_POLYLINE_OBJ(PObjCtlMesh);
	    for (Srf = Srfs; Srf != NULL; Srf = Srf -> Pnext) {
		PCtlMesh = IritSurface2CtlMesh(Srf);

		for (PPolyTemp = PCtlMesh;
		     PPolyTemp -> Pnext;
		     PPolyTemp = PPolyTemp -> Pnext);
		PPolyTemp -> Pnext = PObjCtlMesh -> U.Pl;
		PObjCtlMesh -> U.Pl = PCtlMesh;
	    }
	    AttrSetObjectObjAttrib(PObj, "_ctlmesh", PObjCtlMesh, FALSE);
	}

	IGDrawPolyNormal(AttrGetObjectObjAttrib(PObj, "_ctlmesh"), FALSE);
    }
}

/*****************************************************************************
* DESCRIPTION:                                                               *
* Creates an adaptive isocurve coverage to a given surface.		     *
*                                                                            *
* PARAMETERS:                                                                *
*   Srf:                Surface to convert to adaptive isocurve's coverage.  *
*   Dir:                Direction of isocurves. Either U or V.               *
*   Eps:                Accuracy of coverage.                                *
*   SamplesPerCurve:    Number of samples per isocurve.                      *
*                                                                            *
* RETURN VALUE:                                                              *
*   IPPolygonStruct *:  A list of polylines approximating the coverage.      *
*****************************************************************************/
static IPPolygonStruct *IritSurface2AdapIso(CagdSrfStruct *Srf,
					    CagdSrfDirType Dir,
					    RealType Eps,
					    int SamplesPerCurve)
{
    CagdCrvStruct *Coverage, *OneCoverage, *Crv;
    IPPolygonStruct
	*IsoPoly = NULL;
    CagdSrfStruct *NSrf;

    if (Dir == CAGD_NO_DIR)
	Dir = Srf -> UOrder == 2 ? CAGD_CONST_U_DIR : CAGD_CONST_V_DIR;

    fprintf(stderr, "Generating adaptive isocurve coverage...\n");
    if (GlblRuledSrfApprox) {
	int IsBspline = TRUE;
	CagdSrfStruct *RuledSrfs, *NormalSrf, *TSrf;

	if (CAGD_IS_BEZIER_SRF(Srf)) {
	    IsBspline = FALSE;
	    Srf = CnvrtBezier2BsplineSrf(Srf);
	}
	RuledSrfs = SymbPiecewiseRuledSrfApprox(Srf, TRUE, GlblRuledSrfEps,
						Dir);

	Coverage = NULL;
	NormalSrf = SymbSrfNormalSrf(Srf);
	for (TSrf = RuledSrfs; TSrf != NULL; TSrf = TSrf -> Pnext) {
	    CagdRType UMin, UMax, VMin, VMax;

	    CagdSrfDomain(TSrf, &UMin, &UMax, &VMin, &VMax);
	    if (Dir == CAGD_CONST_V_DIR)
		NSrf = CagdSrfRegionFromSrf(NormalSrf, UMin, UMax,
					    CAGD_CONST_U_DIR);
	    else
		NSrf = CagdSrfRegionFromSrf(NormalSrf, VMin, VMax,
					    CAGD_CONST_V_DIR);
	    OneCoverage = SymbAdapIsoExtract(TSrf, NSrf, NULL, Dir,
					     Eps, FALSE, FALSE);
	    CagdSrfFree(NSrf);

	    for (Crv = OneCoverage; Crv -> Pnext != NULL; Crv = Crv -> Pnext);
	    Crv -> Pnext = Coverage;
	    Coverage = OneCoverage;
	    fprintf(stderr, "Done with one ruled surface approximation.\n");
	}
	CagdSrfFreeList(RuledSrfs);
	CagdSrfFree(NormalSrf);
	if (!IsBspline)
	    CagdSrfFree(Srf);

	/* Scan the adaptive isoline list. Note that normal curve is paired */
	/* after Euclidean curve, so we can step two curves at a time.      */
	for (Crv = Coverage; Crv != NULL; Crv = Crv -> Pnext -> Pnext) {
	    CagdCrvStruct
		*NCrv = Crv -> Pnext;
	    IPPolygonStruct
		*Poly = IritCurve2Polylines(Crv, 1, IGGlblPolylineOptiApprox);

	    if (Poly != NULL) {
		RealType
		    *Nrml1 = Poly -> PVertex -> Normal,
		    *Nrml2 = Poly -> PVertex -> Pnext -> Normal;
		CagdCoerceToE3(Nrml1, NCrv -> Points, 0, NCrv -> PType);
		CagdCoerceToE3(Nrml2, NCrv -> Points, NCrv -> Length - 1,
			       NCrv -> PType);
		PT_NORMALIZE(Nrml1);
		PT_SCALE(Nrml1, -1.0);
		PT_NORMALIZE(Nrml2);
		PT_SCALE(Nrml2, -1.0);

		Poly -> Pnext = IsoPoly;
		IsoPoly = Poly;
	    }
	}
    }
    else {
	NSrf = SymbSrfNormalSrf(Srf);

	Coverage = SymbAdapIsoExtract(Srf, NSrf, NULL, Dir, Eps, FALSE, FALSE);
	    fprintf(stderr,
		    "Done with adaptive isocurve surface approximation.\n");

	CagdSrfFree(NSrf);

	/* Scan the adaptive isoline list. Note that normal curve is paired */
	/* after Euclidean curve, so we can step two curves at a time.      */
	for (Crv = Coverage; Crv != NULL; Crv = Crv -> Pnext -> Pnext) {
	    IPVertexStruct *VP, *VN;
	    IPPolygonStruct
		*Poly = IritCurve2Polylines(Crv, IGGlblSamplesPerCurve,
					    IGGlblPolylineOptiApprox),
		*NPoly = IritCurve2Polylines(Crv -> Pnext,
					     IGGlblSamplesPerCurve,
					     IGGlblPolylineOptiApprox);

	    if (Poly != NULL && NPoly != NULL) {
		for (VP = Poly -> PVertex, VN = NPoly -> PVertex;
		     VP != NULL;
		     VP = VP -> Pnext, VN = VN -> Pnext) {
		    PT_COPY(VP -> Normal, VN -> Coord);
		    PT_NORMALIZE(VP -> Normal);
		    PT_SCALE(VP -> Normal, -1.0);
		}

		IPFreePolygonList(NPoly);

		Poly -> Pnext = IsoPoly;
		IsoPoly = Poly;
	    }
	}
    }

    CagdCrvFreeList(Coverage);

    return IsoPoly;    
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Sets the color of an object according to its color/rgb attributes.	     M
*   If object has an RGB attribute it will be used. Otherwise, if the object M
* has a COLOR attribute it will use. Otherwise, WHITE will be used.	     M
*                                                                            *
* PARAMETERS:                                                                M
*   PObj:      To set the drawing color to its color.                        M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   IGSetColorObj                                                            M
*****************************************************************************/
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);
    }
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Sets the line width to draw the given object, in pixels.		     M
*                                                                            *
* PARAMETERS:                                                                M
*   Width:    In pixels of lines to draw with.                               M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   IGSetWidthObj                                                            M
*****************************************************************************/
void IGSetWidthObj(int Width)
{
    linewidth(Width);
}

/*****************************************************************************
* DESCRIPTION:                                                               *
* Sets the color according to the given color index.		    	     *
*                                                                            *
* PARAMETERS:                                                                *
*   color:     Index of color to use. Must be between 0 and IG_MAX_COLOR.    *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*****************************************************************************/
static void SetColorIndex(int color)
{
    int Color[3];

    if (color < 0 || color > IG_MAX_COLOR)
        color = IG_IRIT_WHITE;

    Color[0] = Colors[color][0];
    Color[1] = Colors[color][1];
    Color[2] = Colors[color][2];

    SetColorRGB(Color);
}

/*****************************************************************************
* DESCRIPTION:                                                               *
* Sets the color according to the given RGB values.			     *
*                                                                            *
* PARAMETERS:                                                                *
*   Color:      An RGB vector of integer values between 0 and 255.           *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*****************************************************************************/
static void SetColorRGB(int Color[3])
{
    int i;
    static float
	Ambient = 0.25,
	Diffuse = 0.75,
	Specular = 1.0;
    static float
	Material[] = {
	    AMBIENT,  0.25, 0.25, 0.25,
	    DIFFUSE,  0.75, 0.75, 0.75,
	    SPECULAR, 1.00, 1.00, 1.00,
	    SHININESS, 50,
	    LMNULL
	};

    c3i((long *) Color);
    if (IGGlblDepthCue)
	lRGBrange(0, 0, 0, Color[0], Color[1], Color[2], 0x0, 0x7fffff);

    /* Prepare material structure in this color and select it. */
    for (i = 0; i < 3; i++) {
	Material[1 + i] = Ambient * Color[i] / 255.0;
	Material[5 + i] = Diffuse * Color[i] / 255.0;
	Material[9 + i] = Specular * Color[i] / 255.0;
    }
    lmdef(DEFMATERIAL, 1, sizeof(Material) / sizeof(float), Material);
}

/*****************************************************************************
* DESCRIPTION:                                                               *
* Sets up and draw a transformation window.				     *
*                                                                            *
* PARAMETERS:                                                                *
*   none								     *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*****************************************************************************/
static void SetTransformWindow(void)
{
    long PrefPos[4];

#ifndef _AIX
    foreground();
#endif

    if (sscanf(IGGlblTransPrefPos, "%ld,%ld,%ld,%ld",
	       &PrefPos[0], &PrefPos[1], &PrefPos[2], &PrefPos[3]) == 4)
	prefposition(PrefPos[0], PrefPos[1], PrefPos[2], PrefPos[3]);
    else if (sscanf(IGGlblTransPrefPos, "%ld,%ld",
		    &PrefPos[0], &PrefPos[1]) == 2)
	prefsize(PrefPos[0], PrefPos[1]);
    winopen("Poly3dTrans");
    winconstraints();
    wintitle("xGLdrvs");
    RGBmode();
    gconfig();
    getorigin(&TransWinLeft, &TransWinLow);
    getsize(&TransWinWidth, &TransWinHeight);
    TransWinWidth2 = TransWinWidth / 2;
    TransWinID = winget();

    SetColorRGB(IGGlblBackGroundColor);
    clear();

    /* This is wierd. without the sleep the gl get mixed up between the two  */
    /* windows. If you have any idea why, let me know...		     */
    sleep(1);
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Redraws the transformation window.					     M
*                                                                            *
* PARAMETERS:                                                                M
*   None                                                                     M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   RedrawTransformWindow                                                    M
*****************************************************************************/
static void RedrawTransformWindow(void)
{
    int i;
    long SubTransPosX, SubTransPosY, SubTransWidth, SubTransHeight;

    /* 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";

    winset(TransWinID);                /* Draw in the transformation window. */

    SubTransWidth = (int) (TransWinWidth * INTERACT_SUB_WINDOW_WIDTH);
    SubTransHeight = (int) (TransWinHeight * INTERACT_SUB_WINDOW_HEIGHT);
    SubTransPosX = (TransWinWidth - SubTransWidth) / 2;

    SetColorRGB(IGGlblBackGroundColor);
    clear();

    for (i = 0; i < INTERACT_NUM_OF_SUB_WNDWS; i++) {
	SetColorIndex(InteractMenu.SubWindows[i].Color);
	SubTransPosY = (int) (TransWinHeight * InteractMenu.SubWindows[i].Y);

	move2i(SubTransPosX, SubTransPosY);
	draw2i(SubTransPosX + SubTransWidth, SubTransPosY);
	draw2i(SubTransPosX + SubTransWidth, SubTransPosY + SubTransHeight);
	draw2i(SubTransPosX, SubTransPosY + SubTransHeight);
	draw2i(SubTransPosX, SubTransPosY);
	if (InteractMenu.SubWindows[i].TextInside) {
	    DrawText(InteractMenu.SubWindows[i].Str,
		     TransWinWidth / 2,
		     SubTransPosY + SubTransHeight / 2);
	}
	else {
	    DrawText(InteractMenu.SubWindows[i].Str,
		     (TransWinWidth - SubTransWidth) / 3,
		     SubTransPosY + SubTransHeight / 2);
	    move2i(SubTransPosX + SubTransWidth / 2, SubTransPosY);
	    draw2i(SubTransPosX + SubTransWidth / 2,
		   SubTransPosY + SubTransHeight);
	}
    }

    for (i = 0; i < INTERACT_NUM_OF_STRINGS; i++) {
	SetColorIndex(InteractMenu.Strings[i].Color);
	DrawText(InteractMenu.Strings[i].Str,
		 (int) (InteractMenu.Strings[i].X * TransWinWidth),
		 (int) (InteractMenu.Strings[i].Y * TransWinHeight));
    }

    winset(ViewWinID);             /* Go back to the default drawing window. */
}

/*****************************************************************************
* DESCRIPTION:                                                               *
* Clears the viewing area.						     *
*                                                                            *
* PARAMETERS:                                                                *
*   None                                                                     *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*****************************************************************************/
static void ClearViewArea(void)
{
    SetColorRGB(IGGlblBackGroundColor);
    clear();
    mmode(MVIEWING);
    shademodel(GOURAUD);

    if (APX_EQ(IGGlblZMinClip, IGGlblZMaxClip))
        IGGlblZMaxClip += EPSILON;
    ortho(-1.0, 1.0, -1.0, 1.0, IGGlblZMinClip, IGGlblZMaxClip);

    if (winget() == ViewWinID) {
	/* activate zbuffer only if we are in solid drawing mode. */
	if (IGGlblDrawSolid) {
	    /* Define necessary staff for Lighting. */
	    lmbind(MATERIAL, 1);
	    lmbind(LMODEL, 1);
	    lmbind(LIGHT1, 1);

	    zbuffer(TRUE);
	    zclear();
	}
	else {
	    zbuffer(FALSE);
	}
    }
}

/*****************************************************************************
* DESCRIPTION:                                                               *
* Sets up a view window.						     *
*                                                                            *
* PARAMETERS:                                                                *
*   argc, argv:   Command line,                                              *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*                                                                            *
* KEYWORDS:                                                                  *
*   SetViewWindow                                                            *
*****************************************************************************/
static void SetViewWindow(void)
{
    long PrefPos[4];
    static int
	UpdateLightPos = FALSE;
    static float Light1[] = {
	AMBIENT, 0.25, 0.25, 0.25,
	POSITION, 0.1, 0.5, 1.0, 0.0,
	LMNULL
    };

    if (!UpdateLightPos) {
	int i;

	for (i = 0; i < 4; i++)
	    Light1[i + 5] = IGGlblLightSrcPos[i];

	UpdateLightPos = TRUE;
    }

#ifndef _AIX
    foreground();
#endif

    if (sscanf(IGGlblViewPrefPos, "%ld,%ld,%ld,%ld",
	       &PrefPos[0], &PrefPos[1], &PrefPos[2], &PrefPos[3]) == 4)
	prefposition(PrefPos[0], PrefPos[1], PrefPos[2], PrefPos[3]);
    else if (sscanf(IGGlblViewPrefPos, "%ld,%ld",
		    &PrefPos[0], &PrefPos[1]) == 2)
	prefsize(PrefPos[0], PrefPos[1]);
    winopen("Poly3dView");
    winconstraints();
    wintitle("xGLdrvs");
    if (IGGlblDoDoubleBuffer)
	doublebuffer();
    else
        singlebuffer();
    RGBmode();
    if (IGGlblDepthCue) {
#	ifndef _AIX
	    glcompat(GLC_ZRANGEMAP, 1);
#	endif /* _AIX */
	lRGBrange(0, 0, 0, 255, 255, 255, 0x0, 0x7fffff);
	depthcue(IGGlblDepthCue);
    }
    else
        depthcue(FALSE);
    if (IGGlblAntiAliasing) {
#	ifndef _AIX
	    if (getgdesc(GD_PNTSMOOTH_CMODE) == 0 ||
		getgdesc(GD_BITS_NORM_DBL_CMODE) < 8)
		IGGlblAntiAliasing = FALSE;
	    else {
		subpixel(TRUE);
		pntsmooth(SMP_ON);
	    }
#	endif /* _AIX */
    }
    gconfig();
    getorigin(&ViewWinLeft, &ViewWinLow);
    getsize(&ViewWinWidth, &ViewWinHeight);
    ViewWinWidth2 = ViewWinWidth / 2;
    ViewWinHeight2 = ViewWinHeight / 2;
    
    ViewWinID = winget();

    concave(TRUE);

    /* Define necessary staff for Lighting. */
    lmdef(DEFMATERIAL, 1, 0, NULL);
    lmdef(DEFLMODEL, 1, 0, NULL);
    lmdef(DEFLIGHT, 1, sizeof(Light1) / sizeof(float), Light1);

    ClearViewArea();
    if (IGGlblDoDoubleBuffer)
	swapbuffers();

    /* This is wierd. without the sleep the gl get mixed up between the two  */
    /* windows. If you have any idea why, let me know...		     */
    sleep(1);
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Redraw the view window.                                                    M
*                                                                            *
* PARAMETERS:                                                                M
*   None                                                                     M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   IGRedrawViewWindow                                                       M
*****************************************************************************/
void IGRedrawViewWindow(void)
{
    IPObjectStruct *PObj, *MatObj;
    Matrix CrntView;
    int i, j;
    MatrixType IritCrntView, IritView;

    ClearViewArea();

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

    for (PObj = IGGlblDisplayList; PObj != NULL; PObj = PObj -> Pnext) {
	int Visible = TRUE;

	if ((MatObj = AttrGetObjectObjAttrib(PObj, "_animation_mat")) != NULL &&
	    IP_IS_MAT_OBJ(MatObj)) {
	    if (Visible = AttrGetObjectIntAttrib(PObj, "_isvisible")) {
		MatMultTwo4by4(IritCrntView,
			       *MatObj -> U.Mat, IritPrsrViewMat);
		if (IGGlblViewMode == IG_VIEW_PERSPECTIVE) 
		    MatMultTwo4by4(IritCrntView,
				   IritCrntView, IritPrsrPrspMat);
	    }
	}
	else
	    GEN_COPY(IritCrntView, IritView, sizeof(MatrixType));

	if (Visible) {
	    for (i = 0; i < 4; i++)
		for (j = 0; j < 4; j++)
		    CrntView[i][j] = IritCrntView[i][j];
	    loadmatrix(CrntView);

	    IGDrawObject(PObj);
	}
    }

    if (IGGlblDoDoubleBuffer)
	swapbuffers();
}

/*****************************************************************************
* DESCRIPTION:                                                               *
* Handles input events                                                       *
*                                                                            *
* PARAMETERS:                                                                *
*   ChangeFactor:        A continuous numeric value between -1 and 1. This   *
*			 value will be used to set amount of event such as   *
*			 rotation or translation.			     *
*   BlockForEvent:	 If TRUE, blocks until event is recieved.	     *
*                                                                            *
* RETURN VALUE:                                                              *
*   IGGraphicEventType:  Type of new event.                                  *
*****************************************************************************/
static IGGraphicEventType GetGraphicEvent(RealType *ChangeFactor,
					  int BlockForEvent)
{
    static IGGraphicEventType
	LastEvent = IG_EVENT_NONE;
    static long
	LastX = -1;
    int i, TransformRequest,
        LeftButtonIsPressed = getbutton(LEFTMOUSE) == 1;
    IGGraphicEventType
	RetVal = IG_EVENT_NONE;
    short data;
    long y, dev,
	x = -1;
    RealType XPos, YPos;

    /* Allow continuous drag on following events only: */
    if (LeftButtonIsPressed && !IG_IS_DRAG_EVENT(LastEvent)) {
	while (getbutton(LEFTMOUSE) == 1);
	LeftButtonIsPressed = FALSE;
    }

    if (LeftButtonIsPressed) {
	/* Allow leaving the Trans window if still pressed, and use last     */
	/* event as the returned event.	Note we wait until current position  */
	/* is different from last one to make sure we do something.          */
	while((x = getvaluator(MOUSEX) - TransWinLeft) == LastX &&
	      getbutton(LEFTMOUSE) == 1);
	if (x != LastX) {
	    *ChangeFactor = (((RealType) x) - LastX) / TransWinWidth2;
	    LastX = x;
	    return LastEvent;
	}
	else
	    LeftButtonIsPressed = FALSE;
    }

    LastEvent = IG_EVENT_NONE;

    while (RetVal == IG_EVENT_NONE) {
	/* Wait for left button to be pressed in the Trans window. Note this */
	/* is the loop we are going to cycle in idle time.		     */
	for (TransformRequest = FALSE; !TransformRequest;) {
	    x = getvaluator(MOUSEX);
	    y = getvaluator(MOUSEY);

	    if (qtest()) {	              /* Any external event occured? */
		switch (dev = qread(&data)) {
		    case REDRAW:
			if (data == ViewWinID) {
			    getorigin(&ViewWinLeft, &ViewWinLow);
			    getsize(&ViewWinWidth, &ViewWinHeight);
			    ViewWinWidth2 = ViewWinWidth / 2;
			    ViewWinHeight2 = ViewWinHeight / 2;
			    reshapeviewport();
			    ortho2(-0.5, ViewWinWidth - 0.5,
				   -0.5, ViewWinHeight - 0.5);
			    IGRedrawViewWindow();
			}
			else if (data == TransWinID) {
			    winset(TransWinID);
			    getorigin(&TransWinLeft, &TransWinLow);
			    getsize(&TransWinWidth, &TransWinHeight);
			    reshapeviewport();
			    ortho2(-0.5, TransWinWidth - 0.5,
				   -0.5, TransWinHeight - 0.5);
			    TransWinWidth2 = TransWinWidth / 2;
			    RedrawTransformWindow();
			    winset(ViewWinID);
			}
			break;
		    case RIGHTMOUSE:
			if (data &&
			    IGHandleState(dopup(GlblStateMenu), FALSE)) {
			    *ChangeFactor = 0.0;
			    return IG_EVENT_STATE;
			}
			break;
		    case LEFTMOUSE:
			TransformRequest = data;
			break;
		}
		continue;
	    }

	    /* Maybe we have something in communication socket. */
	    if (!IGGlblStandAlone &&
		IGReadObjectsFromSocket(IGGlblViewMode, &IGGlblDisplayList))
		IGRedrawViewWindow();


	    if (BlockForEvent)
		IritSleep(10);	       /* So we do not use all CPU on idle. */
	    else
		break;
	}

	if (!TransformRequest && !BlockForEvent)
	    return RetVal;

	x -= TransWinLeft;
	y -= TransWinLow;

	XPos = ((RealType) x) / TransWinWidth;
	YPos = ((RealType) y) / TransWinHeight;

	/* 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)
	    continue;

	/* 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;
	    }
	}
	if (i == INTERACT_NUM_OF_SUB_WNDWS)
	    continue;

	/* 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;
		RedrawTransformWindow();
		break;
	    case IG_EVENT_PERS_ORTHO_TGL:
	        IGGlblViewMode = IGGlblViewMode == IG_VIEW_PERSPECTIVE ?
						   IG_VIEW_ORTHOGRAPHIC :
						   IG_VIEW_PERSPECTIVE;
		RedrawTransformWindow();
		break;
	    default:
		break;
	}

	*ChangeFactor = (((RealType) x) - TransWinWidth2) / TransWinWidth2;
    }

    LastEvent = RetVal;
    LastX = x;

    return RetVal;
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Handles the events of the pop up window.                                   M
*                                                                            *
* PARAMETERS:                                                                M
*   State:      Event to handle.                                             M
*   Refresh:    Do we need to refresh the screen according to what we know   M
*		on entry.						     M
*                                                                            *
* RETURN VALUE:                                                              M
*   int:        TRUE, if we need to refresh the screen.                      M
*                                                                            *
* KEYWORDS:                                                                  M
*   IGHandleState                                                            M
*****************************************************************************/
int IGHandleState(int State, int Refresh)
{
    int UpdateView = TRUE;

    switch (State) {
	case IG_STATE_SCR_OBJ_TGL:
	    IGGlblTransformMode = IGGlblTransformMode == IG_TRANS_OBJECT ?
							 IG_TRANS_SCREEN :
							 IG_TRANS_OBJECT;
	    RedrawTransformWindow();
	    UpdateView = FALSE;
	    break;
	case IG_STATE_PERS_ORTHO_TGL:
	    IGGlblViewMode = IGGlblViewMode == IG_VIEW_PERSPECTIVE ?
					       IG_VIEW_ORTHOGRAPHIC :
					       IG_VIEW_PERSPECTIVE;
	    RedrawTransformWindow();
	    break;
	case IG_STATE_DRAW_SOLID:
	    IGGlblDrawSolid = !IGGlblDrawSolid;
	    /* And fall thru to disable the depth cueing. */
	    IGGlblDepthCue = TRUE; 
	case IG_STATE_DEPTH_CUE:
	    IGGlblDepthCue = !IGGlblDepthCue;
	    if (IGGlblDepthCue) {
#	        ifndef _AIX
		    glcompat(GLC_ZRANGEMAP, 1);
#	        endif /* _AIX */
		lRGBrange(0, 0, 0, 255, 255, 255,
			  0x0, 0x7fffff);
		depthcue(IGGlblDepthCue);
	    }
	    else
		depthcue(FALSE);
	    break;
	case IG_STATE_DOUBLE_BUFFER:
	    IGGlblDoDoubleBuffer = !IGGlblDoDoubleBuffer;
	    winset(ViewWinID);
	    if (IGGlblDoDoubleBuffer)
		doublebuffer();
	    else
		singlebuffer();
	    gconfig();
	    break;
	case IG_STATE_ANTI_ALIASING:
	    IGGlblAntiAliasing = !IGGlblAntiAliasing;
#	    ifndef _AIX
		if (getgdesc(GD_PNTSMOOTH_CMODE) == 0 ||
		    getgdesc(GD_BITS_NORM_DBL_CMODE) < 8)
		    IGGlblAntiAliasing = FALSE;
		else
		    subpixel(IGGlblAntiAliasing);
#	    endif /* _AIX */
	    break;
	case IG_STATE_FINER_APPROX:
	    IGGlblSamplesPerCurve++;
	    IGGlblFineNess++;
	    IGActiveListFreeNamedAttribute("_polygons");
	    IGActiveListFreeNamedAttribute("_adap_iso");
	    IGActiveListFreeNamedAttribute("_isoline");
	    break;
	case IG_STATE_COARSER_APPROX:
	    IGGlblFineNess--;
	    if (IGGlblFineNess < 2)
		IGGlblFineNess = 2;
	    IGGlblSamplesPerCurve--;
	    if (IGGlblSamplesPerCurve < 2)
		IGGlblSamplesPerCurve = 2;
	    IGActiveListFreeNamedAttribute("_polygons");
	    IGActiveListFreeNamedAttribute("_adap_iso");
	    IGActiveListFreeNamedAttribute("_isoline");
	    break;
	case IG_STATE_FINER_ADAP_ISO:
	    GlblAdapIsoEps /= 2.0;
	    IGActiveListFreeNamedAttribute("_adap_iso");
	    break;
	case IG_STATE_COARSER_ADAP_ISO:
	    GlblAdapIsoEps *= 2.0;
	    IGActiveListFreeNamedAttribute("_adap_iso");
	    break;
	case IG_STATE_FINER_RULED_SRF:
	    GlblRuledSrfEps /= 2.0;
	    IGActiveListFreeNamedAttribute("_adap_iso");
	    break;
	case IG_STATE_COARSER_RULED_SRF:
	    GlblRuledSrfEps *= 2.0;
	    IGActiveListFreeNamedAttribute("_adap_iso");
	    break;
	case IG_STATE_RULED_SRF_APPROX:
	    GlblRuledSrfApprox = !GlblRuledSrfApprox;
	    IGActiveListFreeNamedAttribute("_adap_iso");
	    break;
	case IG_STATE_ADAP_ISO_DIR:
	    if (IGGlblAdapIsoDir == CAGD_CONST_U_DIR)
		IGGlblAdapIsoDir = CAGD_CONST_V_DIR;
	    else
		IGGlblAdapIsoDir = CAGD_CONST_U_DIR;
	    IGActiveListFreeNamedAttribute("_adap_iso");
	    break;
	default:
	    UpdateView = IGDefaultStateHandler(State, Refresh);
    }

    if (Refresh && UpdateView)
	IGRedrawViewWindow();

    IGCreateStateMenu();

    return UpdateView;
}

/*****************************************************************************
* DESCRIPTION:                                                               *
* Draws text centered at the given position.				     *
*                                                                            *
* PARAMETERS:                                                                *
*   Str:        String to draw.                                              *
*   PosX, PosY: Location to draw at.                                         *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*****************************************************************************/
static void DrawText(char *Str, long PosX, long PosY)
{
    long Width = strwidth(Str);

    cmov2s(PosX - Width / 2, PosY - (getheight() / 2 - getdescender()));
    charstr(Str);
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Make some sound.                                                           M
*                                                                            *
* PARAMETERS:                                                                M
*   None                                                                     M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   IGIritBeep                                                               M
*****************************************************************************/
void IGIritBeep(void)
{
    ringbell();
}

/*****************************************************************************
* DESCRIPTION:                                                               M
*   Should we stop this animation. Senses the event queue of X11.            M
*                                                                            *
* PARAMETERS:                                                                M
*   Anim:     The animation to abort.                                        M
*                                                                            *
* RETURN VALUE:                                                              M
*   int:      TRUE if we need to abort, FALSE otherwise.                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   AnimCheckInterrupt                                                       M
*****************************************************************************/
int AnimCheckInterrupt(AnimationStruct *Anim)
{
    if (qtest()) {
	short data;
	Device
	    dev = qread(&data);

	if (dev == RIGHTMOUSE) {
	    fprintf(stderr, "\nAnimation was interrupted by the user.\n");

	    Anim -> StopAnim = TRUE;
	}
	else if (dev == LEFTMOUSE) {
	    IGGraphicEventType Event;
	    RealType ChangeFactor;

	    qenter(dev, data);

	    winset(TransWinID);
	    Event = GetGraphicEvent(&ChangeFactor, FALSE);
	    winset(ViewWinID);

	    if (Event != IG_EVENT_NONE) {
		if (IGProcessEvent(Event, ChangeFactor * IGGlblChangeFactor))
		    IGRedrawViewWindow();
	    }

	}
    }

    return Anim -> StopAnim;
}
