/******************************************************************************
* GeoMat3d.c - Trans. Matrices , Vector computation, and Comp.geom.	      *
*******************************************************************************
* Written by Gershon Elber, March 1990.					      *
******************************************************************************/

#include <math.h>
#include <stdio.h>
#include "irit_sm.h"
#include "iritprsr.h"
#include "allocate.h"
#include "convex.h"
#include "geomat3d.h"

/* #define DEBUG1	   Print information on entry and exit of routines. */

static int CGPointRayRelation(PointType Pt, PointType PtRay, int Axes);

/*****************************************************************************
*  Routine to copy one vector to another:				     *
*****************************************************************************/
void GMVecCopy(VectorType Vdst, VectorType Vsrc)
{
    int i;

    for (i = 0; i < 3; i++)
	Vdst[i] = Vsrc[i];
}

/*****************************************************************************
*  Routine to normalize the vector length to a unit length:                  *
*****************************************************************************/
void GMVecNormalize(VectorType V)
{
    int i;
    RealType Len;

    Len = GMVecLength(V);
    if (Len > 0.0)
        for (i = 0; i < 3; i++) {
	    V[i] /= Len;
	    if (ABS(V[i]) < EPSILON)
		V[i] = 0.0;
	}
}

/*****************************************************************************
*  Routine to calculate the magnitude (length) of a given 3D vector:         *
*****************************************************************************/
RealType GMVecLength(VectorType V)
{
    return sqrt(SQR(V[0]) + SQR(V[1]) + SQR(V[2]));
}

/*****************************************************************************
*  Routine to calculate the cross product of two vectors:                    *
* Note Vres might be the same as V1 or V2 !                                  *
*****************************************************************************/
void GMVecCrossProd(VectorType Vres, VectorType V1, VectorType V2)
{
    VectorType Vtemp;

    Vtemp[0] = V1[1] * V2[2] - V2[1] * V1[2];
    Vtemp[1] = V1[2] * V2[0] - V2[2] * V1[0];
    Vtemp[2] = V1[0] * V2[1] - V2[0] * V1[1];

    GMVecCopy(Vres, Vtemp);
}

/*****************************************************************************
*  Routine to calculate the dot product of two vectors:                      *
*****************************************************************************/
RealType GMVecDotProd(VectorType V1, VectorType V2)
{
    return  V1[0] * V2[0] + V1[1] * V2[1] + V1[2] * V2[2];
}

/*****************************************************************************
* Routine to generate rotation object around the X axis in Degree degrees:   *
*****************************************************************************/
IPObjectStruct *GMGenMatObjectRotX(RealType *Degree)
{
    MatrixType Mat;

    MatGenMatRotX1(DEG2RAD(*Degree), Mat);    /* Generate the trans. matrix. */

    return GenMATObject(Mat);
}

/*****************************************************************************
* Routine to generate rotation object around the Y axis in Degree degrees:   *
*****************************************************************************/
IPObjectStruct *GMGenMatObjectRotY(RealType *Degree)
{
    MatrixType Mat;

    MatGenMatRotY1(DEG2RAD(*Degree), Mat);    /* Generate the trans. matrix. */

    return GenMATObject(Mat);
}

/*****************************************************************************
* Routine to generate rotation object around the Z axis in Degree degrees:   *
*****************************************************************************/
IPObjectStruct *GMGenMatObjectRotZ(RealType *Degree)
{
    MatrixType Mat;

    MatGenMatRotZ1(DEG2RAD(*Degree), Mat);    /* Generate the trans. matrix. */

    return GenMATObject(Mat);
}

/*****************************************************************************
* Routine to generate translation object according to XYZ vector Vec.	     *
*****************************************************************************/
IPObjectStruct *GMGenMatObjectTrans(VectorType Vec)
{
    MatrixType Mat;

    /* Generate the transformation matrix */
    MatGenMatTrans(Vec[0], Vec[1], Vec[2], Mat);

    return GenMATObject(Mat);
}

/*****************************************************************************
* Routine to scale an object according to XYZ scaling vector Vec.	     *
*****************************************************************************/
IPObjectStruct *GMGenMatObjectScale(VectorType Vec)
{
    MatrixType Mat;

    /* Generate the transformation matrix */
    MatGenMatScale(Vec[0], Vec[1], Vec[2], Mat);

    return GenMATObject(Mat);
}

/*****************************************************************************
* Routine to transform an object according to the transformation matrix.     *
*****************************************************************************/
IPObjectStruct *GMTransformObject(IPObjectStruct *PObj, MatrixType Mat)
{
    int i;
    IPObjectStruct *NewPObj;

    if (IP_IS_POLY_OBJ(PObj)) {
    	int IsPolygon = !IP_IS_POLYLINE_OBJ(PObj);
    	VectorType Pin, PTemp;
    	IPPolygonStruct *Pl;
    	IPVertexStruct *V, *VFirst;

    	NewPObj = CopyObject(NULL, PObj, FALSE);
	Pl = NewPObj -> U.Pl;

	while (Pl != NULL) {
	    V = VFirst = Pl -> PVertex;
	    PT_ADD(Pin, V -> Coord, Pl -> Plane);  /* Prepare point IN side. */
	    MatMultVecby4by4(Pin, Pin, Mat); /* Transformed relative to new. */

	    do {
		if (IsPolygon)
		    PT_ADD(PTemp, V -> Coord, V -> Normal);

		MatMultVecby4by4(V -> Coord, V -> Coord, Mat);/* Update pos. */

		if (IsPolygon) {
		    MatMultVecby4by4(PTemp, PTemp, Mat);   /* Update normal. */
		    PT_SUB(V -> Normal, PTemp, V -> Coord);
		    PT_NORMALIZE(V -> Normal);
		}

		V = V -> Pnext;
	    }
	    while (V != VFirst && V != NULL);	       /* VList is circular! */

	    if (IsPolygon)
		IritPrsrUpdatePolyPlane2(Pl, Pin);	/* Update plane eqn. */

	    Pl = Pl -> Pnext;
	}
    }
    else if (IP_IS_SRF_OBJ(PObj)) {
	CagdSrfStruct *Srf;

    	NewPObj = CopyObject(NULL, PObj, FALSE);

	for (Srf = NewPObj -> U.Srfs; Srf != NULL; Srf = Srf -> Pnext)
	    CagdSrfMatTransform(Srf, Mat);
    }
    else if (IP_IS_CRV_OBJ(PObj)) {
	CagdCrvStruct *Crv;

    	NewPObj = CopyObject(NULL, PObj, FALSE);

	for (Crv = NewPObj -> U.Crvs; Crv != NULL; Crv = Crv -> Pnext)
	    CagdCrvMatTransform(Crv, Mat);
    }
    else if (IP_IS_POINT_OBJ(PObj)) {
    	NewPObj = CopyObject(NULL, PObj, FALSE);

	MatMultVecby4by4(NewPObj -> U.Pt, NewPObj -> U.Pt, Mat);
    }
    else if (IP_IS_VEC_OBJ(PObj)) {
    	NewPObj = CopyObject(NULL, PObj, FALSE);

	MatMultVecby4by4(NewPObj -> U.Vec, NewPObj -> U.Vec, Mat);
    }
    else if (IP_IS_OLST_OBJ(PObj)) {
	IPObjectStruct *PObjTmp;

	NewPObj = IPAllocObject("", IP_OBJ_LIST_OBJ, NULL);

    	for (i = 0; (PObjTmp = ListObjectGet(PObj, i)) != NULL; i++)
    	    ListObjectInsert(NewPObj, i, GMTransformObject(PObjTmp, Mat));
	ListObjectInsert(NewPObj, i, NULL);
    }
    else {
        NewPObj = CopyObject(NULL, PObj, FALSE);
    }

    return NewPObj;
}

/*****************************************************************************
* Routine to transform an object list according to transformation matrix.    *
*****************************************************************************/
IPObjectStruct *GMTransformObjectList(IPObjectStruct *PObj, MatrixType Mat)
{
    IPObjectStruct
	*PTailObj = NULL,
	*PTransObj = NULL;

    for ( ; PObj != NULL; PObj = PObj -> Pnext) {
	if (PTailObj == NULL)
	    PTailObj = PTransObj = GMTransformObject(PObj, Mat);
	else {
	    PTailObj -> Pnext = GMTransformObject(PObj, Mat);
	    PTailObj = PTailObj -> Pnext;
	}
    }

    return PTransObj;
}

/*****************************************************************************
*   Routine to calc the distance between two 3d points                       *
*****************************************************************************/
RealType CGDistPointPoint(PointType P1, PointType P2)
{
    RealType t;

#ifdef DEBUG1
    printf("CGDistPointPoint entered\n");
#endif /* DEBUG1 */

    t = sqrt(SQR(P2[0]-P1[0]) + SQR(P2[1]-P1[1]) + SQR(P2[2]-P1[2]));

#ifdef DEBUG1
    printf("CGDistPointPoint exit\n");
#endif /* DEBUG1 */

     return t;
}

/*****************************************************************************
*   Routine to create the plane from given 3 points. if two of the points    *
* are the same it returns FALSE, otherwise (succesfull) returns TRUE.	     *
*****************************************************************************/
int CGPlaneFrom3Points(PlaneType Plane, PointType Pt1, PointType Pt2,
							PointType Pt3)
{
    VectorType V1, V2;

#ifdef DEBUG1
    printf("CGPlaneFrom3Points entered\n");
#endif /* DEBUG1 */

    if (PT_APX_EQ(Pt1, Pt2) || PT_APX_EQ(Pt2, Pt3) || PT_APX_EQ(Pt1, Pt3))
	return FALSE;

    PT_SUB(V1, Pt2, Pt1);
    PT_SUB(V2, Pt3, Pt2);
    GMVecCrossProd(Plane, V1, V2);
    PT_NORMALIZE(Plane);

    Plane[3] = -DOT_PROD(Plane, Pt1);

#ifdef DEBUG1
    printf("CGPlaneFrom3Points exit\n");
#endif /* DEBUG1 */

    return TRUE;
}

/*****************************************************************************
*   Routine to calc the closest 3d point to a given 3d line. the line is     *
* given as a point on it (Pl) and vector (Vl).                               *
*****************************************************************************/
void CGPointFromPointLine(PointType Point, PointType Pl, PointType Vl,
							PointType ClosestPoint)
{
    int i;
    PointType V1, V2;
    RealType CosAlfa, VecMag;

#ifdef DEBUG1
    printf("CGPointFromLinePlane entered\n");
#endif /* DEBUG1 */

    for (i = 0; i < 3; i++) {
        V1[i] = Point[i] - Pl[i];
        V2[i] = Vl[i];
    }
    VecMag = GMVecLength(V1);
    GMVecNormalize(V1);/* Normalized vector from Point to a point on line Pl.*/
    GMVecNormalize(V2);			/* Normalized line direction vector. */

    CosAlfa = GMVecDotProd(V1, V2);/* Find the angle between the two vectors.*/

    /* Find P1 - the closest point to Point on the line: */
    for (i = 0; i < 3; i++)
	ClosestPoint[i] = Pl[i] + V2[i] * CosAlfa * VecMag;

#ifdef DEBUG1
    printf("CGPointFromLinePlane exit\n");
#endif /* DEBUG1 */
}

/*****************************************************************************
*   Routine to calc the distance between 3d point and 3d line. the line is   *
* given as a point on it (Pl) and vector (Vl).                               *
*****************************************************************************/
RealType CGDistPointLine(PointType Point, PointType Pl, PointType Vl)
{
    RealType t;
    PointType Ptemp;

#ifdef DEBUG1
    printf("CGDistPointLine entered\n");
#endif /* DEBUG1 */

    CGPointFromPointLine(Point, Pl, Vl, Ptemp);/* Find closest point on line.*/
    t = CGDistPointPoint(Point, Ptemp);

#ifdef DEBUG1
    printf("CGDistPointLine exit\n");
#endif /* DEBUG1 */

    return t;
}

/*****************************************************************************
*   Routine to calc the distance between a Point and a Plane. The Plane is   *
* defined with 4 coef. : Ax + By + Cz + D = 0 given as 4 elements vector.    *
*****************************************************************************/
RealType CGDistPointPlane(PointType Point, PlaneType Plane)
{
    RealType t;

#ifdef DEBUG1
    printf("CGDistPointPlane entered\n");
#endif /* DEBUG1 */

    t = ((Plane[0] * Point [0] +
	  Plane[1] * Point [1] +
	  Plane[2] * Point [2] +
	  Plane[3]) /
	 sqrt(SQR(Plane[0]) + SQR(Plane[1]) + SQR(Plane[2])));

#ifdef DEBUG1
    printf("CGDistPointPlane exit\n");
#endif /* DEBUG1 */

    return t;
}

/*****************************************************************************
*   Routine to find the intersection point of a line and a plane (if any)    *
*   The Plane is defined with 4 coef. : Ax + By + Cz + D = 0 given as 4      *
* elements vector. The line is define via a point on it Pl and a direction   *
* vector Vl. Return TRUE only if such point exists.                          *
*****************************************************************************/
int CGPointFromLinePlane(PointType Pl, PointType Vl, PlaneType Plane,
					PointType InterPoint, RealType *t)
{
    int i;
    RealType DotProd;

#ifdef DEBUG1
    printf("CGPointFromLinePlane entered\n");
#endif /* DEBUG1 */

    /* Check to see if they are vertical - no intersection at all! */
    DotProd = GMVecDotProd(Vl, Plane);
    if (ABS(DotProd) < EPSILON)
	return FALSE;

    /* Else find t in line such that the plane equation plane is satisfied: */
    *t = (-Plane[3] - Plane[0] * Pl[0] - Plane[1] * Pl[1] - Plane[2] * Pl[2])
								/ DotProd;

    /* And so find the intersection point which is at that t: */
    for (i = 0; i < 3; i++)
	InterPoint[i] = Pl[i] + *t * Vl[i];

#ifdef DEBUG1
    printf("CGPointFromLinePlane exit\n");
#endif /* DEBUG1 */

    return TRUE;
}

/*****************************************************************************
*   Routine to find the intersection point of a line and a plane (if any)    *
*   The Plane is defined with 4 coef. : Ax + By + Cz + D = 0 given as 4      *
* elements vector. The line is define via a point on it Pl and a direction   *
* vector Vl: Line == Pl + Vl * t, where t is the line parameter.	     *
*   Return TRUE only if such point exists, in the t parameter range [0..1].  *
*****************************************************************************/
int CGPointFromLinePlane01(PointType Pl, PointType Vl, PlaneType Plane,
					PointType InterPoint, RealType *t)
{
    int i;
    RealType DotProd;

#ifdef DEBUG1
    printf("CGPointFromLinePlane01 entered\n");
#endif /* DEBUG1 */

    /* Check to see if they are vertical - no intersection at all! */
    DotProd = GMVecDotProd(Vl, Plane);
    if (ABS(DotProd) < EPSILON)
	return FALSE;

    /* Else find t in line such that the plane equation plane is satisfied: */
    *t = (-Plane[3] - Plane[0] * Pl[0] - Plane[1] * Pl[1] - Plane[2] * Pl[2])
								/ DotProd;

    if ((*t < 0.0 && !APX_EQ(*t, 0.0)) ||	  /* Not in parameter range. */
	(*t > 1.0 && !APX_EQ(*t, 1.0)))
	return FALSE;

    /* And so find the intersection point which is at that t : */
    for (i = 0; i < 3; i++)
	InterPoint[i] = Pl[i] + *t * Vl[i];

#ifdef DEBUG1
    printf("CGPointFromLinePlane01 exit\n");
#endif /* DEBUG1 */

    return TRUE;
}

/*****************************************************************************
*   Routine to find the two point Pti on the lines (Pli, Vli) ,   i = 1, 2   *
* with the minimal euclidian distance between them. In other words the       *
* distance between Pt1 and Pt2 is defined as distance between the two lines. *
*   The two points are calculated using the fact that if V = (Vl1 cross Vl2) *
* then these two points are the intersection point between the following:    *
* point 1 - a plane (defined by V and line1) and the line line2.             *
* point 2 - a plane (defined by V and line2) and the line line1.             *
*   This function returns TRUE iff the two lines are not parallel!           *
*****************************************************************************/
int CG2PointsFromLineLine(PointType Pl1, PointType Vl1,
			  PointType Pl2, PointType Vl2,
			  PointType Pt1, RealType *t1,
			  PointType Pt2, RealType *t2)
{
    int i;
    PointType Vtemp;
    PlaneType Plane1, Plane2;

#ifdef DEBUG1
    printf("CG2PointsFromLineLine entered\n");
#endif /* DEBUG1 */

    GMVecCrossProd(Vtemp, Vl1, Vl2);   /* Check to see if they are parallel. */
    if (GMVecLength(Vtemp) < EPSILON) {
	for (i = 0; i < 3; i++)
	    Pt1[i] = Pl1[i];		     /* Pick point on line1 and find */
	CGPointFromPointLine(Pl1, Pl2, Vl2, Pt2); /* closest point on line2. */
        return FALSE;
    }

    /* Define the two planes: 1) Vl1, Pl1, Vtemp and 2) Vl2, Pl2, Vtemp	     */
    /* Note this sets the first 3 elements A, B, C out of the 4...	     */
    GMVecCrossProd(Plane1, Vl1, Vtemp);	        /* Find the A, B, C coef.'s. */
    GMVecNormalize(Plane1);
    GMVecCrossProd(Plane2, Vl2, Vtemp);	        /* Find the A, B, C coef.'s. */
    GMVecNormalize(Plane2);

    /* and now use a point on the plane to find the 4th coef. D: */
    Plane1[3] = (-GMVecDotProd(Plane1, Pl1)); /* VecDotProd uses only first  */
    Plane2[3] = (-GMVecDotProd(Plane2, Pl2)); /* three elements in vec.      */

    /* Thats it! now we should solve for the intersection point between a    */
    /* line and a plane but we already familiar with this problem...         */
    i = CGPointFromLinePlane(Pl1, Vl1, Plane2, Pt1, t1) &&
	CGPointFromLinePlane(Pl2, Vl2, Plane1, Pt2, t2);

#ifdef DEBUG1
    printf("CG2PointsFromLineLine exit\n");
#endif /* DEBUG1 */

    return i;
}

/*****************************************************************************
*   Routine to find the distance between two lines (Pli, Vli) ,  i = 1, 2.   *
*****************************************************************************/
RealType CGDistLineLine(PointType Pl1, PointType Vl1,
			PointType Pl2, PointType Vl2)
{
    RealType t1, t2;
    PointType Ptemp1, Ptemp2;

#ifdef DEBUG1
    printf("CGDistLineLine entered\n");
#endif /* DEBUG1 */

    CG2PointsFromLineLine(Pl1, Vl1, Pl2, Vl2, Ptemp1, &t1, Ptemp2, &t2);
    t1 = CGDistPointPoint(Ptemp1, Ptemp2);

#ifdef DEBUG1
    printf("CGDistLineLine exit\n");
#endif /* DEBUG1 */

    return t1;
}

/*****************************************************************************
*   Routine implements Jordan Theorem: Fire a ray from a given point and find*
* number of intersections of ray with the polygon, excluding the given point *
* Pt (start of ray) itself, if on polygon boundary. The ray is fired in +X   *
* (Axes == 0) or +Y (Axes == 1). And only the X/Y coordinates of the polygon *
* are taken into account, i.e. the orthogonal projection of the polygon on   *
* a X/Y parallel plane (equal to polygon itself if on X/Y parallel plane...) *
*   Note that if the point is on polygon boundary, the ray should not be in  *
* its edge direction							     *
*									     *
*   Algorithm:								     *
* 1. Set NumOfIntersection = 0;						     *
*    Find vertex V not on Ray level and set AlgState to its level (below or  *
*    above the ray level). If none goto 3				     *
*    Mark VStart = V;							     *
* 2. 2.1. While State(V) == AlgState do					     *
*		2.1.1. V = V -> Pnext;					     *
*		2.1.2. If V == VStart goto 3				     *
*	  end;								     *
*	  IntersectionMinX = INFINITY;					     *
*    2.2. While State(V) == ON_RAY do					     *
*		IntersectionMin = MIN(IntersectionMin, V -> Coord[Axes]);    *
*		V = V -> Pnext;						     *
*         end;								     *
*    2.3. If State(V) != AlgState do					     *
*		2.3.1. Find the intersection point between polygon edge      *
*		       Vlast, V and the Ray and update IntersectionMin if    *
*		       lower than it.					     *
*		2.3.2. If IntersectionMin is greater than Pt[Axes] increase  *
*		       the NumOfIntersection counter by 1.		     *
*	  end;								     *
*    2.4. AlgState = State(V);						     *
*    2.5. goto 2.1.							     *
* 3. return NumOfIntersection;						     *
*									     *
*****************************************************************************/
int CGPolygonRayInter(IPPolygonStruct *Pl, PointType PtRay, int RayAxes)
{
    int NewState, AlgState, RayOtherAxes,
	NumOfInter = 0,
	Quit = FALSE;
    RealType InterMin, Inter, t;
    IPVertexStruct *V, *VStart,
	*VLast = NULL;

    RayOtherAxes = (RayAxes == 1 ? 0 : 1);     /* Other dir: X -> Y, Y -> X. */

    /* Stage 1 - find a vertex below the ray level: */
    V = VStart = Pl -> PVertex;
    do {
	if ((AlgState = CGPointRayRelation(V -> Coord, PtRay, RayOtherAxes))
							!= ON_RAY)
	    break;
	V = V -> Pnext;
    }
    while (V != VStart && V != NULL);
    if (AlgState == ON_RAY)
	return 0;
    VStart = V; /* Vertex Below Ray level */

    /* Stage 2 - scan the vertices and count number of intersections. */
    while (!Quit) {
	/* Stage 2.1. : */
	while (CGPointRayRelation(V -> Coord, PtRay, RayOtherAxes) == AlgState) {
	    VLast = V;
	    V = V -> Pnext;
	    if (V == VStart) {
		Quit = TRUE;
		break;
	    }
	}
	InterMin = INFINITY;

	/* Stage 2.2. : */
	while (CGPointRayRelation(V -> Coord, PtRay, RayOtherAxes) == ON_RAY) {
	    InterMin = MIN(InterMin, V -> Coord[RayAxes]);
	    VLast = V;
	    V = V -> Pnext;
	    if (V == VStart)
		Quit = TRUE;
	}

	/* Stage 2.3. : */
	if ((NewState = CGPointRayRelation(V -> Coord, PtRay, RayOtherAxes))
								!= AlgState) {
	    /* Stage 2.3.1 Intersection with ray is in middle of edge: */
	    t = (PtRay[RayOtherAxes] - V -> Coord[RayOtherAxes]) /
		(VLast -> Coord[RayOtherAxes] - V -> Coord[RayOtherAxes]);
	    Inter = VLast -> Coord[RayAxes] * t +
		    V -> Coord[RayAxes] * (1.0 - t);
	    InterMin = MIN(InterMin, Inter);

	    /* Stage 2.3.2. comp. with ray base and inc. # of inter if above.*/
	    if (InterMin > PtRay[RayAxes] &&
		!APX_EQ(InterMin, PtRay[RayAxes]))
		NumOfInter++;
	}

	AlgState = NewState;
    }

    return NumOfInter;
}

/*****************************************************************************
*   Routine to return the relation between the ray level and a given point,  *
* to be used in the CGPolygonRayInter routine above.			     *
*****************************************************************************/
static int CGPointRayRelation(PointType Pt, PointType PtRay, int Axes)
{
    if (APX_EQ(PtRay[Axes], Pt[Axes]))
        return ON_RAY;
    else if (PtRay[Axes] < Pt[Axes])
        return ABOVE_RAY;
    else
	return BELOW_RAY;
}


/*****************************************************************************
* Same as CGPolygonRayInter but for arbitrary oriented polygon.		     *
* The polygon (and the point) is first rotated to a XY parallel plane.	     *
*****************************************************************************/
int CGPolygonRayInter3D(IPPolygonStruct *Pl, PointType PtRay, int RayAxes)
{
    int i;
    MatrixType RotMat;
    IPVertexStruct *V, *VHead;
    IPPolygonStruct *RotPl;
    PointType RotPt;

    /* Make a copy of original to work on. */
    RotPl = IPAllocPolygon(1, Pl ->Tags, CopyVertexList(Pl -> PVertex), NULL);

    /* Bring the polygon to a XY parallel plane by rotation. */
    GenRotateMatrix(RotMat, Pl -> Plane);
    V = VHead = RotPl -> PVertex;
    do {				    /* Transform the polygon itself. */
	MatMultVecby4by4(V -> Coord, V -> Coord, RotMat);
	V = V -> Pnext;
    }
    while (V != NULL && V != VHead);
    MatMultVecby4by4(RotPt, PtRay, RotMat);

    i = CGPolygonRayInter(RotPl, RotPt, RayAxes);

    IPFreePolygonList(RotPl);

    return i;
}
