/*****************************************************************************
*   "Irit" - the 3d polygonal solid modeller.				     *
*									     *
* Written by:  Gershon Elber				Ver 0.2, Mar. 1990   *
******************************************************************************
* Module to handle viewing of objects in the ViewWindow.		     *
*****************************************************************************/

#ifdef __MSDOS__
#include <graphics.h>
#include <conio.h>
#endif /* __MSDOS__ */

#include <stdio.h>
#include <math.h>
#include "program.h"
#include "windowsl.h"
#include "windowsg.h"
#include "viewobjl.h"
#include "viewobjg.h"
#include "graphgng.h"
#include "objects.h"
#include "geomat3d.h"

#ifdef __MSDOS__
#include "mousedrv.h"
#else
#include "xgraphic.h"
#endif /* __MSDOS__ */

static int ViewNormals = FALSE, NormalsColor = 1, /* View normals to object. */
	   QuitView = FALSE;
static RealType NormalsSize = 0.1;

/* Interactive mode menu set up structure is define below (See ViewObjL.H):  */
InteractWindowStruct InteractMenu = {
    { { (SW_MIN_X + SW_MAX_X) / 2.0, SW_MAX_Y - 0.2, RED, "Rotate" },
      { (SW_MIN_X + SW_MAX_X) / 2.0, SW_MAX_Y - 0.6, GREEN, "Translate" },
      { (SW_MIN_X + SW_MAX_X) / 2.0, SW_MAX_Y - 1.0, CYAN, "Scale" },
    },
    { { (SW_MIN_X + SW_MAX_X) / 2.0, SW_MAX_Y - 0.1, YELLOW, TRUE, "Screen Coords." },
      { (SW_MIN_X + SW_MAX_X) / 2.0, SW_MAX_Y - 0.3, RED,  FALSE, "X" },    /* Rot */
      { (SW_MIN_X + SW_MAX_X) / 2.0, SW_MAX_Y - 0.4, RED,  FALSE, "Y" },
      { (SW_MIN_X + SW_MAX_X) / 2.0, SW_MAX_Y - 0.5, RED,  FALSE, "Z" },
      { (SW_MIN_X + SW_MAX_X) / 2.0, SW_MAX_Y - 0.7, GREEN,  FALSE, "X" }, /* Trans */
      { (SW_MIN_X + SW_MAX_X) / 2.0, SW_MAX_Y - 0.8, GREEN,  FALSE, "Y" },
      { (SW_MIN_X + SW_MAX_X) / 2.0, SW_MAX_Y - 0.9, GREEN,  FALSE, "Z" },
      { (SW_MIN_X + SW_MAX_X) / 2.0, SW_MAX_Y - 1.1, CYAN,   FALSE, "" }, /* Scale */
      { (SW_MIN_X + SW_MAX_X) / 2.0, SW_MAX_Y - 1.25, WHITE,  TRUE, "Quit" },
    }
};

/*****************************************************************************
*  Routine to interactively display geometric object(s) PObjList on the View *
* window enable rotating/translating/scaling it using the Input Device.      *
*****************************************************************************/
void InteractGeomObject(ObjectStruct *PObjList, RealType *UpdateGlblMat)
{
    struct ObjectStruct *ViewMat;
    MatrixType ViewMatCopy;	 /* Save original transformation to recover. */

    if (!IS_OLST_OBJ(PObjList))
	FatalError("InteractGeomObject: Not object list object!\n");

    if ((ViewMat = GetObject("VIEW_MAT")) == NULL) {
	WndwInputWindowPutStr(
	    "No view transformation matrix VIEW_MAT!", RED);
	return;
    }
    else if (!IS_MAT_OBJ(ViewMat)) {
	WndwInputWindowPutStr(
	    "VIEW_MAT object was modified (not matrix object)", RED);
	return;
    }
    MAT_COPY(ViewMatCopy, ViewMat -> U.Mat);

#ifdef __MSDOS__
    WndwClaimStatus();      /* Gain control on status window - interac menu! */
#endif /* __MSDOS__ */

    InteractDrawMenu();			    /* Draw the transformation menu. */
    /* Get data from input device, interpret it, and display interactively:  */
    InteractHandleInput(PObjList, ViewMat -> U.Mat);

    if (!APX_EQ(*UpdateGlblMat, 0.0))
	MAT_COPY(ViewMat -> U.Mat, ViewMatCopy);		 /* Recover. */

#ifdef __MSDOS__
    WndwReclaimStatus();  /* Status window is not needed - free and recover. */
    WndwStatusWindowDraw();
#endif /* __MSDOS__ */
}

/*****************************************************************************
*  Routine to draw the Interactive Menu in the Status window, using the      *
* InteractiveMenu structure defined above.				     *
*  It is assumed that string not inside of SubWindow will be of length 1.    *
*****************************************************************************/
static void InteractDrawMenu(void)
{
    int i;
#ifdef __MSDOS__
    struct textsettingstype oldtext;

    gettextsettings(&oldtext);
    settextjustify(CENTER_TEXT, CENTER_TEXT);	   /* Draw strings centered. */

    WndwClearWindow(GlblStatusWindow);		 /* Clear the status window. */
#endif /* __MSDOS__ */

    for (i=0; i<INTERACT_NUM_OF_STRINGS; i++) {   /* Draw strings of struct. */
        GGMySetColor(InteractMenu.Strings[i].Color);
        GGXYPutStr(InteractMenu.Strings[i].X,
		   InteractMenu.Strings[i].Y,
		   InteractMenu.Strings[i].Str);
    }

    for (i=0; i<INTERACT_NUM_OF_SUB_WINDOWS; i++) {	/* Draw sub windows. */
        GGMySetColor(InteractMenu.SubWindows[i].Color);
	/* Draw the frame of the SubWindow: */
	GGMyMove(InteractMenu.SubWindows[i].X - INTERACT_SUB_WINDOW_WIDTH,
		 InteractMenu.SubWindows[i].Y - INTERACT_SUB_WINDOW_HEIGHT);
	GGMyDraw(InteractMenu.SubWindows[i].X + INTERACT_SUB_WINDOW_WIDTH,
		 InteractMenu.SubWindows[i].Y - INTERACT_SUB_WINDOW_HEIGHT);
	GGMyDraw(InteractMenu.SubWindows[i].X + INTERACT_SUB_WINDOW_WIDTH,
		 InteractMenu.SubWindows[i].Y + INTERACT_SUB_WINDOW_HEIGHT);
	GGMyDraw(InteractMenu.SubWindows[i].X - INTERACT_SUB_WINDOW_WIDTH,
		 InteractMenu.SubWindows[i].Y + INTERACT_SUB_WINDOW_HEIGHT);
	GGMyDraw(InteractMenu.SubWindows[i].X - INTERACT_SUB_WINDOW_WIDTH,
		 InteractMenu.SubWindows[i].Y - INTERACT_SUB_WINDOW_HEIGHT);

	/* Now the strings inside (and if outside, a middle vertical line): */
	if (InteractMenu.SubWindows[i].TextInside)
	    GGXYPutStr(InteractMenu.SubWindows[i].X,
		       InteractMenu.SubWindows[i].Y,
		       InteractMenu.SubWindows[i].Str);
	else {
	    GGXYPutStr(InteractMenu.SubWindows[i].X -
				INTERACT_SUB_WINDOW_WIDTH - 0.025,
		       InteractMenu.SubWindows[i].Y,
		       InteractMenu.SubWindows[i].Str);
	    GGMyMove(InteractMenu.SubWindows[i].X,
		     InteractMenu.SubWindows[i].Y -
				INTERACT_SUB_WINDOW_HEIGHT);
	    GGMyDraw(InteractMenu.SubWindows[i].X,
		     InteractMenu.SubWindows[i].Y +
				INTERACT_SUB_WINDOW_HEIGHT);
	}
    }

#ifdef __MSDOS__
    settextjustify(oldtext.horiz, oldtext.vert);
#endif /* __MSDOS__ */
}

/*****************************************************************************
*  Routine to handle data from the input device (keyboard, mouse etc.) -     *
* clip it against the sub windows of the interactive menu and perform the    *
* required transfomation, by updating the global view matrix object VIEW_MAT *
*  The input data in the Rotation/Translation/Scaling sub windows is used    *
* (horizontal distance from sub window center) to set amount of change.	     *
*****************************************************************************/
static void InteractHandleInput(ObjectStruct *PObjList, MatrixType GlblViewMat)
{
    int i, UpdateView, ScreenCoord;
    double x, y, ChangeFactor;
    MatrixType Mat;

    ScreenCoord = (InteractMenu.SubWindows[0].Str[0] == 'S');

    WndwClearWindow(GlblViewWindow);		   /* Clear the view window. */
    QuitView = FALSE;
    ViewGeomObjectList(PObjList);	   /* Display it for the first time. */

    while (TRUE) {
	QuitView = FALSE;
#ifdef __MSDOS__
	while (kbhit()) getch();      /* Flush out any keyboard key strokes. */
#endif /* __MSDOS__ */
	MouseFlushBuffer();	  /* Flush out any duplicated points picked. */
	GGGetPoint(&x, &y);		/* Get coordinates from pick device. */

	for (i=0; i<INTERACT_NUM_OF_SUB_WINDOWS; i++)/* Test all sub windows.*/
	    if (ABS(InteractMenu.SubWindows[i].Y - y) <
						INTERACT_SUB_WINDOW_HEIGHT &&
		ABS(InteractMenu.SubWindows[i].X - x) <
						INTERACT_SUB_WINDOW_WIDTH)
		break;		      /* The picked point is in this window! */

	ChangeFactor = (x - InteractMenu.SubWindows[i].X) / /* Between -1..1.*/
						INTERACT_SUB_WINDOW_WIDTH;

	if (i >= INTERACT_NUM_OF_SUB_WINDOWS) {
	    GGTone(1000, 100);
	    GGTone(1500, 100);
	    GGTone(1000, 100);
	    continue;
	}

	UpdateView = TRUE;

	switch (i) {
	    case 0:		       /* Its Coordinate system - toggle it. */
		if (ScreenCoord) {
		    InteractMenu.SubWindows[0].Str = "Object Coords.";
		    ScreenCoord = FALSE;
		}
		else {
		    InteractMenu.SubWindows[0].Str = "Screen Coords.";
		    ScreenCoord = TRUE;
		}
		InteractDrawMenu();	  /* Must update the menu on screen. */
		UpdateView = FALSE;
		break;
	    case 1:			   /* Its rotation along the X axis. */
		MatGenMatRotX1(DEG2RAD(ChangeFactor * MAX_ROTATE_ANGLE), Mat);
		break;
	    case 2:			   /* Its rotation along the Y axis. */
		MatGenMatRotY1(DEG2RAD(ChangeFactor * MAX_ROTATE_ANGLE), Mat);
		break;
	    case 3:			   /* Its rotation along the Z axis. */
		MatGenMatRotZ1(DEG2RAD(ChangeFactor * MAX_ROTATE_ANGLE), Mat);
		break;
	    case 4:			/* Its translation along the X axis. */
		MatGenMatTrans(ChangeFactor * MAX_TRANSLATE_FACTOR, 0.0, 0.0,
									Mat);
		break;
	    case 5:			/* Its translation along the Y axis. */
		MatGenMatTrans(0.0, ChangeFactor * MAX_TRANSLATE_FACTOR, 0.0,
									Mat);
		break;
	    case 6:			/* Its translation along the Z axis. */
		MatGenMatTrans(0.0, 0.0, ChangeFactor * MAX_TRANSLATE_FACTOR,
									Mat);
		break;
	    case 7:			      /* Its scaling along all axes. */
		if (ChangeFactor > 0.0)		      /* Make it around 1... */
		     ChangeFactor = ChangeFactor * MAX_SCALE_FACTOR + 1.0;
		else ChangeFactor = 1.0 /
			(-ChangeFactor * MAX_SCALE_FACTOR + 1.0);
		MatGenMatScale(ChangeFactor, ChangeFactor, ChangeFactor, Mat);
		break;
	    case 8:
		return;						/* Its Quit. */
	    default:
		FatalError("InteractHandleInput: Undefine input type, exit\n");
	}
	if (UpdateView) {
	    if (ScreenCoord)		/* Udpate the global viewing matrix. */
		 MatMultTwo4by4(GlblViewMat, GlblViewMat, Mat);
	    else MatMultTwo4by4(GlblViewMat, Mat, GlblViewMat);
	    WndwClearWindow(GlblViewWindow);	   /* Clear the view window. */
	    ViewGeomObjectList(PObjList);		/* And display it... */
	}
    }
}

/*****************************************************************************
*  Routine to display the geometric objects in PObjList, by simply calling   *
* ViewGeomObject on all of them. PObjList must be of type OBJ_LIST_OBJ.	     *
*****************************************************************************/
static void ViewGeomObjectList(ObjectStruct *PObjList)
{
    int Param = 0;
    struct ObjectStruct *PObj;
    char Line[LINE_LEN];

    while ((PObj = PObjList -> U.PObjList[Param]) != NULL &&
	   Param++ < MAX_OBJ_LIST && !QuitView) {
	if (!IS_GEOM_OBJ(PObj)) {
	    sprintf(Line, "Cannt display none geometric object %s, ignored",
		PObj -> Name);
	    WndwInputWindowPutStr(Line, RED);
	    continue;
	}
	ViewGeomObject(PObj);
    }
}

/*****************************************************************************
*  Routine to fetch the internal parameter from the INTERNAL object.	     *
*****************************************************************************/
static int GetInternal(void)
{
    int Internal;
    struct ObjectStruct *PObj = GetObject("INTERNAL");

    if (PObj == NULL || !IS_NUM_OBJ(PObj)) {
	WndwInputWindowPutStr("No numeric object name INTERNAL is defined",
									RED);
	Internal = DEFAULT_INTERNAL;
    }
    else Internal = !APX_EQ((PObj -> U.R), 0.0);

    return Internal;
}

/*****************************************************************************
*  Routine to display the geometric object PObj on the View Window:	     *
* Uses the global view transformation matrix ViewMat as view point.	     *
*****************************************************************************/
void ViewGeomObject(ObjectStruct *PObj)
{
    int Color, ShowNormals, ViewInternal = GetInternal();
    struct PolygonStruct *Pl;
    struct ObjectStruct *ViewMat;

    QuitView = FALSE;
#ifdef __MSDOS__
    while (kbhit()) getch();	      /* Flush out any keyboard key strokes. */
#endif /* __MSDOS__ */
    MouseFlushBuffer();		  /* Flush out any duplicated points picked. */

    if (!IS_GEOM_OBJ(PObj))
	FatalError("ViewGeomObject: Not geometric object!\n");

    GGWindowViewPort(GlblViewWindow -> MinX, GlblViewWindow -> MinY,
		     GlblViewWindow -> MaxX, GlblViewWindow -> MaxY,
		     (int) VIEW_WINDOW_NAME);

    Pl = PObj -> U.Pl;

    if ((ViewMat = GetObject("VIEW_MAT")) == NULL) {
	WndwInputWindowPutStr(
	    "No view transformation matrix VIEW_MAT!", RED);
	return;
    }
    else if (!IS_MAT_OBJ(ViewMat)) {
	WndwInputWindowPutStr(
	    "VIEW_MAT object was modified (not matrix object)\n", RED);
	return;
    }

    Color = GET_OBJECT_COLOR(PObj);
    ShowNormals = (ViewNormals && !IS_POLYLINE_GEOM_OBJ(PObj));

    while (Pl && !QuitView) {
	ViewPolygon(Pl, Color, ShowNormals, ViewMat -> U.Mat, ViewInternal);
	Pl = Pl -> Pnext;
	TestQuitView();	   /* if break display in the middle - Set QuitView. */
    }
}

/*****************************************************************************
*  Routine to test if quit display event - occured - SPACE was hit on	     *
* keyboard or right button was clicked on mouse.			     *
*****************************************************************************/
static void TestQuitView(void)
{
    int x, y, Buttons;

#ifdef __MSDOS__
    if (kbhit() && getch() == ' ') QuitView = TRUE;
#endif /* __MSDOS__ */
    if (MouseExists && MouseQueryBuffer()) {
	MouseGetBuffer(&x, &y, &Buttons);
	QuitView = (Buttons & 0x02);
    }
}

/*****************************************************************************
*  Routine to display one polygon on the view window using the matrix Mat as *
* a transformation matrix.						     *
*****************************************************************************/
static void ViewPolygon(PolygonStruct *Pl, int Color, int ShowNormals,
					MatrixType Mat, int ViewInternal)
{
    int NumOfPoints, DontDraw;
    RealType CenterX, CenterY;
    PointType P, CenterP;
    struct VertexStruct *V, *VStart;

    CenterX = (GlblViewWindow -> MinX + GlblViewWindow -> MaxX) / 2.0;
    CenterY = (GlblViewWindow -> MinY + GlblViewWindow -> MaxY) / 2.0;

    V = VStart = Pl -> V;
    if (V == NULL) FatalError("ViewPolygon: Empty polygon to view\n");

    MatMultVecby4by4(P, V -> Pt, Mat);	       /* Transform the first point. */
    GGMyMove(CenterX + P[0], CenterY + P[1]);
    DontDraw = IS_INTERNAL_EDGE(V) && !ViewInternal;      /* Draw next edge? */
    GGMySetColor(Color);

    if (ShowNormals) {			/* If display of normal is required. */
	NumOfPoints = 0;
	PT_CLEAR(CenterP);
    }

    do {
	V = V -> Pnext;
	if (ShowNormals) {
	    NumOfPoints++;
	    PT_ADD(CenterP, CenterP, V -> Pt);
	}
	MatMultVecby4by4(P, V -> Pt, Mat);
	/* If edge is INTERNAL (Irit.h) and not ViewInternal - dont draw: */
	if (DontDraw)
	     GGMyMove(CenterX + P[0], CenterY + P[1]);
	else GGMyDraw(CenterX + P[0], CenterY + P[1]);

	DontDraw = IS_INTERNAL_EDGE(V) && !ViewInternal;  /* Draw next edge? */
    } while (V != VStart && V -> Pnext != NULL);

    if (ShowNormals) {
	PT_SCALE(CenterP, 1.0/NumOfPoints);	    /* Estimate for normals. */
	MatMultVecby4by4(P, CenterP, Mat);     /* Transform the first point. */
	GGMyMove(CenterX + P[0], CenterY + P[1]);
	PT_COPY(P, Pl -> Plane);
	PT_SCALE(P, NormalsSize);
	PT_ADD(CenterP, CenterP, P);
	MatMultVecby4by4(P, CenterP, Mat);    /* Transform the second point. */
	GGMySetColor(NormalsColor);
	GGMyDraw(CenterX + P[0], CenterY + P[1]);
    }
}

/*****************************************************************************
*  Routine to set the normals default values:				     *
*****************************************************************************/
void ViewSetNormals(RealType *Active, RealType *Size, RealType *Color)
{
    ViewNormals = !APX_EQ(*Active, 0.0);
    NormalsSize = *Size;
    NormalsColor = (int) *Color;
}
