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

Return to Castle Wolfenstein multiplayer GPL Source Code
Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. 

This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (RTCW MP Source Code).  

RTCW MP 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 3 of the License, or
(at your option) any later version.

RTCW MP 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 RTCW MP Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

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

//===========================================================================
//
// Name:         map.c
// Function:     map loading and writing
// Programmer:   Mr Elusive (MrElusive@demigod.demon.nl)
// Last update:  1997-12-03
// Tab Size:     3
//===========================================================================

#include "qbsp.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 ) {
					if ( fprintf( fp, " %ld %ld %ld", 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; 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 v1.6\n"
					 "//\n"
					 "// BSPC is created by Mr Elusive\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
