/*****************************************************************************
* Filter to convert IRIT data files to NFF format.			     *
*									     *
* Written by:  Gershon Elber				Ver 1.0, Jan 1992    *
*****************************************************************************/

#include <stdio.h>
#include <math.h>
#include <string.h>
#include "irit_sm.h"
#include "iritprsr.h"
#include "allocate.h"
#include "attribut.h"
#include "iritgrap.h"
#include "getarg.h"
#include "genmat.h"
#include "ffcnvrt.h"
#include "ip_cnvrt.h"

#define DEFAULT_KD      " 1.0"
#define DEFAULT_KS      " 0.0"
#define DEFAULT_SHINE   " 0.0"
#define DEFAULT_TRANS   " 0.0"
#define DEFAULT_INDEX   " 0.0"
#define DEFAULT_RGB_COLOR	" 1.0 1.0 1.0"

#define DIST_EPSILON	2e-4
#define SIZE_EPSILON	1e-5

#define STRCAT2(Str1, Str2, Str3) strcat(strcat(Str1, Str2), Str3)

#ifdef NO_CONCAT_STR
static char *VersionStr =
	"Irit2Nff		Version 5.0,	Gershon Elber,\n\
	 (C) Copyright 1989/90-95 Gershon Elber, Non commercial use only.";
#else
static char *VersionStr = "Irit2Nff	" VERSION ",	Gershon Elber,	"
	__DATE__ ",   " __TIME__ "\n" COPYRIGHT ", Non commercial use only.";
#endif /* NO_CONCAT_STR */

static char
#ifdef DOUBLE
    *CtrlStr = "irit2nff l%- 4%- c%- F%-PolyOpti|FineNess!d!F o%-OutName!s T%- g%- z%- DFiles!*s";
#else
    *CtrlStr = "irit2nff l%- 4%- c%- F%-PolyOpti|FineNess!d!f o%-OutName!s T%- g%- z%- DFiles!*s";
#endif /* DOUBLE */

static char
    *OutFileName = "irit2nff";

static int
    GlblCPPSupport = FALSE,
    GlblDumpOnlyGeometry = FALSE;

static MatrixType CrntViewMat;			/* This is the current view! */

static int TransColorTable[][4] = {
    { /* BLACK		*/ 0,    0,   0,   0 },
    { /* BLUE		*/ 1,    0,   0, 255 },
    { /* GREEN		*/ 2,    0, 255,   0 },
    { /* CYAN		*/ 3,    0, 255, 255 },
    { /* RED		*/ 4,  255,   0,   0 },
    { /* MAGENTA 	*/ 5,  255,   0, 255 },
    { /* BROWN		*/ 6,   50,   0,   0 },
    { /* LIGHTGRAY	*/ 7,  127, 127, 127 },
    { /* DARKGRAY	*/ 8,   63,  63,  63 },
    { /* LIGHTBLUE	*/ 9,    0,   0, 255 },
    { /* LIGHTGREEN	*/ 10,   0, 255,   0 },
    { /* LIGHTCYAN	*/ 11,   0, 255, 255 },
    { /* LIGHTRED	*/ 12, 255,   0,   0 },
    { /* LIGHTMAGENTA	*/ 13, 255,   0, 255 },
    { /* YELLOW		*/ 14, 255, 255,   0 },
    { /* WHITE		*/ 15, 255, 255, 255 },
    { /* BROWN		*/ 20,  50,   0,   0 },
    { /* DARKGRAY	*/ 56,  63,  63,  63 },
    { /* LIGHTBLUE	*/ 57,   0,   0, 255 },
    { /* LIGHTGREEN	*/ 58,   0, 255,   0 },
    { /* LIGHTCYAN	*/ 59,   0, 255, 255 },
    { /* LIGHTRED	*/ 60, 255,   0,   0 },
    { /* LIGHTMAGENTA	*/ 61, 255,   0, 255 },
    { /* YELLOW		*/ 62, 255, 255,   0 },
    { /* WHITE		*/ 63, 255, 255, 255 },
    {			   -1,   0,   0,   0 }
};

static void DumpDataForNFF(IPObjectStruct *PObjects);
static int DumpOneObject(FILE *FRay, FILE *FGeom, IPObjectStruct *PObject);
static int DumpOnePolygon(FILE *f, IPPolygonStruct *PPolygon, int IsPolygon);
static RealType *MapPoint(RealType *Pt);
static RealType *MapVector(RealType *Pt, RealType *Vec);
static void Irit2NffExit(int ExitCode);

/*****************************************************************************
* DESCRIPTION:                                                               M
* Main module of irit2nff - Read command line and do what is needed...	     M
*                                                                            *
* PARAMETERS:                                                                M
*   argc, argv:  Command line.                                               M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   main                                                                     M
*****************************************************************************/
void main(int argc, char **argv)
{
    int Error,
	FineNessFlag = FALSE,
	VerFlag = FALSE,
	OutFileFlag = FALSE,
	NumFiles = 0;
    char Line[LINE_LEN_LONG], *p,
	**FileNames = NULL;
    IPObjectStruct *PObjects;

    if ((Error = GAGetArgs(argc, argv, CtrlStr, &FFCState.LinearOnePolyFlag,
			   &FFCState.FourPerFlat, &GlblCPPSupport,
			   &FineNessFlag, &FFCState.OptimalPolygons,
			   &FFCState.FineNess,
			   &OutFileFlag, &OutFileName, &FFCState.Talkative,
			   &GlblDumpOnlyGeometry,
			   &VerFlag, &NumFiles, &FileNames)) != 0) {
	GAPrintErrMsg(Error);
	GAPrintHowTo(CtrlStr);
	Irit2NffExit(1);
    }

    if (VerFlag) {
	fprintf(stderr, "\n%s\n\n", VersionStr);
	GAPrintHowTo(CtrlStr);
	Irit2NffExit(0);
    }

    if (FFCState.LinearOnePolyFlag) {
	CagdSetLinear2Poly(CAGD_ONE_POLY_PER_COLIN);
    }
    else
        CagdSetLinear2Poly(CAGD_REG_POLY_PER_LIN);

    if (GlblDumpOnlyGeometry && !GlblCPPSupport) {
	fprintf(stderr, "Flag -g makes sense only with -c.\n");
	Irit2NffExit(1);
    }
    
    if (!NumFiles) {
	fprintf(stderr, "No data file names were given, exit.\n");
	GAPrintHowTo(CtrlStr);
	Irit2NffExit(1);
    }

    if (!OutFileFlag) {		/* Pick the first input name as output name. */
	strcpy(Line, FileNames[0]);
	if ((p = strrchr(Line, '.')) != NULL)     /* Remove old file type. */
	    *p = 0;
	OutFileName = IritStrdup(Line);
    }

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

    if (IritPrsrWasPrspMat)
        MatMultTwo4by4(CrntViewMat, IritPrsrViewMat, IritPrsrPrspMat);
    else
	GEN_COPY(CrntViewMat, IritPrsrViewMat, sizeof(MatrixType));

    DumpDataForNFF(PObjects);

    Irit2NffExit(0);
}

/*****************************************************************************
* DESCRIPTION:                                                               *
* Dumps the data for NFF to stdout.                                          *
*                                                                            *
* PARAMETERS:                                                                *
*   PObjects:   To dump into file.                                           *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*****************************************************************************/
static void DumpDataForNFF(IPObjectStruct *PObjects)
{
    static char *Header1[] = {
	"#",
	"# This file was automatically created from IRIT solid modeller data",
	"# using Irit2Nff - IRIT to NFF filter.",
	"#",
	"#            (c) Copyright 1991/92 Gershon Elber, Non commercial use only.",
	"#",
	NULL
    };
    static char *Header2[] = {
	"",
	"v",
	"from   0  0 10",
	"at     0  0  0",
	"up     0  1  0",
	"angle  12",
	"resolution 512 512",
	"",
	"l      1 1 1",
	"",
	NULL
    };
    int i,
	TotalPolys = 0;
    char Line[128];
    FILE *FGeom, *FNff;

    sprintf(Line, "%s.nff", OutFileName);
    if (!GlblDumpOnlyGeometry) {
        if ((FNff = fopen(Line, "w")) == NULL) {
	    fprintf(stderr, "Failed to open \"%s\".\n", Line);
	    Irit2NffExit(2);
        }
    }
    else
        FNff = NULL;

    if (GlblCPPSupport) {   /* Separated files for geometry/surface quality. */
	sprintf(Line, "%s.geom", OutFileName);
	if ((FGeom = fopen(Line, "w")) == NULL) {
#	    if defined(OS2GCC) || defined(__WINNT__)
	        sprintf(Line, "%s.geo", OutFileName);
	        if ((FGeom = fopen(Line, "w")) == NULL)
#	    endif /* OS2GCC || __WINNT__ */
		{
		    fprintf(stderr, "Failed to open \"%s\".\n", Line);
		    Irit2NffExit(2);
		}
	}
    }
    else
    	FGeom = NULL;

    if (FNff != NULL) {
        if (GlblCPPSupport)
            fprintf(FNff, "/*\n");
        for (i = 0; Header1[i] != NULL; i++)
	    fprintf(FNff, "%s\n", Header1[i]);
        if (GlblCPPSupport)
            fprintf(FNff, "*/\n");
        fprintf(FNff, "\n");

        for (i = 0; Header2[i] != NULL; i++)
	    fprintf(FNff, "%s\n", Header2[i]);
    }
    
    if (GlblCPPSupport) {
        fprintf(FGeom, "/*\n");
	for (i = 0; Header1[i] != NULL; i++)
	    fprintf(FGeom, "%s\n", Header1[i]);
        fprintf(FGeom, "*/\n\n");
    }

    while (PObjects) {
	IPObjectStruct *PObjNext;

	if (IP_IS_FFGEOM_OBJ(PObjects))
	    PObjects = ProcessFreeForm(PObjects, &FFCState);

	TotalPolys += DumpOneObject(FNff, FGeom, PObjects);

	PObjNext = PObjects -> Pnext;
	IPFreeObject(PObjects);
	PObjects = PObjNext;
    }

    if (GlblCPPSupport && FNff != NULL)
	fprintf(FNff, "#include \"%s\"\n", Line);

    if (FNff != NULL)
	fclose(FNff);
    if (FGeom != NULL)
	fclose(FGeom);

    fprintf(stderr, "\nTotal number of polygons - %d\n", TotalPolys);
}

/*****************************************************************************
* DESCRIPTION:                                                               *
* Dumps one object PObject to files.                                         *
*                                                                            *
* PARAMETERS:                                                                *
*   FNff:         NFF file to dump object to.                                *
*   FGeom:        Geometry file to dump object to.                           *
*   PObject:      Object to dump tofile f.                                   *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*****************************************************************************/
static int DumpOneObject(FILE *FNff, FILE *FGeom, IPObjectStruct *PObject)
{
    static int
	ObjectSeqNum = 1;
    int i, j, Red, Green, Blue, Color,
        PolyCount = 0,
	HasColor = FALSE;
    char *p, Name[LINE_LEN], SrfPropString[LINE_LEN_LONG];
    RealType RGBColor[3];
    IPPolygonStruct *PList;

    if (!IP_IS_POLY_OBJ(PObject) ||
	!IP_IS_POLYGON_OBJ(PObject) ||
	PObject -> U.Pl == NULL)
	return 0;

    PList = PObject -> U.Pl;

    if (strlen(PObject -> Name) == 0)
	sprintf(Name, "ObjSeq%d", ObjectSeqNum);
    else
	strcpy(Name, PObject -> Name);

    SrfPropString[0] = 0;
    if ((p = AttrGetObjectStrAttrib(PObject, "kd")) != NULL)
	STRCAT2(SrfPropString, " ", p);
    else
	strcat(SrfPropString, DEFAULT_KD);
    if ((p = AttrGetObjectStrAttrib(PObject, "ks")) != NULL)
	STRCAT2(SrfPropString, " ", p);
    else
	strcat(SrfPropString, DEFAULT_KS);
    if ((p = AttrGetObjectStrAttrib(PObject, "shine")) != NULL)
	STRCAT2(SrfPropString, " ", p);
    else
	strcat(SrfPropString, DEFAULT_SHINE);
    if ((p = AttrGetObjectStrAttrib(PObject, "trans")) != NULL)
	STRCAT2(SrfPropString, " ", p);
    else
	strcat(SrfPropString, DEFAULT_TRANS);
    if ((p = AttrGetObjectStrAttrib(PObject, "index")) != NULL)
	STRCAT2(SrfPropString, " ", p);
    else
	strcat(SrfPropString, DEFAULT_INDEX);

    if (AttrGetObjectRGBColor(PObject, &Red, &Green, &Blue)) {
	HasColor = TRUE;
	RGBColor[0] = Red;
	RGBColor[1] = Green;
	RGBColor[2] = Blue;
    }
    else if ((Color = AttrGetObjectColor(PObject)) != IP_ATTR_NO_COLOR) {
	for (i = 0; TransColorTable[i][0] >= 0; i++) {
	    if (TransColorTable[i][0] == Color) {
		HasColor = TRUE;
		for (j = 0; j < 3; j++)
		    RGBColor[j] = TransColorTable[i][j+1];
		break;
	    }
	}
    }

    if (FNff != NULL)
        fprintf(FNff, GlblCPPSupport ? "/*\n#\n# %s\n#\n*/\n" : "#\n# %s\n#\n",
	        Name);
    if (GlblCPPSupport) {
	if (FNff != NULL)
	    fprintf(FNff, "#define %s_SRF_PROP ", Name);
	fprintf(FGeom, "/*\n#\n# %s\n#\n*/\n", Name);
	fprintf(FGeom, "f %s_SRF_PROP\n", Name);
    }
    else {
	if (FNff != NULL)
	    fprintf(FNff, "f ");
    }
    if (HasColor) {
	for (i = 0; i < 3; i++)
	    RGBColor[i] /= 255.0;
	if (FNff != NULL)
	    fprintf(FNff, "%7.4f %7.4f %7.4f",
		    RGBColor[0], RGBColor[1], RGBColor[2]);
    }
    else {
	if (FNff != NULL)
	    fprintf(FNff, "%s", DEFAULT_RGB_COLOR);
    }
    if (FNff != NULL)
        fprintf(FNff, " %s\n\n", SrfPropString);

    while (PList) {
	PolyCount += DumpOnePolygon(GlblCPPSupport ? FGeom : FNff,
				    PList, IP_IS_POLYGON_OBJ(PObject));
	PList =	PList -> Pnext;
    }

    if (FFCState.Talkative)
	fprintf(stderr, "Converting \"%s\" - %d triangles.\n",
		Name, PolyCount);

    if (FNff != NULL)
	fprintf(FNff, "\n\n");
    if (GlblCPPSupport)
	fprintf(FGeom, "\n\n");

    ObjectSeqNum++;

    return PolyCount;
}

/*****************************************************************************
* DESCRIPTION:                                                               *
* Dumps one polygon, using global Matrix transform CrntViewMat.		     *
*                                                                            *
* PARAMETERS:                                                                *
*   Fl:           File to dump polygon to.  		                     *
*   PPolygon:     Polygon to dump to file f.                                 *
*   IsPolygon:    Is it a polygon or a polyline?                             *
*                                                                            *
* RETURN VALUE:                                                              *
*   int:          Number of triangles.                                       *
*****************************************************************************/
static int DumpOnePolygon(FILE *Fl, IPPolygonStruct *PPolygon, int IsPolygon)
{
    int i,
	TriCount = 0;
    RealType *MappedNormal[3], *MappedPoint[3], Normal[3], Vec1[3], Vec2[3];
    IPVertexStruct *VFirst, *V1, *V2,
	*VList = PPolygon -> PVertex;

    if (Fl == NULL || VList == NULL)
	return 0;

    if (!IritPrsrIsConvexPolygon(PPolygon)) {
	static int Printed = FALSE;

	if (!Printed) {
	    fprintf(stderr,
		    "\nWARNING: Non convex polygon(s) might be in data (see CONVEX in IRIT),\n\t\t\t\toutput can be wrong as the result!\n");
	    Printed = TRUE;
	}
    }

    if (IsPolygon) {
	VFirst = VList;
	V1 = VFirst -> Pnext;
	V2 = V1 -> Pnext;

	while (V2 != NULL) {
	    MappedPoint[0] = MapPoint(VFirst -> Coord);
	    MappedPoint[1] = MapPoint(V1 -> Coord);
	    MappedPoint[2] = MapPoint(V2 -> Coord);

	    /* Test for two type of degeneracies. Make sure that no two  */
	    /* points in the triangle are the same and that they are     */
	    /* not colinear.					         */
	    if (!PT_APX_EQ(MappedPoint[0], MappedPoint[1]) &&
		!PT_APX_EQ(MappedPoint[0], MappedPoint[2]) &&
		!PT_APX_EQ(MappedPoint[1], MappedPoint[2])) {
		PT_SUB(Vec1, MappedPoint[0], MappedPoint[1]);
		PT_SUB(Vec2, MappedPoint[1], MappedPoint[2]);
		PT_NORMALIZE(Vec1);
		PT_NORMALIZE(Vec2);
		CROSS_PROD(Normal, Vec1, Vec2);

		if (PT_LENGTH(Normal) > SIZE_EPSILON) {
		    PT_NORMALIZE(Normal);

		    MappedNormal[0] =
		        MapVector(VFirst -> Coord, VFirst -> Normal);
		    MappedNormal[1] =
		        MapVector(V1 -> Coord, V1 -> Normal);
		    MappedNormal[2] =
		        MapVector(V2 -> Coord, V2 -> Normal);

		    if (DOT_PROD(Normal, MappedNormal[0]) < -SIZE_EPSILON ||
			DOT_PROD(Normal, MappedNormal[1]) < -SIZE_EPSILON ||
			DOT_PROD(Normal, MappedNormal[2]) < -SIZE_EPSILON) {
			SWAP(RealType *, MappedPoint[1], MappedPoint[2]);
			SWAP(RealType *, MappedNormal[1], MappedNormal[2]);
			PT_SCALE(Normal, -1.0);
		    }

		    /* Make sure all normals are set properly: */
		    if (DOT_PROD(MappedNormal[0], MappedNormal[0]) < SIZE_EPSILON)
		        PT_COPY(MappedNormal[0], Normal);
		    if (DOT_PROD(MappedNormal[1], MappedNormal[1]) < SIZE_EPSILON)
		        PT_COPY(MappedNormal[1], Normal);
		    if (DOT_PROD(MappedNormal[2], MappedNormal[2]) < SIZE_EPSILON)
		        PT_COPY(MappedNormal[2], Normal);

		    TriCount++;

		    fprintf(Fl, "pp 3\n");
		    for (i = 0; i < 3; i++)
		      fprintf(Fl,
			      "  %10.7f %10.7f %10.7f  %9.6f %9.6f %9.6f\n",
			      MappedPoint[i][0],
			      MappedPoint[i][1],
			      MappedPoint[i][2],
			      MappedNormal[i][0],
			      MappedNormal[i][1],
			      MappedNormal[i][2]);
		}
	    }

	    V1 = V2;
	    V2 = V2 -> Pnext;
	}
    }

    return TriCount;
}

/*****************************************************************************
* DESCRIPTION:                                                               *
* Maps the given E3 point using the CrntViewMat.			     *
*                                                                            *
* PARAMETERS:                                                                *
*   Pt:        Point to map.                                                 *
*                                                                            *
* RETURN VALUE:                                                              *
*   RealType *:   Mapped point, in local static place (for up to 3 calls).   *
*****************************************************************************/
static RealType *MapPoint(RealType *Pt)
{
    static int Count = 0;
    static RealType MappedPts[3][3];
    RealType *MappedPt = MappedPts[Count++];

    if (Count >= 3)
	Count = 0;

    MatMultVecby4by4(MappedPt, Pt, CrntViewMat);

    return MappedPt;
}

/*****************************************************************************
* DESCRIPTION:                                                               *
* Maps the given E3 unit vector using the CrntViewMat.			     *
*   This routine will return a zero vector if unit normal is not computable. *
*                                                                            *
* PARAMETERS:                                                                *
*   Pt:         Position of normal.                                          *
*   Vec:        Normal at Pt.                                                *
*                                                                            *
* RETURN VALUE:                                                              *
*   RealType *:    Mapped normal.                                            *
*****************************************************************************/
static RealType *MapVector(RealType *Pt, RealType *Vec)
{
    static int
	Count = 0,
	WasWarning = 0;
    static RealType MappedVecs[3][3];
    RealType MappedPt[3], Pt2[3], MappedPt2[3],
	*MappedVec = MappedVecs[Count++];

    if (Count >= 3)
	Count = 0;

    if (DOT_PROD(Vec, Vec) < SIZE_EPSILON) {
	MappedVec[0] = MappedVec[1] = MappedVec[2] = 0.0;
	if (!WasWarning) {
	    WasWarning = 1;
	    fprintf(stderr, "Non computable normals detected. Approximated from geometry.\n");
	}
    }
    else {
    	MatMultVecby4by4(MappedPt, Pt, CrntViewMat);

    	PT_ADD(Pt2, Pt, Vec);
    	MatMultVecby4by4(MappedPt2, Pt2, CrntViewMat);

    	PT_SUB(MappedVec, MappedPt2, MappedPt);
    	PT_NORMALIZE(MappedVec);
    }

    return MappedVec;
}

/*****************************************************************************
* DESCRIPTION:                                                               *
* Irit2Nff exit routine.						     *
*                                                                            *
* PARAMETERS:                                                                *
*   ExitCode:                                                                *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*****************************************************************************/
static void Irit2NffExit(int ExitCode)
{
    exit(ExitCode);
}

#ifdef DEBUG

/*****************************************************************************
* DESCRIPTION:                                                               *
*    Dummy function to link at debugging time.                               *
*                                                                            *
* PARAMETERS:                                                                *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*                                                                            *
* KEYWORDS:                                                                  *
*****************************************************************************/
void DummyLinkCagdDebug(void)
{
    IritPrsrDbg();
}

#endif /* DEBUG */
