/* zbuf.c,v 1.1.1.1 1995/02/27 07:38:46 explorer Exp */

/*
 * 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".
 *
 */

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

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

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

RSZbuf z_buffer;

static int  PrepareForOutput _PROTO((int, FILE *, char *));
static void WriteRow _PROTO((int, int, FILE *, char *, Float *, int));
static void NoMoreThisImage _PROTO((int, FILE *, char *));
static void ZwriteArgv _PROTO((FILE *, char **));
static int  ZsetOutputType _PROTO((char *));


/* 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 ((z_buffer.fzbuf = fopen(Options.zbufname, "w")) == NULL)
    RLerror(RL_PANIC, "Cannot open Z buffer file \"%s\".\n",
	    Options.zbufname);
  z_buffer.array = (Float **) Malloc(vert * sizeof(Float *));

  /* Initialize all depths to Infinity */

  for (i = 0; i < vert; i++) {
    z_buffer.array[i] = (Float *) Malloc(horiz * sizeof(Float));
    for (j = 0; j < horiz; j++)
      z_buffer.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 (z_buffer.array && i >= 0 && j >= 0 && i < Screen.ysize &&
      j < Screen.xsize) {
    Float before = z_buffer.array[i][j];

    if (before == ZBUF_INF || dist < before)
      z_buffer.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'
 */

void 
ZbufPrint()
{
  int type = ZsetOutputType(Options.zbufname);
  int len = Screen.xsize;
  int y;
  FILE *fp = z_buffer.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, z_buffer.array[y], len);

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

static 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);
    z_buffer.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, z_buffer.argv);
    if (z_buffer.rayfile)
      fprintf(fp, "\n# Input RAY file: \"%s\"\n", z_buffer.rayfile);
    if (z_buffer.outfile)
      fprintf(fp, "# Output file:    \"%s\"\n", z_buffer.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 */

static void
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 (z_buffer.pad) {
      if (pad_len < z_buffer.pad) {
	if (padding)
	  free(padding);
	pad_len = ((z_buffer.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), z_buffer.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 */

static void
NoMoreThisImage(type, fp, filename)
  int type;
  FILE *fp;
  char *filename;
{
  int len;
  int i;
  float *padding;

  if (type == ZBUF_HF) {
    padding = (float *) malloc(sizeof(float) * Screen.xsize);

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

static void
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 */

static 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 = z_buffer.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 (z_buffer.rayfile)
    fprintf(fp, "# Input RAY file: \"%s\"\n", z_buffer.rayfile);
  if (z_buffer.outfile)
    fprintf(fp, "# Output file:    \"%s\"\n", z_buffer.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, z_buffer.array[x][y]);

    fprintf(fp, "\n");

  }				/* for */

  fclose(fp);
  z_buffer.fzbuf = NULL;

}				/* ZbufPrint */

#endif
