/*****************************************************************************
*   "Irit" - the 3d polygonal solid modeller.				     *
*									     *
* Written by:  Gershon Elber				Ver 0.2, Mar. 1990   *
******************************************************************************
*   Module to handle convexity of polygons: Test for convexity, and split    *
* none convex polygons into convex ones.				     *
*****************************************************************************/

#define DEBUG2		 /* Defines some printing/debugging routines. */

#ifdef __MSDOS__
#include <alloc.h>
#endif /* __MSDOS__ */

#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include <string.h>
#include "program.h"
#include "geomat3d.h"
#include "booleang.h"
#include "objects.h"
#include "allocatg.h"
#include "priorqg.h"
#include "convexg.h"
#include "convexl.h"

static void SplitPolyIntoTwo(PolygonStruct *Pl, VertexStruct *V,
				PolygonStruct **Pl1, PolygonStruct **Pl2);
static VertexStruct *FindRayPolyInter(PolygonStruct *Pl,
		VertexStruct *VRay, PointType RayDir, PointType PInter);
static void TestConvexityDir(PolygonStruct *Pl);

#ifdef DEBUG2
static void PrintVrtxList(VertexStruct *V);
#endif /* DEBUG2 */

/*****************************************************************************
*   Routine to prepare transformation martix to rotate such that Dir is	     *
* parallel to the Z axes. Used by the convex decomposition to rotate the     *
* polygons to be XY plane parallel.					     *
*    Algorithm: form a 4 by 4 matrix from Dir as follows:		     *
*                |  Tx  Ty  Tz  0 |   A transformation which takes the coord *
*                |  Bx  By  Bz  0 |  system into T, N & B as required.	     *
* [X  Y  Z  1] * |  Nx  Ny  Nz  0 |					     *
*                |  0   0   0   1 |					     *
* N is exactly Dir, but we got freedom on T & B - T & B must be on	     *
* a plane perpendicular to N and perpendicular between them but thats all!   *
* T is therefore selected using this (heuristic ?) algorithm:		     *
* Let P be the axis of which the absolute N coefficient is the smallest.     *
* Let B be (N cross P) and T be (B cross N).				     *
*****************************************************************************/
void GenRotateMatrix(MatrixType Mat, VectorType Dir)
{
    int i, j;
    RealType R;
    VectorType DirN, T, B, P;

    PT_COPY(DirN, Dir);
    PT_NORMALIZE(DirN);
    PT_CLEAR(P);
    for (i=1, j=0, R = ABS(DirN[0]); i<3; i++) if (R > ABS(DirN[i])) {
	R = DirN[i];
	j = i;
    }
    P[j] = 1.0;/* Now P is set to the axis with the biggest angle from DirN. */

    VecCrossProd(B, DirN, P);			      /* calc the bi-normal. */
    PT_NORMALIZE(B);
    VecCrossProd(T, B, DirN);				/* calc the tangent. */

    MatGenUnitMat(Mat);
    for (i=0; i<3; i++) {
	Mat[i][0] = T[i];
	Mat[i][1] = B[i];
	Mat[i][2] = DirN[i];
    }
}

/*****************************************************************************
*   Routine to test all polygons in a given object for convexity, and split  *
* non convex ones, non destructively - the original object is not modified.  *
*****************************************************************************/
ObjectStruct *ConvexPolyObjectN(ObjectStruct *PObj)
{
    ObjectStruct *PObjCopy;

    PObjCopy = CopyObject(NULL, PObj, FALSE);
    ConvexPolyObject(PObjCopy);


    return PObjCopy;
}

/*****************************************************************************
*   Routine to test all the polygons in a given object for convexity, and    *
* split the non convex ones.						     *
*****************************************************************************/
void ConvexPolyObject(ObjectStruct *PObj)
{
    PolygonStruct *Pl, *PlPrev = NULL, *PlSplit, *PlTemp;

    if (!IS_GEOM_OBJ(PObj))
	FatalError("ConvexPolyObject: parameter given is not geometric object");

    Pl = PObj -> U.Pl;
    while (Pl != NULL) {
	if (!ConvexPolygon(Pl)) {
	    PlSplit = SplitNonConvexPoly(Pl);
	    CleanUpPolygonList(&PlSplit);	   /* Zero length edges etc. */
	    if (PlSplit != NULL) {	 /* Something is wrong here, ignore. */
		if (Pl == PObj -> U.Pl) PObj -> U.Pl = PlSplit;    /* First. */
		else PlPrev -> Pnext = PlSplit;

		PlTemp = PlSplit;
		while (PlTemp -> Pnext != NULL) PlTemp = PlTemp -> Pnext;
		PlTemp -> Pnext = Pl -> Pnext;
		PlPrev = PlTemp;

		Pl -> Pnext = NULL;
		MyFree((char *) Pl, POLYGON_TYPE);	/* Free old polygon. */
		Pl = PlPrev -> Pnext;
	    }
	    else {
		if (Pl == PObj -> U.Pl) PObj -> U.Pl = Pl -> Pnext;
		PlPrev = Pl -> Pnext;
		Pl -> Pnext = NULL;
		MyFree((char *) Pl, POLYGON_TYPE);	/* Free old polygon. */
		Pl = PlPrev;
	    }
	}
	else {
	    PlPrev = Pl;
	    Pl = Pl -> Pnext;
	}
    }
}

/*****************************************************************************
*   Routine to test if the given polygon is convex or not.		     *
* Algorithm: The polygon is convex iff the normals generated from cross      *
* products of two consecutive edges points to the same direction. he same    *
* direction is tested by dot product with the polygon plane normal which     *
* should produce consistent sign for all normals, in order the polygon to    *
* be convex.								     *
*   The routine returns TRUE iff the polygon is convex. In addition the      *
* polygon CONVEX tag (see Irit.h) is also updated.			     *
*   Note that if the polygon IS marked as convex, nothing is tested!	     *
*   Also convex polygons are tested so that the vertices are ordered such    *
* that cross product of two adjacent edges will be in the normal direction.  *
*****************************************************************************/
int ConvexPolygon(PolygonStruct *Pl)
{
    int FirstTime = TRUE;
    RealType NormalSign;
    VectorType V1, V2, PolyNormal, Normal;
    VertexStruct *V = Pl -> V, *VNext;

    if (IS_CONVEX_POLY(Pl)) return TRUE;       /* Nothing to do around here. */

    /* Copy only A, B, C from Ax+By+Cz+D = 0: */
    PT_COPY(PolyNormal, Pl -> Plane);

    do {
	VNext = V -> Pnext;

	PT_SUB(V1, VNext -> Pt, V -> Pt);
	PT_SUB(V2, VNext -> Pnext -> Pt, VNext -> Pt);
	VecCrossProd(Normal, V1, V2);
	if (PT_LENGTH(Normal) < EPSILON) {
	    V = VNext;
	    continue;				    /* Skip colinear points. */
	}

	if (FirstTime) {
	    FirstTime = FALSE;
	    NormalSign = DOT_PROD(Normal, PolyNormal);
	}
	else if (NormalSign * DOT_PROD(Normal, PolyNormal) < 0.0) {
	    RST_CONVEX_POLY(Pl);
	    return FALSE;		  /* Different signs --> not convex. */
	}

	V = VNext;
    }
    while (V != Pl -> V);

    SET_CONVEX_POLY(Pl);

    if (NormalSign < 0.0) ReverseVrtxList(Pl);

    return TRUE;	/* All signs are the same --> the polygon is convex. */
}

/*****************************************************************************
*   Routine to split non convex polygon to list of convex ones.		     *
* This algorithm is much simpler than the one used before:		     *
* 1. Remove a polygon from GlblList. If non exists stop.		     *
* 2. Search for non convex corner. If not found stop - polygon is convex.    *
*    Otherwise let the non convex polygon found be V(i).		     *
* 3. Fire a ray from V(i) in the opposite direction to V(i-1). Find the      *
*    closest intersection of the ray with polygon boundary P.		     *
* 4. Split the polygon into two at V(i)-P edge and push the two new polygons *
*    on the GlblList.							     *
* 5. Goto 1.								     *
*****************************************************************************/
PolygonStruct *SplitNonConvexPoly(PolygonStruct *Pl)
{
    int IsConvex;
    PolygonStruct *GlblList, *GlblSplitPl = NULL, *Pl1, *Pl2;
    VectorType V1, V2, PolyNormal, Normal;
    VertexStruct *V, *VNext;

    TestConvexityDir(Pl);

    GlblList = AllocPolygon(0, 0, CopyVList(Pl -> V), NULL);
    PLANE_COPY(GlblList -> Plane, Pl -> Plane);

    /* Copy only A, B, C from Ax+By+Cz+D = 0 plane equation: */
    PT_COPY(PolyNormal, Pl -> Plane);

    while (GlblList != NULL) {
	Pl = GlblList;
	GlblList = GlblList -> Pnext;
	Pl -> Pnext = NULL;

	IsConvex = TRUE;
	V = Pl -> V;
	do {
	    VNext = V -> Pnext;

	    PT_SUB(V1, VNext -> Pt, V -> Pt);
	    PT_SUB(V2, VNext -> Pnext -> Pt, VNext -> Pt);
	    VecCrossProd(Normal, V1, V2);
	    if (PT_LENGTH(Normal) < EPSILON) {
		V = VNext;
		continue;			    /* Skip colinear points. */
	    }

	    if (DOT_PROD(Normal, PolyNormal) < 0.0) {
		SplitPolyIntoTwo(Pl, V, &Pl1, &Pl2);

		Pl -> V = NULL;	      /* Dont free vertices - used in Pl1/2. */
		Pl -> Pnext = NULL;
		MyFree((char *) Pl, POLYGON_TYPE);

		Pl1 -> Pnext = GlblList;   /* Push the polygons on GlblList. */
		GlblList = Pl1;
		Pl2 -> Pnext = GlblList;
		GlblList = Pl2;

		IsConvex = FALSE;
		break;
	    }

	    V = VNext;
	}
	while (V != Pl -> V);

	if (IsConvex) {
	    SET_CONVEX_POLY(Pl);
	    Pl -> Pnext = GlblSplitPl;
	    GlblSplitPl = Pl;
	}
    }

    return GlblSplitPl;
}

/*****************************************************************************
*   Split polygon at the vertex specified by V -> Pnext, given V, into two,  *
* by firing a ray from V -> Pnext in the opposite direction to V and finding *
* closest intersection with the polygon P. (V -> Pnext, P) is the edge to    *
* split the polygon at.							     *
*****************************************************************************/
static void SplitPolyIntoTwo(PolygonStruct *Pl, VertexStruct *V,
				PolygonStruct **Pl1, PolygonStruct **Pl2)
{
    PointType Vl, PInter;
    VertexStruct *VInter, *VNew1, *VNew2;

    PT_SUB(Vl, V -> Pnext -> Pt, V -> Pt);
    VInter = FindRayPolyInter(Pl, V -> Pnext, Vl, PInter);
    V = V -> Pnext;

    /* Make the two polygon vertices lists. */
    VNew1 = AllocVertex(V -> Count, V -> Tags, NULL, V -> Pnext);
    PT_COPY(VNew1 -> Pt, V -> Pt);
    SET_INTERNAL_EDGE(V);
    if (PT_EQ(VInter -> Pt, PInter)) {
	/* Intersection points is close to VInter point. */
	VNew2 = AllocVertex(VInter -> Count, VInter -> Tags, NULL,
							VInter -> Pnext);
	PT_COPY(VNew2 -> Pt, VInter -> Pt);
	VInter -> Pnext = VNew1;
	SET_INTERNAL_EDGE(VInter);
	V -> Pnext = VNew2;
    }
    else
    if (PT_EQ(VInter -> Pnext -> Pt, PInter)) {
	/* Intersection points is close to VInter -> Pnext point. */
	VNew2 = AllocVertex(VInter -> Pnext -> Count, VInter -> Pnext -> Tags,
					NULL, VInter -> Pnext -> Pnext);
	PT_COPY(VNew2 -> Pt, VInter -> Pnext -> Pt);
	VInter -> Pnext -> Pnext = VNew1;
	SET_INTERNAL_EDGE(VInter -> Pnext);
	V -> Pnext = VNew2;
    }
    else {
	/* PInter is in the middle of (VInter, VInter -> Pnext) edge: */
	VNew2 = AllocVertex(VInter -> Count, VInter -> Tags, NULL,
							VInter -> Pnext);
	PT_COPY(VNew2 -> Pt, PInter);
	VInter -> Pnext = AllocVertex(0, 0, NULL, VNew1);
	PT_COPY(VInter -> Pnext -> Pt, PInter);
	SET_INTERNAL_EDGE(VInter -> Pnext);
	V -> Pnext = VNew2;
    }

    *Pl1 = AllocPolygon(0, 0, VNew1, NULL);
    PLANE_COPY((*Pl1) -> Plane, Pl -> Plane);
    *Pl2 = AllocPolygon(0, 0, VNew2, NULL);
    PLANE_COPY((*Pl2) -> Plane, Pl -> Plane);
}

/*****************************************************************************
*   Find where a ray first intersect a given polygon. The ray starts at one  *
* of the polygon vertices and so distance less than EPSILON is ignored.      *
*   Returns the vertex V in which (V, V -> Pnext) has the closest	     *
* intersection with the ray PRay, DRay at Inter.			     *
*****************************************************************************/
static VertexStruct *FindRayPolyInter(PolygonStruct *Pl,
			VertexStruct *VRay, PointType RayDir, PointType PInter)
{
    RealType t1, t2, MinT = INFINITY;
    PointType Vl, Ptemp1, Ptemp2, PRay;
    VertexStruct *V = Pl -> V, *VNext, *VInter = NULL;

    PT_COPY(PRay, VRay -> Pt);

    do {
	VNext = V -> Pnext;
	if (V != VRay && VNext != VRay) {
	    PT_SUB(Vl, V -> Pnext -> Pt, V -> Pt);
	    if (CGDistPointLine(PRay, V -> Pt, Vl) > EPSILON) {
		/* Only if the point the ray is shoot from is not on line: */
		CG2PointsFromLineLine(PRay, RayDir, V -> Pt, Vl, Ptemp1, &t1,
								 Ptemp2, &t2);
		if (CGDistPointPoint(Ptemp1, Ptemp2) < EPSILON * 10.0 &&
		    t1 > EPSILON && t1 < MinT &&
		    (t2 <= 1.0 || APX_EQ(t2, 1.0)) &&
		    (t2 >= 0.0 || APX_EQ(t2, 0.0))) {
		    PT_COPY(PInter, Ptemp2);
		    VInter = V;
		    MinT = t1;
		}
	    }
	}
	V = VNext;
    }
    while (V != Pl -> V);

    if (VInter == NULL)
	FatalError("FindRayPolyInter: No Ray intersection in Convex decomp.");

    return VInter;
}

/*****************************************************************************
*   Test convexity direction - a cross product of two edges of a convex      *
* corner of the polygon should point in the normal direction. if this is not *
* the case - the polygon vertices are reveresed.			     *
*****************************************************************************/
static void TestConvexityDir(PolygonStruct *Pl)
{
    int Coord = 0;
    VectorType V1, V2, Normal;
    VertexStruct *V, *VExtrem;

    /* Find the minimum component direction of the normal and used that axes */
    /* to find an extremum point on the polygon - that extrmum point must be */
    /* a convex corner so we can find the normal direction of convex corner. */
    if (ABS(Pl -> Plane[1]) < ABS(Pl -> Plane[Coord])) Coord = 1;
    if (ABS(Pl -> Plane[2]) < ABS(Pl -> Plane[Coord])) Coord = 2;
    V = VExtrem = Pl -> V;
    do {
	if (V -> Pt[Coord] > VExtrem -> Pt[Coord]) VExtrem = V;
	V = V -> Pnext;
    }
    while (V != Pl -> V);

    /* Make sure next vertex is not at the extremum value: */
    while (APX_EQ(VExtrem -> Pt[Coord], VExtrem -> Pnext -> Pt[Coord]))
						VExtrem = VExtrem -> Pnext;

    /* O.K. V form a convex corner - evaluate its two edges cross product:   */
    for (V = Pl -> V; V -> Pnext != VExtrem; V = V -> Pnext);  /* Find Prev. */
    PT_SUB(V1, VExtrem -> Pt, V -> Pt);
    PT_SUB(V2, VExtrem -> Pnext -> Pt, VExtrem -> Pt);
    VecCrossProd(Normal, V1, V2);

    if (DOT_PROD(Normal, Pl -> Plane) < 0.0) ReverseVrtxList(Pl);
}

/*****************************************************************************
*   Reverse the vertex list of a given polygon. This is used mainly to       *
* reverse polygons such that cross product of consecutive edges which form   *
* a convex corner will point in the polygon normal direction.		     *
*****************************************************************************/
void ReverseVrtxList(PolygonStruct *Pl)
{
    ByteType Tags, Count;
    VertexStruct *V = Pl -> V, *VNext = V -> Pnext, *VNextNext;

    do {
	VNextNext = VNext -> Pnext;
	VNext -> Pnext = V;			     /* Reverse the pointer! */

	V = VNext;			   /* Advance all 3 pointers by one. */
	VNext = VNextNext;
	VNextNext = VNextNext -> Pnext;
    }
    while (V != Pl -> V);

    V = Pl -> V;	  /* Move the Tags/Count by one - to the right edge. */
    Tags = V -> Tags;
    Count = V -> Count;
    do {
	if (V -> Pnext == Pl -> V) {
	    V -> Tags = Tags;
	    V -> Count = Count;
	}
	else {
	    V -> Tags = V -> Pnext -> Tags;
	    V -> Count = V -> Pnext -> Count;
	}

	V = V -> Pnext;
    }
    while (V != Pl -> V);
}

#ifdef DEBUG2

/*****************************************************************************
*   Print the content of the given vertex list, to standard output.	     *
*****************************************************************************/
static void PrintVrtxList(VertexStruct *V)
{
    VertexStruct *VFirst = V;

    if (V == NULL) return;

    printf("VERTEX LIST:\n");
    do {
	printf("@%p:  %12lf %12lf %12lf, Tags = %02x\n",
	    V, V -> Pt[0], V -> Pt[1], V -> Pt[2], V -> Tags);
	V = V -> Pnext;
    }
    while (V != NULL && V != VFirst);

    if (V == NULL) printf("Loop is not closed (NULL terminated)\n");
}

#endif /* DEBUG2 */
