/*****************************************************************************
* Applies coordinate system transformations to each object and global context*
******************************************************************************
* (C) Gershon Elber, Technion, Israel Institute of Technology                *
******************************************************************************
* Written by:  Bassarab Dmitri & Plavnik Michael       Ver 0.2, Apr. 1995    *
*****************************************************************************/

#include "program.h"
#include "debug.h"

static void Transform(IPVertexStruct *Vertex);
static void MapToScreen(IPVertexStruct *Vertex);
static void ReverseVertexNormal(IPVertexStruct *v);
static void ReversePlaneNormal(IPPolygonStruct *Poly);

static MatrixType ScreenMat, ObjectMat;	    /* Map to screen/object matrix. */

/*****************************************************************************
* DESCRIPTION:                                                               *
*   Apply transformation defined in global view matrix to a vertex coordi-   *
*   nates. Sets reciprotory of homogenious coordinate to computed value.     *
*                                                                            *
* PARAMETERS:                                                                *
*   Vertex: IN OUT, pointer to transofrmee.                                  *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*****************************************************************************/
static void Transform(IPVertexStruct *Vertex)
{
    RealType Homo[4], w;
    
    PT_COPY(Homo, Vertex -> Coord);
    Homo[3] = 1.0;
    MatMultWVecby4by4(Homo, Homo, ObjectMat);
    if (Homo[3] < IRIT_EPS) {
	if (Homo[3] < -IRIT_EPS)	{
	    static int
		WasMessage = FALSE;

	    if (!WasMessage) {
		fprintf(stderr, "Error: negative homogeneous values in view!\n");
		WasMessage = TRUE;
	    }
	}
	else {
	    fprintf(stderr, "Error: bad perspective matrix!\n");
	    exit(11);
	}
    }
    w = 1 / Homo[3];
    PT_SCALE(Homo, w);
    PT_COPY(Vertex -> Coord, Homo);
    AttrSetRealAttrib(&Vertex -> Attrs, "_1/W", w);
}

/*****************************************************************************
* DESCRIPTION:                                                               *
*   Apply transformation defined in global centerX, centerY and scale vars.  *
*   That is actualy 3D to 2D of viewing plane projection.                    *
*                                                                            *
* PARAMETERS:                                                                *
*   Vertex: IN OUT, pointer to transformee.                                  *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*****************************************************************************/
static void MapToScreen(IPVertexStruct *Vertex)
{
    MatMultVecby4by4(Vertex -> Coord, Vertex -> Coord, ScreenMat);
}

/*****************************************************************************
* DESCRIPTION:                                                               *
*   Set vertex normal in opposite direction.                                 *
*                                                                            *
* PARAMETERS:                                                                *
*   v:       IN OUT, pointer to vertex object.                               *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*****************************************************************************/
static void ReverseVertexNormal(IPVertexStruct *v)
{
    PT_SCALE(v -> Normal, -1.0);
}

/*****************************************************************************
* DESCRIPTION:                                                               *
*   Sets plane equation to have normal defined in opposite direction.        *
*                                                                            *
* PARAMETERS:                                                                *
*   v:       IN OUT, pointer to vertex object.                               *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*****************************************************************************/
static void ReversePlaneNormal(IPPolygonStruct *Poly)
{
    PT_SCALE(Poly -> Plane, -1.0);
    Poly -> Plane[3] = -Poly -> Plane[3];
}

/*****************************************************************************
* DESCRIPTION:                                                               M
*   Performs necessary transformation of coordinate system as described in   M
*   irit matrices and generates properly global "all" and "inv" matrices.    M
*   All objects in the system are transformed in accordance. Also clipping   M 
*   and polyline to polygon conversions are performed. Vertex attributes are M 
*   inserted to enable incremental algorithm style.                          M
*                                                                            *
* PARAMETERS:                                                                M
*   Objects: IN, pointer to Irit objects tree.                               M
*                                                                            *
* RETURN VALUE:                                                              M
*   IPObjectStruct *: pointer to the Irit objects tree after transformations.M
*                                                                            *
* KEYWORDS:                                                                  M
*   Map, coordinate transformations, homogenious coordinate, clipping        M
*****************************************************************************/
IPObjectStruct *Map(IPObjectStruct *Objects)
{
    double Y1, Y2;
    RealType YMinClip, YMaxClip;
    MatrixType Mat;
    IPObjectStruct *PObj;
    IPPolygonStruct *Poly;
    IPVertexStruct *Vertex;
    int ClipAllOut = getenv("IRENDER_CLIP_ALL_OUT") != NULL;
    char
	*ClipYLevel = getenv("IRENDER_CLIP_Y_LEVEL");

    if (ClipYLevel != NULL && sscanf(ClipYLevel, "%lf %lf", &Y1, &Y2) == 2) {
	YMinClip = Y1;
	YMaxClip = Y2;
    }
    else {
	YMinClip = -1.0;
	YMaxClip =  1.0;
    }

    MAPPING_MESSAGE();

    MatGenUnitMat(Context.ViewMat);
    
    if (IritPrsrWasViewMat)
        MatMultTwo4by4(Context.ViewMat, Context.ViewMat, IritPrsrViewMat);
    
    if (IritPrsrWasPrspMat) {
        MatMultTwo4by4(Context.ViewMat, Context.ViewMat, IritPrsrPrspMat);
        Context.Viewer[X] = Context.Viewer[Y] = Context.Viewer[Z] = 0.0;
    }

    if (!MatInverseMatrix(Context.ViewMat, Context.InvMat)) {
        fprintf(stderr, "Error: non-invertable matrix.\n");
        exit(11);
    }

    for (PObj = Objects; PObj != NULL; PObj = PObj -> Pnext) {
	int Visible = TRUE;
	IPObjectStruct *MatObj;

	if ((MatObj = AttrGetObjectObjAttrib(PObj,
					     "_animation_mat")) != NULL &&
	    IP_IS_MAT_OBJ(MatObj)) {
	    if ((Visible = AttrGetObjectIntAttrib(PObj,
						  "_isvisible")) != FALSE) {
		MatMultTwo4by4(ObjectMat, *MatObj -> U.Mat, Context.ViewMat);
	    }
	}
	else
	    GEN_COPY(ObjectMat, Context.ViewMat, sizeof(MatrixType));

	if (ATTR_OBJ_IS_INVISIBLE(PObj) || !Visible)
	    continue;

        if (IP_IS_POLY_OBJ(PObj)) {
	    IPPolygonStruct
		*PrevPoly = NULL;

            for (Poly = PObj -> U.Pl; Poly != NULL; ) {
		int AllVerticesOut = TRUE,
		    AllVerticesLeft = TRUE,
		    AllVerticesRight = TRUE,
		    AllVerticesAbove = TRUE,
		    AllVerticesBelow = TRUE;

                for (Vertex = Poly -> PVertex;
		     Vertex != NULL;
		     Vertex = Vertex -> Pnext) {
                    Transform(Vertex);
		    if (Vertex -> Coord[0] <= 1.0)
		        AllVerticesRight = FALSE;
		    if (Vertex -> Coord[0] >= -1.0)
		        AllVerticesLeft = FALSE;
		    if (Vertex -> Coord[1] <= YMaxClip)
		        AllVerticesAbove = FALSE;
		    if (Vertex -> Coord[1] >= YMinClip)
		        AllVerticesBelow = FALSE;

		    if (Vertex -> Coord[0] <= 1.0 &&
			Vertex -> Coord[0] >= -1.0 &&
			Vertex -> Coord[1] <= YMaxClip &&
			Vertex -> Coord[1] >= YMinClip)
			AllVerticesOut = FALSE;
		}

		if ((ClipAllOut && AllVerticesOut) ||
		    AllVerticesLeft ||
		    AllVerticesRight ||
		    AllVerticesAbove ||
		    AllVerticesBelow) {
		    /* This polygon is completely outside screen - purge it. */
		    if (PrevPoly != NULL) {
			PrevPoly -> Pnext = Poly -> Pnext;
		    }
		    else {
			/* First polygon in object. */
			PObj -> U.Pl = Poly -> Pnext;
		    }
		    Poly -> Pnext = NULL;
		    IPFreePolygon(Poly);
		    Poly = PrevPoly != NULL ? PrevPoly -> Pnext : PObj -> U.Pl;
		}
		else {
		    PrevPoly = Poly;
		    Poly = Poly -> Pnext;
		}
	    }

	    if (PObj -> U.Pl == NULL) {
		fprintf(stderr, "Object \"%s\" was found completely outside the screen and was purged\n",
			PObj -> Name);
	    }
	}
    }

    if (Options.Polylines)
        Polyline2Polygons(Objects);
    
    if (Options.NormalReverse){
        IritPrsrForEachVertex(Objects, ReverseVertexNormal);
        IritPrsrForEachPoly(Objects, ReversePlaneNormal);
    }

    MatGenMatScale(Options.XSize / 2, Options.YSize / 2, 1.0, Mat);
    MatGenMatTrans(Options.XSize / 2, Options.YSize / 2, 0.0, ScreenMat);
    MatMultTwo4by4(ScreenMat, Mat, ScreenMat);
    MatMultTwo4by4(Context.ViewMat, Context.ViewMat, ScreenMat);
    IritPrsrForEachVertex(Objects, MapToScreen);

    if (!MatInverseMatrix(Context.ViewMat, Context.InvMat)) {
        fprintf(stderr, "Error: non-invertable matrix.\n");
        exit(11);
    }

    MatMultVecby4by4(Context.Viewer, Context.Viewer, Context.InvMat);

    if (!IS_VIEWER_POINT()) {           /* Only vectors need to be adjusted. */
        static PointType
	    Zero = { 0.0, 0.0, 0.0 };

        MatMultVecby4by4(Zero, Zero, Context.InvMat);
        PT_SUB(Context.Viewer, Context.Viewer, Zero);
        PT_NORMALIZE(Context.Viewer);
    }
    return Objects;
}
