/*
 * Copyright (C) 1997-2009, R3vis Corporation.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA, or visit http://www.gnu.org/copyleft/lgpl.html.
 *
 * Original Contributor:
 *   Wes Bethel, R3vis Corporation, Marin County, California
 *   http://www.r3vis.com/
 * Additional Contributor(s):
 *
 * The OpenRM project is located at http://openrm.sourceforge.net/.
 */
/*
 * $Id: rmps.c,v 1.8 2006/10/15 15:48:29 wes Exp $
 * Version: $Name: v180-alpha-02 $
 * $Revision: 1.8 $
 * $Log: rmps.c,v $
 * Revision 1.8  2006/10/15 15:48:29  wes
 * Added initialization code when computing planar coefficients for points.
 * This fixes a b2f sorting bug revealed by new psTest1 demo.
 *
 * Revision 1.7  2006/08/02 05:00:05  wes
 * Added debug routine to print node & prim index.
 *
 * Revision 1.6  2006/04/16 15:13:58  wes
 * Added RMpipe-level background clears. Previous code had only node-level
 * fbclears.
 *
 * Revision 1.5  2005/06/15 02:10:40  wes
 * Added initial support for application-settable defaults. Apps
 * will use rmSetEnum/rmGetEnum to set or get defaults that are RMenums.
 * The first round of variables that can be set/get by apps are
 * RMnode traversal masks assigned to new scene graph nodes by rmNodeNew.
 *
 * Revision 1.4  2005/06/12 21:48:44  wes
 * Minor tweaks to avoid compiler warnings.
 *
 * Revision 1.3  2005/06/06 02:04:29  wes
 * Lots of small additions to clean up compiler warnings.
 *
 * Revision 1.2  2005/03/16 16:46:40  wes
 * More code merge activities.
 *
 * Revision 1.1  2005/02/19 16:36:40  wes
 * Distro merge and consolidation.
 *
 */
#include <rm/rm.h>
#include "rmprivat.h"
#include "rmprivatps.h"

/*
 *
 * Last Updated: Sat Mar 21 11:41:56 PST 1998
 *
 * Sat Mar 21 11:41:56 PST 1998. add code to support path-preserving
 *   for polylines. dashing patterns will be correctly rendered along
 *   an arbitrary-length polyline so long as all the pieces are contained
 *   within one path. the "path preserve flag" is set in rmLinesConnected
 *   at render time. as feedback tokens are processed, a path index is
 *   incremented on each new polyline (triggered by each new
 *   RM_PATH_PRESERVE_FLAG==1 opcode sent over from rmLinesConnected()).
 *   the hard part is to maintain the notion of the path in the presence
 *   of a possibly broken path, which could potentially happen during
 *   the painters() sort and split operations. therefore, we don't try
 *   to maintain the path except in the following set of strict conditions
 *   (which will always work in 2d, but may have odd side effects in
 *   3d  -- keep in mind this happens only when a dashing pattern is
 *   requested):
 *   line reset token (generated at the start of a polyline) followed
 *   by some arbitrary number of line tokens which have the same
 *   current path index.  whenever these conditions are not met (in
 *   the dispatch() routine in this file), the dashing pattern is
 *   effectively reset.
 *
 * Sat Nov 22 18:53:36 PST 1997
 *   2d ellipses, circles, boxes.
 *
 * Sun Oct  5 07:02:16 PDT 1997
 *   added application callbacks for color conversion.
 *
 * Thu Aug 28 11:27:40 MDT 1997
 *   heartbeat function, early exit from painters, fast vs. slow
 *   vector mode.
 *
 * Wed Aug 27 11:09:47 MDT 1997
 *   graceful error recovery
 *
 * Tue Jul 29 18:46:52 PDT 1997
 *   implement modified painter's sorting. 
 *
 * Sat Jun 14 19:15:03 PDT 1997
 *   support for cached-glyph text primitives (RM_TEXTCHARS prim).
 *
 * Thu Jun  5 15:33:39 MDT 1997 - added support for image tiling.
 *   added rectclip clipping rectangle - this is dumped on the root
 *   node ONLY.  if the root node doesn't have a viewport scene
 *   parameter, there will be no global clipping to the outline
 *   rectangle.
 *
 *
 *
 * TODO:
 *
 * 1. text - does the font need to be inversely scaled so that if the
 *    user asks for 14 point they get 14 point?
 *
 * Bugs.
 * - very small lines don't show up.
 */

/*#define RM_PS_ZOFFSET 0.0005F  */
#define RM_PS_ZOFFSET 0.0 

/*#define PRINT_FEEDBACK_BUFFER 1   */
#ifdef PRINT_FEEDBACK_BUFFER
static FILE *dfile;
#endif

/*
 * when dumping pixel data (ie, image tiles), the maximum number of
 * pixels that can be stored into a single string variable is the
 * following:
 */
/* #define RM_PS_MAX_TILESIZE_PIXELS (65536 / 3) */
/* #define RM_PS_MAX_TILESIZE_PIXELS ((65536 >> 2) / 3) */
#define RM_PS_MAX_TILESIZE_PIXELS ((65536 / 3))

/*
 * the following few arrays are used in constructing a font name that
 * is recognizable to ps interpreters.
 */

/*
 * ----------------------------------------------------
 * @Name rmPSSpecNew
 @pstart
 RMpsSpec * rmPSSpecNew (void)
 @pend

 @astart
 (No arguments)
 @aend

 @dstart
 Use this routine to create a new RMpsSpec object, which is used to specify
 PostScript output options to the RM renderer. Upon success, this routine
 returns a handle to an RMpsSpec object that has been initialized with
 default values (see below). Upon failure, NULL is returned.

 To use the RMpsSpec object to create PostScript output, you may modify
 PostScript output attributes by using the appropriate routines, then
 provide the RMpsSpec object as input along with a scene graph and
 an RMpipe to the RM PostScript rendering utility, rmFramePS().

 Upon creation, the new RMpsSpec object contains the following default values:

 1. Page size : 612 points wide by 792 points high. These dimensions
 correspond to an 8.5 by 11 inch US letter document at 72 points/inch.
 Use the routines rmPSPageSetSize/rmPSPageGetSize to set/get the page
 dimensions for PS output.

 2. Page orientation: RM_PS_PORTAIT. The document uses a default orientation
 of portrait. Use the routines rmPSPageSetOrientation/rmPSPageGetOrientation
 to set/get the page orientation.

 3. Margin: The minimum margin is 36 points (0.5 inches). Use the
 routines rmPSPageSetMargin/rmPSPageGetMargin to set/get the margin.

 4. PS Format: The default PostScript output format is RM_PS_REGULAR, or
 regular PostScript output (the alternative would be RM_PS_EPS for
 Encapsulated PostScript). Use the routines rmPSPageSetOutputFormat/
 rmPSPageGetOutputFormat to set/get the PostScript output format.

 5. Vector output format: The default vector output form is RM_PS_VECTOR
 (the alternative is RM_PS_RASTER). Use the routines rmPSPageSetVectorFormat/
 rmPSPageGetVectorFormat to set/get the output vector format.

 6. Sorting method: The default sorting method used to convert from 3D
 scenes to ordered 2D PostScript primitives is RM_PS_SORT_FULL. Use the
 routines rmPSSetSortMethod/rmPSGetSortMethod to set/get the sort
 attributes.

 7. Output filename: The default output filename is "rmPSFile.ps". Use
 the routines rmPSSetOutputFilename/rmPSGetOutputFilename to set/get
 the file name where PostScript output will be placed.

 Note: you could malloc() your own RMpsSpec object, or use one declared
 off the heap if you so desire. The advantage of using rmPSSpecNew() is
 that the new RMpsSpec object will be initialized to a set of known default
 values.

 @dend
 * ----------------------------------------------------
 */
RMpsSpec *rmPSSpecNew(void)
{
    RMpsSpec *t = (RMpsSpec *)calloc(1, sizeof(RMpsSpec));

    if (RM_ASSERT(t, "rmPSSpecNew() error - unable to malloc space for a new RMpsSpec object. \n") == RM_WHACKED)
	return NULL;

    rmPSPageSetSize(t, RM_PS_DEFAULT_PAGE_WIDTH_POINTS, RM_PS_DEFAULT_PAGE_HEIGHT_POINTS);
    rmPSPageSetOrientation(t, RM_PS_DEFAULT_ORIENTATION);
    rmPSPageSetMargin(t, RM_PS_DEFAULT_MIN_MARGIN);
    rmPSPageSetOutputFormat(t, RM_PS_REGULAR); /* non-EPS, or regular PS, is the default */
    rmPSPageSetVectorFormat(t, RM_PS_VECTOR); /* default is vector, not raster PS output format */
    rmPSSetSortMethod(t, RM_PS_SORT_FULL);
    rmPSSetOutputFilename(t, "rmPSFile.ps");
    
    return t;
}

/*
 * ----------------------------------------------------
 * @Name rmPSSpecDelete
 @pstart
 RMenum rmPSSpecDelete (RMpsSpec *toDelete)
 @pend

 @astart
 RMpsSpec *toDelete - an handle to an RMpsSpec object that will be deleted.
 @aend

 @dstart

 Use this routine to delete an RMpsSpec object created by rmPSSpecNew.
 Upon success, RM_CHILL is returned and the RMpsSpec object toDelete is
 deleted. Upon failure, RM_WHACKED is returned and the object toDelete
 is not deleted.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPSSpecDelete(RMpsSpec *t)
{
    if (RM_ASSERT(t, "rmPSSpecDelete() warning - the input RMpsSpec pointer is NULL") == RM_WHACKED)
	return RM_WHACKED;

    free((void *)t);
    return RM_CHILL;
}

/*
 * ----------------------------------------------------
 * @Name rmPSPageSetSize
 @pstart
 RMenum rmPSPageSetSize (RMpsSpec *toModify, int pagePointsWidth, int pagePointsHeight)
 @pend

 @astart
 RMpsSpec *toModify - an handle to an RMpsSpec object that will be modified.
 int pagePointsWidth, pagePointsHeight - the width and height in points of the
    output PostScript document.
 @aend

 @dstart

 This routine will set the output page size dimensions in points of the
 output PostScript image. Upon success, RM_CHILL is returned and the RMpsSpec
 object toModify will be updated to contain the new size attributes specified
 by the caller. Upon failure, RM_WHACKED is returned and the RMpsSpec object
 toModify is left unmodified.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPSPageSetSize(RMpsSpec *p,
		int pagePointsWidth,
		int pagePointsHeight)
{
    if (RM_ASSERT(p, "rmPSPageSetSize() warning - the input RMpsSpec pointer is NULL") == RM_WHACKED)
	return RM_WHACKED;
    
    p->psWidthPoints = pagePointsWidth;
    p->psHeightPoints = pagePointsHeight;

    return RM_CHILL;
}

/*
 * ----------------------------------------------------
 * @Name rmPSPageGetSize
 @pstart
 RMenum rmPSPageGetSize (const RMpsSpec *toQuery, int *returnPagePointsWidth, int *returnPagePointsHeight)
 @pend

 @astart
 const RMpsSpec *toModify - an handle to an RMpsSpec object that will be queried.
 int *returnPagePointsWidth, *returnPagePointsHeight - pointers to caller-
   supplied memory where results will be placed.
 @aend

 @dstart

 Use this routine to query the PostScript page size dimensions. Upon success,
 RM_CHILL is returned, and the page size width and height (in points) will
 be copied into caller-supplied memory. Upon failure, RM_WHACKED is returned
 and the caller-supplied memory will remain unchanged.

 Note that you may use a value of NULL for either of returnPagePointsWidth
 or returnPagePointsHeight if you wish to obtain only one, both or none
 of the page size attributes.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPSPageGetSize(const RMpsSpec *p,
		int *pagePointsWidth,
		int *pagePointsHeight)
{
    if (RM_ASSERT(p, "rmPSPageGetSize() warning - the input RMpsSpec pointer is NULL") == RM_WHACKED)
	return RM_WHACKED;
    
    if (pagePointsWidth != NULL)
	*pagePointsWidth = p->psWidthPoints;

    if (pagePointsHeight != NULL)
	*pagePointsHeight = p->psHeightPoints;
    
    return RM_CHILL;
}

/*
 * ----------------------------------------------------
 * @Name rmPSPageSetOrientation
 @pstart
 RMenum rmPSPageSetOrientation (RMpsSpec *toModify, RMenum orientation)
 @pend

 @astart
 RMpsSpec *toModify - an handle to an RMpsSpec object that will be modified.
 RMenum orientation - an enumerator value that specifies the orientation for
   the PostScript output. Must be one of RM_PS_PORTRAIT or RM_PS_LANDSCAPE.
 @aend

 @dstart

 Use this routine to specify the page orientation for PostScript output.
 When you specify RM_PS_PORTRAIT, the imageable area is oriented such that
 the width axis of the output PostScript is oriented along the width
 axis of the output page. When you specify RM_PS_LANDSCAPE, the width
 axis of the output PostScript is oriented along the height axis of
 the output page.

 Upon success, this routine returns RM_CHILL and updates the RMpsSpec
 object toModify. Upon failure, which can occur of the RMpsSpec object
 is NULL or if the input orientation enumerator is neither RM_PS_PORTRAIT
 nor RM_PS_LANDSCAPE, RM_WHACKED is returned and the RMpsSpec object is
 not modified.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPSPageSetOrientation(RMpsSpec *p,
		       RMenum orientation)
{
    if (RM_ASSERT(p, "rmPSPageSetOrientation() warning - the input RMpsSpec pointer is NULL") == RM_WHACKED)
	return RM_WHACKED;

    if ((orientation != RM_PS_PORTRAIT) && (orientation != RM_PS_LANDSCAPE))
    {
	rmWarning("rmPSPageSetOrientation() warning - the input orientation parameter is neither RM_PS_LANDSCAPE nor RM_PS_PORTRAIT");
	return RM_WHACKED;
    }
    
    p->psOrientation = orientation;
    return RM_CHILL;
}

/*
 * ----------------------------------------------------
 * @Name rmPSPageGetOrientation
 @pstart
 RMenum rmPSPageGetOrientation (const RMpsSpec *toQuery)
 @pend

 @astart
 const RMpsSpec *toQuery - an handle to an RMpsSpec object that will be queried.
 @aend

 @dstart

 Use this routine to query the page orientation attribute of an RMpsSpec
 object. Upon failure, RM_WHACKED is returned. Upon success, the page
 orientation attribute is returned (RM_PS_LANDSCAPE or RM_PS_PORTRAIT).

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPSPageGetOrientation(const RMpsSpec *p)
{
    if (RM_ASSERT(p, "rmPSPageGetOrientation() warning - the input RMpsSpec pointer is NULL") == RM_WHACKED)
	return RM_WHACKED;

    return p->psOrientation;
    
    return RM_CHILL;
}


/*
 * ----------------------------------------------------
 * @Name rmPSPageSetOutputFormat
 @pstart
 RMenum rmPSPageSetOutputFormat (RMpsSpec *toModify, RMenum newFormat)
 @pend

 @astart
 RMpsSpec *toModify - an RM object holding PostScript generation options.
 RMenum newFormat - a format enumerator for the type of PS to output. Must
    be one of RM_PS_REGULAR or RM_PS_EPS.
 @aend

 @dstart

 There are many parameters that affect how RM produces PostScript output
 from a scene graph. These parameters are all specified to RM via an
 RMpsSpec object. See rmPSSpecNew() for information about creating
 RMpsSpec objects, and see the RM Programming Guide for complete
 information about all the PostScript output attributes.

 Use this routine to specify whether the PostScript output file will be
 "normal" PostScript (RM_PS_REGULAR) or Encapsulated PostScript
 (RM_PS_EPS).

 Returns RM_CHILL upon success. RM_WHACKED is returned if the toModify
 parameter is NULL, or if newFormat is not one of RM_PS_REGULAR or
 RM_PS_EPS.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPSPageSetOutputFormat(RMpsSpec *p,
			RMenum outputFormat)
{
    /* input should be RM_PS_REGULAR or RM_PS_EPS */
    if (RM_ASSERT(p,"rmPSPageSetOutputFormat error - the input RMpsSpec object is NULL") == RM_WHACKED)
	return RM_WHACKED;

    if ((outputFormat != RM_PS_EPS) && (outputFormat != RM_PS_REGULAR))
    {
	rmWarning("rmPSPageSetOutputFormat warning: the outputFormat parameter is neither RM_PS_EPS nor RM_PS_REGULAR");
	return RM_WHACKED;
    }
    
    p->psOutputFormat = outputFormat;
    return RM_CHILL;
}

/*
 * ----------------------------------------------------
 * @Name rmPSPageGetOutputFormat
 @pstart
 RMenum rmPSPageGetOutputFormat (const RMpsSpec *toQuery)
 @pend

 @astart
 const RMpsSpec *toQuery - an RM object holding PostScript generation options.
 @aend

 @dstart

 Use this routine to obtain the PostScript output format specifier.
 Upon success, on of RM_PS_REGULAR or RM_PS_EPS will be returned.
 Upon failure, RM_WHACKED will be returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPSPageGetOutputFormat(const RMpsSpec *p)
{
    if (RM_ASSERT(p,"rmPSPageGetOutputFormat error - the input RMpsSpec object is NULL") == RM_WHACKED)
	return RM_WHACKED;
    
    return p->psOutputFormat;
}

/*
 * ----------------------------------------------------
 * @Name rmPSPageSetVectorFormat
 @pstart
 RMenum rmPSPageSetVectorFormat (RMpsSpec *toModify, RMenum newFormat)
 @pend

 @astart
 RMpsSpec *toModify - an RM object holding PostScript generation options.
 RMenum newFormat - input enumerator that must be either RM_PS_VECTOR or
    RM_PS_RASTER.
 @aend

 @dstart
 
 Use this routine to select between vector (RM_PS_VECTOR) or raster
 (RM_PS_RASTER) PostScript formats. Upon success, RM_CHILL is returned
 and the new PostScript output will be applied to the RMpsSpec object
 toModify. Upon failure, RM_WHACKED will be returned, and the RMpsSpec
 object toModify will not be modified. Failure will occur if the input
 RMpsSpec object toModify is NULL, or if the input RMenum newFormat
 is neither RM_PS_VECTOR nor RM_PS_RASTER. 

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPSPageSetVectorFormat(RMpsSpec *p,
			RMenum newFormat)
{
    /* input should be RM_PS_VECTOR or RM_PS_RASTER */
    if (RM_ASSERT(p,"rmPSPageSetVectorFormat error - the input RMpsSpec object is NULL") == RM_WHACKED)
	return RM_WHACKED;

    if ((newFormat != RM_PS_VECTOR) && (newFormat != RM_PS_RASTER))
    {
        rmWarning("rmPSPageSetVectorFormat warning: the newFormat parameter is neither RM_PS_VECTOR nor RM_PS_RASTER. \n");
	return RM_WHACKED;
    }
    p->psRasterOrVector = newFormat;
    return RM_CHILL;
}

/*
 * ----------------------------------------------------
 * @Name rmPSPageGetVectorFormat
 @pstart
 RMenum rmPSPageGetVectorFormat (const RMpsSpec *toQuery)
 @pend

 @astart
 const RMpsSpec *toQuery - an RM object holding PostScript generation options.
 @aend

 @dstart
 
 Use this routine to obtain the raster vs. vector PostScript output format
 attribute from an RMpsSpec object. Upon success, one of RM_PS_RASTER
 or RM_PS_VECTOR will be returned. Upon failure, RM_WHACKED is returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPSPageGetVectorFormat(const RMpsSpec *p)
{
    if (RM_ASSERT(p,"rmPSPageGetVectorFormat error - the input RMpsSpec object is NULL") == RM_WHACKED)
	return RM_WHACKED;

    return p->psRasterOrVector;
}

/*
 * ----------------------------------------------------
 * @Name rmPSPageSetMargin
 @pstart
 RMenum rmPSPageSetMargin (RMpsSpec *toModify, int minMarginPoints)
 @pend

 @astart
 RMpsSpec *toModify - an handle to an RMpsSpec object that will be modified.
 int minMarginPoints - an integer value specifing the minimum margin size
    in units of points (one point = 1/72 of an inch).
 @aend

 @dstart

 Use this routine to set the minimum margin (in points) attribute of the
 RMpsSpec object toModify. Upon success, RM_CHILL is returned and the
 RMpsSpec object toModify is updated. Upon failure, RM_WHACKED is returned.

 The term "minimum margin" means that a single margin value is used to
 specify minimum margin sizes that will be used when computing the mapping
 to the printable page. Under normal circumstances, the rendered image is
 automatically sized to fill the full PS output page. The margin
 attribute controls how much whitespace remains at the border of the page.

 For example, if you specify a 0.5inch minimum margin (36 points) on
 an A4 output page (8.5inches by 11 inches), portrait orientation and
 your on-screen window is 400 by 300 pixels, the imageable area will
 fill the page width except for 0.5 inches of margin at each of the left
 and right sides. The top and bottom margin will be much larger, however,
 due to the fact that you are mapping the larger imageable area size to
 the smaller page dimension.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPSPageSetMargin(RMpsSpec *p,
		  int minMarginPoints)
{
    if (RM_ASSERT(p,"rmPSPageSetMargin error - the input RMpsSpec object is NULL") == RM_WHACKED)
	return RM_WHACKED;
    
    p->psMinMargin = minMarginPoints;

    return RM_CHILL;
}

/*
 * ----------------------------------------------------
 * @Name rmPSPageGetMargin
 @pstart
 RMenum rmPSPageGetMargin (const RMpsSpec *toModify, int *marginPointsReturn)
 @pend
 @astart
 const RMpsSpec *toQuery - an handle to an RMpsSpec object that will be
   queried.
 int * marginPointsReturn - a handle to a caller-supplied integer. The
   margin size attribute of the input RMpsSpec object will be copied into
   caller-supplied memory. 
 @aend

 @dstart

 Use this routine to obtain the minimum margin attribute from an RMpsSpec
 object. Upon success, RM_CHILL is returned and the margin attribute value
 is copied into caller-supplied memory. Upon failure, RM_WHACKED is returned
 and the caller-supplied memory remains unmodified.

 Note that it is permissible to specify a value of NULL for the
 caller-supplied memory.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPSPageGetMargin(const RMpsSpec *p,
		  int *minMarginPointsReturn)
{
    if (RM_ASSERT(p,"rmPSPageGetMargin error - the input RMpsSpec object is NULL") == RM_WHACKED)
	return RM_WHACKED;
    
    if (minMarginPointsReturn != NULL)
	*minMarginPointsReturn = p->psMinMargin;

    return RM_CHILL;
}
    
/*
 * ----------------------------------------------------
 * @Name rmPSSetOutputFilename
 @pstart
 RMenum rmPSSetOutputFilename (RMpsSpec *toModify, const char *fileName)
 @pend
 @astart
 RMpsSpec *toModify - an handle to an RMpsSpec object that will be modified.
 const char *fileName - a character string (input).
 @aend

 @dstart

 Use this routine to set the name of the file to which PostScript output
 will be placed when you call rmFramePS() to render a scene to PostScript.
 Upon success, this routine will return RM_CHILL, and the input filename
 will be copied into the RMpsSpec object toModify. Upon failure, RM_WHACKED
 is returned and the RMpsSpec object toModify will remain unmodified.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPSSetOutputFilename(RMpsSpec *p,
		      const char *fName)
{
    if (RM_ASSERT(p,"rmPSSetOutputFilename error - the input RMpsSpec object is NULL") == RM_WHACKED)
	return RM_WHACKED;
    
    if ((fName != NULL) && (strlen(fName) > 0))
    {
	if (p->fName != NULL)
	    free((void *)(p->fName));

	p->fName = strdup(fName);
    }
    
    return RM_CHILL;
}

/*
 * ----------------------------------------------------
 * @Name rmPSGetOutputFilename
 @pstart
 const char * rmPSGetOutputFilename (const RMpsSpec *toQuery)
 @pend
 @astart
 const RMpsSpec *toQuery - an handle to an RMpsSpec object to query.
 @aend

 @dstart

 Use this routine to obtain read-only access to the filename from an
 RMpsSpec object where PostScript output will be written. Upon failure,
 NULL is returned. Upon success, a read-only character string is returned.

 @dend
 * ----------------------------------------------------
 */
const char *
rmPSGetOutputFilename(const RMpsSpec *p)
{
    if (RM_ASSERT(p,"rmPSGetOutputFilename error - the input RMpsSpec object is NULL") == RM_WHACKED)
        return NULL;
    
    return p->fName;
}


/*
 * ----------------------------------------------------
 * @Name rmPSSetSortMethod
 @pstart
 RMenum rmPSSetSortMethod (RMpsSpec *toModify, RMenum sortMethod)
 @pend

 @astart
 RMpsSpec *toModify - an RM object holding PostScript generation options.
 RMenum sortMethod - an enumerator that specifies which sorting method
   will be used when creating the PS file.
 @aend

 @dstart

 There are many parameters that affect how RM produces PostScript output
 from a scene graph. These parameters are all specified to RM via an
 RMpsSpec object. See rmPSSpecNew() for information about creating
 RMpsSpec objects, and see the RM Programming Guide for complete
 information about all the PostScript output attributes.

 In generating PostScript output, RM needs to sort primitives in back-to-front
 order since PS has no notion of a depth buffer. Sorting primitives is
 an expensive, time-consuming operation. RM provides four methods you
 can use for generating PostScript files:

 RM_PS_SORT_FAST - will make a single pass through the primitives and sort
  them in back-to-front order using the primitive's centroid. This method
  uses qsort internally, and as such is an O(N logN) operation. If the
  primitives in the scene don't have much overlap in either x/y space or
  in depth, then this method should produce satisfactory results.

 RM_PS_SORT_FULL - this method will perform full geometric sorting of all
  primitives in the scene using a Binary Space Partition algorithm. The
  RM_PS_SORT_FULL algorithm is of O (N logN) complexity, and as such will
  run in comparable run time to RM_PS_SORT_FAST, depending upon
  scene complexity. In all instances, it has greater memory requirements
  than RM_PS_SORT_FAST. In some cases, the memory requirements can be
  substantial.

 RM_PS_SORT_HYBRID_SCREEN_BSP - this sorting algorithm uses a novel hybrid
  method of partitioning: it will first subdivide the primitives into
  screen-space tiles. Primitives that cross tile boundaries are subdivided
  so that the resulting primitive fragments are entirely contained within
  a screen space tile. When the number of primitives within that screen
  tile falls below a certain threshold (a constant in the code) or if the
  tile area falls below a certain threshold (also a constand in the code), then
  the primitives contained within that screen are then sorted using
  RM_PS_SORT_FULL. This approach has the benefit of reducing algorithmic
  complexity, which in turn results in much faster run times than
  RM_PS_SORT_FULL as well as reduced memory requirements. Note, however,
  that RM_PS_SORT_HYBRID_SCREEN_BSP will result in PS rendering artifacts
  if you specify "fat" line drawing styles or point sizes greater than
  one pixel. This sorting technique is interesting, but should be viewed
  as experimental.

 RM_PS_SORT_HYBRID_DEPTH_BSP - another hybrid approach to reduce run time
  and memory requirements as compared to a full BSP sort. This algorithm
  will first subdivide the scene using planes positioned in depth (rather
  than screen tiles). While this approach is not quite as fast or memory
  effecient as RM_PS_SORT_HYBRID_SCREEN_BSP, it does not exhibit any of
  the rendering errors, and its memory and runtime performance are
  substantially better than RM_PS_SORT_FULL.

 When you create an RMpsSpec object with rmPSSpecNew(), the default sorting
 method assigned to the RMpsSpec object is RM_PS_SORT_FULL.

 Returns RM_CHILL upon success. RM_WHACKED is returned if the toModify
 parameter is NULL, or if newFormat is not one of RM_PS_REGULAR or
 RM_PS_EPS.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPSSetSortMethod(RMpsSpec *p,
		  RMenum sortMethod)
{
    /* inout should be RM_PS_SORT_FULL, RM_PS_SORT_FAST, RM_PS_SORT_HYBRID_SCREEN_BSP, RM_PS_SORT_HYBRID_DEPTH_BSP. */
    if (RM_ASSERT(p,"rmPSPageSetSortMethod error - the input RMpsSpec object is NULL") == RM_WHACKED)
	return RM_WHACKED;

    if ((sortMethod != RM_PS_SORT_FULL) && (sortMethod != RM_PS_SORT_FAST) && (sortMethod != RM_PS_SORT_HYBRID_SCREEN_BSP) && (sortMethod != RM_PS_SORT_HYBRID_DEPTH_BSP))
    {
	rmWarning("rmPSSetSortMethod warning: the sortMethod parameter is not one of RM_PS_SORT_FULL, RM_PS_SORT_FAST, RM_PS_SORT_HYBRID_SCREEN_BSP, RM_PS_SORT_HYBRID_DEPTH_BSP");
	return RM_WHACKED;
    }
    
    p->psSortMethod = sortMethod;
    return RM_CHILL;
}

/*
 * ----------------------------------------------------
 * @Name rmPSGetSortMethod
 @pstart
 RMenum rmPSGetSortMethod (const RMpsSpec *toQuery)
 @pend

 @astart
 const RMpsSpec *toQuery - an RMpsSpec object that will be queried.
 @aend

 @dstart

 Use this routine to query the sort method specification from an RMpsSpec
 object. Upon failure, RM_WHACKED is returned. Upon success, the sorting
 method is returned to the caller (RM_PS_SORT_FULL, RM_PS_SORT_FAST,
 RM_PS_SORT_HYBRID_SCREEN_BSP, RM_PS_SORT_HYBRID_DEPTH_BSP).

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPSGetSortMethod(const RMpsSpec *p)
{
    if (RM_ASSERT(p,"rmPSPageGetSortMethod error - the input RMpsSpec object is NULL") == RM_WHACKED)
	return RM_WHACKED;

    return p->psSortMethod;
}



/* PRIVATE */
void
private_rmPSEpilogue(FILE *f,
		     RMpsSpec *p)
{
}

/* PRIVATE */
void
private_rmPSPrintVertex(FILE *f,
			float xval,
			float yval)
{
}

/* PRIVATE */
void
private_rmPSPrologue(FILE *f,
		     const RMpipe *pipe,
		     RMpsSpec *p)
{
}




/*
 * test: we're going to restrict the maximum number of columns per
 * subtile to be, say, 200 pixels. we'll recompute the size of the
 * subtile to reflect this (previously used whole scanlines).
 */
#define MAX_COLS 150
/* PRIVATE */
void
private_rmPSTileATile(FILE *f,
		      RMimage *s,
		      int xmin, /* viewport dims */
		      int ymin,
		      int xmax,
		      int ymax)

{
}


/* PRIVATE */
void
private_PSPipeBackgroundFunc(const RMpipe *p,
			     FILE *f)
{
}

void
private_PSBackgroundFunc(const RMnode *r,
			 float vp[4],
			 FILE *f)
{
}

/* PRIVATE */
int
comparefunc(const void *aa,
	    const void *bb)
{
    return(1);		
}










































/*
 * ----------------------------------------------------
 * @Name rmFramePS
 @pstart
 RMenum rmFramePS (RMpipe *drawOn, RMnode *subTree, RMpsSpec *spec)
 @pend

 @astart
 RMpipe *drawOn - the RMpipe environment used to draw the scene.
 RMnode *subTree - the scene.
 RMpsSpec *spec - an RMpsSpec containing PS parameter specifications.
 @aend

 @dstart

 rmFramePS is used to render a scene into a PostScript[tm] file. The
 final rendered frame contents are a function of the (1) relevant
 parameters specified in the input RMpipe "drawOn" (like display
 window pixel dimensions, etc.); (2) the scene contents as specified
 by the scene graph rooted at the RMnode "subTree"; (3) PS-specific
 parameters and algorithmic controls specified via the RMpsSpec
 parameter.

 See rmPSSpecNew() for an introduction to the numerous parameters provided
 for specifying PS rendering algorithmic control and output options.

 Upon success, RM_CHILL is returned; upon failure, RM_WHACKED is returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmFramePS (RMpipe *drawOn,
	   RMnode *subTree,
	   RMpsSpec *p)
{
    RMenum rstat = RM_WHACKED;
    return rstat;
}
    
/*
 * ----------------------------------------------------
 * @Name rmFramePSHeartbeat
 @pstart
 RMenum rmFramePSHeartbeat(RMpipe *drawOn, RMnode *subTree, RMpsSpec *p, int (*heartBeatFunc)(int percentComplete))
 @pend

 @astart
 RMpipe *drawOn - the RMpipe environment used to draw the scene.
 RMnode *subTree - the scene.
 RMpsSpec *spec - an RMpsSpec containing PS parameter specifications.
 int (*heartBeatFunc)(int percentComplete) - an application supplied callback function.
 @aend

 @dstart

 rmFramePSHeartbeat is identical to function and behavior to rmFramePS
 with one exception - it will accept as input an application-provided
 callback (the "heartbeat function"). The callback will be periodically
 invoked during the PS generation process. When invoked:

 1. The application can "interrupt" and prematurely abort the PS generation
 process by having the heartbeat callback function return a non-zero value
 to RM. In other words, if the heartbeat application callback returns a
 value of zero to RM, PS output processing will continue; if the
 application heartbeat application callback returns a non-zero value,
 PS output processing will terminate. Control will be returned to
 the application, and the return status (RM_CHILL or RM_WHACKED)
 does not indicate whether or not processing was prematurely aborted
 due to a non-zero return status from the application heartbeat
 callback.

 2. The application will be provided an estimate of "percent complete"
 via the integer parameter "percentComplete." A value of zero means
 the algorithm has not yet begun; a value of one hundred (100) means
 the algorithm has completed. See Note D below for more information
 about the meaning of this parameter during lengthy sorting
 operations.


 Notes:

 A. During raster postscript output processing, the heartbeat
 function is invoked at 0, 50, and 100 percent complete. Its
 return status is ignored, which means that the application cannot
 interrupt PS output when the RM_PS_RASTER output format is requested.
 The reasoning is that only lengthy sorting operations should be
 interruptable via the heartbeat callback.

 B. During vector postscript output processing, the heartbeat function
 is invoked at a mixture of "hard coded" percent complete points
 that correspond to algorithmic milestones, and the return status
 from the heartbeat application callback is ignored at those points.

 C.  While lengthy sort operations are conducted during vector PS
 processing, the heartbeat callback is periodically invoked. "Periodically"
 means approximately once per second. The sorting operation may be
 interrupted by the application vis-a-via returning a non-zero value
 from the heartbeat callback function.

 D. A best-effort algorithm is used
 to report an accurate percent-complete value via the integer
 parameter to the heartbeat callback. However, due to the nature of
 how the PS sorting operation works, the percent complete value
 is not guaranteed to be monotonic or accurate. We have found it to
 be both monotonic and reasonably accurate in all cases we have tested.
 Since the underlying method used to compute "percent complete" takes into
 account the number of remaining unsorted primitives; this number can
 increase depending upon the scene complexity and partition planes used
 during binary space partitioning. 
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmFramePSHeartbeat(RMpipe *drawOn,
		   RMnode *subTree,
		   RMpsSpec *p,
		   int (*heartBeatFunc)(int percentComplete))
{
    RMenum rstat=RM_WHACKED;
    return rstat;
}
    
/* EOF */
