/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.

This file is part of Quake III Arena source code.

Quake III Arena source code 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.

Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

#include "qbsp.h"
#include "l_bsp_hl.h"
#include "l_bsp_q1.h"
#include "l_bsp_q2.h"
#include "l_bsp_q3.h"
#include "l_bsp_sin.h"
#include "l_mem.h"
#include "../botlib/aasfile.h"		//aas_bbox_t
#include "aas_store.h"		//AAS_MAX_BBOXES
#include "aas_cfg.h"

#define Sign(x)		(x < 0 ? 1 : 0)

int					nummapbrushes;
mapbrush_t			mapbrushes[MAX_MAPFILE_BRUSHES];

int					nummapbrushsides;
side_t				brushsides[MAX_MAPFILE_BRUSHSIDES];
brush_texture_t		side_brushtextures[MAX_MAPFILE_BRUSHSIDES];

int					nummapplanes;
plane_t				mapplanes[MAX_MAPFILE_PLANES];
int					mapplaneusers[MAX_MAPFILE_PLANES];

#define				PLANE_HASHES	1024
plane_t				*planehash[PLANE_HASHES];
vec3_t				map_mins, map_maxs;

#ifdef SIN
textureref_t		side_newrefs[MAX_MAPFILE_BRUSHSIDES];
#endif

map_texinfo_t		map_texinfo[MAX_MAPFILE_TEXINFO];
int					map_numtexinfo;
int					loadedmaptype;		//loaded map type

// undefine to make plane finding use linear sort
#define	USE_HASHING

int c_boxbevels;
int c_edgebevels;
int c_areaportals;
int c_clipbrushes;
int c_squattbrushes;
int c_writtenbrushes;

/*
=============================================================================

PLANE FINDING

=============================================================================
*/


//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
int PlaneSignBits(vec3_t normal)
{
	int i, signbits;

	signbits = 0;
	for (i = 2; i >= 0; i--)
	{
		signbits = (signbits << 1) + Sign(normal[i]);
	} //end for
	return signbits;
} //end of the function PlaneSignBits
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
int PlaneTypeForNormal(vec3_t normal)
{
	vec_t	ax, ay, az;

// NOTE: should these have an epsilon around 1.0?
	if (normal[0] == 1.0 || normal[0] == -1.0)
		return PLANE_X;
	if (normal[1] == 1.0 || normal[1] == -1.0)
		return PLANE_Y;
	if (normal[2] == 1.0 || normal[2] == -1.0)
		return PLANE_Z;

	ax = fabs(normal[0]);
	ay = fabs(normal[1]);
	az = fabs(normal[2]);

	if (ax >= ay && ax >= az)
		return PLANE_ANYX;
	if (ay >= ax && ay >= az)
		return PLANE_ANYY;
	return PLANE_ANYZ;
} //end of the function PlaneTypeForNormal
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
//ME NOTE: changed from 0.00001
#define	NORMAL_EPSILON	0.0001
//ME NOTE: changed from 0.01
#define	DIST_EPSILON	0.02
qboolean	PlaneEqual(plane_t *p, vec3_t normal, vec_t dist)
{
#if 1
	if (
	   fabs(p->normal[0] - normal[0]) < NORMAL_EPSILON
	&& fabs(p->normal[1] - normal[1]) < NORMAL_EPSILON
	&& fabs(p->normal[2] - normal[2]) < NORMAL_EPSILON
	&& fabs(p->dist - dist) < DIST_EPSILON )
		return true;
#else
	if (p->normal[0] == normal[0]
		&& p->normal[1] == normal[1]
		&& p->normal[2] == normal[2]
		&& p->dist == dist)
		return true;
#endif
	return false;
} //end of the function PlaneEqual
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void AddPlaneToHash(plane_t *p)
{
	int		hash;

	hash = (int)fabs(p->dist) / 8;
	hash &= (PLANE_HASHES-1);

	p->hash_chain = planehash[hash];
	planehash[hash] = p;
} //end of the function AddPlaneToHash
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
int CreateNewFloatPlane (vec3_t normal, vec_t dist)
{
	plane_t	*p, temp;

	if (VectorLength(normal) < 0.5)
		Error ("FloatPlane: bad normal");
	// create a new plane
	if (nummapplanes+2 > MAX_MAPFILE_PLANES)
		Error ("MAX_MAPFILE_PLANES");

	p = &mapplanes[nummapplanes];
	VectorCopy (normal, p->normal);
	p->dist = dist;
	p->type = (p+1)->type = PlaneTypeForNormal (p->normal);
	p->signbits = PlaneSignBits(p->normal);

	VectorSubtract (vec3_origin, normal, (p+1)->normal);
	(p+1)->dist = -dist;
	(p+1)->signbits = PlaneSignBits((p+1)->normal);

	nummapplanes += 2;

	// allways put axial planes facing positive first
	if (p->type < 3)
	{
		if (p->normal[0] < 0 || p->normal[1] < 0 || p->normal[2] < 0)
		{
			// flip order
			temp = *p;
			*p = *(p+1);
			*(p+1) = temp;

			AddPlaneToHash (p);
			AddPlaneToHash (p+1);
			return nummapplanes - 1;
		}
	}

	AddPlaneToHash (p);
	AddPlaneToHash (p+1);
	return nummapplanes - 2;
} //end of the function CreateNewFloatPlane
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void SnapVector(vec3_t normal)
{
	int		i;

	for (i=0 ; i<3 ; i++)
	{
		if ( fabs(normal[i] - 1) < NORMAL_EPSILON )
		{
			VectorClear (normal);
			normal[i] = 1;
			break;
		}
		if ( fabs(normal[i] - -1) < NORMAL_EPSILON )
		{
			VectorClear (normal);
			normal[i] = -1;
			break;
		}
	}
} //end of the function SnapVector
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void SnapPlane(vec3_t normal, vec_t *dist)
{
	SnapVector(normal);

	if (fabs(*dist-Q_rint(*dist)) < DIST_EPSILON)
		*dist = Q_rint(*dist);
} //end of the function SnapPlane
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
#ifndef USE_HASHING
int FindFloatPlane(vec3_t normal, vec_t dist)
{
	int i;
	plane_t *p;

	SnapPlane(normal, &dist);
	for (i = 0, p = mapplanes; i < nummapplanes; i++, p++)
	{
		if (PlaneEqual (p, normal, dist))
		{
			mapplaneusers[i]++;
			return i;
		} //end if
	} //end for
	i = CreateNewFloatPlane (normal, dist);
	mapplaneusers[i]++;
	return i;
} //end of the function FindFloatPlane
#else
int FindFloatPlane (vec3_t normal, vec_t dist)
{
	int i;
	plane_t *p;
	int hash, h;

	SnapPlane (normal, &dist);
	hash = (int)fabs(dist) / 8;
	hash &= (PLANE_HASHES-1);

	// search the border bins as well
	for (i = -1; i <= 1; i++)
	{
		h = (hash+i)&(PLANE_HASHES-1);
		for (p = planehash[h]; p; p = p->hash_chain)
		{
			if (PlaneEqual(p, normal, dist))
			{
				mapplaneusers[p-mapplanes]++;
				return p - mapplanes;
			} //end if
		} //end for
	} //end for
	i = CreateNewFloatPlane (normal, dist);
	mapplaneusers[i]++;
	return i;
} //end of the function FindFloatPlane
#endif
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
int PlaneFromPoints (int *p0, int *p1, int *p2)
{
	vec3_t	t1, t2, normal;
	vec_t	dist;

	VectorSubtract (p0, p1, t1);
	VectorSubtract (p2, p1, t2);
	CrossProduct (t1, t2, normal);
	VectorNormalize (normal);

	dist = DotProduct (p0, normal);

	return FindFloatPlane (normal, dist);
} //end of the function PlaneFromPoints
//===========================================================================
// Adds any additional planes necessary to allow the brush to be expanded
// against axial bounding boxes
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void AddBrushBevels (mapbrush_t *b)
{
	int		axis, dir;
	int		i, j, k, l, order;
	side_t	sidetemp;
	brush_texture_t	tdtemp;
#ifdef SIN
   textureref_t trtemp;
#endif
	side_t	*s, *s2;
	vec3_t	normal;
	float	dist;
	winding_t	*w, *w2;
	vec3_t	vec, vec2;
	float	d;

	//
	// add the axial planes
	//
	order = 0;
	for (axis=0 ; axis <3 ; axis++)
	{
		for (dir=-1 ; dir <= 1 ; dir+=2, order++)
		{
			// see if the plane is allready present
			for (i=0, s=b->original_sides ; i<b->numsides ; i++,s++)
			{
				if (mapplanes[s->planenum].normal[axis] == dir)
					break;
			}

			if (i == b->numsides)
			{	// add a new side
				if (nummapbrushsides == MAX_MAP_BRUSHSIDES)
					Error ("MAX_MAP_BRUSHSIDES");
				nummapbrushsides++;
				b->numsides++;
				VectorClear (normal);
				normal[axis] = dir;
				if (dir == 1)
					dist = b->maxs[axis];
				else
					dist = -b->mins[axis];
				s->planenum = FindFloatPlane (normal, dist);
				s->texinfo = b->original_sides[0].texinfo;
#ifdef SIN
				s->lightinfo = b->original_sides[0].lightinfo;
#endif
				s->contents = b->original_sides[0].contents;
				s->flags |= SFL_BEVEL;
				c_boxbevels++;
			}

			// if the plane is not in it canonical order, swap it
			if (i != order)
			{
				sidetemp = b->original_sides[order];
				b->original_sides[order] = b->original_sides[i];
				b->original_sides[i] = sidetemp;

				j = b->original_sides - brushsides;
				tdtemp = side_brushtextures[j+order];
				side_brushtextures[j+order] = side_brushtextures[j+i];
				side_brushtextures[j+i] = tdtemp;

#ifdef SIN
				trtemp = side_newrefs[j+order];
				side_newrefs[j+order] = side_newrefs[j+i];
				side_newrefs[j+i] = trtemp;
#endif
			}
		}
	}

	//
	// add the edge bevels
	//
	if (b->numsides == 6)
		return;		// pure axial

	// test the non-axial plane edges
	for (i=6 ; i<b->numsides ; i++)
	{
		s = b->original_sides + i;
		w = s->winding;
		if (!w)
			continue;
		for (j=0 ; j<w->numpoints ; j++)
		{
			k = (j+1)%w->numpoints;
			VectorSubtract (w->p[j], w->p[k], vec);
			if (VectorNormalize (vec) < 0.5)
				continue;
			SnapVector (vec);
			for (k=0 ; k<3 ; k++)
				if ( vec[k] == -1 || vec[k] == 1)
					break;	// axial
			if (k != 3)
				continue;	// only test non-axial edges

			// try the six possible slanted axials from this edge
			for (axis=0 ; axis <3 ; axis++)
			{
				for (dir=-1 ; dir <= 1 ; dir+=2)
				{
					// construct a plane
					VectorClear (vec2);
					vec2[axis] = dir;
					CrossProduct (vec, vec2, normal);
					if (VectorNormalize (normal) < 0.5)
						continue;
					dist = DotProduct (w->p[j], normal);

					// if all the points on all the sides are
					// behind this plane, it is a proper edge bevel
					for (k=0 ; k<b->numsides ; k++)
					{
						// if this plane has allready been used, skip it
						if (PlaneEqual (&mapplanes[b->original_sides[k].planenum]
							, normal, dist) )
							break;

						w2 = b->original_sides[k].winding;
						if (!w2)
							continue;
						for (l=0 ; l<w2->numpoints ; l++)
						{
							d = DotProduct (w2->p[l], normal) - dist;
							if (d > 0.1)
								break;	// point in front
						}
						if (l != w2->numpoints)
							break;
					}

					if (k != b->numsides)
						continue;	// wasn't part of the outer hull
					// add this plane
					if (nummapbrushsides == MAX_MAP_BRUSHSIDES)
						Error ("MAX_MAP_BRUSHSIDES");
					nummapbrushsides++;
					s2 = &b->original_sides[b->numsides];
					s2->planenum = FindFloatPlane (normal, dist);
					s2->texinfo = b->original_sides[0].texinfo;
#ifdef SIN
					s2->lightinfo = b->original_sides[0].lightinfo;
#endif
					s2->contents = b->original_sides[0].contents;
					s2->flags |= SFL_BEVEL;
					c_edgebevels++;
					b->numsides++;
				}
			}
		}
	}
} //end of the function AddBrushBevels
//===========================================================================
// creates windigs for sides and mins / maxs for the brush
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
qboolean MakeBrushWindings(mapbrush_t *ob)
{
	int			i, j;
	winding_t	*w;
	side_t		*side;
	plane_t		*plane;

	ClearBounds (ob->mins, ob->maxs);

	for (i = 0; i < ob->numsides; i++)
	{
		plane = &mapplanes[ob->original_sides[i].planenum];
		w = BaseWindingForPlane(plane->normal, plane->dist);
		for (j = 0; j <ob->numsides && w; j++)
		{
			if (i == j) continue;
			if (ob->original_sides[j].flags & SFL_BEVEL) continue;
			plane = &mapplanes[ob->original_sides[j].planenum^1];
			ChopWindingInPlace(&w, plane->normal, plane->dist, 0); //CLIP_EPSILON);
		}

		side = &ob->original_sides[i];
		side->winding = w;
		if (w)
		{
			side->flags |= SFL_VISIBLE;
			for (j = 0; j < w->numpoints; j++)
				AddPointToBounds (w->p[j], ob->mins, ob->maxs);
		}
	}

	for (i = 0; i < 3; i++)
	{
		//IDBUG: all the indexes into the mins and maxs were zero (not using i)
		if (ob->mins[i] < -MAX_MAP_BOUNDS || ob->maxs[i] > MAX_MAP_BOUNDS)
		{
			Log_Print("entity %i, brush %i: bounds out of range\n", ob->entitynum, ob->brushnum);
			ob->numsides = 0; //remove the brush
			break;
		} //end if
		if (ob->mins[i] > MAX_MAP_BOUNDS || ob->maxs[i] < -MAX_MAP_BOUNDS)
		{
			Log_Print("entity %i, brush %i: no visible sides on brush\n", ob->entitynum, ob->brushnum);
			ob->numsides = 0; //remove the brush
			break;
		} //end if
	} //end for
	return true;
} //end of the function MakeBrushWindings
//===========================================================================
// FIXME: currently doesn't mark all bevels
// NOTE: when one brush bevel is found the remaining sides of the brush
//       are bevels as well (when the brush isn't expanded for AAS :))
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void MarkBrushBevels(mapbrush_t *brush)
{
	int i;
	int we;
	side_t *s;

	//check all the sides of the brush
	for (i = 0; i < brush->numsides; i++)
	{
		s = brush->original_sides + i;
		//if the side has no winding
		if (!s->winding)
		{
			Log_Write("MarkBrushBevels: brush %d no winding", brush->brushnum);
			s->flags |= SFL_BEVEL;
		} //end if
		//if the winding is tiny
		else if (WindingIsTiny(s->winding))
		{
			s->flags |= SFL_BEVEL;
			Log_Write("MarkBrushBevels: brush %d tiny winding", brush->brushnum);
		} //end else if
		//if the winding has errors
		else
		{
			we = WindingError(s->winding);
			if (we == WE_NOTENOUGHPOINTS
					|| we == WE_SMALLAREA
					|| we == WE_POINTBOGUSRANGE
//					|| we == WE_NONCONVEX
					)
			{
				Log_Write("MarkBrushBevels: brush %d %s", brush->brushnum, WindingErrorString());
				s->flags |= SFL_BEVEL;
			} //end else if
		} //end else
		if (s->flags & SFL_BEVEL)
		{
			s->flags &= ~SFL_VISIBLE;
			//if the side has a valid plane
			if (s->planenum > 0 && s->planenum < nummapplanes)
			{
				//if it is an axial plane
				if (mapplanes[s->planenum].type < 3) c_boxbevels++;
				else c_edgebevels++;
			} //end if
		} //end if
	} //end for
} //end of the function MarkBrushBevels
//===========================================================================
// returns true if the map brush already exists
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
int BrushExists(mapbrush_t *brush)
{
	int i, s1, s2;
	side_t *side1, *side2;
	mapbrush_t *brush1, *brush2;

	for (i = 0; i < nummapbrushes; i++)
	{
		brush1 = brush;
		brush2 = &mapbrushes[i];
		//compare the brushes
		if (brush1->entitynum != brush2->entitynum) continue;
		//if (brush1->contents != brush2->contents) continue;
		if (brush1->numsides != brush2->numsides) continue;
		for (s1 = 0; s1 < brush1->numsides; s1++)
		{
			side1 = brush1->original_sides + s1;
			//
			for (s2 = 0; s2 < brush2->numsides; s2++)
			{
				side2 = brush2->original_sides + s2;
				//
				if ((side1->planenum & ~1) == (side2->planenum & ~1)
//						&& side1->texinfo == side2->texinfo
//						&& side1->contents == side2->contents
//						&& side1->surf == side2->surf
					) break;
			} //end if
			if (s2 >= brush2->numsides) break;
		} //end for
		if (s1 >= brush1->numsides) return true;
	} //end for
	return false;
} //end of the function BrushExists
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
qboolean WriteMapBrush(FILE *fp, mapbrush_t *brush, vec3_t origin)
{
	int sn, rotate, shift[2], sv, tv, planenum, p1, i, j;
	float scale[2], originshift[2], ang1, ang2, newdist;
	vec3_t vecs[2], axis[2];
	map_texinfo_t *ti;
	winding_t *w;
	side_t *s;
	plane_t *plane;

	if (noliquids)
	{
		if (brush->contents & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA))
		{
			return true;
		} //end if
	} //end if
	//if the brush has no contents
	if (!brush->contents) return true;
	//print the leading {
	if (fprintf(fp, " { //brush %d\n", brush->brushnum) < 0) return false;
	//write brush sides
	for (sn = 0; sn < brush->numsides; sn++)
	{
		s = brush->original_sides + sn;
		//don't write out bevels
		if (!(s->flags & SFL_BEVEL))
		{
			//if the entity has an origin set
			if (origin[0] || origin[1] || origin[2])
			{
				newdist = mapplanes[s->planenum].dist +
					DotProduct(mapplanes[s->planenum].normal, origin);
				planenum = FindFloatPlane(mapplanes[s->planenum].normal, newdist);
			} //end if
			else
			{
				planenum = s->planenum;
			} //end else
			//always take the first plane, then flip the points if necesary
			plane = &mapplanes[planenum & ~1];
			w = BaseWindingForPlane(plane->normal, plane->dist);
			//
			for (i = 0; i < 3; i++)
			{
				for (j = 0; j < 3; j++)
				{
					if (fabs(w->p[i][j]) < 0.2) w->p[i][j] = 0;
					else if (fabs((int)w->p[i][j] - w->p[i][j]) < 0.3) w->p[i][j] = (int) w->p[i][j];
					//w->p[i][j] = (int) (w->p[i][j] + 0.2);
				} //end for
			} //end for
			//three non-colinear points to define the plane
			if (planenum & 1) p1 = 1;
			else p1 = 0;
			if (fprintf(fp,"  ( %5i %5i %5i ) ", (int)w->p[p1][0], (int)w->p[p1][1], (int)w->p[p1][2]) < 0) return false;
			if (fprintf(fp,"( %5i %5i %5i ) ", (int)w->p[!p1][0], (int)w->p[!p1][1], (int)w->p[!p1][2]) < 0) return false;
			if (fprintf(fp,"( %5i %5i %5i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]) < 0) return false;
			//free the winding
			FreeWinding(w);
			//
			if (s->texinfo == TEXINFO_NODE)
			{
				if (brush->contents & CONTENTS_PLAYERCLIP)
				{
					//player clip
					if (loadedmaptype == MAPTYPE_SIN)
					{
						if (fprintf(fp, "generic/misc/clip 0 0 0 1 1") < 0) return false;
					} //end if
					else if (loadedmaptype == MAPTYPE_QUAKE2)
					{	//FIXME: don't always use e1u1
						if (fprintf(fp, "e1u1/clip 0 0 0 1 1") < 0) return false;
					} //end else
					else if (loadedmaptype == MAPTYPE_QUAKE3)
					{
						if (fprintf(fp, "e1u1/clip 0 0 0 1 1") < 0) return false;
					} //end else if
					else
					{
						if (fprintf(fp, "clip 0 0 0 1 1") < 0) return false;
					} //end else
				} //end if
				else if (brush->contents == CONTENTS_MONSTERCLIP)
				{
					//monster clip
					if (loadedmaptype == MAPTYPE_SIN)
					{
						if (fprintf(fp, "generic/misc/monster 0 0 0 1 1") < 0) return false;
					} //end if
					else if (loadedmaptype == MAPTYPE_QUAKE2)
					{
						if (fprintf(fp, "e1u1/clip_mon 0 0 0 1 1") < 0) return false;
					} //end else
					else
					{
						if (fprintf(fp, "clip 0 0 0 1 1") < 0) return false;
					} //end else
				} //end else
				else
				{
					if (fprintf(fp, "clip 0 0 0 1 1") < 0) return false;
					Log_Write("brush->contents = %d\n", brush->contents);
				} //end else
			} //end if
			else if (loadedmaptype == MAPTYPE_SIN && s->texinfo == 0)
			{
				if (brush->contents & CONTENTS_DUMMYFENCE)
				{
					if (fprintf(fp, "generic/misc/fence 0 0 0 1 1") < 0) return false;
				} //end if
				else if (brush->contents & CONTENTS_MIST)
				{
					if (fprintf(fp, "generic/misc/volumetric_base 0 0 0 1 1") < 0) return false;
				} //end if
				else //unknown so far
				{
					if (fprintf(fp, "generic/misc/red 0 0 0 1 1") < 0) return false;
				} //end else
			} //end if
			else if (loadedmaptype == MAPTYPE_QUAKE3)
			{
				//always use the same texture
				if (fprintf(fp, "e2u3/floor1_2 0 0 0 1 1 1 0 0") < 0) return false;
			} //end else if
			else
			{
				//*
				ti = &map_texinfo[s->texinfo];
				//the scaling of the texture
				scale[0] = 1 / VectorNormalize2(ti->vecs[0], vecs[0]);
				scale[1] = 1 / VectorNormalize2(ti->vecs[1], vecs[1]);
				//
				TextureAxisFromPlane(plane, axis[0], axis[1]);
				//calculate texture shift done by entity origin
				originshift[0] = DotProduct(origin, axis[0]);
				originshift[1] = DotProduct(origin, axis[1]);
				//the texture shift without origin shift
				shift[0] = ti->vecs[0][3] - originshift[0];
				shift[1] = ti->vecs[1][3] - originshift[1];
				//
				if (axis[0][0]) sv = 0;
				else if (axis[0][1]) sv = 1;
				else sv = 2;
				if (axis[1][0]) tv = 0;
				else if (axis[1][1]) tv = 1;
				else tv = 2;
				//calculate rotation of texture
				if (vecs[0][tv] == 0) ang1 = vecs[0][sv] > 0 ? 90.0 : -90.0;
				else ang1 = atan2(vecs[0][sv], vecs[0][tv]) * 180 / Q_PI;
				if (ang1 < 0) ang1 += 360;
				if (ang1 >= 360) ang1 -= 360;
				if (axis[0][tv] == 0) ang2 = axis[0][sv] > 0 ? 90.0 : -90.0;
				else ang2 = atan2(axis[0][sv], axis[0][tv]) * 180 / Q_PI;
				if (ang2 < 0) ang2 += 360;
				if (ang2 >= 360) ang2 -= 360;
				rotate = ang2 - ang1;
				if (rotate < 0) rotate += 360;
				if (rotate >= 360) rotate -= 360;
				//write the texture info
				if (fprintf(fp, "%s %d %d %d", ti->texture, shift[0], shift[1], rotate) < 0) return false;
				if (fabs(scale[0] - ((int) scale[0])) < 0.001)
				{
					if (fprintf(fp, " %d", (int) scale[0]) < 0) return false;
				} //end if
				else
				{
					if (fprintf(fp, " %4f", scale[0]) < 0) return false;
				} //end if
				if (fabs(scale[1] - ((int) scale[1])) < 0.001)
				{
					if (fprintf(fp, " %d", (int) scale[1]) < 0) return false;
				} //end if
				else
				{
					if (fprintf(fp, " %4f", scale[1]) < 0) return false;
				} //end else
				//write the extra brush side info
				if (loadedmaptype == MAPTYPE_QUAKE2)
				{
					// ZTM: %ld to %d
					if (fprintf(fp, " %d %d %d", s->contents, ti->flags, ti->value) < 0) return false;
				} //end if
				//*/
			} //end else
			if (fprintf(fp, "\n") < 0) return false;
		} //end if
	} //end if
	if (fprintf(fp, " }\n") < 0) return false;
	c_writtenbrushes++;
	return true;
} //end of the function WriteMapBrush
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
qboolean WriteOriginBrush(FILE *fp, vec3_t origin)
{
	vec3_t normal;
	float dist;
	int i, s;
	winding_t *w;

	if (fprintf(fp, " {\n") < 0) return false;
	//
	for (i = 0; i < 3; i++)
	{
		for (s = -1; s <= 1; s += 2)
		{
			//
			VectorClear(normal);
			normal[i] = s;
			dist = origin[i] * s + 16;
			//
			w = BaseWindingForPlane(normal, dist);
			//three non-colinear points to define the plane
			if (fprintf(fp,"  ( %5i %5i %5i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2]) < 0) return false;
			if (fprintf(fp,"( %5i %5i %5i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2]) < 0) return false;
			if (fprintf(fp,"( %5i %5i %5i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]) < 0) return false;
			//free the winding
			FreeWinding(w);
			//write origin texture:
			// CONTENTS_ORIGIN = 16777216
			// SURF_NODRAW = 128
			if (loadedmaptype == MAPTYPE_SIN)
			{
				if (fprintf(fp, "generic/misc/origin 0 0 0 1 1") < 0) return false;
			} //end if
			else if (loadedmaptype == MAPTYPE_HALFLIFE)
			{
				if (fprintf(fp, "origin 0 0 0 1 1") < 0) return false;
			} //end if
			else
			{
				if (fprintf(fp, "e1u1/origin 0 0 0 1 1") < 0) return false;
			} //end else
			//Quake2 extra brush side info
			if (loadedmaptype == MAPTYPE_QUAKE2)
			{
				//if (fprintf(fp, " 16777216 128 0") < 0) return false;
			} //end if
			if (fprintf(fp, "\n") < 0) return false;
		} //end for
	} //end for
	if (fprintf(fp, " }\n") < 0) return false;
	c_writtenbrushes++;
	return true;
} //end of the function WriteOriginBrush
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
mapbrush_t *GetAreaPortalBrush(entity_t *mapent)
{
	int portalnum, bn;
	mapbrush_t *brush;

	//the area portal number
	portalnum = mapent->areaportalnum;
	//find the area portal brush in the world brushes
	for (bn = 0, brush = NULL; bn < nummapbrushes && portalnum; bn++)
	{
		brush = &mapbrushes[bn];
		//must be in world entity
		if (brush->entitynum == 0)
		{
			if (brush->contents & CONTENTS_AREAPORTAL)
			{
				portalnum--;
			} //end if
		} //end if
	} //end for
	if (bn < nummapbrushes)
	{
		return brush;
	} //end if
	else
	{
		Log_Print("area portal %d brush not found\n", mapent->areaportalnum);
		return NULL;
	} //end else
} //end of the function GetAreaPortalBrush
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
qboolean WriteMapFileSafe(FILE *fp)
{
	char key[1024], value[1024];
	int i, bn, entitybrushes;
	epair_t *ep;
	mapbrush_t *brush;
	entity_t *mapent;
	//vec3_t vec_origin = {0, 0, 0};

	//
	if (fprintf(fp,"//=====================================================\n"
			"//\n"
			"// map file created with BSPC "BSPC_VERSION"\n"
			"//\n"
			"// BSPC is designed to decompile material in which you own the copyright\n"
			"// or have obtained permission to decompile from the copyright owner. Unless\n"
			"// you own the copyright or have permission to decompile from the copyright\n"
			"// owner, you may be violating copyright law and be subject to payment of\n"
			"// damages and other remedies. If you are uncertain about your rights, contact\n"
			"// your legal advisor.\n"
			"//\n") < 0) return false;
	if (loadedmaptype == MAPTYPE_SIN)
	{
		if (fprintf(fp,
						"// generic/misc/red is used for unknown textures\n") < 0) return false;
	} //end if
	if (fprintf(fp,"//\n"
						"//=====================================================\n") < 0) return false;
	//write out all the entities
	for (i = 0; i < num_entities; i++)
	{
		mapent = &entities[i];
		if (!mapent->epairs)
		{
			continue;
		} //end if
		if (fprintf(fp, "{\n") < 0) return false;
		//
		if (loadedmaptype == MAPTYPE_QUAKE3)
		{
			if (!stricmp(ValueForKey(mapent, "classname"), "light"))
			{
				SetKeyValue(mapent, "light", "10000");
			} //end if
		} //end if
		//write epairs
		for (ep = mapent->epairs; ep; ep = ep->next)
		{
			strcpy(key, ep->key);
			StripTrailing (key);
			strcpy(value, ep->value);
			StripTrailing(value);
			//
			if (loadedmaptype == MAPTYPE_QUAKE2 ||
					loadedmaptype == MAPTYPE_SIN)
			{
				//don't write an origin for BSP models
				if (mapent->modelnum >= 0 && !strcmp(key, "origin")) continue;
			} //end if
			//don't write BSP model numbers
			if (mapent->modelnum >= 0 && !strcmp(key, "model") && value[0] == '*') continue;
			//
			if (fprintf(fp, " \"%s\" \"%s\"\n", key, value) < 0) return false;
		} //end for
		//
		if (ValueForKey(mapent, "origin")) GetVectorForKey(mapent, "origin", mapent->origin);
		else mapent->origin[0] = mapent->origin[1] = mapent->origin[2] = 0;
		//if this is an area portal entity
		if (!strcmp("func_areaportal", ValueForKey(mapent, "classname")))
		{
			brush = GetAreaPortalBrush(mapent);
			if (!brush) return false;
			if (!WriteMapBrush(fp, brush, mapent->origin)) return false;
		} //end if
		else
		{
			entitybrushes = false;
			//write brushes
			for (bn = 0; bn < nummapbrushes; bn++)
			{
				brush = &mapbrushes[bn];
				//if the brush is part of this entity
				if (brush->entitynum == i)
				{
					//don't write out area portal brushes in the world
					if (!((brush->contents & CONTENTS_AREAPORTAL) && brush->entitynum == 0))
					{
						/*
						if (!strcmp("func_door_rotating", ValueForKey(mapent, "classname")))
						{
							AAS_PositionFuncRotatingBrush(mapent, brush);
							if (!WriteMapBrush(fp, brush, vec_origin)) return false;
						} //end if
						else //*/
						{
							if (!WriteMapBrush(fp, brush, mapent->origin)) return false;
						} //end else
						entitybrushes = true;
					} //end if
				} //end if
			} //end for
			//if the entity had brushes
			if (entitybrushes)
			{
				//if the entity has an origin set
				if (mapent->origin[0] || mapent->origin[1] || mapent->origin[2])
				{
					if (!WriteOriginBrush(fp, mapent->origin)) return false;
				} //end if
			} //end if
		} //end else
		if (fprintf(fp, "}\n") < 0) return false;
	} //end for
	if (fprintf(fp, "//total of %d brushes\n", c_writtenbrushes) < 0) return false;
	return true;
} //end of the function WriteMapFileSafe
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void WriteMapFile(char *filename)
{
	FILE *fp;
	double start_time;

	c_writtenbrushes = 0;
	//the time started
	start_time = I_FloatTime();
	//
	Log_Print("writing %s\n", filename);
	fp = fopen(filename, "wb");
	if (!fp)
	{
		Log_Print("can't open %s\n", filename);
		return;
	} //end if
	if (!WriteMapFileSafe(fp))
	{
		fclose(fp);
		Log_Print("error writing map file %s\n", filename);
		return;
	} //end if
	fclose(fp);
	//display creation time
	Log_Print("written %d brushes\n", c_writtenbrushes);
	Log_Print("map file written in %5.0f seconds\n", I_FloatTime() - start_time);
} //end of the function WriteMapFile
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void PrintMapInfo(void)
{
	Log_Print("\n");
	Log_Print("%6i brushes\n", nummapbrushes);
	Log_Print("%6i brush sides\n", nummapbrushsides);
//	Log_Print("%6i clipbrushes\n", c_clipbrushes);
//	Log_Print("%6i total sides\n", nummapbrushsides);
//	Log_Print("%6i boxbevels\n", c_boxbevels);
//	Log_Print("%6i edgebevels\n", c_edgebevels);
//	Log_Print("%6i entities\n", num_entities);
//	Log_Print("%6i planes\n", nummapplanes);
//	Log_Print("%6i areaportals\n", c_areaportals);
//	Log_Print("%6i squatt brushes\n", c_squattbrushes);
//	Log_Print("size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", map_mins[0],map_mins[1],map_mins[2],
//		map_maxs[0],map_maxs[1],map_maxs[2]);
} //end of the function PrintMapInfo
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void ResetMapLoading(void)
{
	int i;
	epair_t *ep, *nextep;

	Q2_ResetMapLoading();
	Sin_ResetMapLoading();

	//free all map brush side windings
	for (i = 0; i < nummapbrushsides; i++)
	{
		if (brushsides[i].winding)
		{
			FreeWinding(brushsides[i].winding);
		} //end for
	} //end for

	//reset regular stuff
	nummapbrushes = 0;
	memset(mapbrushes, 0, MAX_MAPFILE_BRUSHES * sizeof(mapbrush_t));
	//
	nummapbrushsides = 0;
	memset(brushsides, 0, MAX_MAPFILE_BRUSHSIDES * sizeof(side_t));
	memset(side_brushtextures, 0, MAX_MAPFILE_BRUSHSIDES * sizeof(brush_texture_t));
	//
	nummapplanes = 0;
	memset(mapplanes, 0, MAX_MAPFILE_PLANES * sizeof(plane_t));
	//
	memset(planehash, 0, PLANE_HASHES * sizeof(plane_t *));
	//
	memset(map_texinfo, 0, MAX_MAPFILE_TEXINFO * sizeof(map_texinfo_t));
	map_numtexinfo = 0;
	//
	VectorClear(map_mins);
	VectorClear(map_maxs);
	//
	c_boxbevels = 0;
	c_edgebevels = 0;
	c_areaportals = 0;
	c_clipbrushes = 0;
	c_writtenbrushes = 0;
	//clear the entities
	for (i = 0; i < num_entities; i++)
	{
		for (ep = entities[i].epairs; ep; ep = nextep)
		{
			nextep = ep->next;
			FreeMemory(ep->key);
			FreeMemory(ep->value);
			FreeMemory(ep);
		} //end for
	} //end for
	num_entities = 0;
	memset(entities, 0, MAX_MAP_ENTITIES * sizeof(entity_t));
} //end of the function ResetMapLoading
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
#ifndef Q1_BSPVERSION
#define Q1_BSPVERSION	29
#endif
#ifndef HL_BSPVERSION
#define HL_BSPVERSION	30
#endif

#define Q2_BSPHEADER				(('P'<<24)+('S'<<16)+('B'<<8)+'I')	//IBSP
#define Q2_BSPVERSION	38

#define SINGAME_BSPHEADER		(('P'<<24)+('S'<<16)+('B'<<8)+'R')	//RBSP
#define SINGAME_BSPVERSION		1

#define SIN_BSPHEADER			(('P'<<24)+('S'<<16)+('B'<<8)+'I')	//IBSP
#define SIN_BSPVERSION	41

typedef struct
{
	int ident;
	int version;
} idheader_t;

int LoadMapFromBSP(struct quakefile_s *qf)
{
	idheader_t idheader;

	if (ReadQuakeFile(qf, &idheader, 0, sizeof(idheader_t)) != sizeof(idheader_t))
	{
		return false;
	} //end if

	idheader.ident = LittleLong(idheader.ident);
	idheader.version = LittleLong(idheader.version);
	//Quake3 BSP file
	if (idheader.ident == Q3_BSP_IDENT && idheader.version == Q3_BSP_VERSION)
	{
		ResetMapLoading();
		Q3_LoadMapFromBSP(qf);
		Q3_FreeMaxBSP();
	} //end if
	//Quake2 BSP file
	else if (idheader.ident == Q2_BSPHEADER && idheader.version == Q2_BSPVERSION)
	{
		ResetMapLoading();
		Q2_AllocMaxBSP();
		Q2_LoadMapFromBSP(qf->filename, qf->offset, qf->length);
		Q2_FreeMaxBSP();
	} //endif
	//Sin BSP file
	else if ((idheader.ident == SIN_BSPHEADER && idheader.version == SIN_BSPVERSION) ||
				//the dorks gave the same format another ident and verions
				(idheader.ident == SINGAME_BSPHEADER && idheader.version == SINGAME_BSPVERSION))
	{
		ResetMapLoading();
		Sin_AllocMaxBSP();
		Sin_LoadMapFromBSP(qf->filename, qf->offset, qf->length);
		Sin_FreeMaxBSP();
	} //end if
	//the Quake1 bsp files don't have a ident only a version
	else if (idheader.ident == Q1_BSPVERSION)
	{
		ResetMapLoading();
		Q1_AllocMaxBSP();
		Q1_LoadMapFromBSP(qf->filename, qf->offset, qf->length);
		Q1_FreeMaxBSP();
	} //end if
	//Half-Life also only uses a version number
	else if (idheader.ident == HL_BSPVERSION)
	{
		ResetMapLoading();
		HL_AllocMaxBSP();
		HL_LoadMapFromBSP(qf->filename, qf->offset, qf->length);
		HL_FreeMaxBSP();
	} //end if
	else
	{
		Error("unknown BSP format %c%c%c%c, version %d\n",
										(idheader.ident & 0xFF),
										((idheader.ident >> 8) & 0xFF),
										((idheader.ident >> 16) & 0xFF),
										((idheader.ident >> 24) & 0xFF), idheader.version);
		return false;
	} //end if
	//
	return true;
} //end of the function LoadMapFromBSP
