/*****************************************************************************
*   Program to draw 3D object as solid objects in GIF image file format.     *
*									     *
* Options:								     *
* 1. -a ambient : Ratio of the ambient color.				     *
* 2. -c n : Number of bit ber pixel (2^n is number of colors possible).	     *
* 3. -l x y z : Light source direction vector.				     *
* 4. -2 : Force 2 light sources at opposite direction of light vectors.      *
* 5. -m : More flag, to print more imformation on input/errors.		     *
* 6. -s x y : Specify new dimensions to generate the resulting image.	     *
* 7. -S SubSample : specifies dimension of pixel sub sampling (1..4).	     *
* 8. -g : Use gouraud shading.						     *
* 9. -b : Delete Back facing polygons.					     *
* 10. -M Mask : Create boolean Mask (coverage) image as well.		     *
* 11. -z : Print current version, and some helpfull data.		     *
* 12. -f FineNess : log based 2 of the surface to polygons subdiv, fineness. *
*									     *
* Note some of those options may be permanently set to a different default   *
* using the configuration file "Poly3D-R.cfg"				     *
*									     *
* Usage: poly3d-r [-a Ambient] [-c N] [-l X Y Z] [-2] [-m] [-s Xsize Ysize]  *
*               [-S SubSample] [-g] [-b] [-M Mask] [-z] [-f FineNess] DFiles *
*									     *
* Written by:  Gershon Elber				Ver 3.0, Aug 1990    *
*****************************************************************************/

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

#ifdef __TURBOC__      /* Malloc debug routine - only on TC++ 1.0 and above. */
#define __DEBUG_MALLOC__
#endif /* __TURBOC__ */

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

static char *CtrlStr =
#ifdef DOUBLE
	"poly3d-r a%-Ambient!F c%-N!d l%-X|Y|Z!F!F!F 2%- m%- s%-Xsize|Ysize!d!d S%-SubSample!d g%- b%- M%-Mask!s z%- f%-FineNess!d DFiles!*s";
#else
	"poly3d-r a%-Ambient!f c%-N!d l%-X|Y|Z!f!f!f 2%- m%- s%-Xsize|Ysize!d!d S%-SubSample!d g%- b%- M%-Mask!s z%- f%-FineNess!d DFiles!*s";
#endif /* DOUBLE */

static long SaveTotalTime;
static GifColorType MaskColorMap[2] = {	 /* Boolean mask GIF file color map. */
    {   0,   0,   0 },
    { 255, 255, 255 }
};
/* Fineness surface subdivision control. */
static int GlblFineNess = DEFAULT_FINENESS;

int GlblNumOfPolys = 0;		 /* Total number of polygons scan converted. */
int GlblNumOfVerts = 0;				/* Total number of vertices. */
MatrixType GlblViewMat;				  /* Current view of object. */

/* Amount scene was scaled up from normalized [-1..1] size on both X & Y: */
RealType GlblScaleUpFctr = 0.0;

/* The following are setable variables (via configuration file poly3d-h.cfg).*/
int GlblMore = FALSE;

ShadeInfoStruct GlblShadeInfo = {
    1,						   /* Sub samples per pixel. */
    DEFAULT_BITS_PER_PIXEL,
    0,
    DEFAULT_COLOR,
    DEFAULT_BACK_GROUND_COLOR,
    FALSE,					    /* No two light sources. */
    DEFAULT_SCREEN_XSIZE,
    DEFAULT_SCREEN_YSIZE,
    FALSE,					      /* No Gouraud shading. */
    FALSE,				 	 /* No back facing deletion. */
    NULL,
    NULL,						/* No color map yet. */
    DEFAULT_LIGHT_SOURCE,
    DEFAULT_AMBIENT,
    DEFAULT_NORMAL_AVG_DEGREE
};

static ConfigStruct SetUp[] = {
  { "Ambient",		(VoidPtr) &GlblShadeInfo.Ambient,	SU_REAL_TYPE },
  { "LightSrcX",	(VoidPtr) &GlblShadeInfo.LightSource[0],SU_REAL_TYPE },
  { "LightSrcY",	(VoidPtr) &GlblShadeInfo.LightSource[1],SU_REAL_TYPE },
  { "LightSrcZ",	(VoidPtr) &GlblShadeInfo.LightSource[2],SU_REAL_TYPE },
  { "AvgDegree",	(VoidPtr) &GlblShadeInfo.NrmlAvgDegree,	SU_REAL_TYPE },
  { "TwoSources",	(VoidPtr) &GlblShadeInfo.TwoSources,	SU_BOOLEAN_TYPE },
  { "Gouraud",		(VoidPtr) &GlblShadeInfo.Gouraud,	SU_BOOLEAN_TYPE },
  { "backFacing",	(VoidPtr) &GlblShadeInfo.BackFacing,	SU_BOOLEAN_TYPE },
  { "SubSample",	(VoidPtr) &GlblShadeInfo.SubSamplePixel,SU_INTEGER_TYPE },
  { "BitsPerPixel",	(VoidPtr) &GlblShadeInfo.BitsPerPixel,	SU_INTEGER_TYPE },
  { "Color",		(VoidPtr) &GlblShadeInfo.DefaultColor,	SU_INTEGER_TYPE },
  { "BackGroundColor",	(VoidPtr) &GlblShadeInfo.BackGroundColor,SU_INTEGER_TYPE },
  { "Xsize",		(VoidPtr) &GlblShadeInfo.ScrnXSize,	SU_INTEGER_TYPE },
  { "Ysize",		(VoidPtr) &GlblShadeInfo.ScrnYSize,	SU_INTEGER_TYPE },
  { "FineNess",		(VoidPtr) &GlblFineNess,		SU_INTEGER_TYPE },
  { "More",		(VoidPtr) &GlblMore,			SU_BOOLEAN_TYPE }
};
#define NUM_SET_UP	(sizeof(SetUp) / sizeof(ConfigStruct))

/* All polygons to be scan convert will be inserted into this hash table     */
/* during the preprocessing (PrepareXXXX functions).			     */
IPPolygonStruct **PolyHashTable;

static IPPolygonStruct *Surface2Polygons(CagdSrfStruct *Srf);

/*****************************************************************************
* Main routine - Read Parameter	line and do what you need...		     *
*****************************************************************************/
void main(int argc, char **argv)
{
    int AmbientFlag = FALSE,
	ColorFlag = FALSE,
	LightSrcFlag = FALSE,
	GifMaskFlag = FALSE,
	VerFlag = FALSE,
	NumFiles = 0,
	ImageSizeFlag = FALSE,
	SubSampleFlag = FALSE,
	FineNessFlag = FALSE,
	Error;
    char *GifMaskName,
	**FileNames = NULL;
    RealType Size, Scale;
    MatrixType Mat;
    IPObjectStruct *PObjects;
    GifFileType *GifFile,
	*GifMask = NULL;

    SaveTotalTime = time(NULL);			      /* Save starting time. */

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

    if ((Error = GAGetArgs (argc, argv, CtrlStr,
		&AmbientFlag, &GlblShadeInfo.Ambient,
		&ColorFlag, &GlblShadeInfo.BitsPerPixel, &LightSrcFlag,
		&GlblShadeInfo.LightSource[0],
		&GlblShadeInfo.LightSource[1],
		&GlblShadeInfo.LightSource[2],
		&GlblShadeInfo.TwoSources, &GlblMore, &ImageSizeFlag,
		&GlblShadeInfo.ScrnXSize, &GlblShadeInfo.ScrnYSize,
		&SubSampleFlag, &GlblShadeInfo.SubSamplePixel,
		&GlblShadeInfo.Gouraud, &GlblShadeInfo.BackFacing,
		&GifMaskFlag, &GifMaskName,
		&VerFlag, &FineNessFlag, &GlblFineNess,
		&NumFiles, &FileNames)) != 0) {
	GAPrintErrMsg(Error);
	GAPrintHowTo(CtrlStr);
	Poly3drExit(1);
    }

    if (GlblShadeInfo.Ambient < 0.0 || GlblShadeInfo.Ambient > 1.0) {
	fprintf(stderr,
    	    "Ambient light specified not in [0.0..1.0] range, %lf selected instead.\n",
		DEFAULT_AMBIENT);
	GlblShadeInfo.Ambient = DEFAULT_AMBIENT;
    }
    if (GlblShadeInfo.BitsPerPixel < 1 || GlblShadeInfo.BitsPerPixel > 8) {
	fprintf(stderr,
    	    "PitsPerPixel not in [1..8] range, %d selected instead.\n",
		DEFAULT_BITS_PER_PIXEL);
	GlblShadeInfo.BitsPerPixel = DEFAULT_BITS_PER_PIXEL;
    }

    Size = sqrt(SQR(GlblShadeInfo.LightSource[0]) +
		SQR(GlblShadeInfo.LightSource[1]) +
		SQR(GlblShadeInfo.LightSource[2]));
    if (ABS(Size) < EPSILON) {
	fprintf(stderr, "Light source vector is zero, Z axis selected instead.\n");
	GlblShadeInfo.LightSource[0] =
	GlblShadeInfo.LightSource[1] = 0.0;
	GlblShadeInfo.LightSource[2] = 1.0;
    }
    else {
	GlblShadeInfo.LightSource[0] /= Size;
	GlblShadeInfo.LightSource[1] /= Size;
	GlblShadeInfo.LightSource[2] /= Size;
    }

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

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

    if (SubSampleFlag) {
	if (GlblShadeInfo.SubSamplePixel < 1 || GlblShadeInfo.SubSamplePixel > 4) {
	    fprintf(stderr, "Sub sampling can be 1 to 4 only (1x1 to 4x4).\n");
	    GAPrintHowTo(CtrlStr);
	    Poly3drExit(1);
	}
    }

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

    /* Compute the viewing matrices and related data: */
    if (IritPrsrWasPrspMat)
	MatMultTwo4by4(GlblViewMat, IritPrsrViewMat, IritPrsrPrspMat);
    else
	GEN_COPY(GlblViewMat, IritPrsrViewMat, sizeof(MatrixType));

    /* Now its time to scale the normalized image (+/-1 on both X & Y) to    */
    /* size specified by the image dimensions. We scale up to the SMALLER    */
    /* dimension, and put the center at the image center.		     */
    /* Also, as the GIF image starts at the top, we must flip the image      */
    /* along Y axis.							     */
    GlblScaleUpFctr = Scale = MIN(GlblShadeInfo.ScrnXSize *
				  GlblShadeInfo.SubSamplePixel,
				  GlblShadeInfo.ScrnYSize *
				  GlblShadeInfo.SubSamplePixel) / 2.0;
    MatGenMatScale(Scale, -Scale, Scale, Mat);
    MatMultTwo4by4(GlblViewMat, GlblViewMat, Mat);
    MatGenMatTrans(GlblShadeInfo.ScrnXSize * GlblShadeInfo.SubSamplePixel / 2.0,
		   GlblShadeInfo.ScrnYSize * GlblShadeInfo.SubSamplePixel / 2.0,
		   0.0, Mat);
    MatMultTwo4by4(GlblViewMat, GlblViewMat, Mat);

    /* Prepare data structures of objects themselves: */
    PrepareViewData(PObjects);

    /* Into shadingInfo global structure: */
    PrepareColorTable(PObjects);

    EvalVrtxColors(PObjects);

#ifndef DEBUG_NO_GIF

    /* Open stdout for the GIF image file: */
    if ((GifFile = EGifOpenFileHandle(1)) == NULL ||
	EGifPutScreenDesc(GifFile,
		GlblShadeInfo.ScrnXSize, GlblShadeInfo.ScrnYSize,
		GlblShadeInfo.BitsPerPixel, 0,
		GlblShadeInfo.BitsPerPixel, GlblShadeInfo.PColorMap) ==
								GIF_ERROR ||
	EGifPutImageDesc(GifFile,
		0, 0, GlblShadeInfo.ScrnXSize, GlblShadeInfo.ScrnYSize, FALSE,
		GlblShadeInfo.BitsPerPixel, NULL) == GIF_ERROR)
	QuitGifError();
    /* Open special mask file if required: */
    if (GifMaskFlag &&
	((GifMask = EGifOpenFileName(GifMaskName, FALSE)) == NULL ||
	 EGifPutScreenDesc(GifMask,
			   GlblShadeInfo.ScrnXSize, GlblShadeInfo.ScrnYSize,
			   1, 0, 1, MaskColorMap) == GIF_ERROR ||
	 EGifPutImageDesc(GifMask, 0, 0,
			  GlblShadeInfo.ScrnXSize, GlblShadeInfo.ScrnYSize,
			  FALSE, 1, NULL) == GIF_ERROR))
	QuitGifError();

#endif /* DEBUG_NO_GIF */

    ScanConvertData(GifFile, GifMask);	 /* Do the real interesting stuff... */

#ifndef DEBUG_NO_GIF

    EGifCloseFile(GifFile);
    if (GifMask)
	EGifCloseFile(GifMask);

#endif /* DEBUG_NO_GIF */

    Poly3drExit(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 *Crvs;
    CagdSrfStruct *Srf, *Srfs;
    IPObjectStruct *PObj, *PObjNext;
    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) {
	/* Curves are not rendered at this time and they are ignored. */
	for (PObj = CrvObjs; PObj != NULL;) {
	    Crvs = PObj -> U.Crvs;
	    CagdCrvFreeList(Crvs);
	    PObjNext = PObj -> Pnext;
	    IPFreeObject(PObj);
	    PObj = PObjNext;
	}
	CrvObjs = NULL;
    }

    if (SrfObjs) {
	for (PObj = SrfObjs; PObj != NULL; PObj = PObj -> Pnext) {
	    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);
	}
    }

    return SrfObjs;
}

/*****************************************************************************
* Routine to convert a single surface into a polygons with GlblFineNess      *
* samples as the subdivision 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, TRUE, TRUE,
								       FALSE);

    for (CagdPolygon = CagdPolygonHead;
	 CagdPolygon != NULL;
	 CagdPolygon = CagdPolygon -> Pnext) {
	/* All polygons are triangles! */

	for (i = 0, VHead = NULL; 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;
	    }
	    IP_SET_NORMAL_VRTX(V);		       /* Vertex has normal. */

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

	}

	P = IPAllocPolygon(0, 0, VHead, PHead);
	PHead = P;
    }

    CagdPolygonFreeList(CagdPolygonHead);

    return PHead;
}

/*****************************************************************************
* Poly3d-r 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 Poly3drExit(int ExitCode)
{
    fprintf(stderr,
	"\nPoly3D-R: Total RealTime %ld seconds.\n",
	    time(NULL) - SaveTotalTime);

    exit(ExitCode);
}

/******************************************************************************
* Close output file (if open), and exit.				      *
******************************************************************************/
void QuitGifError(void)
{
    PrintGifError();
    Poly3drExit('G');
}
