/*****************************************************************************
*   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 "iritgrap.h"
#include "irit_soc.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 void RedrawViewWindow(void);
static int IsAbortKeyPressed(void);
static void ProcessEvent(IGGraphicEventType Event, RealType ChangeFactor);
static IGGraphicEventType GetGraphicEvent(RealType *ChangeFactor);
static void DrawText(char *Str, long PosX, long PosY);

/****************************************************************************
* Pop up all windows, read input and display.				    *
****************************************************************************/
void main(int argc, char **argv)
{
    RealType ChangeFactor;
    IGGraphicEventType Event;
    IPObjectStruct *PObjects;

    IGConfigureGlobals("xgladap", argc, argv);

    SetViewWindow();
    RedrawViewWindow();
    SetTransformWindow();
    RedrawTransformWindow();

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

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

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

    IGCreateStateMenu();

    if (!IGGlblStandAlone)
	SocClientCreateSocket();

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

    if (!IGGlblStandAlone)
	SocClientCloseSocket();
}

/*****************************************************************************
* Construct global pop up menu.						     *
*****************************************************************************/
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,
	     IGGlblDrawSolid ? "Draw Solid" : "Draw Wireframe", 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, "Thicker Lines.", 0);
    addtopup(GlblStateMenu, "Thinner Lines%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);
}

/****************************************************************************
* Draw a Point/Vector object using current modes and transformations.	    *
****************************************************************************/
void IGDrawPtVec(IPObjectStruct *PObj)
{
    static PointType
	Zero = { 0.0, 0.0, 0.0 };
    int i;
    PointType Ends[6];
    RealType
	*Pt = PObj -> U.Pt;

    IGSetColorObj(PObj);

    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();
    }
}

/****************************************************************************
* Draw a Poly object using current modes and transformations.		    *
****************************************************************************/
void IGDrawPoly(IPObjectStruct *PObj)
{
    IGDrawPolyNormal(PObj, FALSE);
}

/****************************************************************************
* Draw a Poly object using current modes and transformations.		    *
*   This function allowes polylines to have normals so we can actually draw *
* the adaptive isolines to render a surface.				    *
****************************************************************************/
static void IGDrawPolyNormal(IPObjectStruct *PObj, int HasNormals)
{
    IPVertexStruct *V;
    IPPolygonStruct
	*Pl = PObj -> U.Pl;

    IGSetColorObj(PObj);

    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;
	PointType PNormal, VNormal;

	for (; Pl != NULL; Pl = Pl -> Pnext) {
	    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();
		    }
		}
	    }
	}
    }
}

/****************************************************************************
* Draw a single Surface object using current modes and transformations.	    *
*   Surface must be with either E3 or P3 point type and must be NURB.	    *
*   Control points in SGI's format must be found in "_ctlpoints" attribute. *
****************************************************************************/
void IGDrawSurface(IPObjectStruct *PObj)
{
    IPObjectStruct *PObjPolylines, *PObjCtlMesh, *PObjPolygons;
    IPPolygonStruct *PPolylines, *PCtlMesh, *PPolygons, *PPolyTemp;

    if ((PObjPolylines = AttrGetObjectObjAttrib(PObj, "_isoline")) == 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) {
	    int NumOfIso[2];

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

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

    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,
						     1 << IGGlblFineNess, FALSE);

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

	    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);
	    }

	    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);
	}

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

/****************************************************************************
* Create an adaptive isocurve coverage to a given surface.		    *
****************************************************************************/
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 = CagdPiecewiseRuledSrfApprox(Srf, TRUE, GlblRuledSrfEps,
						Dir);

	Coverage = NULL;
	NormalSrf = CagdSrfNormalSrf(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 = CagdAdapIsoExtract(TSrf, NSrf, 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);
	    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 = CagdSrfNormalSrf(Srf);

	Coverage = CagdAdapIsoExtract(Srf, NSrf, 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;
	    CagdCrvStruct
		*NCrv = Crv -> Pnext;
	    IPPolygonStruct
		*Poly = IritCurve2Polylines(Crv, IGGlblSamplesPerCurve),
		*NPoly = IritCurve2Polylines(Crv -> Pnext, IGGlblSamplesPerCurve);

	    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;    
}

/****************************************************************************
* 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 c)
{
    int Color[3];

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

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

    SetColorRGB(Color);
}

/****************************************************************************
* Routine to set the color according to the given RGB values.		    *
****************************************************************************/
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);
}

/*****************************************************************************
* Set up and draw a transformation window.				     *
*****************************************************************************/
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);
}

/*****************************************************************************
* Set up and draw a transformation window.				     *
*****************************************************************************/
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. */
}

/*****************************************************************************
*   Routine to clear the viewing area.					     *
*****************************************************************************/
static void ClearViewArea(void)
{
    SetColorRGB(IGGlblBackGroundColor);
    clear();
    mmode(MVIEWING);
    shademodel(GOURAUD);
    linewidth(IGGlblLineWidth);

    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);
	    lmbind(LIGHT2, 2);

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

/*****************************************************************************
* Set up and draw a view window.				             *
*****************************************************************************/
static void SetViewWindow(void)
{
    long PrefPos[4];
    static float Light1[] = {
	AMBIENT, 0.25, 0.25, 0.25,
	POSITION, 0.1, 0.5, 1.0, 0.0,
	LMNULL
    };
    static float Light2[] = {
	AMBIENT, 0.25, 0.25, 0.25,
	POSITION, 0.5, -1.5, -1.0, 0.0,
	LMNULL
    };

#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);
    lmdef(DEFLIGHT, 2, sizeof(Light2) / sizeof(float), Light2);

    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);
}

/*****************************************************************************
* Set up and draw a viewing window.					     *
*****************************************************************************/
static void RedrawViewWindow(void)
{
    IPObjectStruct *PObj;
    Matrix CrntView;
    int i, j, k;

    ClearViewArea();

    if (IGGlblViewMode == IG_VIEW_PERSPECTIVE) {
	for (i = 0; i < 4; i++)
	    for (j = 0; j < 4; j++) {
		CrntView[i][j] = 0;
		for (k = 0; k < 4; k++)
		    CrntView[i][j] += IritPrsrViewMat[i][k] *
				      IritPrsrPrspMat[k][j];
	    }
    }
    else {
	for (i = 0; i < 4; i++)
	    for (j = 0; j < 4; j++)
		CrntView[i][j] = IritPrsrViewMat[i][j];
    }
    loadmatrix(CrntView);

    for (PObj = IGGlblDisplayList; PObj != NULL; PObj = PObj -> Pnext)
	IGDrawObject(PObj);

    if (IGGlblDoDoubleBuffer)
	swapbuffers();
}

/******************************************************************************
* Handle gl events							      *
******************************************************************************/
static IGGraphicEventType GetGraphicEvent(RealType *ChangeFactor)
{
    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 x, y, dev;
    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);
			    RedrawViewWindow();
			}
			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))
		RedrawViewWindow();

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

	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;
	    case IG_EVENT_QUIT:
		break;
	}

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

    LastEvent = RetVal;
    LastX = x;

    return RetVal;
}
/******************************************************************************
* Handle the event of a pop up window.					      *
******************************************************************************/
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_WIDER_LINES:
	    IGGlblLineWidth *= 2;
	    linewidth(IGGlblLineWidth);
	    break;
	case IG_STATE_NARROW_LINES:
	    IGGlblLineWidth /= 2;
	    if (IGGlblLineWidth < 1)
		IGGlblLineWidth = 1;
	    linewidth(IGGlblLineWidth);
	    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)
	RedrawViewWindow();

    IGCreateStateMenu();

    return UpdateView;
}

/******************************************************************************
* Draw text centered at the given position.				      *
******************************************************************************/
static void DrawText(char *Str, long PosX, long PosY)
{
    long Width = strwidth(Str);

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

/****************************************************************************
* Make noise function.							    *
****************************************************************************/
void IGIritBeep(void)
{
    ringbell();
}
