/*****************************************************************************
* Filter to convert IRIT data files to a PostScript file.		     *
*									     *
* Written by:  Gershon Elber				Ver 1.0, Apr 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 "geomat3d.h"
#include "getarg.h"
#include "bbox.h"
#include "genmat.h"
#include "ffcnvrt.h"
#include "ip_cnvrt.h"

#define DEFAULT_LINE_WIDTH		0.004
#define DEFAULT_SIZE			7.0
#define DEFAULT_DEPTH_CUE_CRV_SIZE	1.0
#define MAX_NUM_OF_PTS			100
#define MIN_PS_LINE_WIDTH		0.002

#define WIDEN_END_START			1
#define WIDEN_END_END			2
#define WIDEN_LN_SUBDIV_FACTOR		0.1

#define DIST_PT_PT_2D(Pt1, Pt2) sqrt(SQR(Pt1[0] - Pt2[0]) + \
				     SQR(Pt1[1] - Pt2[1]))

typedef enum {
    DRAW_POINT_CROSS,
    DRAW_POINT_FULL_CIRC,
    DRAW_POINT_HOLLOW_CIRC
} DrawPointType;

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

static char *CtrlStr =
#ifdef DOUBLE
	"irit2ps s%-Size!F I%-#UIso[:#VIso]!s S%-#SampPerCrv!d M%- P%- W%-LineWidth!F w%-WidenLen|WidenWidth!F!F c%- C%- T%- i%- o%-OutName!s d%-[Zmin|Zmax]%F%F D%-[Zmin|Zmax]%F%F p%-PtType|PtSize!s!F u%- z%- DFiles!*s";
#else
	"irit2ps s%-Size!f I%-#UIso[:#VIso]!s S%-#SampPerCrv!d M%- P%- W%-LineWidth!f w%-WidenLen|WidenWidth!f!f c%- C%- T%- i%- o%-OutName!s d%-[Zmin|Zmax]%f%f D%-[Zmin|Zmax]%f%f p%-PtType|PtSize!s!f u%- z%- DFiles!*s";
#endif /* DOUBLE */

static char
    *GlblPSTitle = NULL,
    *GlblStrNumOfIsolines = NULL,
    *OutFileName = "irit2ps.ps";

static DrawPointType
    GlblDrawPoint = DRAW_POINT_FULL_CIRC;

static int
    GlblTalkative = FALSE,
    GlblColorPS = FALSE,
    GlblCubicCurves = TRUE,
    GlblPointIsCircle = TRUE,
    GlblShowInternal = FALSE,
    GlblWidenPolyEnds = FALSE,
    GlblDrawSurfaceMesh = FALSE,
    GlblDrawSurface = TRUE,
    GlblDepthCue = FALSE,
    GlblDepthCueGray = FALSE,
    GlblNumOfIsolines[2] = { IG_DEFAULT_NUM_OF_ISOLINES,
			     IG_DEFAULT_NUM_OF_ISOLINES },
    GlblSamplesPerCurve = IG_DEFAULT_SAMPLES_PER_CURVE;

static RealType
    GlblLineWidth = DEFAULT_LINE_WIDTH,
    GlblDepthCueZ[2] = {INFINITY, -INFINITY},
    GlblPointScale = 0.01,
    GlblWidenEndLength = 0.1,
    GlblWidenEndWidthScale = 2.0,
    GlblPrintSize = DEFAULT_SIZE;

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 DumpDataForPostScript(char *FileName, IPObjectStruct *PObjects);
static void DumpAttrState(FILE *f, RealType Width, RealType Fill,
			  RealType *RGBColor, char *Dash);
static void DumpOneObject(FILE *f, IPObjectStruct *PObject);
static void DumpOnePoly(FILE *f, IPPolygonStruct *PPolygon, int IsPolygon,
			int Fill, RealType LineWidth, RealType *RGBColor);
static RealType PolylineLength2D(IPVertexStruct *V);
static RealType EdgeLength2D(IPVertexStruct *V);
static void SubdivAndWidenEdge(IPVertexStruct *V, RealType Scale1,
	       RealType Scale2, RealType LineWidth, RealType EdgePortion);
static void DumpOneCubicCurve(FILE *f, CagdCrvStruct *Crv,
			     int Fill, RealType LineWidth, RealType *RGBColor);
static void DumpOnePoint(FILE *f, RealType *Pt, int IsVector,
			 RealType *RGBColor, RealType Fill);
static void Irit2PsExit(int ExitCode);

/*****************************************************************************
* Main routine - Read Parameter	line and do what you need...		     *
*****************************************************************************/
void main(int argc, char **argv)
{
    int Error,
	PrintSizeFlag = FALSE,
	NumOfIsolinesFlag = FALSE,
	SamplesPerCurveFlag = FALSE,
        LineWidthFlag = FALSE,
	VerFlag = FALSE,
	PointFlag = FALSE,
	ForceUnitMat = FALSE,
	OutFileFlag = FALSE,
	NumFiles = 0;
    char *DrawPoint,
	**FileNames = NULL;
    IPObjectStruct *PObjects;

    if ((Error = GAGetArgs(argc, argv, CtrlStr,
			   &PrintSizeFlag, &GlblPrintSize,
			   &NumOfIsolinesFlag, &GlblStrNumOfIsolines,
			   &SamplesPerCurveFlag, &GlblSamplesPerCurve,
			   &GlblDrawSurfaceMesh, &GlblDrawSurface,
			   &LineWidthFlag, &GlblLineWidth,
			   &GlblWidenPolyEnds, &GlblWidenEndLength,
			   &GlblWidenEndWidthScale, &GlblColorPS,
			   &GlblCubicCurves, &GlblTalkative,
			   &GlblShowInternal, &OutFileFlag, &OutFileName,
			   &GlblDepthCue,
			   &GlblDepthCueZ[0], &GlblDepthCueZ[1],
			   &GlblDepthCueGray,
			   &GlblDepthCueZ[0], &GlblDepthCueZ[1],
			   &PointFlag, &DrawPoint, &GlblPointScale,
			   &ForceUnitMat,
			   &VerFlag, &NumFiles, &FileNames)) != 0) {
	GAPrintErrMsg(Error);
	GAPrintHowTo(CtrlStr);
	Irit2PsExit(1);
    }

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

    if (!NumFiles) {
	fprintf(stderr, "No data file names were given, exit.\n");
	GAPrintHowTo(CtrlStr);
	Irit2PsExit(1);
    }
    GlblPSTitle = FileNames[0];

    if (GlblDepthCueGray) {
	if (GlblDepthCue) {
	    fprintf(stderr, "Only one of '-d' or '-D' at once please, exit.\n");
	    GAPrintHowTo(CtrlStr);
	    Irit2PsExit(1);
	}
	GlblDepthCue = TRUE;
    }
    if (GlblWidenPolyEnds && !GlblDepthCue) {
	fprintf(stderr, "If '-w' is set, either '-d' or -'D' must be set as well.\n");
	Irit2PsExit(1);
    }

    if (PointFlag) {
	switch (DrawPoint[0]) {
	    case 'h':
	    case 'H':
	        GlblDrawPoint = DRAW_POINT_HOLLOW_CIRC;
		break;
	    case 'f':
	    case 'F':
	        GlblDrawPoint = DRAW_POINT_FULL_CIRC;
		break;
	    case 'c':
	    case 'C':
	        GlblDrawPoint = DRAW_POINT_CROSS;
		break;
	    default:
		fprintf(stderr, "Point type drawing supported: H)ollowed circle, F)ull circle, C)ross.\n");
		GAPrintHowTo(CtrlStr);
		Irit2PsExit(1);
	}
    }

    if (GlblPointScale < 0.0) {
	GlblPointIsCircle = FALSE;
	GlblPointScale = -GlblPointScale;
    }

    if (NumOfIsolinesFlag && GlblStrNumOfIsolines != NULL) {
	if (sscanf(GlblStrNumOfIsolines, "%d:%d",
		   &GlblNumOfIsolines[0], &GlblNumOfIsolines[1]) != 2) {
	    if (sscanf(GlblStrNumOfIsolines, "%d",
		       &GlblNumOfIsolines[1]) != 1) {
		fprintf(stderr,
			"Number(s) of isolines (-I) cannot be parsed.\n");
		GAPrintHowTo(CtrlStr);
		Irit2PsExit(1);
	    }
	    else {
		GlblNumOfIsolines[1] = GlblNumOfIsolines[0];
	    }
	}
    }

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

    if (!ForceUnitMat) {
	IPObjectStruct
	    *PTmpObjs = PObjects;

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

	PObjects = GMTransformObjectList(PTmpObjs, CrntViewMat);
	IPFreeObjectList(PTmpObjs);
    }

    if (GlblDepthCue) {
	if (GlblDepthCueZ[0] == INFINITY ||
	    GlblDepthCueZ[1] == -INFINITY ||
	    APX_EQ(GlblDepthCueZ[0], GlblDepthCueZ[1])) {
	    BBBboxStruct
		*Bbox = BBComputeBboxObjectList(PObjects);

	    GlblDepthCueZ[0] = Bbox -> Min[2];
	    GlblDepthCueZ[1] = Bbox -> Max[2];
	}
	fprintf(stderr, "Z Depth Cue Bounding Box is [%lf...%lf]\n",
		GlblDepthCueZ[0], GlblDepthCueZ[1]);
    }

    DumpDataForPostScript(OutFileFlag ? OutFileName : NULL, PObjects);

    Irit2PsExit(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 polylines are saved in the appropriate        *
* surface/curve slots.							     *
*****************************************************************************/
IPObjectStruct *IritPrsrProcessFreeForm(IPObjectStruct *CrvObjs,
					IPObjectStruct *SrfObjs)
{
    CagdCrvStruct *Crv, *Crvs;
    CagdSrfStruct *Srf, *Srfs;
    IPObjectStruct *PObj;
    IPPolygonStruct *PPolygon, *PPolygonTemp;

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

    if (GlblCubicCurves) {
	CagdCrvStruct *CubicBzrCrvs;

	/* Convert curves and surfaces into cubic polynomials (bezier). */
	if (CrvObjs) {
	    for (PObj = CrvObjs; PObj != NULL; PObj = PObj -> Pnext) {
		if (GlblTalkative)
		    fprintf(stderr, "Processing curve object \"%s\"\n",
			    PObj -> Name);

		PPolygon = NULL;
		CubicBzrCrvs = IritCurvesToCubicBzrCrvs(PObj -> U.Crvs,
					   &PPolygon, GlblDrawSurface,
					   GlblDrawSurfaceMesh,
					   GlblDepthCue ?
					       DEFAULT_DEPTH_CUE_CRV_SIZE /
						   (1 << GlblSamplesPerCurve) :
					       INFINITY);


		if (CubicBzrCrvs) {
		    CagdCrvFreeList(PObj -> U.Crvs);
		    PObj -> U.Crvs = CubicBzrCrvs;
		    PObj -> ObjType = IP_OBJ_CURVE;
		}
		else
		    PObj -> ObjType = IP_OBJ_UNDEF;

		if (PPolygon) {
		    PObj -> Pnext = IPAllocObject("", IP_OBJ_POLY,
						  PObj -> Pnext);
		    IP_SET_POLYLINE_OBJ(PObj -> Pnext);
		    PObj = PObj -> Pnext;
		    PObj -> U.Pl = PPolygon;
		}
		else {
		}
	    }
	}

	if (SrfObjs) {
	    for (PObj = SrfObjs; PObj != NULL; PObj = PObj -> Pnext) {
		if (GlblTalkative)
		    fprintf(stderr, "Processing surface object \"%s\"\n",
			    PObj -> Name);

		PPolygon = NULL;
		CubicBzrCrvs =
		    IritSurfacesToCubicBzrCrvs(PObj -> U.Srfs, &PPolygon,
					   GlblDrawSurface,
					   GlblDrawSurfaceMesh,
					   GlblNumOfIsolines,
					   GlblDepthCue ?
					       DEFAULT_DEPTH_CUE_CRV_SIZE /
						   (1 << GlblSamplesPerCurve) :
					       INFINITY);
		if (CubicBzrCrvs) {
		    CagdSrfFreeList(PObj -> U.Srfs);
		    PObj -> U.Crvs = CubicBzrCrvs;
		    PObj -> ObjType = IP_OBJ_CURVE;
		}
		else
		    PObj -> ObjType = IP_OBJ_UNDEF;

		if (PPolygon) {
		    PObj -> Pnext = IPAllocObject("", IP_OBJ_POLY,
						  PObj -> Pnext);
		    IP_SET_POLYLINE_OBJ(PObj -> Pnext);
		    PObj = PObj -> Pnext;
		    PObj -> U.Pl = PPolygon;
		}
	    }
	}
    }
    else {
	if (CrvObjs) {
	    for (PObj = CrvObjs; PObj != NULL; PObj = PObj -> Pnext) {
		if (GlblTalkative)
		    fprintf(stderr, "Processing curve 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,
							GlblDrawSurface,
							GlblDrawSurfaceMesh,
							GlblSamplesPerCurve);
		    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 (GlblTalkative)
		    fprintf(stderr, "Processing surface object \"%s\"\n",
			    PObj -> Name);
		
		Srfs = PObj -> U.Srfs;
		PObj -> U.Pl = NULL;
		PObj -> ObjType = IP_OBJ_POLY;
		IP_SET_POLYLINE_OBJ(PObj);
		for (Srf = Srfs; Srf != NULL; Srf = Srf -> Pnext) {
		    PPolygon = PPolygonTemp =
			Surface2Polylines(Srf, GlblDrawSurface,
					  GlblDrawSurfaceMesh,
					  GlblNumOfIsolines,
					  GlblSamplesPerCurve);
		    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;
    }
}

/*****************************************************************************
* Dumps the data for postscript into FileName(stdout in NULL).		     *
*****************************************************************************/
static void DumpDataForPostScript(char *FileName, IPObjectStruct *PObjects)
{
    static char *PSHeader[] = {
	"%!PS-Adobe-2.0 EPSF-1.2",
	"BBOX",
	"TITLE",
	"%%Creator: irit2ps",
	"DATE",
	"%%EndComments",
	"",
	"% This file was automatically created from IRIT solid modeller data",
	"% using Irit2ps - IRIT to PostScript filter.",
	"",
	"gsave",
	"",
	"72 72 scale",	/* First scale to inches, se we can speak in inches. */
	"TRANSFORM",
	"",
	"/polyline {",
	"    newpath",
	"    moveto",
        "    {counttomark 0 ne {lineto} {exit} ifelse} loop",
	"    pop % the mark",
	"    stroke",
	"} def",
	"",
	"/polyfill {",
	"    newpath",
	"    moveto",
        "    {counttomark 0 ne {lineto} {exit} ifelse} loop",
	"    pop % the mark",
	"    fill",
	"} def",
	"",
	"/curveline {",
	"    newpath",
	"    moveto",
        "    curveto",
	"    stroke",
	"} def",
	"",
	"/curvefill {",
	"    newpath",
	"    moveto",
        "    curveto",
	"    fill",
	"} def",
	"",
	"/ln { % Line",
	"    newpath",
	"    moveto",
	"    lineto",
	"    stroke",
	"} def",
	"",
	"/lw { % Line with Width control",
	"    newpath",
	"    setlinewidth",
	"    moveto",
	"    lineto",
	"    stroke",
	"} def",
	"",
	"/ldg { % Line with Depth control - Gray",
	"    newpath",
	"    setgray",
	"    moveto",
	"    lineto",
	"    stroke",
	"} def",
	"",
	"/ldc { % Line with Depth control - Color",
	"    newpath",
	"    setrgbcolor",
	"    moveto",
	"    lineto",
	"    stroke",
	"} def",
	"",
	"/cw { % Curve with Width control",
	"    newpath",
	"    setlinewidth",
	"    moveto",
	"    curveto",
	"    stroke",
	"} def",
	"",
	"/cdg { % Curve with Width control",
	"    newpath",
	"    setgray",
	"    moveto",
	"    curveto",
	"    stroke",
	"} def",
	"",
	"/cdc { % Curve with Width control",
	"    newpath",
	"    setrgbcolor",
	"    moveto",
	"    curveto",
	"    stroke",
	"} def",
	"",
	"1 setlinecap",
	"1 setlinejoin",
	"WIDTH",
	"",
	NULL
    };
    static char *PSTrailer[] = {
	"",
	"showpage",
	"grestore",
	NULL
    };
    IPObjectStruct *PObj,
	*PObjHead = NULL;
    FILE *f;
    RealType
	PrintWidth = GlblPrintSize,
	PrintHeight = GlblPrintSize,
	PrintBot = (11.0 - GlblPrintSize) / 2.0,
	PrintLeft = (8.0 - GlblPrintSize) / 2.0;
    int i,
	IPrintBot = (int) (72.0 * PrintBot),
	IPrintLeft = (int) (72.0 * PrintLeft),
	IPrintTop = IPrintBot + (int) (72.0 * PrintWidth),
	IPrintRight = IPrintLeft + (int) (72.0 * PrintWidth);

    if (FileName != NULL) {
	if ((f = fopen(FileName, "w")) == NULL) {
	    fprintf(stderr, "Failed to open \"%s\".\n", FileName);
	    Irit2PsExit(2);
	}
    }
    else
	f = stdout;

    for (i = 0; PSHeader[i] != NULL; i++) {
	if (strcmp(PSHeader[i], "BBOX") == 0)
	    fprintf(f, "%%%%BoundingBox: %d %d %d %d\n",
		    IPrintLeft, IPrintBot, IPrintRight, IPrintTop);
	else if (strcmp(PSHeader[i], "DATE") == 0) {
	    fprintf(f, "%%%%CreationDate: %s\n", IritRealTimeDate());
	}
	else if (strcmp(PSHeader[i], "TITLE") == 0)
	    fprintf(f, "%%%%Title: %s\n", GlblPSTitle);
	else if (strcmp(PSHeader[i], "TRANSFORM") == 0) {
	    fprintf(f, "4.0 %f translate\t\t%% center image\n",
		    1.0 + PrintHeight / 2.0);
	    fprintf(f, "%f %f scale\t\t%% maps -1..1 to this domain\n",
		    PrintWidth / 2.0, PrintHeight / 2.0);
	}
	else if (strcmp(PSHeader[i], "WIDTH") == 0)
	    fprintf(f, "%f setlinewidth\n", GlblLineWidth);
	else
	    fprintf(f, "%s\n", PSHeader[i]);
    }
    
    /* Reverse object list since it was loaded in reverse by iritprsr module.*/
    while (PObjects != NULL) {
	PObj = PObjects;
	PObjects = PObjects -> Pnext;
	PObj -> Pnext = PObjHead;
	PObjHead = PObj;
    }
    PObjects = PObjHead;

    while (PObjects) {
	DumpOneObject(f, PObjects);
	PObjects = PObjects -> Pnext;
    }

    for (i = 0; PSTrailer[i] != NULL; i++)
	fprintf(f, "%s\n", PSTrailer[i]);

    fclose(f);
}

/*****************************************************************************
* Routine to dump attribute's state.					     *
*****************************************************************************/
static void DumpAttrState(FILE *f, RealType Width, RealType Fill,
			  RealType *RGBColor, char *Dash)
{
    fprintf(f, "%f setlinewidth\n", Width);

    if (Dash != NULL)
	fprintf(f, "%s setdash\n", Dash);
    else
	fprintf(f, "[] 0 setdash\n");

    if (GlblColorPS)
	fprintf(f, "%lf %lf %lf setrgbcolor\n",
		RGBColor[0] / 255.0, RGBColor[1] / 255.0, RGBColor[2] / 255.0);
    else
	fprintf(f, "%f setgray\n", Fill);
}

/*****************************************************************************
* Routine to dump one object PObject.					     *
*****************************************************************************/
static void DumpOneObject(FILE *f, IPObjectStruct *PObject)
{
    static RealType
	RGBWhite[3] = {255.0, 255.0, 255.0};
    int i, j, Red, Green, Blue, Color,
	HasColor = FALSE;
    char *p, *Dash;
    RealType *R, RGBColor[3], Fill, Width, TubeRatio;
    PointType Pt;
    IPPolygonStruct *PList;
    CagdCrvStruct *CList;

    if ((Width = AttrGetObjectRealAttrib(PObject, "width")) > IP_ATTR_BAD_REAL)
	Width = GlblLineWidth;

    Dash = AttrGetObjectStrAttrib(PObject, "dash");

    if ((Fill = AttrGetObjectRealAttrib(PObject, "fill")) > IP_ATTR_BAD_REAL)
	Fill = -1.0;

    if ((TubeRatio = AttrGetObjectRealAttrib(PObject, "tubular")) >
							IP_ATTR_BAD_REAL)
	TubeRatio = 0.0;

    /* Do not attempt to make tubes on such skiny lines. */
    if (Width < MIN_PS_LINE_WIDTH)
	TubeRatio = 0.0;

    if (GlblColorPS) {
	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 (!HasColor)
	    for (i = 0; i < 3; i++)
		RGBColor[i] = 0.0;
    }

    fprintf(f, "\n%%\n%% Object: %s\n%%\n",
	    PObject -> Name ? PObject ->Name : "Noname");
    DumpAttrState(f, Width, Fill, RGBColor, Dash);

    switch (PObject -> ObjType) {
	case IP_OBJ_POLY:
	    PList = PObject -> U.Pl;

	    while (PList) {
		if (IP_IS_POINTLIST_OBJ(PObject)) {
		    IPVertexStruct *V;

		    for (V = PList -> PVertex; V != NULL; V = V -> Pnext) {
			DumpOnePoint(f, V -> Coord, FALSE, RGBColor, Fill);
			if (V -> Pnext != NULL)
			    DumpAttrState(f, Width, Fill, RGBColor, Dash);
		    }
		}
		else {
		    if (TubeRatio > 0.0)
			DumpAttrState(f, Width, Fill, RGBColor, Dash);

		    DumpOnePoly(f, PList, IP_IS_POLYGON_OBJ(PObject),
				Fill >= 0.0, Width, RGBColor);

		    if (TubeRatio > 0.0) {
			DumpAttrState(f, Width * TubeRatio, 1.0,
				      RGBWhite, Dash);

			DumpOnePoly(f, PList, IP_IS_POLYGON_OBJ(PObject),
				    Fill >= 0.0, Width * TubeRatio, RGBColor);
		    }
		}

		PList = PList -> Pnext;
	    }
	    break;
	case IP_OBJ_CURVE:
	    if (GlblCubicCurves) {
		if (GlblDrawSurface) {
		    CList = PObject -> U.Crvs;

		    while (CList) {
			if (TubeRatio > 0.0)
			    DumpAttrState(f, Width, Fill, RGBColor, Dash);

			DumpOneCubicCurve(f, CList, Fill >= 0.0, Width,
					  RGBColor);

			if (TubeRatio > 0.0) {
			    DumpAttrState(f, Width * TubeRatio, 1.0,
					  RGBWhite, Dash);

			    DumpOneCubicCurve(f, CList, Fill >= 0.0,
					      Width * TubeRatio, RGBColor);
			}
			CList = CList -> Pnext;
		    }
	
		}
		break;
	    }
	case IP_OBJ_SURFACE:
	    IritFatalError("Should not process surfaces, at this level.\n");
	    break;
	case IP_OBJ_VECTOR:
	    DumpOnePoint(f, PObject -> U.Vec, TRUE, RGBColor, Fill);
	    break;
	case IP_OBJ_CTLPT:
	    R = PObject -> U.CtlPt.Coords;
	    CagdCoercePointTo(Pt, CAGD_PT_E3_TYPE,
			      &R, -1, PObject -> U.CtlPt.PtType);
	    DumpOnePoint(f, Pt, FALSE, RGBColor, Fill);
	    break;
	case IP_OBJ_POINT:
	    DumpOnePoint(f, PObject -> U.Vec, FALSE, RGBColor, Fill);
	default:
	    break;
    }
}

/*****************************************************************************
* Routine to dump one polygon/line, using global transform CrntViewMat.      *
*****************************************************************************/
static void DumpOnePoly(FILE *f, IPPolygonStruct *PPolygon, int IsPolygon,
			int Fill, RealType LineWidth, RealType *RGBColor)
{
    int Length, WidenEnd;
    IPVertexStruct
	*VList = PPolygon -> PVertex;

    if (!IsPolygon && GlblWidenPolyEnds) {
	if ((WidenEnd = AttrGetIntAttrib(PPolygon -> Attrs, "widenend")) !=
	       						     IP_ATTR_BAD_INT) {
	    RealType
		PolyLength = PolylineLength2D(PPolygon -> PVertex),
		WidenEndLen = GlblWidenEndLength,
		EndScale = GlblWidenEndWidthScale;

	    if (WidenEndLen > PolyLength / 2.0) {
		EndScale *= (PolyLength / 2.0) / WidenEndLen;
		if (EndScale < 1.0)
		    EndScale = 1.0;
		WidenEndLen = PolyLength / 2.0;
	    }

	    if (WidenEnd & WIDEN_END_START) {
		RealType
		    Scale2 = EndScale,
		    WidenEndLenLeft = WidenEndLen;
		IPVertexStruct
		    *V = VList;

		while (WidenEndLenLeft > 0.0) {
		    RealType
			EdgeLength = EdgeLength2D(V),
			ProcessedLength = MIN(EdgeLength, WidenEndLenLeft),
			Scale1 = Scale2;

		    Scale2 = Scale2 * (WidenEndLenLeft - ProcessedLength) /
							    WidenEndLenLeft;
		    if (Scale2 < 1.0)
			Scale2 = 1.0;

		    SubdivAndWidenEdge(V, Scale1, Scale2, LineWidth,
				       WidenEndLenLeft > EdgeLength ?
				            1.0 :
				            WidenEndLenLeft / EdgeLength);
		    WidenEndLenLeft -= ProcessedLength;

		    V = V -> Pnext;
		}
	    }
	    if (WidenEnd & WIDEN_END_END) {
		RealType
		    Scale2 = EndScale,
		    WidenEndLenLeft = WidenEndLen;
		IPVertexStruct
		    *V = IritPrsrGetPrevVrtx(VList,
					     IritPrsrGetLastVrtx(VList));

		while (WidenEndLenLeft > 0.0) {
		    RealType
			EdgeLength = EdgeLength2D(V),
			ProcessedLength = MIN(EdgeLength, WidenEndLenLeft),
			Scale1 = Scale2;

		    Scale2 = Scale2 * (WidenEndLenLeft - ProcessedLength) /
							    WidenEndLenLeft;
		    if (Scale2 < 1.0)
			Scale2 = 1.0;

		    SubdivAndWidenEdge(V, Scale2, Scale1, LineWidth,
				       WidenEndLenLeft > EdgeLength ?
				            1.0 :
				            WidenEndLenLeft / EdgeLength);
		    WidenEndLenLeft -= ProcessedLength;

		    V = IritPrsrGetPrevVrtx(VList, V);
		}
	    }
	}
	else
	    WidenEnd = 0;
    }

    if (GlblDepthCue) {
	if (VList != NULL && VList -> Pnext != NULL) {
	    RealType Z, Ztemp, Scale;

	    do {
		if (GlblShowInternal || !IP_IS_INTERNAL_VRTX(VList)) {
		    if ((Scale = AttrGetRealAttrib(VList -> Attrs,
					  "widthscale")) > IP_ATTR_BAD_REAL)
			Scale = 1.0;
		    /* Disable rounding of ends. */
		    if (Scale != 1.0)
			fprintf(f, "0 setlinecap\n");

		    Z = VList -> Coord[2];

		    fprintf(f, "%8.5f %8.5f",
			    VList -> Coord[0], VList -> Coord[1]);

		    Ztemp = (VList -> Pnext -> Coord[2] - GlblDepthCueZ[0]) /
			    (GlblDepthCueZ[1] - GlblDepthCueZ[0]);
		    Ztemp = Scale * MAX(Ztemp, EPSILON);
		    if (GlblDepthCueGray) {
			if (GlblColorPS)
			    fprintf(f, " %8.5f %8.5f %8.5f %8.5f %8.5f ldc\n",
				    VList -> Pnext -> Coord[0],
				    VList -> Pnext -> Coord[1],
				    RGBColor[0] * Ztemp / 255.0 + 1.0 - Ztemp,
				    RGBColor[1] * Ztemp / 255.0 + 1.0 - Ztemp,
				    RGBColor[2] * Ztemp / 255.0 + 1.0 - Ztemp);
			else
			    fprintf(f, " %8.5f %8.5f %8.5f ldg\n",
				    VList -> Pnext -> Coord[0],
				    VList -> Pnext -> Coord[1],
				    (1.0 - Fill) * (1.0 - Ztemp) );
		    }
		    else
			fprintf(f, " %8.5f %8.5f %8.5f lw\n",
				VList -> Pnext -> Coord[0],
				VList -> Pnext -> Coord[1],
				LineWidth * Ztemp);

		    /* Enable rounding of ends. */
		    if (Scale != 1.0)
			fprintf(f, "1 setlinecap\n");
		}

		VList = VList -> Pnext;
	    }
	    while (VList -> Pnext != NULL &&
		   VList != PPolygon -> PVertex);

	    if (IsPolygon) {
		if ((Scale = AttrGetRealAttrib(PPolygon -> PVertex -> Attrs,
					  "widthscale")) > IP_ATTR_BAD_REAL)
			Scale = 1.0;

		fprintf(f, "%8.5f %8.5f",
			PPolygon -> PVertex -> Coord[0],
			PPolygon -> PVertex -> Coord[1]);

		Ztemp = (PPolygon -> PVertex -> Coord[2] - GlblDepthCueZ[0]) /
			(GlblDepthCueZ[1] - GlblDepthCueZ[0]);
		Ztemp = Scale * MAX(Ztemp, EPSILON);
		if (GlblDepthCueGray) {
		    if (GlblColorPS)
			fprintf(f, " %8.5f %8.5f %8.5f %8.5f %8.5f ldc\n",
				PPolygon -> PVertex -> Coord[0],
				PPolygon -> PVertex -> Coord[1],
				RGBColor[0] * Ztemp / 255.0 + 1.0 - Ztemp,
				RGBColor[1] * Ztemp / 255.0 + 1.0 - Ztemp,
				RGBColor[2] * Ztemp / 255.0 + 1.0 - Ztemp);
		    else
			fprintf(f, " %8.5f %8.5f %8.5f ldg\n",
				PPolygon -> PVertex -> Coord[0],
				PPolygon -> PVertex -> Coord[1],
				(1.0 - Fill) * (1.0 - Ztemp));
		}
		else
		    fprintf(f, " %8.5f %8.5f %8.5f lw\n",
			    PPolygon -> PVertex -> Coord[0],
			    PPolygon -> PVertex -> Coord[1],
			    LineWidth * Ztemp);
	    }
	}
    }
    else {
	Length = 0;
	do {
	    if ((!GlblShowInternal && IP_IS_INTERNAL_VRTX(VList)) ||
		Length > MAX_NUM_OF_PTS) {
		if (Length > 0) {
		    fprintf(f, "%8.5f %8.5f\n",
			    VList -> Coord[0], VList -> Coord[1]);
		    fprintf(f, Fill ? "polyfill\n" : "polyline\n");
		    Length = 0;
		}
	    }
	    else {
		if (Length == 0)
		    fprintf(f, "mark\n");
		fprintf(f, "%8.5f %8.5f\n",
			VList -> Coord[0], VList -> Coord[1]);
		Length++;
	    }
		    
	    VList = VList -> Pnext;
	}
	while (VList != NULL && VList != PPolygon -> PVertex);

	if (IsPolygon && Length > 0) {
	    fprintf(f, "%8.5f %8.5f\n",
		    PPolygon -> PVertex -> Coord[0],
		    PPolygon -> PVertex -> Coord[1]);
	}
	if (Length > 0)
	    fprintf(f, Fill ? "polyfill\n" : "polyline\n");
    }
}

/*****************************************************************************
* Computes the length of a polyline in the XY plane.			     *
*****************************************************************************/
static RealType PolylineLength2D(IPVertexStruct *V)
{
    RealType Len = 0.0;

    for ( ; V -> Pnext != NULL; V = V -> Pnext)
	Len += EdgeLength2D(V);

    return Len;
}

/*****************************************************************************
* Computes the length of an edge in the XY plane.			     *
*****************************************************************************/
static RealType EdgeLength2D(IPVertexStruct *V)
{
    return sqrt(SQR(V -> Pnext -> Coord[0] - V -> Coord[0]) +
		SQR(V -> Pnext -> Coord[1] - V -> Coord[1]));
}

/*****************************************************************************
* Subdivides the EdgePortion from V to V->Pnext into small segments and set  *
* their width attribute to be from Scale1 to Scale2 of LineWidth.	     *
*****************************************************************************/
static void SubdivAndWidenEdge(IPVertexStruct *V, RealType Scale1,
	       RealType Scale2, RealType LineWidth, RealType EdgePortion)
{
    IPVertexStruct *Vtmp;
    RealType Len,
	SubdivFactor = GlblWidenEndLength * WIDEN_LN_SUBDIV_FACTOR;

    AttrSetRealAttrib(&V -> Attrs, "widthscale", Scale1);

    if (EdgePortion < 1.0) {
	/* Clip edge to make a full edge that needs to be subdivided. */
	Vtmp = IPAllocVertex(0, 0, NULL, V -> Pnext);

	if (Scale1 > Scale2) {
	    /* It is the beginning of the edge. */
	    PT_BLEND(Vtmp -> Coord, V -> Pnext -> Coord, V -> Coord,
		     EdgePortion);
	    V -> Pnext = Vtmp;
	}
	else {
	    /* It is the end of the edge. */
	    PT_BLEND(Vtmp -> Coord, V -> Coord, V -> Pnext -> Coord,
		     EdgePortion);
	    V -> Pnext = Vtmp;
	    V = Vtmp;
	}
    }

    while ((Len = EdgeLength2D(V)) > SubdivFactor) {
	EdgePortion = SubdivFactor / Len;
	Vtmp = IPAllocVertex(0, 0, NULL, V -> Pnext);
	PT_BLEND(Vtmp -> Coord, V -> Pnext -> Coord, V -> Coord, EdgePortion);
	V -> Pnext = Vtmp;
 	V = Vtmp;
	Scale1 = Scale1 * (1.0 - EdgePortion) + Scale2 * EdgePortion;
	AttrSetRealAttrib(&Vtmp -> Attrs, "widthscale", Scale1);
    }
}

/*****************************************************************************
* Routine to dump one polygon/line, using global transform CrntViewMat.      *
*****************************************************************************/
static void DumpOneCubicCurve(FILE *f, CagdCrvStruct *Crv,
			      int Fill, RealType LineWidth, RealType *RGBColor)
{
    CagdBType
	NewCrv = FALSE;
    int i, j;
    PointType Pt;
    CagdRType **Points;

    if (Crv ->  PType != CAGD_PT_E3_TYPE) {
	Crv = CagdCoerceCrvTo(Crv, CAGD_PT_E3_TYPE);
	NewCrv = TRUE;
    }
    Points = Crv -> Points;

    if (GlblDepthCue) {
	RealType Z, Ztemp;

	for (j = 1; j < 4; j++)
	    fprintf(f, "%8.5f %8.5f ", Points[1][j], Points[2][j]);
	for (i = 0; i < 3; i++)
	    Pt[i] = Points[i+1][0];
	fprintf(f, "%8.5f %8.5f ", Points[1][0], Points[2][0]);
	Z = Points[2][0];

	Ztemp = (Z - GlblDepthCueZ[0]) /
		(GlblDepthCueZ[1] - GlblDepthCueZ[0]);
	Ztemp = MAX((1.0 - Ztemp), EPSILON);

	if (GlblDepthCueGray) {
	    if (GlblColorPS)
		fprintf(f, "%8.5f %8.5f %8.5f cdc\n",
			RGBColor[0] * Ztemp / 255.0 + 1.0 - Ztemp,
			RGBColor[1] * Ztemp / 255.0 + 1.0 - Ztemp,
			RGBColor[2] * Ztemp / 255.0 + 1.0 - Ztemp);
	    else
		fprintf(f, "%8.5f cdg\n", (1.0 - Fill) * (1.0 - Ztemp));
	}
	else
	    fprintf(f, "%8.5f cw\n", LineWidth * Ztemp);
    }
    else {
	for (j = 1; j < 4; j++)
	    fprintf(f, "%8.5f %8.5f ", Points[1][j], Points[2][j]);
	fprintf(f, "%8.5f %8.5f ", Points[1][0], Points[2][0]);
	fprintf(f, Fill ? "curvefill\n" : "curveline\n");
    }

    if (NewCrv)
	CagdCrvFree(Crv);
}

/*****************************************************************************
* Routine to dump one point/vector, using global transform CrntViewMat.      *
*****************************************************************************/
static void DumpOnePoint(FILE *f, RealType *Pt, int IsVector,
			 RealType *RGBColor, RealType Fill)
{
    RealType
	Z = Pt[2],
	Ztemp = (Z - GlblDepthCueZ[0]) / (GlblDepthCueZ[1] - GlblDepthCueZ[0]),
	Size = GlblDepthCue ? GlblPointScale * MAX(Ztemp, EPSILON)
			    : GlblPointScale;

    if (GlblDepthCueGray) {
	if (GlblColorPS)
	    fprintf(f, "%lf %lf %lf setrgbcolor\n",
		    RGBColor[0] * Ztemp / 255.0 + 1.0 - Ztemp,
		    RGBColor[1] * Ztemp / 255.0 + 1.0 - Ztemp,
		    RGBColor[2] * Ztemp / 255.0 + 1.0 - Ztemp);
	else
	    fprintf(f, "%lf setgray\n", (1.0 - Fill) * (1.0 - Ztemp));
    }

    if (IsVector) {
	fprintf(f, "0 0 %8.5f %8.5f 0 lw\n", Pt[0], Pt[1]);
    }

    switch (GlblDrawPoint) {
	case DRAW_POINT_FULL_CIRC:
	    /* Dump a point as a full circle. */
	    fprintf(f, "newpath %8.5f %8.5f %8.5f 0 360 arc fill\n",
		    Pt[0], Pt[1], GlblDepthCueGray ? GlblPointScale : Size); 
	    break;
	case DRAW_POINT_HOLLOW_CIRC:
	    /* Dump a point as a hollowed circle. */
	    fprintf(f, "newpath %8.5f %8.5f %8.5f 0 360 arc fill\n",
		    Pt[0], Pt[1], GlblDepthCueGray ? GlblPointScale : Size); 
	    if (GlblColorPS)
		fprintf(f, "1.0 1.0 1.0 setrgbcolor\n");
	    else
		fprintf(f, "1.0 setgray\n");
	    fprintf(f, "newpath %8.5f %8.5f %8.5f 0 360 arc fill\n",
		    Pt[0], Pt[1], GlblDepthCueGray ? GlblPointScale * 0.7
						   : Size * 0.7); 
	    break;
	case DRAW_POINT_CROSS:
	    /* Dump a point as a cross. */
	    fprintf(f, "%8.5f %8.5f %8.5f %8.5f  %8.5f lw\n",
		    Pt[0] - Size, Pt[1],
		    Pt[0] + Size, Pt[1],
		    GlblDepthCueGray ? GlblPointScale * 0.2 : Size * 0.2);
	    fprintf(f, "%8.5f %8.5f %8.5f %8.5f  %8.5f lw\n",
		    Pt[0], Pt[1] - Size,
		    Pt[0], Pt[1] + Size,
		    GlblDepthCueGray ? GlblPointScale * 0.2 : Size * 0.2);
	    break;
    }
}

/*****************************************************************************
* Irit2Ps exit routine. 						     *
*****************************************************************************/
static void Irit2PsExit(int ExitCode)
{
    exit(ExitCode);
}
