/*
 * zbuf.c
 *
 * Add Z buffer support to Rayshade 4.0, by Mark W. Maimone (mwm@cs.cmu.edu)
 *
 * Rayshade is a rendering package by Craig Kolb, et al
 * Copyright (C) 1989, 1991, Craig E. Kolb
 * All rights reserved.
 *
 * This software may be freely copied, modified, and redistributed
 * provided that this copyright notice is preserved on all copies.
 *
 * You may not distribute this software, in whole or in part, as part of
 * any commercial product without the express consent of the authors.
 *
 * There is no warranty or other guarantee of fitness of this software
 * for any purpose.  It is provided solely "as is".
 *
 * $Id$
 *
 * $Log$
 */

#include "rayshade.h"
#include "options.h"
#include "zbuf.h"
#include "viewing.h"

typedef struct {
    char   *s;
    int	   i;
} PairT;

RSZbuf zbuffer;

/* Must set the proper window size before calling ZbufSetup() */

void
ZbufSetup()
{
        int horiz = Screen.xsize;
        int vert  = Screen.ysize;
        int i, j;

        if (Options.zbufname == (char *)NULL)
                return;
        if ((zbuffer.fzbuf = fopen (Options.zbufname, "w")) == NULL)
                RLerror(RL_PANIC, "Cannot open Z buffer file \"%s\".\n",
                        Options.zbufname);
        zbuffer.array = (Float **) Malloc (vert * sizeof (Float *));

/* Initialize all depths to Infinity */

        for (i = 0; i < vert; i++) {
                zbuffer.array[i] = (Float *) Malloc (horiz * sizeof (Float));
                for (j = 0; j < horiz; j++)
                        zbuffer.array[i][j] = ZBUF_INF;
        } /* for */
} /* ZbufSetup */


/* ZbufAdd -- Some object in pixel (x,y) is distance   dist   from the
   eye.  Add it to the Z buffer if it's nearer than all other objects seen
   so far.  This simple-minded storage scheme relies on the anti-aliasing
   to sample the pixel at appropriate places.  It also assumes you want
   the distance of the *nearest* object touching the pixel as the final
   distance measure. */

void ZbufAdd (x, y, dist)
Float x, y, dist;
{
    int j = ((int) (x + 0.5)) - Screen.minx;
    int i = ((int) (y + 0.5)) - Screen.miny;
    
    if (zbuffer.array && i >= 0 && j >= 0 && i < Screen.ysize &&
	j < Screen.xsize) 
    {
	Float before = zbuffer.array[i][j];
	
	if (before == ZBUF_INF || dist < before)
	    zbuffer.array[i][j] = dist;
    }
} /* ZbufAdd */


/* ZbufPrint -- Write the Z buffer data to a file.  Output format is
   determined by the filename, or by DEFAULT_ZBUF_TYPE.  Three formats
   are available:

	HF  --  Output is written in Heightfield format
	TXT --  After some opening comments, distances are written in
		ascii.  Each line in the output file contains distances for
		the corresponding image line, separated by a single space.
	TXT3 -- After some opening comments, distances are
		output as a triple: pixel coordinate and distance (e.g.:

                   0 3 3.14146

   means at position (0,3) the depth is 3.13146.  This is very
   space-inefficient, but is quite portable, and can be directly read by
   GNUplot for easy viewing.

        % rayshade -R 50 50 -z zbuf.dat Examples/balls.ray > balls.rle
        % gnuplot
        gnuplot> set term x11           [or whatever you're on]
        gnuplot> set sample 2500        [for a 50x50 image]
        gnuplot> set parametric         [needed to plot 3D data]
        gnuplot> splot 'zbuf.dat' with lines
                                        [gives a nice height map grid]
        gnuplot> help set view          [info on changing viewpoint]
        gnuplot> set view 45, 45        [a sample viewpoint]
        gnuplot> quit

   Warning!  Distances are expected to always be positive, so background
   pixels are represented by a distance of ZBUF_INF (i.e., -1000).  To get
   nice looking GNUplot output you should replace all -1's with the maximum
   distance.  On UNIX:

        % rayshade -R 50 50 -z zbuf.dat Examples/planet.ray > balls.rle
        % awk 'BEGIN {max = -1} NF == 3 { if ($3 > max) max = $3} END \
                {print max}' < zbuf.dat         [this gives you the max]
        3.69359
        % sed s/-1000/3.69359/g < zbuf.dat | awk 'NF == 3 { print $1" "$2" -"$3 \
                ; next} {print}' > zbuf.plot    [replace -1 with max, make all
                                                 distances negative so the
                                                 plot appears rightside up]
        % gnuplot
                .
                .
        gnuplot> set zrange [-3.7:0]
        gnuplot> splot 'zbuf.plot'
*/

static PairT exts[] = {
    "hf",	ZBUF_HF,
    "rle",	ZBUF_RLE,
    "txt",	ZBUF_TXT,
    "txt3",	ZBUF_TXT3,	/* silly suffix, for backward compatability */
    "etxt",	ZBUF_TXT3,
}; /* exts */

void ZbufPrint ()
{
    int type = ZsetOutputType (Options.zbufname);
    int len = Screen.xsize;
    int y;
    FILE *fp = zbuffer.fzbuf;
    char *filename = Options.zbufname;

    if (PrepareForOutput (type, fp, filename) == 0)
	return;
#ifdef URT
    for (y = Screen.ysize-1; y >= 0; y--)
#else
    for (y = 0; y < Screen.ysize; y++)
#endif
	WriteRow (type, y + Screen.miny, fp, filename, zbuffer.array[y], len);

    NoMoreThisImage (type, fp, filename);
} /* ZbufPrint */

int PrepareForOutput (type, fp, filename)
int type;
FILE *fp;
char *filename;
{
    HEIGHTFIELD_SIZE i;
    
    if (fp == NULL) return 0;

    switch (type) {
        case ZBUF_HF:
	    i = Screen.xsize;
	    if (i < Screen.ysize) i = Screen.ysize;
	    fwrite (&i, sizeof (HEIGHTFIELD_SIZE), 1, fp);
	    zbuffer.pad = i - Screen.xsize;
	    break;
	case ZBUF_RLE:
	    fprintf (stderr, "Sorry, RLE output not available\n");
	    return 0;
	case ZBUF_TXT:
	case ZBUF_TXT3:
	    fprintf (fp, "# Rayshade Z-buffer output file for command:\n");
	    fprintf (fp, "#   ");
	    ZwriteArgv (fp, zbuffer.argv);
	    if (zbuffer.rayfile)
		fprintf (fp, "\n# Input RAY file: \"%s\"\n", zbuffer.rayfile);
	    if (zbuffer.outfile)
		fprintf (fp, "# Output file:    \"%s\"\n", zbuffer.outfile);
	    fprintf (fp, "# Resolution for this rendering is %dx%d\n",
		     Screen.xres, Screen.yres);
	    fprintf (fp, "# window from (%d,%d) to (%d,%d)\n",
		     Screen.minx, Screen.miny, Screen.maxx, Screen.maxy);
	    break;
	default:
	    fprintf (stderr, "Error!  Unknown Z buffer type '%d'\n", type);
	    return 0;
    } /* switch */
    return 1;
} /* PrepareForOutput */

WriteRow (type, y, fp, filename, row, len)
int type, y;
FILE *fp;
char *filename;
Float *row;
int len;
{
    int i;
    char *sep = "";
    static float *padding = NULL;
    static int pad_len = 0, vec_len = 0;
    static float *vec = NULL;

    switch (type) {
        case ZBUF_HF:
	    if (vec_len < len) {
		if (vec) free (vec);
		vec_len = ((len+9) / 10) * 10;
		vec = (float *) malloc (sizeof (float) * vec_len);
	    }
	    for (i = 0; i < len; i++)
		vec[i] = (float) row[i];
	    fwrite (vec, sizeof (float), len, fp);
	    if (zbuffer.pad) {
		if (pad_len < zbuffer.pad) {
		    if (padding) free (padding);
		    pad_len = ((zbuffer.pad+9) / 10) * 10;
		    padding = (float *) malloc (sizeof (float) * pad_len);
		    for (i = 0; i < pad_len; i++) padding[i] = ZBUF_INF;
		} /* if */
		fwrite (padding, sizeof (float), zbuffer.pad, fp);
	    }
	    break;
	case ZBUF_TXT:
	    for (i = 0; i < len; i++) {
		fprintf (fp, "%s%g", sep, row[i]);
		sep = " ";
	    } /* for */
	    fprintf (fp, "\n");
	    break;
	case ZBUF_TXT3:
	    for (i = 0; i < len; i++)
		fprintf (fp, "%d %d %g\n", i + Screen.minx, y, row[i]);
	    fprintf (fp, "\n");
	    break;
	default:
	    fprintf (stderr, "*Invalid type %d in WriteRow\n", type);
	    break;
    } /* switch */
} /* WriteRow */

NoMoreThisImage (type, fp, filename)
int type;
FILE *fp;
char *filename;
{
    if (type == ZBUF_HF)
	if (Screen.xsize > Screen.ysize) {
	    int len = Screen.xsize - Screen.ysize;
	    int i;
	    float *padding = (float *) malloc (sizeof (float) * Screen.xsize);

	    for (i = 0; i < Screen.xsize; i++)
		padding[i] = ZBUF_INF;
	    while (len-- > 0)
		fwrite (padding, sizeof (float), Screen.xsize, fp);
	    free (padding);
	} /* if */
    fclose (fp);
    zbuffer.fzbuf = NULL;
} /* NoMoreThisImage */

ZwriteArgv (fp, argv)
FILE *fp;
char **argv;
{
    char *sep = "";

    if (fp == NULL || argv == NULL) return;

    while (*argv) {
	fprintf (fp, "%s%s", sep, *argv);
	sep = " ";
	argv++;
    }
} /* ZwriteArgv */

int ZsetOutputType (filename)
char *filename;
{
    int i, len;
    int type = DEFAULT_ZBUF_TYPE;

    if (filename != NULL) {
	len = strlen (filename);
	for (i = 0; i < sizeof (exts) / sizeof (PairT); i++)
	    if (len >= strlen (exts[i].s) && strcmp (exts[i].s,
		    filename + len - strlen (exts[i].s)) == 0) {
		type = exts[i].i;
		break;
	    } /* if */
    } /* if */

/* No RLE output yet */

    if (type == ZBUF_RLE) {
	fprintf (stderr,
		"  (Z buffer not in available in RLE format yet, sorry)\n");
	type = DEFAULT_ZBUF_TYPE;
    } /* if */

    if (type) {
	fprintf (stderr, "Z buffer will be written in ");
	switch (type) {
	    case ZBUF_HF:	fprintf (stderr, "HEIGHTFIELD"); break;
	    case ZBUF_TXT:	fprintf (stderr, "TEXT"); break;
	    case ZBUF_TXT3:	fprintf (stderr, "EXPANDED TEXT"); break;
	    case ZBUF_RLE:	fprintf (stderr, "UTAH RASTER TOOLKIT"); break;
	    default:		fprintf (stderr, "UNKNOWN!!");
				type = 0; break;
	} /* switch */
	fprintf (stderr, " format to \"%s\"\n", Options.zbufname);
    } /* if */
    return type;
} /* ZsetOutputType */


#ifdef SDfl

void ZbufPrint ()
{
    FILE *fp = zbuffer.fzbuf;
    int minx = Screen.minx, maxx = Screen.maxx;
    int miny = Screen.miny, maxy = Screen.maxy;
    int horiz = Screen.xsize;
    int vert  = Screen.ysize;
    int x, y;
    
    if (fp == NULL)
	return;
    
    fprintf (fp, "# Depth Map for Rayshade output file\n");
    if (zbuffer.rayfile)
	fprintf (fp, "# Input RAY file: \"%s\"\n", zbuffer.rayfile);
    if (zbuffer.outfile)
	fprintf (fp, "# Output file:    \"%s\"\n", zbuffer.outfile);
    fprintf (fp, "# Resolution for this rendering is %dx%d\n",
	     Screen.xres, Screen.yres);
    fprintf (fp, "# window from (%d,%d) to (%d,%d)\n",
	     minx, miny, maxx, maxy);
    fprintf (fp, "# Screen X unit vector:  (%g,%g,%g)\n",
	    Screen.scrnx.x, Screen.scrnx.y, Screen.scrnx.z);
    fprintf (fp, "# Screen Y unit vector:  (%g,%g,%g)\n",
	    Screen.scrny.x, Screen.scrny.y, Screen.scrny.z);

/* Would be nice to save command-line options too */
    
    for (x = 0; x < horiz; x++) 
    {
	for (y = 0; y < vert; y++)
	    fprintf (fp, "%d %d %g\n", x, y, zbuffer.array[x][y]);

	fprintf (fp, "\n");

    } /* for */
    
    fclose (fp);
    zbuffer.fzbuf = NULL;

} /* ZbufPrint */
#endif
