/*****************************************************************************
*   An SGI GL low level drawing routines.				     *
******************************************************************************
* (C) Gershon Elber, Technion, Israel Institute of Technology                *
******************************************************************************
* 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 "attribut.h"
#include "genmat.h"
#include "cagd_lib.h"
#include "symb_lib.h"
#include "xgldrvs.h"
#include "iritgrap.h"
#include "allocate.h"

/* Handle the positional as well as possible RGB attribute of a vertex. */
#define HANDLE_VERTEX(V) { char *Str; \
			   int Color[3]; \
			   if ((Str = AttrGetStrAttrib((V) -> Attrs, \
						       "RGB")) != NULL && \
			       sscanf(Str, "%d,%d,%d", \
				      &Color[0], &Color[1], &Color[2]) == 3) {\
			       c3i((long *) Color); \
			   } \
			   v3d((V) -> Coord); \
			 }

long
    ViewWinID = 0,
    ViewWinWidth = 100,
    ViewWinWidth2 = 50,
    ViewWinHeight = 100,
    ViewWinHeight2 = 50,
    ViewWinLow = 0,
    ViewWinLeft = 0;

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 SetColorIndex(int c);
static void SetColorRGB(int Color[3]);
static void SetParamViewWindow(void);
static void ClearViewArea(void);
static void ViewObject(IPObjectStruct *PObj, MatrixType Mat);

/*****************************************************************************
* 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)
{
    int i;
    PointType Ends[6], Zero;
    RealType
	*Pt = PObj -> U.Pt;

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

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

    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);
	Zero[0] = Zero[1] = Zero[2] = 0.0;
	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)
{
    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) {
		HANDLE_VERTEX(V);
	    }
	    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] -= IGGlblPointWidth;
		Ends[1][0] += IGGlblPointWidth;
		Ends[2][1] -= IGGlblPointWidth;
		Ends[3][1] += IGGlblPointWidth;
		Ends[4][2] -= IGGlblPointWidth;
		Ends[5][2] += IGGlblPointWidth;

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

		    switch (IGGlblShadingModel) {
			case IG_SHADING_NONE:
			    break;
			case IG_SHADING_FLAT:
			    n[0] = -Pl -> Plane[0];
			    n[1] = -Pl -> Plane[1];
			    n[2] = -Pl -> Plane[2];
			    n3f(n);
			    break;
			case IG_SHADING_GOURAUD:
			case IG_SHADING_PHONG:
			    n[0] = -V -> Normal[0];
			    n[1] = -V -> Normal[1];
			    n[2] = -V -> Normal[2];
			    n3f(n);
			    break;
		    }

		    HANDLE_VERTEX(V);

		    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) {
		    HANDLE_VERTEX(V);

		    if (IP_IS_INTERNAL_VRTX(V) && !IGGlblDrawInternal) {
			endline();
			bgnline();
		    }

		    if (IGGlblDrawPNormal) {
			for (j = 0; j < 3; j++)
			    PNormal[j] += V -> Coord[j];
			NumOfVertices++;
		    }
		}
		HANDLE_VERTEX(Pl -> PVertex);
		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
* 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)
{
    if (IGGlblAntiAliasing)       /* No support for antialiased wide lines!? */
	linewidth(1);
    else
	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:                                                               *
* Clears the viewing area.						     *
*                                                                            *
* PARAMETERS:                                                                *
*   None                                                                     *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*****************************************************************************/
static void ClearViewArea(void)
{
    SetColorRGB(IGGlblBackGroundColor);
    clear();
    mmode(MVIEWING);

    if (APX_EQ(IGGlblZMinClip, IGGlblZMaxClip))
        IGGlblZMaxClip += IRIT_EPS;
    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);
	}
    }
}

/*****************************************************************************
* DESCRIPTION:                                                               *
* Sets up the parameters of the view window.				     *
*                                                                            *
* PARAMETERS:                                                                *
*   None		                                                     *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*****************************************************************************/
static void SetParamViewWindow(void)
{
    static int
        OldDoDoubleBuffer = -1,
	OldDepthCue = -1,
	OldAntiAliasing = -1;
    int Modified = FALSE;

    winset(ViewWinID);

    if (OldDoDoubleBuffer != IGGlblDoDoubleBuffer) {
	if (IGGlblDoDoubleBuffer)
	    doublebuffer();
	else
	    singlebuffer();
	gconfig();

	Modified = TRUE;
	OldDoDoubleBuffer = IGGlblDoDoubleBuffer;
    }

    if (OldDepthCue != 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);

	Modified = TRUE;
	OldDepthCue = IGGlblDepthCue;
    }

    if (OldAntiAliasing != IGGlblAntiAliasing) {
	if (IGGlblAntiAliasing) {
#	    ifndef _AIX
	    linesmooth(SML_ON | SML_SMOOTHER);
	        if (getgdesc(GD_PNTSMOOTH_CMODE) == 0 ||
		    getgdesc(GD_BITS_NORM_DBL_CMODE) < 8) {
		}
		else {
		    subpixel(TRUE);
		    pntsmooth(SMP_ON);
		}
#	    endif /* _AIX */
	}
	else
	    linesmooth(SML_OFF);

	Modified = TRUE;
	OldAntiAliasing = IGGlblAntiAliasing;
    }

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

/*****************************************************************************
* DESCRIPTION:                                                               M
* Sets up a view window.						     M
*                                                                            *
* PARAMETERS:                                                                M
*   argc, argv:   Command line.						     M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   SetViewWindow                                                            M
*****************************************************************************/
void SetViewWindow(int argc, char **argv)
{
    long PrefPos[4];
    static int
	UpdateLightPos = FALSE;
    static float
	Light1[] = {
	    AMBIENT, 0.25, 0.25, 0.25,
	    POSITION, 1.0, 2.0, 10.0, 0.0,
	    LMNULL
	},
	Light2[] = {
	    AMBIENT, 0.25, 0.25, 0.25,
	    POSITION, -5.0, -1.0, -10.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");
    wintitle("xGLdrvs");
    ViewWinID = winget();

    RGBmode();
    SetParamViewWindow();

    getorigin(&ViewWinLeft, &ViewWinLow);
    getsize(&ViewWinWidth, &ViewWinHeight);
    ViewWinWidth2 = ViewWinWidth / 2;
    ViewWinHeight2 = ViewWinHeight / 2;

    if (ViewWinWidth > ViewWinHeight) {
	int i = (ViewWinWidth - ViewWinHeight) / 2;

	viewport((Screencoord) 0,
		 (Screencoord) ViewWinWidth,
		 (Screencoord) -i, 
		 (Screencoord) (ViewWinWidth - i));
    }
    else {
	int i = (ViewWinHeight - ViewWinWidth) / 2;

	viewport((Screencoord) -i,
		 (Screencoord) (ViewWinHeight - i),
		 (Screencoord) 0,
		 (Screencoord) ViewWinHeight);
    }

    concave(FALSE);

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

    IGRedrawViewWindow();
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Redraws the view window.						     M
*                                                                            *
* PARAMETERS:                                                                M
*   None                                                                     M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   IGRedrawViewWindow                                                       M
*****************************************************************************/
void IGRedrawViewWindow(void)
{
    MatrixType IritView;

    IGPredefinedAnimation();

    SetParamViewWindow();

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

    IPTraverseObjListHierarchy(IGGlblDisplayList, IritView, ViewObject);

    if (IGGlblDoDoubleBuffer)
	swapbuffers();
}

/*****************************************************************************
* DESCRIPTION:                                                               *
*   Call back function of the IPTraverseObjListHierarchy above.              *
*                                                                            *
* PARAMETERS:                                                                *
*   PObj:      Object to display.                                            *
*   Mat:       Viewing matrix of object.                                     *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*****************************************************************************/
static void ViewObject(IPObjectStruct *PObj, MatrixType Mat)
{
    int i, j;
    Matrix CrntView;

    for (i = 0; i < 4; i++)
        for (j = 0; j < 4; j++)
	    CrntView[i][j] = Mat[i][j];
    loadmatrix(CrntView);

    IGDrawObject(PObj);
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Handles redraw of view window event.                                       M
*                                                                            *
* PARAMETERS:                                                                M
*   None                                                                     M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*****************************************************************************/
void IGHandleInternalEvents(void)
{
    long dev;
    short data;

    if (qtest()) {
	if ((dev = qread(&data)) == REDRAW && data == ViewWinID) {
	    getorigin(&ViewWinLeft, &ViewWinLow);
	    getsize(&ViewWinWidth, &ViewWinHeight);
	    ViewWinWidth2 = ViewWinWidth / 2;
	    ViewWinHeight2 = ViewWinHeight / 2;

	    if (ViewWinWidth > ViewWinHeight) {
		int i = (ViewWinWidth - ViewWinHeight) / 2;

		viewport((Screencoord) 0,
			 (Screencoord) ViewWinWidth,
			 (Screencoord) -i,
			 (Screencoord) (ViewWinWidth - i));
	    }
	    else {
		int i = (ViewWinHeight - ViewWinWidth) / 2;

		viewport((Screencoord) -i,
			 (Screencoord) (ViewWinHeight - i),
			 (Screencoord) 0,
			 (Screencoord) ViewWinHeight);
	    }

	    ortho2(-0.5, ViewWinWidth - 0.5, -0.5, ViewWinHeight - 0.5);
	    IGRedrawViewWindow();
	}
	else {
	    /* push it back onto the queue. */
	    qenter((Device) dev, data);
	}
    }
}
