/*****************************************************************************
* Module to handle PostScript output.					     *
*									     *
* Written by:  Gershon Elber				Ver 1.0, Apr. 1990   *
*****************************************************************************/

#include <stdio.h>
#include <string.h>
#include "program.h"
#include "genmat.h"
#include "postscrp.h"

static FILE *PSFile = NULL;	       /* Used to save PostScript text file. */

static void ViewGeomObjectListPS(FileDescription **FD, int NumOfObjects,
								char **Objects);
static ObjectStruct *SearchObjectPS(FileDescription **FD, char *Object);
static void DrawAllObjectsPS(FileDescription **FD);
static void VisitObjectTreePS(BinTree *PBinTree);
static void ViewOneObjectPS(ObjectStruct *PObject);
static void ViewOnePolygonPS(PolygonStruct *PPolygon);
static void MyMoveToPS(float Coord[3]);
static void MyDrawToPS(float Coord[3]);

/*****************************************************************************
* Routine to save the current view as PostScript text file.		     *
*****************************************************************************/
void SavePostScript(FileDescription **FD, int NumOfObjects, char **Objects)
{
    int	i, j;
    char *p, FileName[FILE_NAME_LEN];
    static char FileCount = '0';
    static char *PSHeader[] = {
	"%!",
	"%",
	"% Creator: Poly3d",
	"%",
	"",
	"gsave",
	"",
	"72 72 scale",	/* First scale to inches, se we can speak in inches. */
	"4.0 5.0 translate",			 /* To Center of image area. */
	"3.5 3.5 scale",	 /* Make the universe -1..1 on both X and Y. */
	"",
	"/line {",
	"    newpath",
	"    moveto",
	"    lineto",
	"    stroke",
	"} def",
	"",
	"/setdashline {",
	"    [0.012 0.024] 0 setdash",
	"} def",
	"",
	"/setfullline {",
	"    [] 0 setdash",
	"} def",
	"",
	"0.006 setlinewidth",		/* Set some default line attributes. */
	"1 setlinecap",
	"1 setlinejoin",
	"setfullline",
	"",
	NULL
    };
    static char *PSTrailer[] = {
	"",
	"showpage",
	"grestore",
	NULL
    };

    strcpy(FileName, GENERIC_PS_FILE);
    if ((p = strchr(FileName, '#')) != NULL) {
	*p = FileCount;
	if (FileCount++ == '9') FileCount = '0';
    }
    if ((PSFile = fopen(FileName, "wt")) == NULL) {
	GGTone(700, 200);
	return;
    }

    for (i=0; PSHeader[i] != NULL; i++)
	fprintf(PSFile, "%s\n", PSHeader[i]);

    ViewGeomObjectListPS(FD, NumOfObjects, Objects);

    for (i=0; PSTrailer[i] != NULL; i++)
	fprintf(PSFile, "%s\n", PSTrailer[i]);

    fclose(PSFile);
}

/*****************************************************************************
* PS Routine to draw NumOfObjects given in Objects from FileDescription FD   *
* according to view matrix Mat.	If NumOfObjects	== 0 then all the objects    *
* are drawn. If NumEdges != 0 only the first NumEdges edges in each polygon  *
* are drawn. If InterFlag then INTERNAL edges (created by IRIT solid	     *
* modeller) are also drawn.						     *
*****************************************************************************/
static void ViewGeomObjectListPS(FileDescription **FD, int NumOfObjects,
								char **Objects)
{
    int	i;
    ObjectStruct *PObject;

    if (NumOfObjects > 0)	   /* There was something on command line... */
	for (i=0; i<NumOfObjects; i++) {
	    if ((PObject = SearchObjectPS(FD, *Objects)) == NULL)
		printf("Given Object %s not found in data files\n", *Objects);
	    else ViewOneObjectPS(PObject);
	    Objects++;
	}
    else {
	/* Draw all objects not in other object by scanning object trees.    */
	DrawAllObjectsPS(FD);	    /* and drawing ones with Reference == 0. */
    }
}

/*****************************************************************************
* Routine to search for	an object in the File descriptions FD. Note that if  *
* an object exists more	than once only the first will be returned. If none   *
* is found then	NULL is	returned, else a pointer to that object	struct.	     *
*****************************************************************************/
static ObjectStruct *SearchObjectPS(FileDescription **FD, char *Object)
{
     BinTree *PBinTree;

     while (*FD) {
	  if ((PBinTree	= GetBinTree(Object, (*FD++) -> ObjectPointer)) !=
						 (BinTree *) NULL)
	  return PBinTree -> Data.PObject;
     }
     return (ObjectStruct *) NULL;
}

/*****************************************************************************
* Routine to draw all the objects not in other objects...		     *
* by scanning all the object trees and drawing the objects with	Reference==0 *
* meaning nobody referred to them yet...				     *
*****************************************************************************/
static void DrawAllObjectsPS(FileDescription **FD)
{
    while (*FD)	VisitObjectTreePS((*FD++) -> ObjectPointer);
}

/*****************************************************************************
* Routine to draw all the objects not in other objects...		     *
* by scanning all the object in	tree PBinTree and drawing the objects with   *
* Reference==0,	meaning	nobody referred	to them	yet...			     *
*****************************************************************************/
static void VisitObjectTreePS(BinTree *PBinTree)
{
    if (PBinTree == (BinTree *)	NULL) return;

    VisitObjectTreePS(PBinTree -> right);

    ViewOneObjectPS(PBinTree -> Data.PObject);

    VisitObjectTreePS(PBinTree -> left);
}

/*****************************************************************************
* Routine to draw one object Object, using the Matrix transform	Mat.	     *
*****************************************************************************/
static void ViewOneObjectPS(ObjectStruct *PObject)
{
    PolygonStruct *PList = PObject -> PPolygon;

    while (PList) {
	ViewOnePolygonPS(PList);
	PList =	PList -> Pnext;
    }
}

/*****************************************************************************
* Routine to draw one polygon, using the Matrix	transform Mat.		     *
* Note this is the routine that	makes the real drawing...		     *
*****************************************************************************/
static void ViewOnePolygonPS(PolygonStruct *PPolygon)
{
    int	i, Count, DrawNextEdge, NumOfVertices;
    float MappedNormal[3], PolyNormal[3];
    VertexStruct *PList = PPolygon -> PVertex;

    Count = NumEdges;

    if (PList == NULL) return;

    switch (PPolygon -> PolyType) {
	case POINTLIST:
	    while (PList) {
		MyMoveToPS(PList -> Coord);
		MyDrawToPS(PList -> Coord);
		PList = PList -> Pnext;
	    }
	    break;
	case POLYLINE:
	    MyMoveToPS(PList -> Coord);
	    DrawNextEdge = !PList -> Internal;
	    PList = PList -> Pnext;

	    while (PList) {
		MyDrawToPS(PList -> Coord);

		PList = PList -> Pnext;
	    }
	    break;
	case POLYGON:
	    if (DrawPNormalsFlag && PPolygon->HasPlane)
	    {
		for (i=0; i<3; i++) PolyNormal[i] = PList -> Coord[i];
		NumOfVertices = 1;
	    }

	    MyMoveToPS(PList -> Coord);
	    DrawNextEdge = !PList -> Internal;
	    PList = PList -> Pnext;

	    while (PList) {
		if (DrawNextEdge || InterFlag)
		    MyDrawToPS(PList -> Coord);
		else MyMoveToPS(PList -> Coord);

		if (DrawVNormalsFlag && PList -> HasNormal) {
		    for (i=0; i<3; i++) MappedNormal[i] =
					PList -> Coord[i] + PList -> Normal[i];
		    i = ClosedObject;
		    ClosedObject = FALSE;
		    MyDrawToPS(MappedNormal);
		    MyMoveToPS(PList -> Coord);
		    ClosedObject = i;
		}

		if (DrawPNormalsFlag && PPolygon->HasPlane)
		{
		    for (i=0; i<3; i++) PolyNormal[i] += PList -> Coord[i];
		    NumOfVertices++;
		}
		/* If -e option specified #Edges to draw. */
		if (!(--Count)) return;
		DrawNextEdge = !PList -> Internal;
		PList = PList -> Pnext;
	    }

	    /* Close polygon by drawing a line to first vertex. */
	    if (DrawNextEdge || InterFlag)
		MyDrawToPS(PPolygon -> PVertex -> Coord);

	    if (DrawPNormalsFlag && PPolygon->HasPlane)
	    {
		for (i=0; i<3; i++) PolyNormal[i] /= NumOfVertices;
		MyMoveToPS(PolyNormal);
		for (i=0; i<3; i++) PolyNormal[i] += PPolygon->Plane[i];
		i = ClosedObject;
		ClosedObject = FALSE;
		MyDrawToPS(PolyNormal);
		ClosedObject = i;
	    }
	    break;
    }
}

static double LastCoord[3];    /* Used to store last point we moved/draw to. */

/*****************************************************************************
* Routine to mave to 3D	point given as Coord[3], using Mat transform.	     *
*****************************************************************************/
static void MyMoveToPS(float Coord[3])
{
    MultVecby4by4(LastCoord, Coord, CrntViewMat);   /* Set last point coord. */
}

/*****************************************************************************
* Routine to draw to 3D	point given as Coord[3], using Mat transform, from   *
* the last point we moved to.						     *
*****************************************************************************/
static void MyDrawToPS(float Coord[3])
{
    static DashState = FALSE;
    double NewCoord[3], MiddleCoord[3], t;

    MultVecby4by4(NewCoord, Coord, CrntViewMat);    /* Set last point coord. */
    if (ClosedObject && NewCoord[2] < LastCoord[2]) {
	GEN_COPY(LastCoord, NewCoord, 3 * sizeof(double));
	return;
    }

    /* Implementation of simple depth cue - if line is >Z or <Z ... */
    if (LastCoord[2] <= 0.0 && NewCoord[2] <= 0.0) {
	if (GlblDepthCue && !DashState) {
	    fprintf(PSFile, "setdashline\n");
	    DashState = TRUE;
	}
	fprintf(PSFile, "%10.7lf %10.7lf  ", LastCoord[0], LastCoord[1]);
	fprintf(PSFile, "%10.7lf %10.7lf line\n", NewCoord[0], NewCoord[1]);
    }
    else
    if (LastCoord[2] >= 0.0 && NewCoord[2] >= 0.0 ||
	ABS(LastCoord[2] - NewCoord[2]) < EPSILON) {
	if (GlblDepthCue && DashState) {
	    fprintf(PSFile, "setfullline\n");
	    DashState = FALSE;
	}
	fprintf(PSFile, "%10.7lf %10.7lf  ", LastCoord[0], LastCoord[1]);
	fprintf(PSFile, "%10.7lf %10.7lf line\n", NewCoord[0], NewCoord[1]);
    }
    else {				      /* Line intersect Z = 0 plane. */
	t = LastCoord[2] / (LastCoord[2] - NewCoord[2]);
	MiddleCoord[0] = LastCoord[0] * (1.0 - t) + NewCoord[0] * t;
	MiddleCoord[1] = LastCoord[1] * (1.0 - t) + NewCoord[1] * t;

	if (GlblDepthCue && DashState) {
	    fprintf(PSFile, "setfullline\n");
	    DashState = FALSE;
	}
	if (LastCoord[2] > 0.0) {
	    fprintf(PSFile, "%10.7lf %10.7lf  ", LastCoord[0], LastCoord[1]);
	    fprintf(PSFile, "%10.7lf %10.7lf line\n", MiddleCoord[0], MiddleCoord[1]);
	}
	else {
	    fprintf(PSFile, "%10.7lf %10.7lf  ", MiddleCoord[0], MiddleCoord[1]);
	    fprintf(PSFile, "%10.7lf %10.7lf line\n", NewCoord[0], NewCoord[1]);
	}

	if (GlblDepthCue && !DashState) {
	    fprintf(PSFile, "setdashline\n");
	    DashState = TRUE;
	}
	if (LastCoord[2] < 0.0) {
	    fprintf(PSFile, "%10.7lf %10.7lf  ", LastCoord[0], LastCoord[1]);
	    fprintf(PSFile, "%10.7lf %10.7lf line\n", MiddleCoord[0], MiddleCoord[1]);
	}
	else {
	    fprintf(PSFile, "%10.7lf %10.7lf  ", MiddleCoord[0], MiddleCoord[1]);
	    fprintf(PSFile, "%10.7lf %10.7lf line\n", NewCoord[0], NewCoord[1]);
	}
    }

    GEN_COPY(LastCoord, NewCoord, 3 * sizeof(double)); /* Set current point. */
}
