/*****************************************************************************
*   "Irit" - the 3d polygonal solid modeller.				     *
*									     *
* Written by:  Gershon Elber				Ver 0.2, Mar. 1990   *
******************************************************************************
*   Module to generate the geometric primitives defined in the system. The   *
* primitives currently defined are:					     *
* 1. BOX - main planes parallel box.					     *
* 2. GBOX - generalized box - 6 arbitrary planes.			     *
* 3. CYLIN - cylinder with any main direction.				     *
* 4. CONE - cone with any main direction.				     *
* 5. SPHERE								     *
* 6. TORUS - with any main direction.					     *
* 7. PLANE - non closed, single polygon object: circle with resolution edges *
* 8. POLY - directly define single polygon object by specifing its vertices. *
*   In addition the following lower level operations are defined to create   *
* objects - EXTRUDE, and SURFREV, both require a polygon and a vector to     *
* extrude/rotate the polygon along.					     *
*****************************************************************************/

#ifdef __MSDOS__
#include <graphics.h>
#endif /* __MSDOS__ */

#include <stdio.h>
#include <math.h>
#include "program.h"
#include "allocatg.h"
#include "objects.h"
#include "primitvl.h"
#include "primitvg.h"
#include "geomat3d.h"
#include "windowsg.h"
#include "convexg.h"

#ifndef __MSDOS__
#include "xgraphic.h"
#endif /* __MSDOS__ */

/*****************************************************************************
*   Routine to create a BOX geometric object defined by Pt - the minimun     *
* 3d point, and Width - Dx Dy & Dz vector.		4		     *
* Order of vertices is as                           5       7		     *
* follows in the picture:                           |   6   |		     *
*						    |   |   |		     *
* (Note vertex 0 is hidden behind edge 2-6)	    |	|   |		     *
*						    1   |   3                *
*							2		     *
*****************************************************************************/
struct ObjectStruct * GenBOXObject(VectorType Pt, RealType *WidthX,
					RealType *WidthY, RealType *WidthZ)
{
    VectorType Dir1, Dir2, Dir3;

    PT_CLEAR(Dir1);	Dir1[0] = (*WidthX);   /* Prepare direction vectors. */
    PT_CLEAR(Dir2);	Dir2[1] = (*WidthY);	   /* Parallel to main axes. */
    PT_CLEAR(Dir3);	Dir3[2] = (*WidthZ);		   /* For GBOX call. */

    return GenGBOXObject(Pt, Dir1, Dir2, Dir3);
}

/*****************************************************************************
*   Routine to create a GBOX geometric object defined by Pt - the minimun    *
* 3d point, and 3 direction Vectors Dir1, Dir2, Dir3. If two of the	     *
* direction vectors are parallel the GBOX converges to zero volume. A NULL   *
* pointer is returned in that case!					     *
* 							4		     *
* Order of vertices is as                           5       7		     *
* follows in the picture:                           |   6   |		     *
*						    |   |   |		     *
* (Note vertex 0 is hidden behind edge 2-6)	    |	|   |		     *
*						    1   |   3                *
*							2		     *
*****************************************************************************/
struct ObjectStruct * GenGBOXObject(VectorType Pt,
			VectorType Dir1, VectorType Dir2, VectorType Dir3)
{
    int i;
    VectorType Temp;
    VectorType V[8];				  /* Hold 8 vertices of BOX. */
    struct ObjectStruct *PBox;

    VecCrossProd(Temp, Dir1, Dir2);
    if (APX_EQ(PT_LENGTH(Temp), 0.0)) return NULL;
    VecCrossProd(Temp, Dir2, Dir3);
    if (APX_EQ(PT_LENGTH(Temp), 0.0)) return NULL;
    VecCrossProd(Temp, Dir3, Dir1);
    if (APX_EQ(PT_LENGTH(Temp), 0.0)) return NULL;

    /* Also the 0..7 sequence is binary decoded such that bit 0 is Dir1, */
    /* bit 1 Dir2, and bit 2 is Dir3 increment:				 */
    for (i=0; i<8; i++) {
	PT_COPY(V[i], Pt);

	if (i & 1) { PT_ADD(V[i], V[i], Dir1); }
	if (i & 2) { PT_ADD(V[i], V[i], Dir2); }
	if (i & 4) { PT_ADD(V[i], V[i], Dir3); }
    }

    PBox = GenGeomObject("", NULL, NULL); /* Generate the BOX object itself: */

    /* And generate the 6 polygons (Bottom, top and 4 sides in this order):  */
    PBox -> U.Pl = GenPolygon4Vrtx(V[0], V[1], V[3], V[2], V[4], PBox -> U.Pl);
    PBox -> U.Pl = GenPolygon4Vrtx(V[6], V[7], V[5], V[4], V[0], PBox -> U.Pl);
    PBox -> U.Pl = GenPolygon4Vrtx(V[4], V[5], V[1], V[0], V[2], PBox -> U.Pl);
    PBox -> U.Pl = GenPolygon4Vrtx(V[5], V[7], V[3], V[1], V[0], PBox -> U.Pl);
    PBox -> U.Pl = GenPolygon4Vrtx(V[7], V[6], V[2], V[3], V[1], PBox -> U.Pl);
    PBox -> U.Pl = GenPolygon4Vrtx(V[6], V[4], V[0], V[2], V[3], PBox -> U.Pl);

    SET_OBJECT_COLOR(PBox, PrimColor);		   /* Set its default color. */

    return PBox;
}

/*****************************************************************************
*  Routine to fetch the resolution parameter from the RESOLUTION object.     *
*****************************************************************************/
static int GetResolution(void)
{
    int Resolution;
    struct ObjectStruct *PObj = GetObject("RESOLUTION");

    if (PObj == NULL || !IS_NUM_OBJ(PObj)) {
	WndwInputWindowPutStr("No numeric object name RESOLUTION is defined",
									RED);
	Resolution = DEFAULT_RESOLUTION;
    }
    else Resolution = MAX(((int) (PObj -> U.R)), MIN_RESOLUTION);

    Resolution = (Resolution / 2) * 2;	    /* Make sure its an even number. */

    return Resolution;
}

/*****************************************************************************
*   Routine to create a CONE geometric object defined by Pt - the base       *
* 3d center point, Dir - the cone direction and length, and base radius R.   *
*****************************************************************************/
struct ObjectStruct * GenCONEObject(VectorType Pt, VectorType Dir, RealType *R)
{
    int i, Resolution;
    RealType Angle, AngleStep;
    PointType LastCirclePt, CirclePt, ApexPt;
    MatrixType Mat;
    struct VertexStruct *VBase;
    struct PolygonStruct *PBase;
    struct ObjectStruct *PCone;

    Resolution = GetResolution();	 /* Get refinement factor of object. */

    GenTransformMatrix(Mat, Pt, Dir, *R);     /* Transform from unit circle. */

    PT_COPY(ApexPt, Pt);		   /* Find the apex point: Pt + Dir. */
    PT_ADD(ApexPt, ApexPt, Dir);

    PCone = GenGeomObject("", NULL, NULL);   /* Gen. the CONE object itself: */
    /* Also allocate the base polygon header with first vertex on it: */
    PBase = AllocPolygon(0, 0, VBase = AllocVertex(0, 0, NULL, NULL), NULL);

    LastCirclePt[0] = 1.0;		/* First point is allways Angle = 0. */
    LastCirclePt[1] = 0.0;
    LastCirclePt[2] = 0.0;
    MatMultVecby4by4(LastCirclePt, LastCirclePt, Mat);

    PT_COPY(VBase -> Pt, LastCirclePt);  /* Update first pt in base polygon. */

    AngleStep = M_PI * 2 / Resolution;

    for (i=1; i<=Resolution; i++) {	      /* Pass the whole base circle. */
	Angle = AngleStep * i;		     /* Prevent from additive error. */

	CirclePt[0] = cos(Angle);
	CirclePt[1] = sin(Angle);
	CirclePt[2] = 0.0;

	MatMultVecby4by4(CirclePt, CirclePt, Mat);

	PCone -> U.Pl = GenPolygon3Vrtx(LastCirclePt, ApexPt, CirclePt,
							Pt, PCone -> U.Pl);
	/* And add this vertex to base polygon: */
	if (i == Resolution)	       /* Its last point - make it circular. */
	    VBase -> Pnext = PBase -> V;
	else {
	    VBase -> Pnext = AllocVertex(0, 0, NULL, NULL);
	    VBase = VBase -> Pnext;
	    PT_COPY(VBase -> Pt, CirclePt);
	}

	PT_COPY(LastCirclePt, CirclePt);/* Save pt in last pt for next time. */
    }

    UpdatePolyPlane(PBase, ApexPt);   /* Update base polygon plane equation. */
    SET_CONVEX_POLY(PBase);		       /* Mark it as convex polygon. */
    PBase -> Pnext = PCone -> U.Pl;  /* And stick it into the cone polygons. */
    PCone -> U.Pl = PBase;

    SET_OBJECT_COLOR(PCone, PrimColor);		   /* Set its default color. */

    return PCone;
}

/*****************************************************************************
*   Routine to create a CYLINder geometric object defined by Pt - the base   *
* 3d center point, Dir - the cylin direction and length, and base radius R.  *
*   The second base is defined from first one by translating it by vector    *
* Dir, and its points are prefixed with T.				     *
*****************************************************************************/
struct ObjectStruct * GenCYLINObject(VectorType Pt, VectorType Dir, RealType *R)
{
    int i, Resolution;
    RealType Angle, AngleStep;
    PointType LastCirclePt, CirclePt, TLastCirclePt, TCirclePt, TPt;
    MatrixType Mat;
    struct VertexStruct *VBase1, *VBase2 = NULL;
    struct PolygonStruct *PBase1, *PBase2;
    struct ObjectStruct *PCylin;

    Resolution = GetResolution();	 /* Get refinement factor of object. */

    GenTransformMatrix(Mat, Pt, Dir, *R);     /* Transform from unit circle. */

    PCylin = GenGeomObject("", NULL, NULL); /* Gen. the CYLIN object itself: */
    /* Also allocate the bases polygon header with first vertex on it: */
    PBase1 = AllocPolygon(0, 0, VBase1 = AllocVertex(0, 0, NULL, NULL), NULL);
    PBase2 = AllocPolygon(0, 0, VBase2 = AllocVertex(0, 0, NULL, NULL), NULL);

    PT_ADD(TPt, Pt, Dir);	       /* Translated circle center (by Dir). */

    LastCirclePt[0] = 1.0;		/* First point is allways Angle = 0. */
    LastCirclePt[1] = 0.0;
    LastCirclePt[2] = 0.0;
    MatMultVecby4by4(LastCirclePt, LastCirclePt, Mat);
    PT_COPY(VBase1 -> Pt, LastCirclePt);/* Update first pt in base1 polygon. */
    PT_ADD(TLastCirclePt, LastCirclePt, Dir); /* Translated circle (by Dir). */
    PT_COPY(VBase2 -> Pt, TLastCirclePt);/* Update first pt in base2 polygon.*/

    AngleStep = M_PI * 2 / Resolution;

    for (i=1; i<=Resolution; i++) {	      /* Pass the whole base circle. */
	Angle = AngleStep * i;		     /* Prevent from additive error. */

	CirclePt[0] = cos(Angle);
	CirclePt[1] = sin(Angle);
	CirclePt[2] = 0.0;

	MatMultVecby4by4(CirclePt, CirclePt, Mat);
	PT_ADD(TCirclePt, CirclePt, Dir);     /* Translated circle (by Dir). */

	PCylin -> U.Pl = GenPolygon4Vrtx(TLastCirclePt, TCirclePt, CirclePt,
					 LastCirclePt, Pt, PCylin -> U.Pl);
	/* And add this vertices to the two cylinder bases: 		     */
	/* Note Base1 is build forward, while Base2 is build backward so it  */
	/* will be consistent - cross product of 2 consecutive edges will    */
	/* point into the model.					     */
	if (i == Resolution) {	       /* Its last point - make it circular. */
	    VBase1 -> Pnext = PBase1 -> V;
	    VBase2 -> Pnext = PBase2 -> V;
	}
	else {
	    VBase1 -> Pnext = AllocVertex(0, 0, NULL, NULL);
	    VBase1 = VBase1 -> Pnext;
	    PT_COPY(VBase1 -> Pt, CirclePt);
	    PBase2 -> V = AllocVertex(0, 0, NULL, PBase2 -> V);
	    PT_COPY(PBase2 -> V -> Pt, TCirclePt);
	}

	PT_COPY(LastCirclePt, CirclePt);/* Save pt in last pt for next time. */
	PT_COPY(TLastCirclePt, TCirclePt);
    }

    UpdatePolyPlane(PBase1, TPt);     /* Update base polygon plane equation. */
    SET_CONVEX_POLY(PBase1);		       /* Mark it as convex polygon. */
    PBase1 -> Pnext = PCylin -> U.Pl;   /* And stick it into cylin polygons. */
    PCylin -> U.Pl = PBase1;
    UpdatePolyPlane(PBase2, Pt);      /* Update base polygon plane equation. */
    SET_CONVEX_POLY(PBase2);		       /* Mark it as convex polygon. */
    PBase2 -> Pnext = PCylin -> U.Pl;   /* And stick it into cylin polygons. */
    PCylin -> U.Pl = PBase2;

    SET_OBJECT_COLOR(PCylin, PrimColor);	   /* Set its default color. */

    return PCylin;
}

/*****************************************************************************
*   Routine to create a SPHERE geometric object defined by Center - sphere   *
* 3d center point, and R its radius.					     *
*   Note polygons on the poles are triangles, while others are rectangles    *
*   Teta is horizontal circle angle, Fee is the vertical (spherical coords.) *
*   The vertical axes here is assumed to be the Z axes.			     *
*****************************************************************************/
struct ObjectStruct * GenSPHEREObject(VectorType Center, RealType *R)
{
    int i, j, Resolution;
    RealType TetaAngle, TetaAngleStep, FeeAngle, FeeAngleStep,
	CosFeeAngle1, SinFeeAngle1, CosFeeAngle2, SinFeeAngle2;
    PointType LastCircleLastPt, LastCirclePt, CirclePt, CircleLastPt;
    struct ObjectStruct *PSphere;

    Resolution = GetResolution();	 /* Get refinement factor of object. */

    PSphere = GenGeomObject("", NULL, NULL);/* Gen the SPHERE object itself: */

    TetaAngleStep = M_PI * 2.0 / Resolution;	     /* Runs from 0 to 2*PI. */
    FeeAngleStep = M_PI * 2.0 / Resolution;	/* Runs from -PI/2 yo +PI/2. */

    /* Generate the lowest (south pole) triangular polygons: */
    FeeAngle = (-M_PI/2.0) + FeeAngleStep; /* First circle above south pole. */
    CosFeeAngle1 = cos(FeeAngle) * (*R);
    SinFeeAngle1 = sin(FeeAngle) * (*R);
    PT_COPY(LastCirclePt, Center);		/* Calculate the south pole. */
    LastCirclePt[2] -= (*R);
    PT_COPY(CircleLastPt, Center);    /* Calc. last point on current circle. */
    CircleLastPt[0] += CosFeeAngle1;
    CircleLastPt[2] += SinFeeAngle1;

    for (i=1; i<=Resolution; i++) {   /* Pass the whole (horizontal) circle. */
	TetaAngle = TetaAngleStep * i;	     /* Prevent from additive error. */

	PT_COPY(CirclePt, Center); /* Calc. current point on current circle. */
	CirclePt[0] += cos(TetaAngle) * CosFeeAngle1;
	CirclePt[1] += sin(TetaAngle) * CosFeeAngle1;
	CirclePt[2] += SinFeeAngle1;

	PSphere -> U.Pl = GenPolygon3Vrtx(LastCirclePt, CircleLastPt,
					CirclePt, Center, PSphere -> U.Pl);

	PT_COPY(CircleLastPt, CirclePt);/* Save pt in last pt for next time. */
    }

    /* Generate the middle rectangular polygons: */
    for (i=1; i<Resolution/2-1; i++) { /* For all the horizontal circles do. */
	FeeAngle = (-M_PI/2.0) + FeeAngleStep * i;
	CosFeeAngle1 = cos(FeeAngle) * (*R);
	SinFeeAngle1 = sin(FeeAngle) * (*R);
	FeeAngle = (-M_PI/2.0) + FeeAngleStep * (i + 1);
	CosFeeAngle2 = cos(FeeAngle) * (*R);
	SinFeeAngle2 = sin(FeeAngle) * (*R);
	PT_COPY(CircleLastPt, Center);/* Calc. last point on current circle. */
	CircleLastPt[0] += CosFeeAngle2;
	CircleLastPt[2] += SinFeeAngle2;
	PT_COPY(LastCircleLastPt, Center);/* Calc. last point on last circle.*/
	LastCircleLastPt[0] += CosFeeAngle1;
	LastCircleLastPt[2] += SinFeeAngle1;

	for (j=1; j<=Resolution; j++) {/* Pass the whole (horizontal) circle.*/
	    TetaAngle = TetaAngleStep * j;   /* Prevent from additive error. */

	    PT_COPY(CirclePt, Center);/* Calc. current pt on current circle. */
	    CirclePt[0] += cos(TetaAngle) * CosFeeAngle2;
	    CirclePt[1] += sin(TetaAngle) * CosFeeAngle2;
	    CirclePt[2] += SinFeeAngle2;
	    PT_COPY(LastCirclePt, Center);/* Calc. current pt on last circle.*/
	    LastCirclePt[0] += cos(TetaAngle) * CosFeeAngle1;
	    LastCirclePt[1] += sin(TetaAngle) * CosFeeAngle1;
	    LastCirclePt[2] += SinFeeAngle1;

	    PSphere -> U.Pl = GenPolygon4Vrtx(LastCirclePt, LastCircleLastPt,
			CircleLastPt, CirclePt, Center, PSphere -> U.Pl);

	    PT_COPY(CircleLastPt, CirclePt);	      /* Save pt in last pt. */
	    PT_COPY(LastCircleLastPt, LastCirclePt);
	}
    }

    /* Generate the upper most (north pole) triangular polygons: */
    FeeAngle = (M_PI/2.0) - FeeAngleStep;  /* First circle below north pole. */
    CosFeeAngle1 = cos(FeeAngle) * (*R);
    SinFeeAngle1 = sin(FeeAngle) * (*R);
    PT_COPY(LastCirclePt, Center);		/* Calculate the north pole. */
    LastCirclePt[2] += (*R);
    PT_COPY(CircleLastPt, Center);    /* Calc. last point on current circle. */
    CircleLastPt[0] += CosFeeAngle1;
    CircleLastPt[2] += SinFeeAngle1;

    for (i=1; i<=Resolution; i++) {   /* Pass the whole (horizontal) circle. */
	TetaAngle = TetaAngleStep * i;	     /* Prevent from additive error. */

	PT_COPY(CirclePt, Center); /* Calc. current point on current circle. */
	CirclePt[0] += cos(TetaAngle) * CosFeeAngle1;
	CirclePt[1] += sin(TetaAngle) * CosFeeAngle1;
	CirclePt[2] += SinFeeAngle1;

	PSphere -> U.Pl = GenPolygon3Vrtx(LastCirclePt, CirclePt, CircleLastPt,
					Center, PSphere -> U.Pl);

	PT_COPY(CircleLastPt, CirclePt);/* Save pt in last pt for next time. */
    }

    SET_OBJECT_COLOR(PSphere, PrimColor);	   /* Set its default color. */

    return PSphere;
}

/*****************************************************************************
*   Routine to create a TORUS geometric object defined by Center - torus 3d  *
* center point, the main torus plane normal Normal, major radius Rmajor and  *
* minor radius Rminor (Tube radius).					     *
*   Teta runs on the major circle, Fee on the minor one (Before Mat trans.): *
* X = (Rmajor + Rminor * cos(Fee)) * cos(Teta)				     *
* Y = (Rmajor + Rminor * cos(Fee)) * sin(Teta)				     *
* Z = Rminor * sin(Fee)							     *
*****************************************************************************/
struct ObjectStruct * GenTORUSObject(VectorType Center, VectorType Normal,
					RealType *Rmajor, RealType *Rminor)
{
    int i, j, Resolution;
    RealType TetaAngle, TetaAngleStep, FeeAngle, FeeAngleStep,
	CosFeeAngle1, SinFeeAngle1, CosFeeAngle2, SinFeeAngle2;
    PointType LastCircleLastPt, LastCirclePt, CirclePt, CircleLastPt, InPt;
    MatrixType Mat;
    struct ObjectStruct *PTorus;

    Resolution = GetResolution();	 /* Get refinement factor of object. */

    GenTransformMatrix(Mat, Center, Normal, 1.0); /* Trans from unit circle. */

    PTorus = GenGeomObject("", NULL, NULL); /* Gen. the Torus object itself: */

    TetaAngleStep = M_PI * 2.0 / Resolution;	     /* Runs from 0 to 2*PI. */
    FeeAngleStep = M_PI * 2.0 / Resolution;	     /* Runs from 0 to 2*PI. */

    for (i=1; i<=Resolution; i++) {
	FeeAngle = FeeAngleStep * (i - 1);
	CosFeeAngle1 = cos(FeeAngle) * (*Rminor);
	SinFeeAngle1 = sin(FeeAngle) * (*Rminor);
	FeeAngle = FeeAngleStep * i;
	CosFeeAngle2 = cos(FeeAngle) * (*Rminor);
	SinFeeAngle2 = sin(FeeAngle) * (*Rminor);
	LastCircleLastPt[0] = (*Rmajor) + CosFeeAngle1;
	LastCircleLastPt[1] = 0.0;
	LastCircleLastPt[2] = SinFeeAngle1;
	MatMultVecby4by4(LastCircleLastPt, LastCircleLastPt, Mat);
	LastCirclePt[0] = (*Rmajor) + CosFeeAngle2;
	LastCirclePt[1] = 0.0;
	LastCirclePt[2] = SinFeeAngle2;
	MatMultVecby4by4(LastCirclePt, LastCirclePt, Mat);

	for (j=1; j<=Resolution; j++) {
	    TetaAngle = TetaAngleStep * j;   /* Prevent from additive error. */

	    CircleLastPt[0] = ((*Rmajor) + CosFeeAngle1) * cos(TetaAngle);
	    CircleLastPt[1] = ((*Rmajor) + CosFeeAngle1) * sin(TetaAngle);
	    CircleLastPt[2] = SinFeeAngle1;
	    MatMultVecby4by4(CircleLastPt, CircleLastPt, Mat);
	    CirclePt[0] = ((*Rmajor) + CosFeeAngle2) * cos(TetaAngle);
	    CirclePt[1] = ((*Rmajor) + CosFeeAngle2) * sin(TetaAngle);
	    CirclePt[2] = SinFeeAngle2;
	    MatMultVecby4by4(CirclePt, CirclePt, Mat);
	    /* Point inside the object relative to this polygon: */
	    InPt[0] = (*Rmajor) * cos(TetaAngle);
	    InPt[1] = (*Rmajor) * sin(TetaAngle);
	    InPt[2] = 0.0;
	    MatMultVecby4by4(InPt, InPt, Mat);

	    PTorus -> U.Pl = GenPolygon4Vrtx(CirclePt, CircleLastPt,
		LastCircleLastPt, LastCirclePt, InPt, PTorus -> U.Pl);

	    PT_COPY(LastCirclePt, CirclePt);	      /* Save pt in last pt. */
	    PT_COPY(LastCircleLastPt, CircleLastPt);
	}
    }

    SET_OBJECT_COLOR(PTorus, PrimColor);	   /* Set its default color. */

    return PTorus;
}

/*****************************************************************************
*   Routine to create a PLANE geometric object defined by the normal N and   *
* the translation vector T. The object is a FINITE plane (a circle of	     *
* RESOLUTION point in it...) and its radius is equal to R.		     *
*   The normal direction is assumed to point to the inside of the object.    *
*****************************************************************************/
struct ObjectStruct * GenPLANEObject(VectorType N, VectorType T, RealType *R)
{
    int i, Resolution;
    RealType Angle, AngleStep;
    PointType CirclePt;
    MatrixType Mat;
    struct VertexStruct *V;
    struct PolygonStruct *PCirc;
    struct ObjectStruct *PPlane;

    Resolution = GetResolution();	 /* Get refinement factor of object. */

    GenTransformMatrix(Mat, T, N, *R);	      /* Transform from unit circle. */

    PPlane = GenGeomObject("", NULL, NULL); /* Gen. the PLANE object itself: */
    PCirc = AllocPolygon(0, 0, V = AllocVertex(0, 0, NULL, NULL), NULL);
    PPlane -> U.Pl = PCirc;

    CirclePt[0] = 1.0;			/* First point is allways Angle = 0. */
    CirclePt[1] = 0.0;
    CirclePt[2] = 0.0;
    MatMultVecby4by4(CirclePt, CirclePt, Mat);
    PT_COPY(V -> Pt, CirclePt);	       /* Update first pt in circle polygon. */

    AngleStep = M_PI * 2 / Resolution;

    for (i=1; i<=Resolution; i++) {	      /* Pass the whole base circle. */
	Angle = AngleStep * i;		     /* Prevent from additive error. */

	CirclePt[0] = cos(Angle);
	CirclePt[1] = sin(Angle);
	CirclePt[2] = 0.0;

	MatMultVecby4by4(CirclePt, CirclePt, Mat);

	/* And add this vertices to the two cylinder bases: */
	if (i == Resolution) {	       /* Its last point - make it circular. */
	    V -> Pnext = PCirc -> V;
	}
	else {
	    V -> Pnext = AllocVertex(0, 0, NULL, NULL);
	    V = V -> Pnext;
	    PT_COPY(V -> Pt, CirclePt);
	}
    }

    PT_ADD(CirclePt, CirclePt, N);   /* Make a point "IN" the circle object. */
    UpdatePolyPlane(PCirc, CirclePt); /* Update base polygon plane equation. */
    SET_CONVEX_POLY(PCirc);		       /* Mark it as convex polygon. */

    SET_OBJECT_COLOR(PPlane, PrimColor);	   /* Set its default color. */

    return PPlane;
}

/*****************************************************************************
*   Routine to create a POLYgon directly from its specifed vertices.	     *
*   The validity of list elements is tested to make sure they are vectors as *
* nobody else checked it till now (lists can hold any object type!).	     *
*   No test is made to make sure all vertices are on one plane, and that no  *
* two vertices are similar.						     *
*****************************************************************************/
struct ObjectStruct *GenPOLYObject(ObjectStruct *PObjList)
{
    int i, NumVertices = 0;
    VertexStruct *V, *VHead, *VTail;
    PolygonStruct *PPoly;
    ObjectStruct *PObj, *PObjPoly;

    if (!IS_OLST_OBJ(PObjList))
	FatalError("GenPOLYObject: Not object list object!\n");

    i = 0;
    while ((PObj = PObjList -> U.PObjList[i]) != NULL && i++ < MAX_OBJ_LIST) {
	if (!IS_VEC_OBJ(PObj)) {
	    WndwInputWindowPutStr("POLY: None vector object found in list, empty object result.", RED);
	    return NULL;
	}
	NumVertices++;
    }

    if (NumVertices < 3) {
	WndwInputWindowPutStr("POLY: Less than 3 vertices, empty object result.", RED);
	return NULL;
    }

    PPoly = AllocPolygon(0, 0, VHead = NULL, NULL);
    i = 0;
    while ((PObj = PObjList -> U.PObjList[i]) != NULL && i++ < MAX_OBJ_LIST) {
	V = AllocVertex(0, 0, NULL, NULL);
	PT_COPY(V -> Pt, PObj -> U.Vec);
	if (VHead == NULL)
	{
	    PPoly -> V = VHead = VTail = V;
	}
	else
	{
	    VTail -> Pnext = V;
	    VTail = V;
	}
    }
    VTail -> Pnext = VHead;		      /* Close the vertex list loop. */

    CGPlaneFrom3Points(PPoly -> Plane, VHead -> Pt, VHead -> Pnext -> Pt,
						VHead -> Pnext -> Pnext -> Pt);

    PObjPoly = GenGeomObject("", NULL, NULL);/* Gen. the POLY object itself: */
    PObjPoly -> U.Pl = PPoly;
    SET_OBJECT_COLOR(PObjPoly, PrimColor);	   /* Set its default color. */

    return PObjPoly;
}

/*****************************************************************************
*   Routine to a cross section polygon, by interactively allowing the user   *
* to create it. This polygon is returned as a one polygon object. This       *
* polygon is mainly for Surface of Revolution (SURFREV) and extrusion        *
* (EXTRUDE) operations, although it may be used as an open object by itself. *
*****************************************************************************/
struct ObjectStruct * GenCROSSECObject(ObjectStruct *PObj)
{
    if (PObj && !IS_GEOM_OBJ(PObj))
	FatalError("CrossSec: operation on none geometric object\n");

    WndwInputWindowPutStr("GenCrossSecObject not implemented", RED);

    return NULL;
}

/*****************************************************************************
*   Routine to a create surface of revolution by rotating the given cross    *
* section along Z axes. The polygon plane must not be perpendicular to Z.    *
*****************************************************************************/
struct ObjectStruct * GenSURFREVObject(ObjectStruct *Cross)
{
    int Resolution, i;
    MatrixType Mat;			   /* Rotation Matrix around Z axes. */
    VertexStruct *V1, *V1Head, *V2, *V2Head, *VIn, *VInHead;
    PolygonStruct *Pl1, *Pl2, *PlIn, *PlNew = NULL;
    ObjectStruct *PSurfRev;

    if (!IS_GEOM_OBJ(Cross))
	FatalError("SurfRev: cross section is not geometric object\n");

    if (APX_EQ(Cross -> U.Pl -> Plane[0], 0.0) &&
	APX_EQ(Cross -> U.Pl -> Plane[1], 0.0)) {
	WndwInputWindowPutStr("SurfRev: cross-section perpendicular to Z. Empty object result", RED);
	return NULL;
    }

    Pl1 = AllocPolygon(0, 0, V1Head = CopyVList(Cross -> U.Pl -> V), NULL);
    PLANE_COPY(Pl1 -> Plane, Cross -> U.Pl -> Plane);
    Pl2 = AllocPolygon(0, 0, V2Head = CopyVList(Cross -> U.Pl -> V), NULL);
    PLANE_COPY(Pl2 -> Plane, Cross -> U.Pl -> Plane);
    PlIn = GenInsidePoly(Pl1);
    VInHead = PlIn -> V;
    Resolution = GetResolution();	 /* Get refinement factor of object. */
    MatGenMatRotZ1(2.0 * M_PI / Resolution, Mat);

    for (i=0; i<Resolution; i++)
    {
	V2 = V2Head;
	do {
	    MatMultVecby4by4(V2 -> Pt, V2 -> Pt , Mat);
	    V2 = V2 -> Pnext;
	}
	while (V2 != NULL && V2 != V2Head);

	V1 = V1Head;
	if (i < Resolution - 1) /* If this is the last loop use the original */
	     V2 = V2Head; /* polygon as we might accumulate error during the */
	else V2 = Cross -> U.Pl -> V;   /* transformations along the circle. */
	VIn = VInHead;
	do
	{
	    PlNew = GenPolygon4Vrtx(V1 -> Pt, V1 -> Pnext -> Pt,
				    V2 -> Pnext -> Pt, V2 -> Pt,
				    VIn -> Pt, PlNew);

	    VIn = VIn -> Pnext;
	    V1 = V1 -> Pnext;
	    V2 = V2 -> Pnext;
	}
	while (V1 -> Pnext != NULL && V1 != V1Head);

	V1 = V1Head;
	do {
	    MatMultVecby4by4(V1 -> Pt, V1 -> Pt , Mat);
	    V1 = V1 -> Pnext;
	}
	while (V1 != NULL && V1 != V1Head);
	VIn = VInHead;
	do {
	    MatMultVecby4by4(VIn -> Pt, VIn -> Pt , Mat);
	    VIn = VIn -> Pnext;
	}
	while (VIn != NULL && VIn != VInHead);
    }

    MyFree((char *) PlIn, POLYGON_TYPE);
    MyFree((char *) Pl1, POLYGON_TYPE);
    MyFree((char *) Pl2, POLYGON_TYPE);

    PSurfRev = GenGeomObject("", NULL, NULL);
    PSurfRev -> U.Pl = PlNew;
    SET_OBJECT_COLOR(PSurfRev, PrimColor);	   /* Set its default color. */

    return PSurfRev;
}

/*****************************************************************************
*   Routine to a create surface of extrusion out of the given cross section  *
* and the given direction. A NULL object will be returned, if the direction  *
* vector is in the cross section plane.					     *
*****************************************************************************/
struct ObjectStruct * GenEXTRUDEObject(ObjectStruct *Cross, VectorType Dir)
{
    int i;
    RealType R;
    VertexStruct *V1, *V1Head, *V2, *VIn;
    PolygonStruct *PBase1, *PBase2, *Pl, *PlIn;
    ObjectStruct *PExtrude;

    if (!IS_GEOM_OBJ(Cross))
	FatalError("Extrude: cross section is not geometric object\n");

    R = DOT_PROD(Cross -> U.Pl -> Plane, Dir);
    if (APX_EQ(R, 0.0)) {
	WndwInputWindowPutStr("Extrusion direction in cross-section plane. Empty object result", RED);
	return NULL;
    }

    /* Prepare the two bases (update their plane normal to point INDISE): */
    PBase1 = AllocPolygon(0, 0, CopyVList(Cross -> U.Pl -> V), NULL);
    Pl = PBase2 = AllocPolygon(0, 0, CopyVList(Cross -> U.Pl -> V), PBase1);
    V1 = V1Head = PBase2 -> V;
    do {
	PT_ADD(V1 -> Pt, Dir, V1 -> Pt);
	V1 = V1 -> Pnext;
    }
    while (V1 != NULL && V1 != V1Head);
    if (R > 0.0) {
	PLANE_COPY(PBase1 -> Plane, Cross -> U.Pl -> Plane);
	for (i=0; i<3; i++) PBase2 -> Plane[i] = (-Cross -> U.Pl -> Plane[i]);
	PBase2 -> Plane[3] = (-DOT_PROD(PBase2 -> Plane, PBase2 -> V -> Pt));
    }
    else {
	for (i=0; i<4; i++) PBase1 -> Plane[i] = (-Cross -> U.Pl -> Plane[i]);
	PLANE_COPY(PBase2 -> Plane, Cross -> U.Pl -> Plane);
	PBase2 -> Plane[3] = (-DOT_PROD(PBase2 -> Plane, PBase2 -> V -> Pt));
    }

    /* Now generate all the 4 corner polygon between the two bases: */
    V1 = V1Head = PBase1 -> V;
    V2 = PBase2 -> V;
    PlIn = GenInsidePoly(PBase1);
    VIn = PlIn -> V;
    do {
	Pl = GenPolygon4Vrtx(V1 -> Pt, V1 -> Pnext -> Pt,
			     V2 -> Pnext -> Pt, V2 -> Pt, VIn -> Pt, Pl);
	VIn = VIn -> Pnext;
	V1 = V1 -> Pnext;
	V2 = V2 -> Pnext;
    }
    while (V1 -> Pnext != NULL && V1 != V1Head);

    MyFree((char *) PlIn, POLYGON_TYPE);

    PExtrude = GenGeomObject("", NULL, NULL);
    PExtrude -> U.Pl = Pl;
    SET_OBJECT_COLOR(PExtrude, PrimColor);	   /* Set its default color. */

    return PExtrude;
}

/*****************************************************************************
*   Routine to create a pseudo polygon out of a given polygon such that each *
* vertex Vi is in the inside side of the corresponding edge ViVi+1 in the    *
* given polygon. Used in polygon generation for EXTRUDE/SURFREV operations.  *
*****************************************************************************/
static struct PolygonStruct *GenInsidePoly(PolygonStruct *Pl)
{
    int Axes;
    RealType Dx, Dy;
    PointType Pt;
    MatrixType Mat;
    PolygonStruct *PlIn;
    VertexStruct *VHead, *V, *Vnext, *VInHead, *VIn = NULL;

    PlIn = AllocPolygon(0, 0, VInHead = NULL, NULL);

    /* Generate transformation matrix to bring polygon to a XY parallel      */
    /* plane, and transform a copy of the polygon to that plane.	     */
    GenRotateMatrix(Mat, Pl -> Plane);
    VHead = V = CopyVList(Pl -> V);      /* We dont want to modify original! */
    Pl = AllocPolygon(0, 0, VHead, NULL);
    do {
	MatMultVecby4by4(V -> Pt, V -> Pt, Mat);
	V = V -> Pnext;
    }
    while (V != NULL && V != VHead);

    V = VHead;
    do {
	Vnext = V -> Pnext;
	Dx = ABS(V -> Pt[0] - Vnext -> Pt[0]);
	Dy = ABS(V -> Pt[1] - Vnext -> Pt[1]);
	Pt[0] = (V -> Pt[0] + Vnext -> Pt[0]) / 2.0;/* Prepare middle point. */
	Pt[1] = (V -> Pt[1] + Vnext -> Pt[1]) / 2.0;
	Pt[2] = V -> Pt[2];
	/* If Dx > Dy fire ray in +Y direction, otherwise in +X direction    */
	/* and if number of intersections is even (excluding the given point */
	/* itself) then that direction is the outside, otherwise, its inside.*/
	Axes = (Dx > Dy ? 1 : 0);
	if (CGPolygonRayInter(Pl, Pt, Axes) % 2 == 0)
	{
	    /* The amount we move along Axes is not of a big meaning as long */
	    /* as it is not zero, so MAX(Dx, Dy) guarantee non zero value... */
	    Pt[Axes] -= MAX(Dx, Dy);
	}
	else {
	    Pt[Axes] += MAX(Dx, Dy);
	}

	/* Now Pt holds point which is in the inside part of vertex V, Vnext.*/
	/* Put it in the pseudo inside polygon PlIn:			     */
	if (VInHead) {
	    VIn -> Pnext = AllocVertex(0, 0, NULL, NULL);
	    VIn = VIn -> Pnext;
	}
	else {
	    PlIn -> V = VInHead = VIn = AllocVertex(0, 0, NULL, NULL);
	}
	PT_COPY(VIn ->Pt, Pt);

	V = Vnext;
    }
    while (V != NULL && V != VHead);
    VIn -> Pnext = VInHead;

    MyFree((char *) Pl, POLYGON_TYPE);/* Free copied (and trans.) vrtx list. */

    /* Transform PlIn to the plane where original Pl is... */
    if (!MatInverseMatrix(Mat, Mat))		 /* Find the inverse matrix. */
	FatalError("GenInsidePoly: Inverse matrix does not exits");
    VIn = VInHead;
    do {
	MatMultVecby4by4(VIn -> Pt, VIn -> Pt, Mat);
	VIn = VIn -> Pnext;
    }
    while (VIn != NULL && VIn != VInHead);

    return PlIn;
}

/*****************************************************************************
*   Routine to create a polygon out of a list of 4 vertices V1/2/3/4	     *
* The fifth vertex is inside (actually, this is not true, as this point will *
* be in the positive part of the plane, which only locally in the object...) *
* the object, so the polygon normal direction can be evaluated uniquely.     *
*  No test is made to make sure the 4 points are co-planar...		     *
*****************************************************************************/
static struct PolygonStruct *GenPolygon4Vrtx(VectorType V1, VectorType V2,
	VectorType V3, VectorType V4, VectorType Vin, PolygonStruct *Pnext)
{
    struct PolygonStruct *PPoly;
    struct VertexStruct *V;

    PPoly = AllocPolygon(0, 0, V = AllocVertex(0, 0, NULL, NULL), Pnext);
    PT_COPY(V -> Pt, V1);

    V -> Pnext = AllocVertex(0, 0, NULL, NULL); V = V -> Pnext;
    PT_COPY(V -> Pt, V2);

    V -> Pnext = AllocVertex(0, 0, NULL, NULL); V = V -> Pnext;
    PT_COPY(V -> Pt, V3);

    V -> Pnext = AllocVertex(0, 0, NULL, NULL); V = V -> Pnext;
    PT_COPY(V -> Pt, V4);

    V -> Pnext = PPoly -> V;		   /* Make the Vertex list circular. */

    UpdatePolyPlane(PPoly, Vin);		   /* Update plane equation. */

    SET_CONVEX_POLY(PPoly);		       /* Mark it as convex polygon. */

    return PPoly;
}

/*****************************************************************************
*   Routine to create a polygon out of a list of 3 vertices V1/2/3	     *
* The forth vertex is inside (actually, this is not true, as this point will *
* be in the positive part of the plane, which only locally in the object...) *
* the object, so the polygon normal direction can be evaluated uniquely.     *
*****************************************************************************/
static struct PolygonStruct *GenPolygon3Vrtx(VectorType V1, VectorType V2,
			VectorType V3, VectorType Vin, PolygonStruct *Pnext)
{
    struct PolygonStruct *PPoly;
    struct VertexStruct *V;

    PPoly = AllocPolygon(0, 0, V = AllocVertex(0, 0, NULL, NULL), Pnext);
    PT_COPY(V -> Pt, V1);

    V -> Pnext = AllocVertex(0, 0, NULL, NULL); V = V -> Pnext;
    PT_COPY(V -> Pt, V2);

    V -> Pnext = AllocVertex(0, 0, NULL, NULL); V = V -> Pnext;
    PT_COPY(V -> Pt, V3);

    V -> Pnext = PPoly -> V;		   /* Make the Vertex list circular. */

    UpdatePolyPlane(PPoly, Vin);		   /* Update plane equation. */

    SET_CONVEX_POLY(PPoly);		       /* Mark it as convex polygon. */

    return PPoly;
}

/*****************************************************************************
*   Routine to preper transformation martix to do the following (in this     *
* order): scale by Scale, rotate such that the Z axis is in direction Dir    *
* and then translate by Trans.						     *
*    Algorithm: given the Trans vector, it forms the 4th line of Mat. Dir is *
* used to form the second line (the first 3 lines set the rotation), and     *
* finally Scale is used to scale first 3 lines/columns to the needed scale:  *
*                |  Tx  Ty  Tz  0 |   A transformation which takes the coord *
*                |  Bx  By  Bz  0 |  system into T, N & B as required and    *
* [X  Y  Z  1] * |  Nx  Ny  Nz  0 |  then translate it to C. T, N, B are     *
*                |  Cx  Cy  Cz  1 |  scaled by Scale.			     *
* N is exactly Dir (unit vec) 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).				     *
*****************************************************************************/
static void GenTransformMatrix(MatrixType Mat, VectorType Trans,
						VectorType Dir, RealType Scale)
{
    int i, j;
    RealType R;
    VectorType DirN, T, B, P;
    MatrixType TempMat;

    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. */
    VecCrossProd(T, B, DirN);				/* Calc the tangent. */

    MatGenUnitMat(Mat);
    for (i=0; i<3; i++) {
	Mat[0][i] = T[i];
	Mat[1][i] = B[i];
	Mat[2][i] = DirN[i];
    }
    MatGenMatScale(Scale, Scale, Scale, TempMat);
    MatMultTwo4by4(Mat, TempMat, Mat);

    MatGenMatTrans(Trans[0], Trans[1], Trans[2], TempMat);
    MatMultTwo4by4(Mat, Mat, TempMat);
}

/*****************************************************************************
*   Routine to update the Plane equation of the given polygon such that the  *
* Vin vertex will be in the positive side of it.			     *
*   It is assumed the polygon has at list 3 points...			     *
*****************************************************************************/
void UpdatePolyPlane(PolygonStruct *PPoly, VectorType Vin)
{
    int i;
    VectorType V1, V2;
    RealType Len;
    struct VertexStruct *V;

    V = PPoly -> V;	PT_SUB(V1, V -> Pt, V -> Pnext -> Pt);
    V = V -> Pnext;	PT_SUB(V2, V -> Pt, V -> Pnext -> Pt);

    VecCrossProd(PPoly -> Plane, V1, V2);	       /* Find Plane Normal. */
    /* Normalize the plane such that the normal has length of 1: */
    Len = PT_LENGTH(PPoly -> Plane);
    for (i=0; i<3; i++) PPoly -> Plane[i] /= Len;

    PPoly -> Plane[3] = (-DOT_PROD(PPoly -> Plane, PPoly -> V -> Pt));

    if (DOT_PROD(PPoly -> Plane, Vin) + PPoly -> Plane[3] < 0) {
	/* Flip plane normal and reverse the vertex list. */
	ReverseVrtxList(PPoly);
	for (i=0; i<4; i++) PPoly -> Plane[i] = (-PPoly -> Plane[i]);
    }
}
