/*****************************************************************************
*   Program to draw 3D object as solid objects in GIF image file format.     *
*									     *
* Options:								     *
* 1. -m : More flag, to print more imformation on input/errors.		     *
* 2. -a ambient : Ratio of the ambient color.				     *
* 3. -c n : Number of bit ber pixel (2^n is number of colors possible).	     *
* 4. -l x y z : Light source direction vector.				     *
* 5. -s x y : Specify new dimensions to generate the resulting image.	     *
* 6. -g : Use gouraud shading.						     *
* 7. -b : Delete Back facing polygons.					     *
* 8. -M Mask : Create boolean Mask (coverage) image as well.		     *
* 9. -o Objects : List of objects to be displayed from the input data files. *
*	  This option should not be last and Object list terminates with     *
*	  next option (- as first char). ALL objects are displayed otherwise.*
* 10. -v ViewFile : Optional view to start with.			     *
* 11. -z : Print current version, and some helpfull data.		     *
* 12. -2 : Force 2 light sources at opposite direction of light vectors.     *
*									     *
* 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] [-o Objects...] [-v ViewFile] [-z]  *
*								     DFiles  *
*									     *
* Written by:  Gershon Elber				Ver 2.0, Mar 1990    *
*****************************************************************************/

#ifdef __MSDOS__
#include <stdlib.h>
#include <conio.h>
#include <io.h>
#include <dos.h>
#include <alloc.h>
#endif /* __MSDOS__ */

#include <stdio.h>
#include <math.h>
#include <time.h>
#include "program.h"
#include "getarg.h"
#include "genmat.h"
#include "parser.h"
#include "config.h"
#include "ctrl-brk.h"

#ifdef __MSDOS__
/* This huge stack is mainly from second phase - the segment intersections   */
/* which may cause recursive calls - a lot...				     */
extern unsigned int _stklen = 32766;
#endif /* __MSDOS__ */

#ifdef SYSV
static char *VersionStr =
	"Poly3D-R	version 2.0,	Gershon Elber,\n\
	(C) Copyright 1989 Gershon Elber, Non commercial use only.";
#else
static char *VersionStr = "Poly3D-R	version 2.0,	Gershon Elber,	"
		__DATE__ ",  " __TIME__ "\n"
		"(C) Copyright 1989 Gershon Elber, Non commercial use only.";
#endif /* SYSV */

static char *CtrlStr =
	"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 o%-Objects!*s v%-ViewFile!s z%- DFiles!*s";


static long SaveTotalTime;
static int ViewFlag = FALSE;
static GifColorType MaskColorMap[2] = {	 /* Boolean mask GIF file color map. */
    {   0,   0,   0 },
    { 255, 255, 255 }
};

int NumOfPolygons = 0;		 /* Total number of polygons scan converted. */
int NumOfVertices = 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: */
float ScaleUpFactor = 0.0;

/* The following are setable variables (via configuration file poly3d-h.cfg).*/
int MoreFlag = FALSE, GouraudFlag = FALSE, BackFacingFlag = FALSE,
    ScreenXSize = DEFAULT_SCREEN_XSIZE, ScreenYSize = DEFAULT_SCREEN_YSIZE;
double NormalAvgDegree = DEFAULT_NORMAL_AVG_DEGREE;
struct ShadingInfoStruct ShadingInfo = {
    1,
    DEFAULT_BITS_PER_PIXEL,
    0,
    DEFAULT_COLOR,
    DEFAULT_BACK_GROUND_COLOR,
    FALSE,
    NULL,						 /* No color map yet */
    NULL,
    DEFAULT_LIGHT_SOURCE,
    DEFAULT_AMBIENT,
};

static ConfigStruct SetUp[] =
{ { "AvgDegree",	(VoidPtr) &NormalAvgDegree,		SU_REAL_TYPE },
  { "Ambient",		(VoidPtr) &ShadingInfo.Ambient,		SU_REAL_TYPE },
  { "SubSample",	(VoidPtr) &ShadingInfo.SubSamplePixel,	SU_INTEGER_TYPE },
  { "BitsPerPixel",	(VoidPtr) &ShadingInfo.BitsPerPixel,	SU_INTEGER_TYPE },
  { "LightSrcX",	(VoidPtr) &ShadingInfo.LightSource[0],	SU_REAL_TYPE },
  { "LightSrcY",	(VoidPtr) &ShadingInfo.LightSource[1],	SU_REAL_TYPE },
  { "LightSrcZ",	(VoidPtr) &ShadingInfo.LightSource[2],	SU_REAL_TYPE },
  { "TwoSources",	(VoidPtr) &ShadingInfo.TwoSources,	SU_BOOLEAN_TYPE },
  { "Xsize",		(VoidPtr) &ScreenXSize,			SU_INTEGER_TYPE },
  { "Ysize",		(VoidPtr) &ScreenYSize,			SU_INTEGER_TYPE },
  { "Color",		(VoidPtr) &ShadingInfo.DefaultColor,	SU_INTEGER_TYPE },
  { "BackGroundColor",	(VoidPtr) &ShadingInfo.BackGroundColor,	SU_INTEGER_TYPE },
  { "More",		(VoidPtr) &MoreFlag,			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).			     */
struct PolygonStruct **PolyHashTable;


static void MainGetViewFile(char *ViewFileName);
static FileDescription **MainGetDataFiles(char **DataFileNames,
							int NumOfDataFiles);
static void FreeBinTree(struct BinTree *Pbin);

/*****************************************************************************
* Main routine - Read Parameter	line and do what you need...		     *
*****************************************************************************/
void main(int argc, char **argv)
{
    int AmbientFlag = FALSE, ColorFlag = FALSE, LightSrcFlag = FALSE,
	TwoSourcesFlag = FALSE, GifMaskFlag = FALSE, ObjFlag = FALSE,
	NumObjs = 0, RealNumObjs, VerFlag = FALSE, NumFiles = 0,
	ImageSizeFlag = FALSE, SubSampleFlag = FALSE, Error;
    char **Objects = NULL, *ViewName = NULL, **FileNames = NULL, *GifMaskName;
    double Size;
    FileDescription **FD;
    GifFileType *GifFile, *GifMask = NULL;

    SaveTotalTime = time(NULL);			      /* Save starting time. */
#ifdef __MSDOS__
    ctrlbrk((int (*)()) MyExit);	       /* Kill this program if ^C... */
#endif /* __MSDOS__ */
    SetUpHardErr();	    /* Set up hardware error trap routine (int 24h). */

    Config("poly3d-r", SetUp, NUM_SET_UP);   /* Read config. file if exists. */

    if ((Error = GAGetArgs (argc, argv, CtrlStr,
		&AmbientFlag, &ShadingInfo.Ambient,
		&ColorFlag, &ShadingInfo.BitsPerPixel,
		&LightSrcFlag, &ShadingInfo.LightSource[0],
			       &ShadingInfo.LightSource[1],
			       &ShadingInfo.LightSource[2],
		&TwoSourcesFlag,
		&MoreFlag, &ImageSizeFlag, &ScreenXSize, &ScreenYSize,
		&SubSampleFlag, &ShadingInfo.SubSamplePixel,
		&GouraudFlag, &BackFacingFlag, &GifMaskFlag, &GifMaskName,
		&ObjFlag, &NumObjs, &Objects,
		&ViewFlag, &ViewName, &VerFlag, &NumFiles, &FileNames)) != 0) {
	GAPrintErrMsg(Error);
	GAPrintHowTo(CtrlStr);
	MyExit(1);
    }

    if (TwoSourcesFlag) ShadingInfo.TwoSources = TRUE;

    if (ShadingInfo.Ambient < 0.0 || ShadingInfo.Ambient > 1.0) {
	fprintf(stderr, "Ambient light specified not in [0.0..1.0] range, %lf selected instead\n",
		DEFAULT_AMBIENT);
	ShadingInfo.Ambient = DEFAULT_AMBIENT;
    }
    if (ShadingInfo.BitsPerPixel < 1 || ShadingInfo.BitsPerPixel > 8) {
	fprintf(stderr, "PitsPerPixel not in [1..8] range, %d selected instead\n",
		DEFAULT_BITS_PER_PIXEL);
	ShadingInfo.BitsPerPixel = DEFAULT_BITS_PER_PIXEL;
    }

    Size = sqrt(SQR(ShadingInfo.LightSource[0]) +
		SQR(ShadingInfo.LightSource[1]) +
		SQR(ShadingInfo.LightSource[2]));
    if (ABS(Size) < EPSILON) {
	fprintf(stderr, "Light source vector is zero, Z axis selected instead\n");
	ShadingInfo.LightSource[0] =
	ShadingInfo.LightSource[1] = 0.0;
	ShadingInfo.LightSource[2] = 1.0;
    }
    else {
	ShadingInfo.LightSource[0] /= Size;
	ShadingInfo.LightSource[1] /= Size;
	ShadingInfo.LightSource[2] /= Size;
    }

    if (VerFlag) {
	fprintf(stderr, "\n%s\n\n", VersionStr);
	GAPrintHowTo(CtrlStr);
	ConfigPrint(SetUp, NUM_SET_UP);
	MyExit(0);
    }

    if (!NumFiles) {
	fprintf(stderr, "No data file names were given, exit\n");
	GAPrintHowTo(CtrlStr);
	MyExit(1);
    }

    if (SubSampleFlag) {
	if (ShadingInfo.SubSamplePixel < 1 || ShadingInfo.SubSamplePixel > 4) {
	    fprintf(stderr, "Sub sampling can be 1 to 4 only (1x1 to 4x4)\n");
	    GAPrintHowTo(CtrlStr);
	    MyExit(1);
	}
    }

    MainGetViewFile(ViewName);

    /* Get the data files: */
    FD = MainGetDataFiles(FileNames, NumFiles);

    /* Prepare data structures of objects themselves: */
    RealNumObjs = NumObjs;
    PrepareViewData(FD, &RealNumObjs, Objects);
    if (NumObjs != 0) RealNumObjs = NumObjs;

    /* Into shadingInfo global structure: */
    PrepareColorTable(FD, NumObjs, RealNumObjs, Objects);

    EvalVrtxColors(FD, NumObjs, Objects);

#ifndef DEBUG_NO_GIF

    /* Open stdout for the GIF image file: */
    if ((GifFile = EGifOpenFileHandle(1)) == NULL ||
	EGifPutScreenDesc(GifFile,
		ScreenXSize, ScreenYSize, ShadingInfo.BitsPerPixel, 0,
		ShadingInfo.BitsPerPixel, ShadingInfo.PColorMap) == ERROR ||
	EGifPutImageDesc(GifFile,
		0, 0, ScreenXSize, ScreenYSize, FALSE,
		ShadingInfo.BitsPerPixel, NULL) == ERROR)
	QuitGifError();
    /* Open special mask file if required: */
    if (GifMaskFlag &&
	((GifMask = EGifOpenFileName(GifMaskName, FALSE)) == NULL ||
	 EGifPutScreenDesc(GifMask,
		ScreenXSize, ScreenYSize, 1, 0, 1, MaskColorMap) == ERROR ||
	 EGifPutImageDesc(GifMask,
		0, 0, ScreenXSize, ScreenYSize, FALSE, 1, NULL) == 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

    MyExit(0);
}

/*****************************************************************************
* Main routine to read the view	description file, into GlblViewMat:	     *
*****************************************************************************/
static void MainGetViewFile(char *ViewFileName)
{
    double Scale;
    MatrixType Mat;
    FILE *f;

    if (ViewFlag && (f = fopen(ViewFileName, "rt")) == NULL) {
	fprintf(stderr, "Can't open view file %s\n", ViewFileName);
	MyExit(1);
    }

    /* Get view data if ViewFlag, otherwise set default view (isometric): */
    GetViewFile(f, ViewFlag);

    /* 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.							     */
    ScaleUpFactor = Scale = MIN(ScreenXSize * ShadingInfo.SubSamplePixel,
				ScreenYSize * ShadingInfo.SubSamplePixel) / 2.0;
    GenMatScale(Scale, -Scale, Scale, Mat);
    MultTwo4by4(GlblViewMat, GlblViewMat, Mat);
    GenMatTrans(ScreenXSize * ShadingInfo.SubSamplePixel / 2.0,
		ScreenYSize * ShadingInfo.SubSamplePixel / 2.0, 0.0, Mat);
    MultTwo4by4(GlblViewMat, GlblViewMat, Mat);

    if (ViewFlag) fclose(f);				  /* Close the file. */
}

/*****************************************************************************
* Main routine to read the data	description files:			     *
* Returns pointer to pointers on FileDescription structures (one per file).  *
*****************************************************************************/
static FileDescription **MainGetDataFiles(char **DataFileNames,
							int NumOfDataFiles)
{
    int	i;
    long SaveTime = time(NULL);
    FILE *f;
    FileDescription **FDbase, **FDinc;  /* Used to save memory pointers loc. */

    NumOfPolygons = 0;
    fprintf(stderr, "\nPass 1, Polys =      ");

    FDbase = FDinc = (FileDescription **)    /* Allocate the pointers block. */
	 MyMalloc((unsigned) sizeof(FileDescription *) * (NumOfDataFiles + 1));

    for	(i=0; i<NumOfDataFiles;	i++) {
	if (MoreFlag) {
	    fprintf(stderr, "\rReading data file %s", *DataFileNames);
	    fprintf(stderr, "\nPass 1, Polys =      ");
	}
	f = fopen(*DataFileNames, "rt");		   /* Open the file. */
	if (!f)
	{
	    fprintf(stderr, "Can't open data file %s\n", *DataFileNames);
	    MyExit(1);
	}

	*FDinc = GetDataFile(f);		       /* Get the data file. */
	FreeBinTree((*FDinc) ->	VertexPointer); /* We dont need these trees. */
	FreeBinTree((*FDinc) ->	PolygonPointer);  /* objects are all loaded. */
	(*FDinc) -> VertexPointer = (*FDinc) ->	PolygonPointer = NULL;
	fprintf(stderr, " \b");		  /* Erase the FreeBinTree marker... */
	*FDinc++;

	fclose(f);					  /* Close the file. */

	DataFileNames++;			  /* Skip to next file name. */
    }

    *FDinc = NULL;

    fprintf(stderr, ",  %ld seconds.", time(NULL) - SaveTime);

    return FDbase;
}

/*****************************************************************************
* My Routine to	allocate dynamic memory. All program requests must call	     *
* this routine (no direct call to malloc). Dies if no memory.		     *
*****************************************************************************/
char *MyMalloc(unsigned size)
{
    char *p;

    if ((p = malloc(size)) != NULL) return p;

    fprintf(stderr, "\nNot enough memory, exit\n");
    MyExit(2);

    return NULL;				    /* Makes warning silent. */
}

/*****************************************************************************
* Routine to free a binary tree, which is not needed any more:		     *
*****************************************************************************/
static void FreeBinTree(BinTree *Pbin)
{
    static Flip = FALSE;

    if (Flip) {
	Flip = FALSE;
	fprintf(stderr, "m\b");
    }
    else {
	Flip = TRUE;
	fprintf(stderr, "M\b");
    }

    if (Pbin) {
	FreeBinTree(Pbin -> right);
	FreeBinTree(Pbin -> left);
	free((char *) Pbin);
    }
}

/*****************************************************************************
* MyExit 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 MyExit(int ExitCode)
{
#ifdef __MSDOS__
    fprintf(stderr,
	"\nPoly3D-R: Total RealTime %ld seconds, Core left %ldk.\n",
	    time(NULL) - SaveTotalTime, coreleft() / 1024);
#else
    fprintf(stderr,
	"\nPoly3D-R: Total RealTime %ld seconds.\n",
	    time(NULL) - SaveTotalTime);
#endif /* __MSDOS__ */

    exit(ExitCode);
}

/******************************************************************************
* Close output file (if open), and exit.				      *
******************************************************************************/
void QuitGifError(void)
{
    PrintGifError();
    MyExit('G');
}
