/*****************************************************************************
*   Program to draw 3D object as wireframe after removing the hidden lines.  *
* This porgram works in object space, and if redirect stdout to a file, dump *
* the visible polylines into it instead of drawing them on current device.   *
* This may be used to display the results on any device (a plotter !?) later.*
*									     *
* Written by:  Gershon Elber				Ver 3.0, Aug. 1990   *
*****************************************************************************/

#include <stdio.h>
#include <math.h>
#include "program.h"
#include "getarg.h"
#include "genmat.h"
#include "config.h"

#ifdef NO_CONCAT_STR
static char *VersionStr =
	"Poly3D-H	Version 4.0,	Gershon Elber,\n\
	(C) Copyright 1989/90/91/92/93 Gershon Elber, Non commercial use only.";
#else
static char *VersionStr = "Poly3D-H	" VERSION ",	Gershon Elber,	"
	__DATE__ ",  " __TIME__ "\n" COPYRIGHT ", Non commercial use only.";
#endif /* NO_CONCAT_STR */

static char *CtrlStr =
	"poly3d-h b%- m%- i%- e%-#Edges!d f%-FineNess!d H%- 4%- q%- o%-OutName!s c%- z%- DFiles!*s";

static int
    GlblFourPerFlat = FALSE;

int NumOfPolygons = 0;		      /* Total number of polygons to handle. */
MatrixType GlblViewMat;				  /* Current view of object. */

/* Data structures used by the hidden line modules: */
int EdgeCount = 0;
EdgeStruct *EdgeHashTable[EDGE_HASH_TABLE_SIZE];
IPPolygonStruct *PolyHashTable[POLY_HASH_TABLE_SIZE];

/* The following are setable variables (via configuration file poly3d-h.cfg).*/
int GlblMore = FALSE,
    GlblClipScreen = TRUE,
    GlblQuiet = FALSE,
    GlblOutputHasRGB = FALSE,
    GlblOutputRGB[3] = { 255, 255, 255 },
    GlblOutputColor = VISIBLE_COLOR,
    GlblNumEdge = 0,
    GlblBackFacing = FALSE,
    GlblInternal = FALSE,
    GlblOutputHiddenData = FALSE,
    GlblFineNess = DEFAULT_FINENESS;

RealType
    GlblOutputWidth = VISIBLE_WIDTH;

static ConfigStruct SetUp[] =
{
  { "Internal",		(VoidPtr) &GlblInternal,	SU_BOOLEAN_TYPE },
  { "BackFacing",	(VoidPtr) &GlblBackFacing,	SU_BOOLEAN_TYPE },
  { "More",		(VoidPtr) &GlblMore,		SU_BOOLEAN_TYPE },
  { "ClipScreen",	(VoidPtr) &GlblClipScreen,	SU_BOOLEAN_TYPE },
  { "Quiet",		(VoidPtr) &GlblQuiet,		SU_BOOLEAN_TYPE },
  { "FourPerFlat",	(VoidPtr) &GlblFourPerFlat,	SU_BOOLEAN_TYPE },
  { "DumpHidden",	(VoidPtr) &GlblOutputHiddenData,SU_BOOLEAN_TYPE },
  { "FineNess",		(VoidPtr) &GlblFineNess,	SU_INTEGER_TYPE },
  { "NumOfEdges",	(VoidPtr) &GlblNumEdge,		SU_INTEGER_TYPE }
};

#define NUM_SET_UP	(sizeof(SetUp) / sizeof(ConfigStruct))

static IPPolygonStruct *Curve2Polylines(CagdCrvStruct *Crv);
static IPPolygonStruct *Surface2Polygons(CagdSrfStruct *Srf);

/*****************************************************************************
* Main routine - Read Parameter	line and do what you need...		     *
*****************************************************************************/
void main(int argc, char **argv)
{
    int EdgesFlag = FALSE,
	VerFlag = FALSE,
	OutFlag = FALSE,
	NumFiles = FALSE,
	FineNessFlag = FALSE, Error;
    char
	*OutFileName = NULL,
	**FileNames = NULL;
    FILE *OutFile;
    IPObjectStruct *PObjects, *PTmpObj,
	*PGeomObj = NULL;

    Config("poly3d-h", SetUp, NUM_SET_UP);   /* Read config. file if exists. */

    if ((Error = GAGetArgs(argc, argv, CtrlStr,
			   &GlblBackFacing, &GlblMore, &GlblInternal,
			   &EdgesFlag, &GlblNumEdge, &FineNessFlag,
			   &GlblFineNess, &GlblOutputHiddenData,
			   &GlblFourPerFlat, &GlblQuiet,
			   &OutFlag, &OutFileName, &GlblClipScreen,
			   &VerFlag, &NumFiles, &FileNames)) != 0) {
	GAPrintErrMsg(Error);
	GAPrintHowTo(CtrlStr);
	Poly3dhExit(1);
    }

    if (VerFlag) {
	fprintf(stderr, "\n%s\n\n", VersionStr);
	GAPrintHowTo(CtrlStr);
	ConfigPrint(SetUp, NUM_SET_UP);
	Poly3dhExit(0);
    }

    if (!NumFiles) {
	fprintf(stderr, "No data file names were given, exit\n");
	GAPrintHowTo(CtrlStr);
	Poly3dhExit(1);
    }

    if (GlblQuiet)
	GlblMore = FALSE;

    if (GlblOutputHiddenData && GlblBackFacing) {
	fprintf(stderr, "Warning: You have activated both output of hidden data (-H) and back facing\n");
	fprintf(stderr, "\t elimination (-b). Chances are you did not want to do that.\n");
    }

    /* Get the data files: */
    if ((PObjects = IritPrsrGetDataFiles(FileNames, NumFiles, TRUE,
					 GlblMore)) == NULL)
	Poly3dhExit(1);

    /* If only one object in input stream, save his attributes so we */
    /* can update the output.					     */
    for (PTmpObj = PObjects; PTmpObj != NULL; PTmpObj = PTmpObj -> Pnext) {
	if (IP_IS_GEOM_OBJ(PTmpObj)) {
	    if (PGeomObj == NULL)
		PGeomObj = PTmpObj;
	    else {
		PGeomObj = NULL;		    /* More than one object. */
		break;
	    }
	}
    }
    if (PGeomObj != NULL) {
	char *p;
	int Color;
	float Width;

	if ((p = AttrGetObjectStrAttrib(PGeomObj, "width")) != NULL &&
	    sscanf(p, "%f", &Width) == 1)
	    GlblOutputWidth = Width;
	GlblOutputHasRGB = AttrGetObjectRGBColor(PGeomObj,
						 &GlblOutputRGB[0],
						 &GlblOutputRGB[1],
						 &GlblOutputRGB[2]);
	if ((Color = AttrGetObjectColor(PGeomObj)) != IP_ATTR_NO_COLOR)
	    GlblOutputColor = Color;
    }

    /* And update the global viewing matrix: */
    if (IritPrsrWasPrspMat)
	MatMultTwo4by4(GlblViewMat, IritPrsrViewMat, IritPrsrPrspMat);
    else
	GEN_COPY(GlblViewMat, IritPrsrViewMat, sizeof(MatrixType));

    /* Prepare data structures to be able to decide on visibility: */
    PrepareViewData(PObjects);

    if (OutFlag) {
	if ((OutFile = fopen(OutFileName, "w")) == NULL) {
	    fprintf(stderr, "Failed to open \"%s\".\n", OutFileName);
	    Poly3dhExit(2);
	}
    }
    else
	OutFile = stdout;

    OutVisibleEdges(OutFile);	       /* Scan all sub-edges output visible. */

    if (OutFile != stdout)
	fclose(OutFile);

    Poly3dhExit(0);
}

/*****************************************************************************
* Routine to convert all surfaces/curves into polylines as follows:	     *
* Curves are converted to single polyline with SamplesPerCurve samples.	     *
* Surface are converted into GlblNumOfIsolines curves in each axes, each     *
* handled as Curves above. The curves and surfaces are then deleted.	     *
*****************************************************************************/
IPObjectStruct *IritPrsrProcessFreeForm(IPObjectStruct *CrvObjs,
					IPObjectStruct *SrfObjs)
{
    CagdCrvStruct *Crv, *Crvs;
    CagdSrfStruct *Srf, *Srfs;
    IPObjectStruct *PObj;
    IPPolygonStruct *PPolygon, *PPolygonTemp;

    if (CrvObjs == NULL && SrfObjs == NULL)
	return NULL;

    /* Make sure requested format is something reasonable. */
    if (GlblFineNess < 2) {
	GlblFineNess = 2;
	if (GlblMore)
	    fprintf(stderr, "FineNess is less than 2, 2 picked instead.\n");
    }

    if (CrvObjs) {
	for (PObj = CrvObjs; PObj != NULL; PObj = PObj -> Pnext) {
	    if (GlblMore)
		fprintf(stderr, "Processing surface object \"%s\".\n", 
			PObj -> Name);
	    Crvs = PObj -> U.Crvs;
	    PObj -> U.Pl = NULL;
	    PObj -> ObjType = IP_OBJ_POLY;
	    IP_SET_POLYLINE_OBJ(PObj);
	    for (Crv = Crvs; Crv != NULL; Crv = Crv -> Pnext) {
		PPolygon = PPolygonTemp = Curve2Polylines(Crv);
		while (PPolygonTemp -> Pnext)
		    PPolygonTemp = PPolygonTemp -> Pnext;
		PPolygonTemp -> Pnext = PObj -> U.Pl;
		PObj -> U.Pl = PPolygon;
	    }
	    CagdCrvFreeList(Crvs);
	}
    }

    if (SrfObjs) {
	for (PObj = SrfObjs; PObj != NULL; PObj = PObj -> Pnext) {
	    if (GlblMore)
		fprintf(stderr, "Processing surface object \"%s\".\n", 
			PObj -> Name);
	    Srfs = PObj -> U.Srfs;
	    PObj -> U.Pl = NULL;
	    PObj -> ObjType = IP_OBJ_POLY;
	    IP_SET_POLYGON_OBJ(PObj);
	    for (Srf = Srfs; Srf != NULL; Srf = Srf -> Pnext) {
		PPolygon = PPolygonTemp = Surface2Polygons(Srf);
		while (PPolygonTemp -> Pnext)
		    PPolygonTemp = PPolygonTemp -> Pnext;
		PPolygonTemp -> Pnext = PObj -> U.Pl;
		PObj -> U.Pl = PPolygon;
	    }
	    CagdSrfFreeList(Srfs);
	}
    }

    if (SrfObjs == NULL)
	return CrvObjs;
    else if (CrvObjs == NULL)
	return SrfObjs;
    else {
	for (PObj = SrfObjs; PObj -> Pnext != NULL; PObj = PObj -> Pnext);
	PObj -> Pnext = CrvObjs;
	return SrfObjs;
    }
}

/*****************************************************************************
* Routine to convert a single curve into a polyline with SamplesPerCurve     *
* samples, into a polyline object.					     *
*****************************************************************************/
static IPPolygonStruct *Curve2Polylines(CagdCrvStruct *Crv)
{
    int i, j;
    IPVertexStruct *V,
	*VHead = NULL;
    IPPolygonStruct *P;
    CagdPolylineStruct
	*CagdPoly = CagdCrv2Polyline(Crv, GlblFineNess);

    for (i = 0; i < CagdPoly -> Length; i++) {
	if (VHead == NULL)
	    VHead = V = IPAllocVertex(0, 0, NULL, NULL);
	else {
	    V -> Pnext = IPAllocVertex(0, 0, NULL, NULL);
	    V = V -> Pnext;
        }

	for (j = 0; j < 3; j++)		   	   /* Convert to our format. */
	   V -> Coord[j] = CagdPoly -> Polyline[i].Pt[j];
    }

    V -> Pnext = NULL;
    P = IPAllocPolygon(0, 0, VHead, NULL);

    CagdPolylineFree(CagdPoly);

    return P;
}

/*****************************************************************************
* Routine to convert a single surface into a polygons with 2^GlblFineNess    *
* as fineness measure.							     *
*****************************************************************************/
static IPPolygonStruct *Surface2Polygons(CagdSrfStruct *Srf)
{
    int i, j;
    IPVertexStruct *V, *VHead;
    IPPolygonStruct *P,
	*PHead = NULL;
    CagdPolygonStruct *CagdPolygon,
	*CagdPolygonHead = NULL;

    CagdPolygonHead = CagdSrf2Polygons(Srf, 1 << GlblFineNess,
				       FALSE, GlblFourPerFlat, FALSE);

    for (CagdPolygon = CagdPolygonHead;
	 CagdPolygon != NULL;
	 CagdPolygon = CagdPolygon -> Pnext) {
	/* All polygons are triangles! */
	VHead = NULL;
	for (i = 0; i < 3; i++) {		     /* Convert to vertices. */
	    if (VHead == NULL)
		VHead = V = IPAllocVertex(0, 0, NULL, NULL);
	    else {
		V -> Pnext = IPAllocVertex(0, 0, NULL, NULL);
		V = V -> Pnext;
	    }

	    for (j = 0; j < 3; j++)		   /* Convert to our format. */
	       V -> Coord[j] = CagdPolygon -> Polygon[i].Pt[j];
	}

	V -> Pnext = NULL;
	P = IPAllocPolygon(0, 0, VHead, PHead);
	PHead = P;
    }

    CagdPolygonFreeList(CagdPolygonHead);

    return PHead;
}

/*****************************************************************************
* Poly3d-h Exit routine. Note it might call to CloseGraph without calling    *
* InitGraph(), or call MouseClose() without MouseInit(), or call	     *
* RestoreCtrlBrk() without SetUpCtrlBrk() and it is the responsibility	     *
* of the individual modules to do nothing in these cases.		     *
*****************************************************************************/
void Poly3dhExit(int ExitCode)
{
    if (!GlblQuiet)
	fprintf(stderr, "\n");

    exit(ExitCode);
}
