/*

bsp2bp - converts Quake I BSP's to a bitmap (map!) of the level
Copyright (C) 1999  Matthew Wong <mkyw@iname.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <math.h>
#include <limits.h>

#include "QF/bspfile.h"
#include "QF/cmd.h"
#include "QF/cvar.h"
#include "QF/pcx.h"
#include "QF/quakeio.h"
#include "QF/sys.h"
#include "QF/zone.h"

#define PROGNAME  "bsp2img"
#define V_MAJOR   0
#define V_MINOR   0
#define V_REV     12

#define Z_PAD_HACK    16
#define MAX_REF_FACES 4

#define MEMSIZE (12 * 1024 * 1024)

#define X point[0]
#define Y point[1]
#define Z point[2]

/*
Thanks fly to Id for a hackable game! :)
*/

/* MW */
typedef struct edge_extra_t {
	long        num_face_ref;
	long        ref_faces[MAX_REF_FACES];	// which faces are referenced
	dvertex_t   ref_faces_normal[MAX_REF_FACES];	// normal of referenced
													// faces
	int         ref_faces_area[MAX_REF_FACES];	// area of the referenced faces 
} edge_extra_t;

typedef unsigned char eightbit;

typedef struct options_t {
	char       *bspf_name;
	char       *outf_name;

	float       scaledown;
	float       z_pad;

	float       image_pad;

	int         z_direction;
	int         camera_axis;			// 1 - X, 2 - Y, 3 - Z, negatives will
										// come from negative side of axis

	int         edgeremove;
	float       flat_threshold;
	int         area_threshold;
	int         linelen_threshold;

	int         negative_image;

	int         write_raw;
	int         write_nocomp;
} options_t;

typedef struct {
	eightbit   *image;
	long        width;
	long        height;
} image_t;

static void
show_help (void)
{
	printf ("BSP->bitmap, version %d.%d.%d\n\n", V_MAJOR, V_MINOR, V_REV);
	printf ("Usage:\n");
	printf ("  %s [options] <bspfile> <outfile>\n\n", PROGNAME);
	printf ("Options:\n");
	printf ("    -s<scaledown>     default: 4, ie 1/4 scale\n");
	printf ("    -z<z_scaling>     default: 0 for flat map, >0 for iso 3d, -1 for auto\n");
	printf ("    -p<padding>       default: 16-pixel border around final image\n");
	printf ("    -d<direction>     iso 3d direction: 7  0  1\n");
	printf ("                                         \\ | /\n");
	printf ("                                        6--+--2\n");
	printf ("                                         / | \\\n");
	printf ("                                        5  4  3\n");
	printf ("                      default: 7\n");
	printf ("    -c<camera_axis>   default: +Z (+/- X/Y/Z axis)\n");
	printf ("    -t<flatness>      threshold of dot product for edge removal;\n");
	printf ("                      default is 0.90\n");
	printf ("    -e                disable extraneous edges removal\n");
	printf ("    -a<area>          minimum area for a polygon to be drawn\n");
	printf ("                      default is 0\n");
	printf ("    -l<length>        minimum length for an edge to be drawn\n");
	printf ("                      default is 0\n");
	printf ("    -n                negative image (black on white\n");
	printf ("    -r                write raw data, rather than bmp file\n");

	return;
}

static void
plotpoint (image_t * image, long xco, long yco, unsigned int color)
{
	unsigned int bigcol = 0;

	if (xco < 0 || xco > image->width || yco < 0 || yco > image->height)
		return;

	bigcol = (unsigned int) image->image[yco * image->width + xco];

	bigcol = bigcol + color;

	if (bigcol < 0)
		bigcol = 0;
	if (bigcol > 255)
		bigcol = 255;

	image->image[yco * image->width + xco] = (eightbit) bigcol;

	return;
}

static void
bresline (image_t * image, long x1, long y1, long x2, long y2,
		  unsigned int color)
{
	long        x = 0, y = 0;
	long        deltax = 0, deltay = 0;
	long        xchange = 0, ychange = 0;
	long        error, length, i;

	x = x1;
	y = y1;

	deltax = x2 - x1;
	deltay = y2 - y1;

	if (deltax < 0) {
		xchange = -1;
		deltax = -deltax;
	} else {
		xchange = 1;
	}

	if (deltay < 0) {
		ychange = -1;
		deltay = -deltay;
	} else {
		ychange = 1;
	}

	/* Main seq */
	error = 0;
	i = 0;

	if (deltax < deltay) {
		length = deltay + 1;
		while (i < length) {
			y = y + ychange;
			error = error + deltax;
			if (error > deltay) {
				x = x + xchange;
				error = error - deltay;
			}

			i++;

			plotpoint (image, x, y, color);
		}
	} else {
		length = deltax + 1;
		while (i < length) {
			x = x + xchange;
			error = error + deltay;
			if (error > deltax) {
				y = y + ychange;
				error = error - deltax;
			}

			i++;

			plotpoint (image, x, y, color);
		}
	}
}

static void
def_options (struct options_t *opt)
{
	static struct options_t locopt;

	locopt.bspf_name = NULL;
	locopt.outf_name = NULL;

	locopt.scaledown = 4.0;
	locopt.image_pad = 16.0;
	locopt.z_pad = 0.0;

	locopt.z_direction = 1;
	locopt.camera_axis = 3;				// default is from +Z

	locopt.edgeremove = 1;
	locopt.flat_threshold = 0.90;
	locopt.area_threshold = 0;
	locopt.linelen_threshold = 0;

	locopt.negative_image = 0;

	locopt.write_raw = 0;
	locopt.write_nocomp = 1;

	memcpy (opt, &locopt, sizeof (struct options_t));
	return;
}

static void
get_options (struct options_t *opt, int argc, char *argv[])
{
	static struct options_t locopt;
	int         i = 0;
	char       *arg;
	long        lnum = 0;
	float       fnum = 0.0;
	char        pm = '+', axis = 'Z';

	/* Copy curr options */
	memcpy (&locopt, opt, sizeof (struct options_t));

	/* Go through command line */
	for (i = 1; i < argc; i++) {
		arg = argv[i];
		if (arg[0] == '-') {
			/* Okay, dash-something */
			switch (arg[1]) {
				case 's':
					if (sscanf (&arg[2], "%ld", &lnum) == 1)
						if (lnum > 0)
							locopt.scaledown = (float) lnum;
					break;

				case 'z':
					if (sscanf (&arg[2], "%ld", &lnum) == 1)
						if (lnum >= -1)
							locopt.z_pad = (float) lnum;
					break;

				case 'p':
					if (sscanf (&arg[2], "%ld", &lnum) == 1)
						if (lnum >= 0)
							locopt.image_pad = (float) lnum;
					break;

				case 'd':
					if (sscanf (&arg[2], "%ld", &lnum) == 1)
						if (lnum >= 0 && lnum <= 7)
							locopt.z_direction = (int) lnum;
					break;

				case 'c':
					if (strlen (&arg[2]) == 2) {
						pm = arg[2];
						axis = arg[3];
						printf ("-c%c%c\n", pm, axis);
						switch (axis) {
							case 'x':
							case 'X':
								locopt.camera_axis = 1;
								break;

							case 'y':
							case 'Y':
								locopt.camera_axis = 2;
								break;

							case 'z':
							case 'Z':
								locopt.camera_axis = 3;
								break;

							default:
								printf ("Must specify a valid axis.\n");
								show_help ();
								exit (1);
								break;
						}

						switch (pm) {
							case '+':
								break;
							case '-':
								locopt.camera_axis = -locopt.camera_axis;
								break;
							default:
								printf ("Must specify +/-\n");
								show_help ();
								exit (1);
								break;
						}
					} else {
						printf ("Unknown option: -%s\n", &arg[1]);
						show_help ();
						exit (1);
					}
					break;
				case 't':
					if (sscanf (&arg[2], "%f", &fnum) == 1)
						if (fnum >= 0.0 && fnum <= 1.0)
							locopt.flat_threshold = (float) fnum;
					break;

				case 'e':
					locopt.edgeremove = 0;
					break;

				case 'n':
					locopt.negative_image = 1;
					break;

				case 'a':
					if (sscanf (&arg[2], "%ld", &lnum) == 1)
						if (lnum >= 0)
							locopt.area_threshold = (int) lnum;
					break;

				case 'l':
					if (sscanf (&arg[2], "%ld", &lnum) == 1)
						if (lnum >= 0)
							locopt.linelen_threshold = (int) lnum;
					break;

				case 'r':
					locopt.write_raw = 1;
					break;

				case 'u':
					locopt.write_nocomp = 1;
					break;

				default:
					printf ("Unknown option: -%s\n", &arg[1]);
					show_help ();
					exit (1);
					break;
			}							/* switch */
		} else {
			if (locopt.bspf_name == NULL) {
				locopt.bspf_name = arg;
			} else if (locopt.outf_name == NULL) {
				locopt.outf_name = arg;
			} else {
				printf ("Unknown option: %s\n", arg);
				show_help ();
				exit (1);
			}
		}								/* if */
	}									/* for */

	memcpy (opt, &locopt, sizeof (struct options_t));
	return;
}

static void
show_options (struct options_t *opt)
{
	char        dirstr[80];

	printf ("Options:\n");
	printf ("  Scale down by: %.0f\n", opt->scaledown);
	printf ("  Z scale: %.0f\n", opt->z_pad);
	printf ("  Border: %.0f\n", opt->image_pad);
	/* Zoffset calculations */
	switch (opt->z_direction) {
		case 0:
			sprintf (dirstr, "up");
			break;
		case 1:
			sprintf (dirstr, "up & right");
			break;
		case 2:
			sprintf (dirstr, "right");
			break;
		case 3:
			sprintf (dirstr, "down & right");
			break;
		case 4:
			sprintf (dirstr, "down");
			break;
		case 5:
			sprintf (dirstr, "down & left");
			break;
		case 6:
			sprintf (dirstr, "left");
			break;
		case 7:
			sprintf (dirstr, "up & left");
			break;
		default:
			sprintf (dirstr, "unknown!");
			break;
	}

	printf ("  Z direction: %d [%s]\n", opt->z_direction, dirstr);
	if (opt->z_pad == 0) {
		printf ("    Warning: direction option has no effect with Z scale set to 0.\n");
	}

	/* Camera axis */
	switch (opt->camera_axis) {
		case 1:
			sprintf (dirstr, "+X");
			break;
		case -1:
			sprintf (dirstr, "-X");
			break;
		case 2:
			sprintf (dirstr, "+Y");
			break;
		case -2:
			sprintf (dirstr, "-Y");
			break;
		case 3:
			sprintf (dirstr, "+Z");
			break;
		case -3:
			sprintf (dirstr, "-Z");
			break;
		default:
			sprintf (dirstr, "unknown!");
			break;
	}

	printf ("  Camera axis: %s\n", dirstr);
	printf ("  Remove extraneous edges: %s\n",
			(opt->edgeremove == 1) ? "yes" : "no");
	printf ("  Edge removal dot product theshold: %f\n", opt->flat_threshold);
	printf ("  Minimum polygon area threshold (approximate): %d\n",
			opt->area_threshold);
	printf ("  Minimum line length threshold: %d\n", opt->linelen_threshold);
	printf ("  Creating %s image.\n",
			(opt->negative_image == 1) ? "negative" : "positive");

	printf ("\n");
	printf ("  Input (bsp) file: %s\n", opt->bspf_name);
	if (opt->write_raw)
		printf ("  Output (raw) file: %s\n\n", opt->outf_name);
	else
		printf ("  Output (%s bmp) file: %s\n\n",
				opt->write_nocomp ? "uncompressed" : "RLE compressed",
				opt->outf_name);

	return;
}

int
main (int argc, char *argv[])
{
	QFile      *bspfile = NULL;
	QFile      *outfile = NULL;
	long        i = 0, j = 0, k = 0, x = 0;

	dvertex_t  *vertexlist;
	dedge_t    *edgelist;
	dface_t    *facelist;
	int        *ledges;

	/* edge removal stuff */
	struct edge_extra_t *edge_extra = NULL;
	dvertex_t   v0, v1, vect;
	int         area = 0, usearea;

	float       minX = 0.0, maxX = 0.0, minY = 0.0, maxY = 0.0, minZ = 0.0;
	float       maxZ = 0.0, midZ = 0.0, tempf = 0.0;
	long        Zoffset0 = 0, Zoffset1 = 0;
	long        Z_Xdir = 1, Z_Ydir = -1;

	image_t    *image;
	struct options_t options;
	int         drawcol;

	bsp_t      *bsp;


	/* Enough args? */
	if (argc < 3) {
		show_help ();
		return 1;
	}

	/* Setup options */
	def_options (&options);
	get_options (&options, argc, argv);
	show_options (&options);

	bspfile = Qopen (options.bspf_name, "rbz");
	if (bspfile == NULL) {
		fprintf (stderr, "Error opening bsp file %s.\n", options.bspf_name);
		return 1;
	}
	bsp = LoadBSPFile (bspfile, Qfilesize (bspfile));
	Qclose (bspfile);

	vertexlist = bsp->vertexes;
	edgelist = bsp->edges;
	facelist = bsp->faces;
	ledges = bsp->surfedges;

	/* Precalc stuff if we're removing edges - - - - - - - - - - - */

	if (options.edgeremove) {
		printf ("Precalc edge removal stuff...\n");
		edge_extra = malloc (sizeof (struct edge_extra_t) * bsp->numedges);
		if (edge_extra == NULL) {
			fprintf (stderr, "Error allocating %ld bytes for extra edge info.",
					 (long) sizeof (struct edge_extra_t) * bsp->numedges);
			return 2;
		}
		/* initialize the array */
		for (i = 0; i < bsp->numedges; i++) {
			edge_extra[i].num_face_ref = 0;
			for (j = 0; j < MAX_REF_FACES; j++) {
				edge_extra[i].ref_faces[j] = -1;
			}
		}

		for (i = 0; i < bsp->numfaces; i++) {
			/* calculate the normal (cross product) */
			/* starting edge: edgelist[ledges[facelist[i].firstedge]] */
			/* number of edges: facelist[i].numedges; */

			/* quick hack - just take the first 2 edges */
			j = facelist[i].firstedge;
			k = j;
			vect.X = 0.0;
			vect.Y = 0.0;
			vect.Z = 0.0;
			while (vect.X == 0.0 && vect.Y == 0.0 && vect.Z == 0.0
				   && k < (facelist[i].numedges + j)) {
				/* If the first 2 are parallel edges, go with the next one */
				k++;

				if (ledges[j] > 0) {
					v0.X =
						vertexlist[edgelist[abs ((int) ledges[j])].v[0]].X -
						vertexlist[edgelist[abs ((int) ledges[j])].v[1]].X;
					v0.Y =
						vertexlist[edgelist[abs ((int) ledges[j])].v[0]].Y -
						vertexlist[edgelist[abs ((int) ledges[j])].v[1]].Y;
					v0.Z =
						vertexlist[edgelist[abs ((int) ledges[j])].v[0]].Z -
						vertexlist[edgelist[abs ((int) ledges[j])].v[1]].Z;

					v1.X =
						vertexlist[edgelist[abs ((int) ledges[k])].v[0]].X -
						vertexlist[edgelist[abs ((int) ledges[k])].v[1]].X;
					v1.Y =
						vertexlist[edgelist[abs ((int) ledges[k])].v[0]].Y -
						vertexlist[edgelist[abs ((int) ledges[k])].v[1]].Y;
					v1.Z =
						vertexlist[edgelist[abs ((int) ledges[k])].v[0]].Z -
						vertexlist[edgelist[abs ((int) ledges[k])].v[1]].Z;
				} else {
					/* negative index, therefore walk in reverse order */
					v0.X =
						vertexlist[edgelist[abs ((int) ledges[j])].v[1]].X -
						vertexlist[edgelist[abs ((int) ledges[j])].v[0]].X;
					v0.Y =
						vertexlist[edgelist[abs ((int) ledges[j])].v[1]].Y -
						vertexlist[edgelist[abs ((int) ledges[j])].v[0]].Y;
					v0.Z =
						vertexlist[edgelist[abs ((int) ledges[j])].v[1]].Z -
						vertexlist[edgelist[abs ((int) ledges[j])].v[0]].Z;

					v1.X =
						vertexlist[edgelist[abs ((int) ledges[k])].v[1]].X -
						vertexlist[edgelist[abs ((int) ledges[k])].v[0]].X;
					v1.Y =
						vertexlist[edgelist[abs ((int) ledges[k])].v[1]].Y -
						vertexlist[edgelist[abs ((int) ledges[k])].v[0]].Y;
					v1.Z =
						vertexlist[edgelist[abs ((int) ledges[k])].v[1]].Z -
						vertexlist[edgelist[abs ((int) ledges[k])].v[0]].Z;
				}

				/* cross product */
				vect.X = (v0.Y * v1.Z) - (v0.Z * v1.Y);
				vect.Y = (v0.Z * v1.X) - (v0.X * v1.Z);
				vect.Z = (v0.X * v1.Y) - (v0.Y * v1.X);

				/* Okay, it's not the REAL area, but i'm lazy, and since a lot
				   of mapmakers use rectangles anyways... */
				area = (int) (sqrt (v0.X * v0.X + v0.Y * v0.Y + v0.Z * v0.Z) *
							  sqrt (v1.X * v1.X + v1.Y * v1.Y + v1.Z * v1.Z));
			}							/* while */

			/* reduce cross product to a unit vector */
			tempf =
				(float)
				sqrt ((double)
					  (vect.X * vect.X + vect.Y * vect.Y + vect.Z * vect.Z));
			if (tempf > 0.0) {
				vect.X = vect.X / tempf;
				vect.Y = vect.Y / tempf;
				vect.Z = vect.Z / tempf;
			} /* if tempf */
			else {
				vect.X = 0.0;
				vect.Y = 0.0;
				vect.Z = 0.0;
			}

			/* Now go put ref in all edges... */
			for (j = 0; j < facelist[i].numedges; j++) {
				k = j + facelist[i].firstedge;
				x = edge_extra[abs ((int) ledges[k])].num_face_ref;
				if (edge_extra[abs ((int) ledges[k])].num_face_ref <
					MAX_REF_FACES) {
					x++;
					edge_extra[abs ((int) ledges[k])].num_face_ref = x;
					edge_extra[abs ((int) ledges[k])].ref_faces[x - 1] = i;
					edge_extra[abs ((int) ledges[k])].ref_faces_normal[x -
																	   1].X =
						vect.X;
					edge_extra[abs ((int) ledges[k])].ref_faces_normal[x -
																	   1].Y =
						vect.Y;
					edge_extra[abs ((int) ledges[k])].ref_faces_normal[x -
																	   1].Z =
						vect.Z;
					edge_extra[abs ((int) ledges[k])].ref_faces_area[x - 1] =
						area;
				}

			}							/* for */
		}
	}

	/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */

	printf ("Collecting min/max\n");
	/* Collect min and max */
	for (i = 0; i < bsp->numvertexes; i++) {

		/* Ugly hack - flip stuff around for different camera angles */
		switch (options.camera_axis) {
			case -1:					/* -X -- (-y <--> +y, +x into screen,
										   -x out of screen; -z down, +z up) */
				tempf = vertexlist[i].X;
				vertexlist[i].X = vertexlist[i].Y;
				vertexlist[i].Y = vertexlist[i].Z;
				vertexlist[i].Z = -tempf;
				break;

			case 1:					/* +X -- (+y <--> -y; -x into screen,
										   +x out of screen; -z down, +z up) */
				tempf = vertexlist[i].X;
				vertexlist[i].X = -vertexlist[i].Y;
				vertexlist[i].Y = vertexlist[i].Z;
				vertexlist[i].Z = tempf;
				break;

			case -2:					/* -Y -- (+x <--> -x; -y out of screen, 
										   +z up) */
				vertexlist[i].X = -vertexlist[i].X;
				tempf = vertexlist[i].Z;
				vertexlist[i].Z = vertexlist[i].Y;
				vertexlist[i].Y = tempf;;
				break;

			case 2:					/* +Y -- (-x <--> +x; +y out of screen, 
										   +z up) */
				tempf = vertexlist[i].Z;
				vertexlist[i].Z = -vertexlist[i].Y;
				vertexlist[i].Y = tempf;;
				break;

			case -3:					/* -Z -- negate X and Z (ie. 180 rotate 
										   along Y axis) */
				vertexlist[i].X = -vertexlist[i].X;
				vertexlist[i].Z = -vertexlist[i].Z;
				break;

			case 3:					/* +Z -- do nothing! */
			default:					/* do nothing! */
				break;
		}

		/* flip Y for proper screen cords */
		vertexlist[i].Y = -vertexlist[i].Y;

		/* max and min */
		if (i == 0) {
			minX = vertexlist[i].X;
			maxX = vertexlist[i].X;

			minY = vertexlist[i].Y;
			maxY = vertexlist[i].Y;

			minZ = vertexlist[i].Z;
			maxZ = vertexlist[i].Z;
		} else {
			if (vertexlist[i].X < minX)
				minX = vertexlist[i].X;
			if (vertexlist[i].X > maxX)
				maxX = vertexlist[i].X;

			if (vertexlist[i].Y < minY)
				minY = vertexlist[i].Y;
			if (vertexlist[i].Y > maxY)
				maxY = vertexlist[i].Y;

			if (vertexlist[i].Z < minZ)
				minZ = vertexlist[i].Z;
			if (vertexlist[i].Z > maxZ)
				maxZ = vertexlist[i].Z;
		}
	}
	if (options.z_pad == -1)
		options.z_pad = (long) (maxZ - minZ) / (options.scaledown * Z_PAD_HACK);

	midZ = (maxZ + minZ) / 2.0;
	printf ("\n");
	printf ("Bounds: X [%8.4f .. %8.4f] %8.4f\n", minX, maxX, (maxX - minX));
	printf ("        Y [%8.4f .. %8.4f] %8.4f\n", minY, maxY, (maxY - minY));
	printf ("        Z [%8.4f .. %8.4f] %8.4f - mid: %8.4f\n", minZ, maxZ,
			(maxZ - minZ), midZ);

	/* image array */
	image = malloc (sizeof (image_t));
	image->width =
		(long) ((maxX - minX) / options.scaledown) + (options.image_pad * 2) +
		(options.z_pad * 2);
	image->height =
		(long) ((maxY - minY) / options.scaledown) + (options.image_pad * 2) +
		(options.z_pad * 2);
	if (!
		(image->image =
		 malloc (sizeof (eightbit) * image->width * image->height))) {
		fprintf (stderr, "Error allocating image buffer %ldx%ld.\n",
				 image->width, image->height);
		return 0;
	} else {
		printf ("Allocated buffer %ldx%ld for image.\n", image->width,
				image->height);
		memset (image->image, 0,
				sizeof (eightbit) * image->width * image->height);
	}

	/* Zoffset calculations */
	switch (options.z_direction) {
		case 0:
			Z_Xdir = 0;					/* up */
			Z_Ydir = 1;
			break;

		case 1:
			Z_Xdir = 1;					/* up & right */
			Z_Ydir = -1;
			break;

		case 2:
			Z_Xdir = 1;					/* right */
			Z_Ydir = 0;
			break;

		case 3:
			Z_Xdir = 1;					/* down & right */
			Z_Ydir = 1;
			break;

		case 4:
			Z_Xdir = 0;					/* down */
			Z_Ydir = 1;
			break;

		case 5:
			Z_Xdir = -1;				/* down & left */
			Z_Ydir = 1;
			break;

		case 6:
			Z_Xdir = -1;				/* left */
			Z_Ydir = 0;
			break;

		case 7:
			Z_Xdir = -1;				/* up & left */
			Z_Ydir = -1;
			break;

		default:
			Z_Xdir = 1;					/* unknown - go with case 1 */
			Z_Ydir = -1;
			break;
	}

	/* Plot edges on image */
	fprintf (stderr, "Plotting edges...");
	k = 0;
	drawcol = (options.edgeremove) ? 64 : 32;
	for (i = 0; i < bsp->numedges; i++) {
		/* Do a check on this line ... see if we keep this line or not */

		/* run through all referenced faces */

		/* ICK ... do I want to check area of all faces? */
		usearea = INT_MAX;
		if (options.edgeremove) {
			if (edge_extra[i].num_face_ref > 1) {
				tempf = 1.0;
				/* dot products of all referenced faces */
				for (j = 0; j < edge_extra[i].num_face_ref - 1; j = j + 2) {
					/* dot product */

					tempf =
						tempf * (edge_extra[i].ref_faces_normal[j].X *
								 edge_extra[i].ref_faces_normal[j + 1].X +
								 edge_extra[i].ref_faces_normal[j].Y *
								 edge_extra[i].ref_faces_normal[j + 1].Y +
								 edge_extra[i].ref_faces_normal[j].Z *
								 edge_extra[i].ref_faces_normal[j + 1].Z);

					/* What is the smallest area this edge references? */
					if (usearea > edge_extra[i].ref_faces_area[j])
						usearea = edge_extra[i].ref_faces_area[j];
					if (usearea > edge_extra[i].ref_faces_area[j + 1])
						usearea = edge_extra[i].ref_faces_area[j + 1];
				}
			} else {
				tempf = 0.0;
			}
		} else {
			tempf = 0.0;
		}

		if ((abs (tempf) < options.flat_threshold) &&
			(usearea > options.area_threshold) &&
			(sqrt
			 ((vertexlist[edgelist[i].v[0]].X -
			   vertexlist[edgelist[i].v[1]].X) *
			  (vertexlist[edgelist[i].v[0]].X -
			   vertexlist[edgelist[i].v[1]].X) +
			  (vertexlist[edgelist[i].v[0]].Y -
			   vertexlist[edgelist[i].v[1]].Y) *
			  (vertexlist[edgelist[i].v[0]].Y -
			   vertexlist[edgelist[i].v[1]].Y) +
			  (vertexlist[edgelist[i].v[0]].Z -
			   vertexlist[edgelist[i].v[1]].Z) *
			  (vertexlist[edgelist[i].v[0]].Z -
			   vertexlist[edgelist[i].v[1]].Z)) >
			 options.linelen_threshold)) {
			Zoffset0 =
				(long) (options.z_pad *
						(vertexlist[edgelist[i].v[0]].Z - midZ) / (maxZ -
																	  minZ));
			Zoffset1 =
				(long) (options.z_pad *
						(vertexlist[edgelist[i].v[1]].Z - midZ) / (maxZ -
																	  minZ));

			bresline (image,
					  (long) ((vertexlist[edgelist[i].v[0]].X -
							   minX) / options.scaledown + options.image_pad +
							  options.z_pad + (float) (Zoffset0 * Z_Xdir)),
					  (long) ((vertexlist[edgelist[i].v[0]].Y -
							   minY) / options.scaledown + options.image_pad +
							  options.z_pad + (float) (Zoffset0 * Z_Ydir)),
					  (long) ((vertexlist[edgelist[i].v[1]].X -
							   minX) / options.scaledown + options.image_pad +
							  options.z_pad + (float) (Zoffset1 * Z_Xdir)),
					  (long) ((vertexlist[edgelist[i].v[1]].Y -
							   minY) / options.scaledown + options.image_pad +
							  options.z_pad + (float) (Zoffset1 * Z_Ydir)),
					  drawcol);
		} else {
			k++;
		}

	}
	printf ("%d edges plotted", bsp->numedges);
	if (options.edgeremove) {
		printf (" (%ld edges removed)\n", k);
	} else {
		printf ("\n");
	}

	/* Little gradient */
	for (i = 0; i <= 255; i++) {
		// across from top left
		plotpoint (image, i, 0, 255 - i);
		// down from top left
		plotpoint (image, 0, i, 255 - i);

		// back from top right
		plotpoint (image, image->width - i - 1, 0, 255 - i);
		// down from top right
		plotpoint (image, image->width - 1, i, 255 - i);

		// back from bottom right
		plotpoint (image, image->width - i - 1, image->height - 1, 255 - i);
		// up from bottom right
		plotpoint (image, image->width - 1, image->height - i - 1, 255 - i);

		// across from bottom left
		plotpoint (image, i, image->height - 1, 255 - i);
		// up from bottom left
		plotpoint (image, 0, image->height - i - 1, 255 - i);
	}

	/* Negate image if necessary */
	if (options.negative_image) {
		for (i = 0; i < image->height; i++) {
			for (j = 0; j < image->width; j++) {
				image->image[i * image->width + j] =
					255 - image->image[i * image->width + j];
			}
		}
	}

	/* Write image */

	outfile = Qopen (options.outf_name, "w");
	if (outfile == NULL) {
		fprintf (stderr, "Error opening output file %s.\n", options.outf_name);

		return 1;
	} else {
		pcx_t      *pcx;
		int         pcx_len, i;
		byte        palette[768];

		// quick and dirty greyscale palette
		for (i = 0; i < 256; i++) {
			palette[i * 3 + 0] = i;
			palette[i * 3 + 1] = i;
			palette[i * 3 + 2] = i;
		}

		Cvar_Init_Hash ();
		Cmd_Init_Hash ();
		Cvar_Init ();
		Sys_Init_Cvars ();
		Cmd_Init ();

		Memory_Init (malloc (MEMSIZE), MEMSIZE);
		pcx = EncodePCX (image->image, image->width, image->height,
						 image->width, palette, false, &pcx_len);
		if (Qwrite (outfile, pcx, pcx_len) != pcx_len) {
			fprintf (stderr, "Error writing to %s\n", options.outf_name);
			return 1;
		}
	}

	printf ("File written to %s.\n", options.outf_name);
	Qclose (outfile);

	/* Close, done! */

	free (image->image);
	free (image);
	if (options.edgeremove) {
		free (edge_extra);
	}

	if (options.write_raw) {
		printf ("\nIf you want to:\n"
				"  convert -verbose -colors 256 -size %ldx%ld gray:%s map.jpg\n"
				"(this is using ImageMagick's convert)\n",
				image->width, image->height, options.outf_name);
	} else {
		printf ("\nIf you want to:\n"
				"  convert -verbose -colors 256 pcx:%s map.jpg\n"
				"(this is using ImageMagick's convert)\n",
				options.outf_name);
	}

	return 0;
}