Module hmap2: Change committed

havoc at icculus.org havoc at icculus.org
Sun Feb 15 21:50:48 EST 2004


Commiter   : havoc
CVSROOT    : /cvs/cvsroot/twilight
Module     : hmap2
Commit time: 2004-02-16 02:50:47 UTC

Log message:

Vic's greatly improved hmap, now called hmap2 (this is hqbsp/hvis/hlight/bsp2prt with tons of bugfixes and improvements, combined into one program)

Added files:
     Makefile brush.c bsp2prt.c bsp5.h bspfile.c bspfile.h cmdlib.c
     cmdlib.h csg4.c faces.c fast.bat full.bat hmap2.dsp
     hmapreadme.txt light.c light.h light_face.c light_trace.c map.c
     map.h mathlib.c mathlib.h mem.c mem.h outside.c portals.c qbsp.c
     relight.bat revis.bat scriptlib.c solidbsp.c tjunc.c tree.c vis.c
     vis.h vis_flow.c vis_sound.c wad.c winding.c winding.h writebsp.c

------=MIME.bae42047cce5199d623e0cd9135a8d56
Content-Type: text/plain; name="hmap2.20040216.025047.havoc.diff"
Content-Disposition: attachment; filename="hmap2.20040216.025047.havoc.diff"
Content-Transfer-Encoding: 8bit

Index: hmap2/Makefile
diff -u /dev/null hmap2/Makefile:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/Makefile	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,53 @@
+##### Win32 variables #####
+
+WIN32_HMAP_EXE=hmap.exe
+WIN32_LDFLAGS=-lwinmm
+
+##### Unix variables #####
+
+UNIX_HMAP_EXE=hmap
+UNIX_LDFLAGS=-lm
+
+##### Common variables #####
+
+HMAPOBJECTS= cmdlib.o mathlib.o bspfile.o mem.o winding.o scriptlib.o \
+    brush.o csg4.o faces.o map.o outside.o portals.o \
+    qbsp.o solidbsp.o tjunc.o tree.o writebsp.o \
+    wad.o bsp2prt.o vis.o vis_flow.o vis_sound.o light.o \
+    light_face.o light_trace.o
+
+CC=gcc
+CFLAGS=-Wall -O2
+
+##### Commands #####
+
+.PHONY: all mingw clean
+
+all:
+ifdef windir
+	$(MAKE) HMAP_EXE=$(WIN32_HMAP_EXE) LDFLAGS="$(WIN32_LDFLAGS)" $(WIN32_HMAP_EXE)
+else
+	$(MAKE) HMAP_EXE=$(UNIX_HMAP_EXE) LDFLAGS="$(UNIX_LDFLAGS)" $(UNIX_HMAP_EXE)
+endif
+
+mingw:
+	$(MAKE) HMAP_EXE=$(WIN32_HMAP_EXE) LDFLAGS="$(WIN32_LDFLAGS)" $(WIN32_HMAP_EXE)
+
+.c.o:
+	$(CC) $(CFLAGS) -c $*.c -o $*.o
+
+$(HMAP_EXE): $(HMAPOBJECTS)
+	$(CC) -o $@ $^ $(LDFLAGS)
+
+clean:
+ifdef windir
+	del $(WIN32_HMAP_EXE)
+	del *.o
+	del *.d
+else
+	rm -f $(WIN32_HMAP_EXE) $(UNIX_HMAP_EXE) *.[od]
+endif
+
+.PHONY: clean
+
+-include *.d
Index: hmap2/brush.c
diff -u /dev/null hmap2/brush.c:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/brush.c	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,585 @@
+// brush.c
+
+#include "bsp5.h"
+
+int			numbrushfaces;
+mface_t		faces[MAX_FACES];		// beveled clipping hull can generate many extra
+
+entity_t *CurrentEntity;
+
+//============================================================================
+
+/*
+===========
+AllocBrush
+===========
+*/
+brush_t *AllocBrush( void )
+{
+	brush_t	*b;
+
+	b = qmalloc( sizeof( brush_t ) );
+	memset( b, 0, sizeof( brush_t ) );
+
+	return b;
+}
+
+/*
+===========
+FreeBrush
+===========
+*/
+void FreeBrush( brush_t *b ) {
+	qfree( b );
+}
+
+//============================================================================
+
+/*
+=============================================================================
+
+TURN BRUSHES INTO GROUPS OF FACES
+
+=============================================================================
+*/
+
+vec3_t		brush_mins, brush_maxs;
+face_t		*brush_faces;
+
+entity_t *FindTargetEntity(char *targetname)
+{
+	int			entnum;
+
+	for (entnum = 0;entnum < num_entities;entnum++)
+		if (!strcmp(targetname, ValueForKey(&entities[entnum], "targetname")))
+			return &entities[entnum];
+	return NULL;
+}
+
+/*
+=================
+CreateBrushFaces
+=================
+*/
+#define	ZERO_EPSILON	0.001
+void CreateBrushFaces (void)
+{
+	int				i,j, k, rotate;
+	vec_t			r;
+	face_t			*f;
+	winding_t		*w;
+	plane_t			*p, plane;
+	mface_t			*mf;
+	vec3_t			offset, point;
+
+	offset[0] = offset[1] = offset[2] = 0;
+	brush_mins[0] = brush_mins[1] = brush_mins[2] = BOGUS_RANGE;
+	brush_maxs[0] = brush_maxs[1] = brush_maxs[2] = -BOGUS_RANGE;
+
+	brush_faces = NULL;
+
+	rotate = !strncmp(ValueForKey(CurrentEntity, "classname"), "rotate_", 7);
+	if (rotate)
+	{
+		entity_t	*FoundEntity;
+		char 		*searchstring;
+		char		text[20];
+
+		searchstring = ValueForKey (CurrentEntity, "target");
+		FoundEntity = FindTargetEntity(searchstring);
+		if (FoundEntity)
+			GetVectorForKey(FoundEntity, "origin", offset);
+
+		sprintf(text, "%g %g %g", offset[0], offset[1], offset[2]);
+		SetKeyValue(CurrentEntity, "origin", text);
+	}
+
+	for (i = 0;i < numbrushfaces;i++)
+	{
+		mf = &faces[i];
+		VectorNegate( mf->plane.normal, point );
+
+		w = BaseWindingForPlane (&mf->plane);
+
+		for (j = 0;j < numbrushfaces && w;j++)
+		{
+			p = &faces[i].plane;
+			if( j == i/* || VectorCompare( p->normal, point )*/ )
+				continue;
+
+			// flip the plane, because we want to keep the back side
+			VectorNegate(faces[j].plane.normal, plane.normal);
+			plane.dist = -faces[j].plane.dist;
+
+			w = ClipWindingEpsilon (w, &plane, true, ON_EPSILON);
+		}
+
+		if (!w)
+			continue;	// overcontrained plane
+
+		CheckWinding( w );
+
+		// this face is a keeper
+		f = AllocFace ();
+		f->winding = w;
+
+		for (j = 0;j < w->numpoints;j++)
+		{
+			for (k = 0;k < 3;k++)
+			{
+				point[k] = w->points[j][k] - offset[k];
+				r = Q_rint( point[k] );
+				if ( fabs( point[k] - r ) < ZERO_EPSILON)
+					w->points[j][k] = r;
+				else
+					w->points[j][k] = point[k];
+
+				AddPointToBounds( w->points[j], brush_mins, brush_maxs );
+			}
+		}
+
+		VectorCopy (mf->plane.normal, plane.normal);
+		VectorScale (mf->plane.normal, mf->plane.dist, point);
+		VectorSubtract (point, offset, point);
+		plane.dist = DotProduct (plane.normal, point);
+
+		f->texturenum = mf->texinfo;
+		f->planenum = FindPlane (&plane, &f->planeside);
+		f->next = brush_faces;
+		brush_faces = f;
+	}
+
+	// Rotatable objects have to have a bounding box big enough
+	// to account for all its rotations.
+	if (rotate)
+	{
+		vec_t delta;
+
+		delta = RadiusFromBounds( brush_mins, brush_maxs );
+
+		for (k = 0;k < 3;k++)
+		{
+			brush_mins[k] = -delta;
+			brush_maxs[k] = delta;
+		}
+	}
+}
+
+
+
+/*
+==============================================================================
+
+BEVELED CLIPPING HULL GENERATION
+
+This is done by brute force, and could easily get a lot faster if anyone cares.
+==============================================================================
+*/
+
+vec3_t	hull_size[3][2] =
+{
+{{  0,  0,  0}, {  0,  0,  0}},
+{{-16,-16,-32}, { 16, 16, 24}},
+{{-32,-32,-64}, { 32, 32, 24}}
+};
+
+// LordHavoc: these were 32 and 64 respectively
+#define	MAX_HULL_POINTS	512
+#define	MAX_HULL_EDGES	1024
+
+int		num_hull_points;
+vec3_t	hull_points[MAX_HULL_POINTS];
+vec3_t	hull_corners[MAX_HULL_POINTS*8];
+int		num_hull_edges;
+int		hull_edges[MAX_HULL_EDGES][2];
+
+/*
+============
+AddBrushPlane
+=============
+*/
+void AddBrushPlane (plane_t *plane)
+{
+	int		i;
+	plane_t	*pl;
+	double	l;
+
+	if (numbrushfaces == MAX_FACES)
+		Error ("AddBrushPlane: numbrushfaces == MAX_FACES");
+	l = VectorLength (plane->normal);
+	if (l < 0.999 || l > 1.001)
+		Error ("AddBrushPlane: bad normal (%f %f %f, length %f)", plane->normal[0], plane->normal[1], plane->normal[2], l);
+
+	for (i=0 ; i<numbrushfaces ; i++)
+	{
+		pl = &faces[i].plane;
+		if (VectorCompare (pl->normal, plane->normal) && fabs(pl->dist - plane->dist) < ON_EPSILON)
+			return;
+	}
+	faces[i].plane = *plane;
+	faces[i].texinfo = faces[0].texinfo;
+	numbrushfaces++;
+}
+
+
+/*
+============
+TestAddPlane
+
+Adds the given plane to the brush description if all of the original brush
+vertexes can be put on the front side
+=============
+*/
+void TestAddPlane (plane_t *plane)
+{
+	int		i, c;
+	vec_t	d;
+	vec_t	*corner;
+	plane_t	flip;
+	vec3_t	inv;
+	int		counts[3];
+	plane_t	*pl;
+
+	// see if the plane has allready been added
+	for (i=0 ; i<numbrushfaces ; i++)
+	{
+		pl = &faces[i].plane;
+		if (VectorCompare (plane->normal, pl->normal) && fabs(plane->dist - pl->dist) < ON_EPSILON)
+			return;
+		VectorNegate (plane->normal, inv);
+		if (VectorCompare (inv, pl->normal) && fabs(plane->dist + pl->dist) < ON_EPSILON)
+			return;
+	}
+
+	// check all the corner points
+	counts[0] = counts[1] = counts[2] = 0;
+	c = num_hull_points * 8;
+
+	corner = hull_corners[0];
+	for (i=0 ; i<c ; i++, corner += 3)
+	{
+		d = DotProduct (corner, plane->normal) - plane->dist;
+		if (d < -ON_EPSILON)
+		{
+			if (counts[0])
+				return;
+			counts[1]++;
+		}
+		else if (d > ON_EPSILON)
+		{
+			if (counts[1])
+				return;
+			counts[0]++;
+		}
+		else
+			counts[2]++;
+	}
+
+	// the plane is a seperator
+
+	if (counts[0])
+	{
+		VectorNegate (plane->normal, flip.normal);
+		flip.dist = -plane->dist;
+		plane = &flip;
+	}
+
+	AddBrushPlane (plane);
+}
+
+/*
+============
+AddHullPoint
+
+Doesn't add if duplicated
+=============
+*/
+int AddHullPoint (vec3_t p, int hullnum)
+{
+	int		i;
+	vec_t	*c;
+	int		x,y,z;
+
+	for (i=0 ; i<num_hull_points ; i++)
+		if (VectorCompare (p, hull_points[i]))
+			return i;
+
+	VectorCopy (p, hull_points[num_hull_points]);
+
+	c = hull_corners[i*8];
+
+	for (x=0 ; x<2 ; x++)
+		for (y=0 ; y<2 ; y++)
+			for (z=0; z<2 ; z++)
+			{
+				c[0] = p[0] + hull_size[hullnum][x][0];
+				c[1] = p[1] + hull_size[hullnum][y][1];
+				c[2] = p[2] + hull_size[hullnum][z][2];
+				c += 3;
+			}
+
+	if (num_hull_points == MAX_HULL_POINTS)
+		Error ("AddHullPoint: MAX_HULL_POINTS");
+
+	num_hull_points++;
+
+	return i;
+}
+
+
+/*
+============
+AddHullEdge
+
+Creates all of the hull planes around the given edge, if not done allready
+=============
+*/
+void AddHullEdge (vec3_t p1, vec3_t p2, int hullnum)
+{
+	int		pt1, pt2;
+	int		i;
+	int		a, b, c, d, e;
+	vec3_t	edgevec, planeorg, planevec;
+	plane_t	plane;
+	vec_t	l;
+
+	pt1 = AddHullPoint (p1, hullnum);
+	pt2 = AddHullPoint (p2, hullnum);
+
+	for (i=0 ; i<num_hull_edges ; i++)
+		if ((hull_edges[i][0] == pt1 && hull_edges[i][1] == pt2) || (hull_edges[i][0] == pt2 && hull_edges[i][1] == pt1))
+			return;	// already added
+
+	if (num_hull_edges == MAX_HULL_EDGES)
+		Error ("AddHullEdge: MAX_HULL_EDGES");
+
+	hull_edges[i][0] = pt1;
+	hull_edges[i][1] = pt2;
+	num_hull_edges++;
+
+	VectorSubtract (p1, p2, edgevec);
+	VectorNormalize (edgevec);
+
+	for (a=0 ; a<3 ; a++)
+	{
+		b = (a+1)%3;
+		c = (a+2)%3;
+		for (d=0 ; d<=1 ; d++)
+			for (e=0 ; e<=1 ; e++)
+			{
+				VectorCopy (p1, planeorg);
+				planeorg[b] += hull_size[hullnum][d][b];
+				planeorg[c] += hull_size[hullnum][e][c];
+
+				VectorClear (planevec);
+				planevec[a] = 1;
+
+				CrossProduct (planevec, edgevec, plane.normal);
+				l = VectorLength (plane.normal);
+				if (l < 1-ANGLEEPSILON || l > 1+ANGLEEPSILON)
+					continue;
+				plane.dist = DotProduct (planeorg, plane.normal);
+				TestAddPlane (&plane);
+			}
+	}
+}
+
+/*
+============
+ExpandBrush
+=============
+*/
+void ExpandBrush (int hullnum)
+{
+	int			i, x, s;
+	vec3_t		corner;
+	face_t		*f;
+	winding_t	*w;
+	plane_t		plane, *p;
+
+	num_hull_points = 0;
+	num_hull_edges = 0;
+
+	// create all the hull points
+	for (f=brush_faces ; f ; f=f->next) {
+		w = f->winding;
+		for (i=0 ; i<w->numpoints ; i++)
+			AddHullPoint (w->points[i], hullnum);
+	}
+
+	// expand all of the planes
+	for (i=0 ; i<numbrushfaces ; i++)
+	{
+		p = &faces[i].plane;
+		VectorClear (corner);
+		for (x=0 ; x<3 ; x++)
+		{
+			if (p->normal[x] > 0)
+				corner[x] = hull_size[hullnum][1][x];
+			else if (p->normal[x] < 0)
+				corner[x] = hull_size[hullnum][0][x];
+		}
+		p->dist += DotProduct (corner, p->normal);
+	}
+
+	// add any axis planes not contained in the brush to bevel off corners
+	for (x=0 ; x<3 ; x++)
+		for (s=-1 ; s<=1 ; s+=2)
+		{
+			// add the plane
+			VectorClear (plane.normal);
+			plane.normal[x] = s;
+			if (s == -1)
+				plane.dist = -brush_mins[x] + -hull_size[hullnum][0][x];
+			else
+				plane.dist = brush_maxs[x] + hull_size[hullnum][1][x];
+			AddBrushPlane (&plane);
+		}
+
+	// add all of the edge bevels
+	for (f=brush_faces ; f ; f=f->next) {
+		w = f->winding;
+		for (i=0 ; i<w->numpoints ; i++)
+			AddHullEdge (w->points[i], w->points[(i+1)%w->numpoints], hullnum);
+	}
+}
+
+//============================================================================
+
+
+/*
+===============
+LoadBrush
+
+Converts a mapbrush to a bsp brush
+===============
+*/
+brush_t *LoadBrush (mbrush_t *mb, int hullnum)
+{
+	brush_t		*b;
+	int			contents;
+	char		*name;
+	mface_t		*f;
+
+	//
+	// check texture name for attributes
+	//
+	name = miptex[texinfo[mb->faces->texinfo].miptex];
+
+	if (!Q_strcasecmp(name, "clip") && hullnum == 0)
+		return NULL;		// "clip" brushes don't show up in the draw hull
+
+	if (name[0] == '*')		// entities never use water merging
+	{
+		if (!Q_strncasecmp(name+1,"lava",4))
+			contents = CONTENTS_LAVA;
+		else if (!Q_strncasecmp(name+1,"slime",5))
+			contents = CONTENTS_SLIME;
+		else
+			contents = CONTENTS_WATER;
+	}
+	else if (!Q_strncasecmp (name, "sky",3) && hullnum == 0)
+		contents = CONTENTS_SKY;
+	else
+		contents = CONTENTS_SOLID;
+
+	if (hullnum && contents != CONTENTS_SOLID && contents != CONTENTS_SKY)
+		return NULL;		// water brushes don't show up in clipping hulls
+
+	// no seperate textures on clip hull
+
+	//
+	// create the faces
+	//
+	brush_faces = NULL;
+
+	numbrushfaces = 0;
+	for (f=mb->faces ; f ; f=f->next)
+	{
+		faces[numbrushfaces] = *f;
+		if (hullnum)
+			faces[numbrushfaces].texinfo = 0;
+		numbrushfaces++;
+	}
+
+	CreateBrushFaces ();
+
+	if (!brush_faces)
+	{
+		printf ("WARNING: couldn't create brush faces\n");
+		return NULL;
+	}
+
+	if (hullnum)
+	{
+		ExpandBrush (hullnum);
+		CreateBrushFaces ();
+	}
+
+	//
+	// create the brush
+	//
+	b = AllocBrush ();
+	b->contents = contents;
+	b->faces = brush_faces;
+	VectorCopy (brush_mins, b->mins);
+	VectorCopy (brush_maxs, b->maxs);
+
+	return b;
+}
+
+//=============================================================================
+
+/*
+============
+Brush_LoadEntity
+============
+*/
+void Brush_LoadEntity( entity_t *ent, tree_t *tree, int hullnum )
+{
+	mbrush_t	*mbr;
+	int			numbrushes;
+	brush_t		*b, *next, *water, *other;
+
+	numbrushes = 0;
+	other = water = NULL;
+
+	qprintf ("--- Brush_LoadEntity ---\n");
+
+	CurrentEntity = ent;
+
+	for (mbr = ent->brushes ; mbr ; mbr=mbr->next)
+	{
+		b = LoadBrush (mbr, hullnum);
+		if (!b)
+			continue;
+
+		numbrushes++;
+
+		if (b->contents != CONTENTS_SOLID)
+		{
+			b->next = water;
+			water = b;
+		}
+		else
+		{
+			b->next = other;
+			other = b;
+		}
+
+		AddPointToBounds (b->mins, tree->mins, tree->maxs);
+		AddPointToBounds (b->maxs, tree->mins, tree->maxs);
+	}
+
+	// add all of the water textures at the start
+	for (b=water ; b ; b=next)
+	{
+		next = b->next;
+		b->next = other;
+		other = b;
+	}
+
+	tree->brushes = other;
+
+	qprintf( "%i brushes read\n", numbrushes );
+}
Index: hmap2/bsp2prt.c
diff -u /dev/null hmap2/bsp2prt.c:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/bsp2prt.c	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,133 @@
+#include "bsp5.h"
+
+/*
+==================
+Bsp2Prt_BuildTree_r
+==================
+*/
+static node_t *Bsp2Prt_BuildTree_r( int nodenum )
+{
+	node_t		*n;
+
+	if( nodenum < 0 ) {
+		dleaf_t *leaf = &dleafs[-1-nodenum];
+
+		n = AllocNode ();
+		n->planenum = PLANENUM_LEAF;
+		n->contents = leaf->contents;
+	} else {
+		int		side;
+		plane_t plane;
+		dnode_t *node = &dnodes[nodenum];
+
+		n = AllocNode ();
+		plane.dist = dplanes[node->planenum].dist;
+		VectorCopy( dplanes[node->planenum].normal, plane.normal );
+		n->planenum = FindPlane( &plane, &side );
+
+		if( side )
+			Error( "Bad node plane" );
+
+		n->children[0] = Bsp2Prt_BuildTree_r( node->children[0] );
+		n->children[1] = Bsp2Prt_BuildTree_r( node->children[1] );
+	}
+
+	return n;
+}
+
+/*
+==================
+Bsp2Prt_GetWorldBounds
+==================
+*/
+static void Bsp2Prt_GetWorldBounds( vec3_t mins, vec3_t maxs )
+{
+	int			i, j, e;
+	dface_t		*face;
+	dvertex_t	*v;
+	vec3_t		point;
+
+	ClearBounds( mins, maxs );
+
+	for( i = 0, face = dfaces; i < dmodels[0].numfaces; i++, face++ ) {
+		for( j = 0; j < face->numedges; j++ ) {
+			e = dsurfedges[face->firstedge + j];
+			if( e >= 0 )
+				v = dvertexes + dedges[e].v[0];
+			else
+				v = dvertexes + dedges[-e].v[1];
+
+			VectorCopy( v->point, point );
+			AddPointToBounds( point, mins, maxs );
+		}
+	}
+}
+
+/*
+==================
+Bsp2Prt_ProcessFile
+==================
+*/
+static void Bsp2Prt_ProcessFile( char *filename )
+{
+	tree_t		*tree;
+
+	// create filenames
+	strcpy( bspfilename, filename );
+	strcpy( portfilename, filename );
+	DefaultExtension( bspfilename, ".bsp" );
+	ReplaceExtension( portfilename, ".prt" );
+
+	LoadBSPFile( bspfilename );
+
+	tree = AllocTree ();
+
+	Bsp2Prt_GetWorldBounds( tree->mins, tree->maxs );
+
+	tree->headnode = Bsp2Prt_BuildTree_r( 0 );
+
+	PortalizeTree( tree );
+	WritePortalfile( tree );
+	FreeTreePortals( tree );
+
+	FreeTree( tree );
+}
+
+/*
+==================
+Bsp2Prt_Main
+==================
+*/
+int Bsp2Prt_Main( int argc, char **argv )
+{
+	double		start, end;
+
+	if( argc < 2 ) {
+		Error ("%s",
+"usage: bsp2prt sourcefile\n"
+		);
+	}
+
+	// init memory
+	Q_InitMem ();
+
+	// do it!
+	start = I_DoubleTime ();
+
+	end = I_DoubleTime ();
+	Bsp2Prt_ProcessFile( argv[argc-1] );
+	printf( "%5.2f seconds elapsed\n\n", end - start );
+
+	// print memory stats
+	Q_PrintMem ();
+
+#if _MSC_VER && _DEBUG
+	printf( "press any key\n" );
+	getchar ();
+#endif
+
+	// free allocated memory
+	Q_ShutdownMem ();
+
+	return 0;
+}
Index: hmap2/bsp5.h
diff -u /dev/null hmap2/bsp5.h:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/bsp5.h	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,213 @@
+
+// bsp5.h
+
+#include <math.h>
+#include "cmdlib.h"
+#include "mathlib.h"
+#include "bspfile.h"
+#include "mem.h"
+#include "winding.h"
+#include "map.h"
+
+#define	ON_EPSILON	0.05
+
+// the exact bounding box of the brushes is expanded some for the headnode
+// volume.  is this still needed?
+#define	SIDESPACE	24
+
+//============================================================================
+ 
+typedef struct visfacet_s
+{
+	struct visfacet_s	*next;
+
+	int					planenum;
+	int					planeside;		// which side is the front of the face
+	int					texturenum;
+	int					contents[2];	// 0 = front side
+
+	struct visfacet_s	*original;		// face on node
+	int					outputnumber;	// only valid for original faces after write surfaces
+	winding_t			*winding;
+} face_t;
+
+typedef struct surface_s
+{
+	struct surface_s	*next;
+
+	int					planenum;
+	vec3_t				mins, maxs;
+	qboolean			onnode;			// true if surface has already been used as a splitting node
+	face_t				*faces;			// links to all the faces on either side of the surf
+} surface_t;
+
+typedef struct brush_s
+{
+	struct brush_s		*next;
+
+	vec3_t				mins, maxs;
+	face_t				*faces;
+	int					contents;
+} brush_t;
+
+//
+// there is a node_t structure for every node and leaf in the bsp tree
+//
+#define	PLANENUM_LEAF		-1
+
+typedef struct node_s
+{
+	vec3_t				mins, maxs;		// bounding volume, not just points inside
+	int					planenum;		// PLANENUM_LEAF = leaf node	
+
+	// information for decision nodes	
+	int					outputplanenum;	// only valid after EmitNodePlanes
+	int					firstface;		// decision node only
+	int					numfaces;		// decision node only
+	struct node_s		*children[2];	// only valid for decision nodes
+	face_t				*faces;			// decision nodes only, list for both sides
+	
+	// information for leafs
+	int					contents;		// leaf nodes (0 for decision nodes)
+	face_t				**markfaces;	// leaf nodes only, point to node faces
+	struct portal_s		*portals;
+	int					visleafnum;		// -1 = solid
+	int					valid;			// for flood filling
+	int					occupied;		// light number in leaf for outside filling
+} node_t;
+
+typedef struct portal_s
+{
+	int					planenum;
+	node_t				*nodes[2];		// [0] = front side of planenum
+	struct portal_s		*next[2];	
+	winding_t			*winding;
+} portal_t;
+
+typedef struct
+{
+	vec3_t				mins, maxs;		// bounding box
+	brush_t				*brushes;		// NULL terminated list
+	face_t				*validfaces[MAX_MAP_PLANES];	// build surfaces is also used by GatherNodeFaces
+	surface_t			*surfaces;
+	node_t				*headnode;
+} tree_t;
+
+//=============================================================================
+
+// brush.c
+brush_t *AllocBrush( void );
+void FreeBrush( brush_t *b );
+void Brush_LoadEntity( entity_t *ent, tree_t *tree, int hullnum );
+
+//=============================================================================
+
+// csg4.c
+surface_t *AllocSurface( void );
+void FreeSurface( surface_t *s );
+void BuildSurfaces( tree_t *tree );
+void CSGFaces( tree_t *tree );
+
+//=============================================================================
+
+// solidbsp.c
+node_t *AllocNode( void );
+void FreeNode( node_t *n );
+
+void CalcSurfaceInfo( surface_t *surf );
+void SolidBSP( tree_t *tree, qboolean midsplit );
+
+//=============================================================================
+
+// merge.c
+
+//=============================================================================
+
+// surfaces.c
+face_t *AllocFace( void );
+void FreeFace( face_t *f );
+face_t *NewFaceFromFace( face_t *in );
+
+void SplitFace( face_t *in, plane_t *split, face_t **front, face_t **back );
+int SubdivideFace( face_t *f, face_t **prevptr );
+
+face_t *MergeFaceToList_r( face_t *face, face_t *list );
+face_t *FreeMergeListScraps( face_t *merged );
+void MergeTreeFaces( tree_t *tree );
+
+void GatherTreeFaces( tree_t *tree );
+
+//=============================================================================
+
+// portals.c
+extern	node_t	outside_node;		// portals outside the world face this
+
+portal_t *AllocPortal( void );
+void FreePortal( struct portal_s *p );
+
+void PortalizeTree( tree_t *tree );
+void FreeTreePortals( tree_t *tree );
+void WritePortalfile( tree_t *tree );
+
+//=============================================================================
+
+// tjunc.c
+void FixTJunctions( tree_t *tree );
+
+//=============================================================================
+
+// tree.c
+tree_t *AllocTree( void );
+void FreeTree( tree_t *t );
+tree_t *Tree_ProcessEntity( entity_t *ent, int modnum, int hullnum );
+
+//=============================================================================
+
+// writebsp.c
+void EmitVertex( vec3_t point );
+void EmitEdge( int v1, int v2 );
+void EmitNodeFaceEdges( node_t *headnode );
+void EmitNodeFaces( node_t *node );
+void EmitNodePlanes( node_t *headnode );
+void EmitClipNodes( node_t *nodes, int modnum, int hullnum );
+void EmitDrawNodes( node_t *headnode );
+
+void BeginBSPFile( void );
+void FinishBSPFile( void );
+
+//=============================================================================
+
+// outside.c
+
+qboolean FillOutside( tree_t *tree, int hullnum );
+
+//=============================================================================
+
+// wad.c
+
+void WriteMiptex (void);
+
+//=============================================================================
+
+extern	qboolean nofill;
+extern	qboolean notjunc;
+extern	qboolean noclip;
+extern	qboolean verbose;
+extern	qboolean forcevis;
+extern	qboolean oldbsp;
+
+extern	int		subdivide_size;
+
+void qprintf (char *fmt, ...);
+
+extern	int		valid;
+
+extern	char	portfilename[1024];
+extern	char	bspfilename[1024];
+extern	char	pointfilename[1024];
+
+// misc functions
+
+void qprintf( char *fmt, ... );
+
+//=============================================================================
Index: hmap2/bspfile.c
diff -u /dev/null hmap2/bspfile.c:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/bspfile.c	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,710 @@
+
+#include "cmdlib.h"
+#include "mathlib.h"
+#include "bspfile.h"
+#include "mem.h"
+
+//=============================================================================
+
+int			nummodels;
+dmodel_t	dmodels[MAX_MAP_MODELS];
+
+int			visdatasize;
+byte		dvisdata[MAX_MAP_VISIBILITY];
+
+int			lightdatasize;
+byte		dlightdata[MAX_MAP_LIGHTING];
+
+// LordHavoc: stored in .lit file
+int			rgblightdatasize;
+byte		drgblightdata[MAX_MAP_LIGHTING*3];
+
+int			texdatasize;
+byte		dtexdata[MAX_MAP_MIPTEX]; // (dmiptexlump_t)
+
+int			entdatasize;
+char		dentdata[MAX_MAP_ENTSTRING];
+
+int			numleafs;
+dleaf_t		dleafs[MAX_MAP_LEAFS];
+
+int			numplanes;
+dplane_t	dplanes[MAX_MAP_PLANES];
+
+int			numvertexes;
+dvertex_t	dvertexes[MAX_MAP_VERTS];
+
+int			numnodes;
+dnode_t		dnodes[MAX_MAP_NODES];
+
+int			numtexinfo;
+texinfo_t	texinfo[MAX_MAP_TEXINFO];
+
+int			numfaces;
+dface_t		dfaces[MAX_MAP_FACES];
+
+int			numclipnodes;
+dclipnode_t	dclipnodes[MAX_MAP_CLIPNODES];
+
+int			numedges;
+dedge_t		dedges[MAX_MAP_EDGES];
+
+int			nummarksurfaces;
+unsigned short		dmarksurfaces[MAX_MAP_MARKSURFACES];
+
+int			numsurfedges;
+int			dsurfedges[MAX_MAP_SURFEDGES];
+
+//=============================================================================
+
+/*
+=============
+SwapBSPFile
+
+Byte swaps all data in a bsp file.
+=============
+*/
+void SwapBSPFile (qboolean todisk)
+{
+	int				i, j, c;
+	dmodel_t		*d;
+	dmiptexlump_t	*mtl;
+
+
+	// models
+	for (i=0 ; i<nummodels ; i++)
+	{
+		d = &dmodels[i];
+
+		for (j=0 ; j<MAX_MAP_HULLS ; j++)
+			d->headnode[j] = LittleLong (d->headnode[j]);
+
+		d->visleafs = LittleLong (d->visleafs);
+		d->firstface = LittleLong (d->firstface);
+		d->numfaces = LittleLong (d->numfaces);
+
+		for (j=0 ; j<3 ; j++)
+		{
+			d->mins[j] = LittleFloat(d->mins[j]);
+			d->maxs[j] = LittleFloat(d->maxs[j]);
+			d->origin[j] = LittleFloat(d->origin[j]);
+		}
+	}
+
+	//
+	// vertexes
+	//
+	for (i=0 ; i<numvertexes ; i++)
+		for (j=0 ; j<3 ; j++)
+			dvertexes[i].point[j] = LittleFloat (dvertexes[i].point[j]);
+
+	//
+	// planes
+	//
+	for (i=0 ; i<numplanes ; i++)
+	{
+		for (j=0 ; j<3 ; j++)
+			dplanes[i].normal[j] = LittleFloat (dplanes[i].normal[j]);
+		dplanes[i].dist = LittleFloat (dplanes[i].dist);
+		dplanes[i].type = LittleLong (dplanes[i].type);
+	}
+
+	//
+	// texinfos
+	//
+	for (i=0 ; i<numtexinfo ; i++)
+	{
+		for (j=0 ; j<8 ; j++)
+			texinfo[i].vecs[0][j] = LittleFloat (texinfo[i].vecs[0][j]);
+		texinfo[i].miptex = LittleLong (texinfo[i].miptex);
+		texinfo[i].flags = LittleLong (texinfo[i].flags);
+	}
+
+	//
+	// faces
+	//
+	for (i=0 ; i<numfaces ; i++)
+	{
+		dfaces[i].texinfo = LittleShort (dfaces[i].texinfo);
+		dfaces[i].planenum = LittleShort (dfaces[i].planenum);
+		dfaces[i].side = LittleShort (dfaces[i].side);
+		dfaces[i].lightofs = LittleLong (dfaces[i].lightofs);
+		dfaces[i].firstedge = LittleLong (dfaces[i].firstedge);
+		dfaces[i].numedges = LittleShort (dfaces[i].numedges);
+	}
+
+	//
+	// nodes
+	//
+	for (i=0 ; i<numnodes ; i++)
+	{
+		dnodes[i].planenum = LittleLong (dnodes[i].planenum);
+		for (j=0 ; j<3 ; j++)
+		{
+			dnodes[i].mins[j] = LittleShort (dnodes[i].mins[j]);
+			dnodes[i].maxs[j] = LittleShort (dnodes[i].maxs[j]);
+		}
+		dnodes[i].children[0] = LittleShort (dnodes[i].children[0]);
+		dnodes[i].children[1] = LittleShort (dnodes[i].children[1]);
+		dnodes[i].firstface = LittleShort (dnodes[i].firstface);
+		dnodes[i].numfaces = LittleShort (dnodes[i].numfaces);
+	}
+
+	//
+	// leafs
+	//
+	for (i=0 ; i<numleafs ; i++)
+	{
+		dleafs[i].contents = LittleLong (dleafs[i].contents);
+		for (j=0 ; j<3 ; j++)
+		{
+			dleafs[i].mins[j] = LittleShort (dleafs[i].mins[j]);
+			dleafs[i].maxs[j] = LittleShort (dleafs[i].maxs[j]);
+		}
+
+		dleafs[i].firstmarksurface = LittleShort (dleafs[i].firstmarksurface);
+		dleafs[i].nummarksurfaces = LittleShort (dleafs[i].nummarksurfaces);
+		dleafs[i].visofs = LittleLong (dleafs[i].visofs);
+	}
+
+	//
+	// clipnodes
+	//
+	for (i=0 ; i<numclipnodes ; i++)
+	{
+		dclipnodes[i].planenum = LittleLong (dclipnodes[i].planenum);
+		dclipnodes[i].children[0] = LittleShort (dclipnodes[i].children[0]);
+		dclipnodes[i].children[1] = LittleShort (dclipnodes[i].children[1]);
+	}
+
+	//
+	// miptex
+	//
+	if (texdatasize)
+	{
+		mtl = (dmiptexlump_t *)dtexdata;
+		if (todisk)
+			c = mtl->nummiptex;
+		else
+			c = LittleLong(mtl->nummiptex);
+		mtl->nummiptex = LittleLong (mtl->nummiptex);
+		for (i=0 ; i<c ; i++)
+			mtl->dataofs[i] = LittleLong(mtl->dataofs[i]);
+	}
+
+	//
+	// marksurfaces
+	//
+	for (i=0 ; i<nummarksurfaces ; i++)
+		dmarksurfaces[i] = LittleShort (dmarksurfaces[i]);
+
+	//
+	// surfedges
+	//
+	for (i=0 ; i<numsurfedges ; i++)
+		dsurfedges[i] = LittleLong (dsurfedges[i]);
+
+	//
+	// edges
+	//
+	for (i=0 ; i<numedges ; i++)
+	{
+		dedges[i].v[0] = LittleShort (dedges[i].v[0]);
+		dedges[i].v[1] = LittleShort (dedges[i].v[1]);
+	}
+}
+
+
+dheader_t	*header;
+
+int CopyLump (int lump, void *dest, int size)
+{
+	int		length, ofs;
+
+	length = header->lumps[lump].filelen;
+	ofs = header->lumps[lump].fileofs;
+
+	if (length % size)
+		Error ("LoadBSPFile: odd lump size");
+
+	memcpy (dest, (byte *)header + ofs, length);
+
+	return length / size;
+}
+
+/*
+=============
+LoadBSPFile
+=============
+*/
+void	LoadBSPFile (char *filename)
+{
+	int			i;
+
+	//
+	// load the file header
+	//
+	LoadFile (filename, (void **)&header);
+
+	// swap the header
+	for (i=0 ; i< (int)sizeof(dheader_t)/4 ; i++)
+		((int *)header)[i] = LittleLong ( ((int *)header)[i]);
+
+	if (header->version != BSPVERSION)
+		Error ("%s is version %i, should be %i", filename, i, BSPVERSION);
+
+	nummodels = CopyLump (LUMP_MODELS, dmodels, sizeof(dmodel_t));
+	numvertexes = CopyLump (LUMP_VERTEXES, dvertexes, sizeof(dvertex_t));
+	numplanes = CopyLump (LUMP_PLANES, dplanes, sizeof(dplane_t));
+	numleafs = CopyLump (LUMP_LEAFS, dleafs, sizeof(dleaf_t));
+	numnodes = CopyLump (LUMP_NODES, dnodes, sizeof(dnode_t));
+	numtexinfo = CopyLump (LUMP_TEXINFO, texinfo, sizeof(texinfo_t));
+	numclipnodes = CopyLump (LUMP_CLIPNODES, dclipnodes, sizeof(dclipnode_t));
+	numfaces = CopyLump (LUMP_FACES, dfaces, sizeof(dface_t));
+	nummarksurfaces = CopyLump (LUMP_MARKSURFACES, dmarksurfaces, sizeof(dmarksurfaces[0]));
+	numsurfedges = CopyLump (LUMP_SURFEDGES, dsurfedges, sizeof(dsurfedges[0]));
+	numedges = CopyLump (LUMP_EDGES, dedges, sizeof(dedge_t));
+
+	texdatasize = CopyLump (LUMP_TEXTURES, dtexdata, 1);
+	visdatasize = CopyLump (LUMP_VISIBILITY, dvisdata, 1);
+	lightdatasize = CopyLump (LUMP_LIGHTING, dlightdata, 1);
+	entdatasize = CopyLump (LUMP_ENTITIES, dentdata, 1);
+
+	qfree (header);		// everything has been copied out
+
+	//
+	// swap everything
+	//
+	SwapBSPFile (false);
+}
+
+//============================================================================
+
+FILE		*wadfile;
+dheader_t	outheader;
+
+void AddLump (int lumpnum, void *data, int len)
+{
+	lump_t *lump;
+
+	lump = &header->lumps[lumpnum];
+
+	lump->fileofs = LittleLong( ftell(wadfile) );
+	lump->filelen = LittleLong(len);
+	SafeWrite (wadfile, data, (len+3)&~3);
+}
+
+void CheckLightmaps(void)
+{
+	/*
+	int i, l;
+	dface_t *face;
+	if (lightdatasize == 0)
+		return;
+
+	for (i = 0;i < numfaces;i++)
+	{
+		face = dfaces + i;
+		l = face->lightofs - 3;//1000;
+		if (l >= 0)
+		{
+			for (;l < face->lightofs;l++)
+				if (dlightdata[l] != (l & 0xFF))
+					printf("%i = %i\n", l, dlightdata[l]);
+		}
+	}
+	*/
+}
+
+/*
+=============
+WriteBSPFile
+
+Swaps the bsp file in place, so it should not be referenced again
+=============
+*/
+void WriteBSPFile (char *filename, qboolean litonly)
+{
+	if (!litonly)
+	{
+		header = &outheader;
+		memset (header, 0, sizeof(dheader_t));
+
+		SwapBSPFile (true);
+
+		header->version = LittleLong (BSPVERSION);
+
+		wadfile = SafeOpenWrite (filename);
+		SafeWrite (wadfile, header, sizeof(dheader_t));	// overwritten later
+
+		AddLump (LUMP_PLANES, dplanes, numplanes*sizeof(dplane_t));
+		AddLump (LUMP_LEAFS, dleafs, numleafs*sizeof(dleaf_t));
+		AddLump (LUMP_VERTEXES, dvertexes, numvertexes*sizeof(dvertex_t));
+		AddLump (LUMP_NODES, dnodes, numnodes*sizeof(dnode_t));
+		AddLump (LUMP_TEXINFO, texinfo, numtexinfo*sizeof(texinfo_t));
+		AddLump (LUMP_FACES, dfaces, numfaces*sizeof(dface_t));
+		AddLump (LUMP_CLIPNODES, dclipnodes, numclipnodes*sizeof(dclipnode_t));
+		AddLump (LUMP_MARKSURFACES, dmarksurfaces, nummarksurfaces*sizeof(dmarksurfaces[0]));
+		AddLump (LUMP_SURFEDGES, dsurfedges, numsurfedges*sizeof(dsurfedges[0]));
+		AddLump (LUMP_EDGES, dedges, numedges*sizeof(dedge_t));
+		AddLump (LUMP_MODELS, dmodels, nummodels*sizeof(dmodel_t));
+
+		AddLump (LUMP_LIGHTING, dlightdata, lightdatasize);
+		AddLump (LUMP_VISIBILITY, dvisdata, visdatasize);
+		AddLump (LUMP_ENTITIES, dentdata, entdatasize);
+		AddLump (LUMP_TEXTURES, dtexdata, texdatasize);
+
+		fseek (wadfile, 0, SEEK_SET);
+		SafeWrite (wadfile, header, sizeof(dheader_t));
+		fclose (wadfile);
+	}
+
+	if (rgblightdatasize)
+	{
+		FILE *litfile;
+		char litfilename[1024];
+
+		strcpy(litfilename, filename);
+		ReplaceExtension(litfilename, ".lit");
+		litfile = SafeOpenWrite(litfilename);
+
+		if (litfile)
+		{
+			fputc('Q', litfile);
+			fputc('L', litfile);
+			fputc('I', litfile);
+			fputc('T', litfile);
+			fputc(1, litfile);
+			fputc(0, litfile);
+			fputc(0, litfile);
+			fputc(0, litfile);
+			SafeWrite(litfile, drgblightdata, rgblightdatasize);
+			fclose(litfile);
+		}
+		else
+			printf("unable to write \"%s\"\n", litfilename);
+	}
+}
+
+//============================================================================
+
+/*
+=============
+PrintBSPFileSizes
+
+Dumps info about current file
+=============
+*/
+void PrintBSPFileSizes (void)
+{
+	printf ("%5i models       %6i\n", nummodels, (int)(nummodels*sizeof(dmodel_t)));
+	printf ("%5i planes       %6i\n", numplanes, (int)(numplanes*sizeof(dplane_t)));
+	printf ("%5i vertexes     %6i\n", numvertexes, (int)(numvertexes*sizeof(dvertex_t)));
+	printf ("%5i nodes        %6i\n", numnodes, (int)(numnodes*sizeof(dnode_t)));
+	printf ("%5i texinfo      %6i\n", numtexinfo, (int)(numtexinfo*sizeof(texinfo_t)));
+	printf ("%5i faces        %6i\n", numfaces, (int)(numfaces*sizeof(dface_t)));
+	printf ("%5i clipnodes    %6i\n", numclipnodes, (int)(numclipnodes*sizeof(dclipnode_t)));
+	printf ("%5i leafs        %6i\n", numleafs, (int)(numleafs*sizeof(dleaf_t)));
+	printf ("%5i marksurfaces %6i\n", nummarksurfaces, (int)(nummarksurfaces*sizeof(dmarksurfaces[0])));
+	printf ("%5i surfedges    %6i\n", numsurfedges, (int)(numsurfedges*sizeof(dmarksurfaces[0])));
+	printf ("%5i edges        %6i\n", numedges, (int)(numedges*sizeof(dedge_t)));
+	if (!texdatasize)
+		printf ("    0 textures          0\n");
+	else
+		printf ("%5i textures     %6i\n", ((dmiptexlump_t*)dtexdata)->nummiptex, texdatasize);
+	printf ("      lightdata    %6i\n", lightdatasize);
+	printf ("      visdata      %6i\n", visdatasize);
+	printf ("      entdata      %6i\n", entdatasize);
+}
+
+//============================================================================
+
+/*
+===============
+CompressVis
+===============
+*/
+int CompressVis (byte *vis, byte *dest, int visrow)
+{
+	int		j;
+	int		rep;
+	byte	*dest_p;
+
+	dest_p = dest;
+
+	for (j=0 ; j<visrow ; j++)
+	{
+		*dest_p++ = vis[j];
+		if (vis[j])
+			continue;
+
+		rep = 1;
+		for (j++; j<visrow ; j++)
+			if (vis[j] || rep == 255)
+				break;
+			else
+				rep++;
+		*dest_p++ = rep;
+		j--;
+	}
+
+	return dest_p - dest;
+}
+
+/*
+===============
+DecompressVis
+===============
+*/
+void DecompressVis(byte *in, byte *out, int size)
+{
+	byte *end = out + size;
+	int n;
+	while (out < end)
+	{
+		n = *in++;
+		if (n)
+			*out++ = n;
+		else
+		{
+			n = *in++;
+			while (n--)
+				*out++ = 0;
+		}
+	}
+}
+
+//============================================================================
+
+int			num_entities = 0;
+entity_t	entities[MAX_MAP_ENTITIES];
+
+void PrintEntity (entity_t *ent)
+{
+	epair_t	*ep;
+
+	for (ep=ent->epairs ; ep ; ep=ep->next)
+		printf ("%20s : %s\n", ep->key, ep->value);
+}
+
+
+char *ValueForKey (entity_t *ent, char *key)
+{
+	epair_t	*ep;
+
+	for (ep=ent->epairs ; ep ; ep=ep->next)
+		if (!strcmp (ep->key, key) )
+			return ep->value;
+		return "";
+}
+
+vec_t FloatForKey (entity_t *ent, char *key)
+{
+	epair_t	*ep;
+
+	for (ep=ent->epairs ; ep ; ep=ep->next)
+		if (!strcmp (ep->key, key) )
+			return atof( ep->value );
+
+	return 0;
+}
+
+void SetKeyValue (entity_t *ent, char *key, char *value)
+{
+	epair_t	*ep;
+
+	for (ep=ent->epairs ; ep ; ep=ep->next)
+		if (!strcmp (ep->key, key) )
+		{
+			qfree (ep->value);
+			ep->value = copystring(value);
+			return;
+		}
+	ep = qmalloc (sizeof(*ep));
+	ep->next = ent->epairs;
+	ent->epairs = ep;
+	ep->key = copystring(key);
+	ep->value = copystring(value);
+}
+
+/*
+=================
+ParseEpair
+=================
+*/
+epair_t *ParseEpair (void)
+{
+	epair_t	*e;
+
+	e = qmalloc (sizeof(epair_t));
+	memset (e, 0, sizeof(epair_t));
+
+	if (strlen(token) >= MAX_KEY-1)
+		Error ("ParseEpair: token too long");
+	e->key = copystring(token);
+	GetToken (false);
+	if (strlen(token) >= MAX_VALUE-1)
+		Error ("ParseEpair: token too long");
+	e->value = copystring(token);
+
+	return e;
+}
+
+entity_t *FindEntityWithKeyPair( char *key, char *value )
+{
+	entity_t *ent;
+	epair_t	*ep;
+	int i;
+
+	for (i=0 ; i<num_entities ; i++)
+	{
+		ent = &entities[ i ];
+		for (ep=ent->epairs ; ep ; ep=ep->next)
+		{
+			if (!strcmp (ep->key, key) )
+			{
+				if ( !strcmp( ep->value, value ) )
+					return ent;
+				break;
+			}
+		}
+	}
+	return NULL;
+}
+
+void GetVectorForKey (entity_t *ent, char *key, vec3_t vec)
+{
+	char	*k;
+	double	v1, v2, v3;
+
+	k = ValueForKey (ent, key);
+	v1 = v2 = v3 = 0;
+	// scanf into doubles, then assign, so it is vec_t size independent
+	sscanf (k, "%lf %lf %lf", &v1, &v2, &v3);
+	vec[0] = v1;
+	vec[1] = v2;
+	vec[2] = v3;
+}
+
+/*
+==================
+ParseEntities
+==================
+*/
+void ParseEntities (void)
+{
+	char 		*data;
+	entity_t	*entity;
+	char		key[MAX_KEY];
+	epair_t		*epair;
+
+	data = dentdata;
+
+	//
+	// start parsing
+	//
+	num_entities = 0;
+
+	// go through all the entities
+	while (1)
+	{
+		// parse the opening brace
+		data = COM_Parse (data);
+		if (!data)
+			break;
+		if (com_token[0] != '{')
+			Error ("LoadEntities: found %s when expecting {", com_token);
+
+		if (num_entities == MAX_MAP_ENTITIES)
+			Error ("LoadEntities: MAX_MAP_ENTITIES");
+
+		entity = &entities[num_entities++];
+
+		// go through all the keys in this entity
+		while (1)
+		{
+			int		c;
+
+			// parse key
+			data = COM_Parse (data);
+			if (!data)
+				Error ("LoadEntities: EOF without closing brace");
+			if (!strcmp(com_token,"}"))
+				break;
+
+			strcpy (key, com_token);
+
+			// parse value
+			data = COM_Parse (data);
+			if (!data)
+				Error ("LoadEntities: EOF without closing brace");
+			c = com_token[0];
+			if (c == '}')
+				Error ("LoadEntities: closing brace without data");
+
+			epair = malloc (sizeof(epair_t));
+			epair->key = copystring( key );
+			epair->value = copystring( com_token );
+			epair->next = entity->epairs;
+			entity->epairs = epair;
+		}
+	}
+}
+
+/*
+==================
+UnparseEntity
+==================
+*/
+static void UnparseEntity( entity_t *ent, char *buf, char **end )
+{
+	epair_t		*ep;
+	char		line[16384];
+
+	ep = ent->epairs;
+	if( !ep )
+		return;
+
+	strcat (*end,"{\n");
+	*end += 2;
+
+	for ( ; ep ; ep=ep->next)
+	{
+		sprintf (line, "\"%s\" \"%s\"\n", ep->key, ep->value);
+		strcat (*end, line);
+		*end += strlen(line);
+	}
+	strcat (*end,"}\n");
+	*end += 2;
+
+	if (*end > buf + MAX_MAP_ENTSTRING)
+		Error ("Entity text too long");
+}
+
+/*
+==================
+UnparseEntities
+==================
+*/
+void UnparseEntities (void)
+{
+	int			i;
+	entity_t	*ent;
+	char		*buf, *end;
+
+	buf = dentdata;
+	end = buf;
+	*end = 0;
+
+	// Vic: write "worldspawn" as the very first entity (engines might depend on it)
+	ent = FindEntityWithKeyPair( "classname", "worldspawn" );
+	if( ent )
+		UnparseEntity( ent, buf, &end );
+
+	for (i=0 ; i<num_entities ; i++) {
+		if( &entities[i] != ent )
+			UnparseEntity( &entities[i], buf, &end );
+	}
+
+	entdatasize = end - buf + 1;
+}
Index: hmap2/bspfile.h
diff -u /dev/null hmap2/bspfile.h:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/bspfile.h	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,287 @@
+
+#ifndef BSPFILE_H
+#define BSPFILE_H
+
+// upper design bounds
+
+#define	MAX_MAP_HULLS			4
+
+#define	MAX_MAP_MODELS			256
+#define	MAX_MAP_BRUSHES			32768		// LordHavoc: I ran into this myself, was 4096
+#define	MAX_MAP_ENTITIES		2048		// LordHavoc: was 1024
+#define	MAX_MAP_ENTSTRING		0x100000	// LordHavoc: was 65536
+
+#define	MAX_MAP_PLANES			32767		// LordHavoc: I ran into this myself, was 8192
+#define	MAX_MAP_NODES			32767		// because negative shorts are contents
+#define	MAX_MAP_CLIPNODES		32767		//
+#define	MAX_MAP_LEAFS			32767		//
+#define	MAX_MAP_VERTS			65535
+#define	MAX_MAP_FACES			65535
+#define	MAX_MAP_MARKSURFACES	65535
+#define	MAX_MAP_TEXINFO			4096
+#define	MAX_MAP_EDGES			0x100000	// LordHavoc: was 256000
+#define	MAX_MAP_SURFEDGES		0x200000	// LordHavoc: was 512000
+#define	MAX_MAP_MIPTEX			0x800000	// LordHavoc: quadrupled (was 0x200000)
+#define	MAX_MAP_LIGHTING		0x400000	// LordHavoc: raised from 0x100000 (1mb) to 0x400000 (4mb)
+#define	MAX_MAP_VISIBILITY		0x400000	// LordHavoc: quadrupled (was 0x100000)
+
+// key / value pair sizes
+
+#define	MAX_KEY		32
+#define	MAX_VALUE	1024
+
+//=============================================================================
+
+
+#define BSPVERSION	29
+
+typedef struct
+{
+	int		fileofs, filelen;
+} lump_t;
+
+#define	LUMP_ENTITIES	0
+#define	LUMP_PLANES		1
+#define	LUMP_TEXTURES	2
+#define	LUMP_VERTEXES	3
+#define	LUMP_VISIBILITY	4
+#define	LUMP_NODES		5
+#define	LUMP_TEXINFO	6
+#define	LUMP_FACES		7
+#define	LUMP_LIGHTING	8
+#define	LUMP_CLIPNODES	9
+#define	LUMP_LEAFS		10
+#define	LUMP_MARKSURFACES 11
+#define	LUMP_EDGES		12
+#define	LUMP_SURFEDGES	13
+#define	LUMP_MODELS		14
+
+#define	HEADER_LUMPS	15
+
+typedef struct
+{
+	float		mins[3], maxs[3];
+	float		origin[3];
+	int			headnode[MAX_MAP_HULLS];
+	int			visleafs;		// not including the solid leaf 0
+	int			firstface, numfaces;
+} dmodel_t;
+
+typedef struct
+{
+	int			version;
+	lump_t		lumps[HEADER_LUMPS];
+} dheader_t;
+
+typedef struct
+{
+	int			nummiptex;
+	int			dataofs[4];		// [nummiptex]
+} dmiptexlump_t;
+
+#define	MIPLEVELS	4
+typedef struct miptex_s
+{
+	char		name[16];
+	unsigned	width, height;
+	unsigned	offsets[MIPLEVELS];		// four mip maps stored
+} miptex_t;
+
+
+typedef struct
+{
+	float	point[3];
+} dvertex_t;
+
+
+// 0-2 are axial planes
+#define	PLANE_X			0
+#define	PLANE_Y			1
+#define	PLANE_Z			2
+
+// 3-5 are non-axial planes snapped to the nearest
+#define	PLANE_ANYX		3
+#define	PLANE_ANYY		4
+#define	PLANE_ANYZ		5
+
+typedef struct
+{
+	float	normal[3];
+	float	dist;
+	int		type;		// PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate
+} dplane_t;
+
+
+
+#define	CONTENTS_EMPTY		-1
+#define	CONTENTS_SOLID		-2
+#define	CONTENTS_WATER		-3
+#define	CONTENTS_SLIME		-4
+#define	CONTENTS_LAVA		-5
+#define	CONTENTS_SKY		-6
+
+// !!! if this is changed, it must be changed in asm_i386.h too !!!
+typedef struct
+{
+	int			planenum;
+	short		children[2];	// negative numbers are -(leafs+1), not nodes
+	short		mins[3];		// for sphere culling
+	short		maxs[3];
+	unsigned short	firstface;
+	unsigned short	numfaces;	// counting both sides
+} dnode_t;
+
+typedef struct
+{
+	int			planenum;
+	short		children[2];	// negative numbers are contents
+} dclipnode_t;
+
+
+typedef struct texinfo_s
+{
+	float		vecs[2][4];		// [s/t][xyz offset]
+	int			miptex;
+	int			flags;
+} texinfo_t;
+#define	TEX_SPECIAL		1		// sky or slime, no lightmap or 256 subdivision
+
+// note that edge 0 is never used, because negative edge nums are used for
+// counterclockwise use of the edge in a face
+typedef struct
+{
+	unsigned short	v[2];		// vertex numbers
+} dedge_t;
+
+#define	MAXLIGHTMAPS	4
+typedef struct
+{
+	short		planenum;
+	short		side;
+
+	int			firstedge;		// we must support > 64k edges
+	short		numedges;	
+	short		texinfo;
+
+// lighting info
+	byte		styles[MAXLIGHTMAPS];
+	int			lightofs;		// start of [numstyles*surfsize] samples
+} dface_t;
+
+
+
+#define	AMBIENT_WATER	0
+#define	AMBIENT_SKY		1
+#define	AMBIENT_SLIME	2
+#define	AMBIENT_LAVA	3
+
+#define	NUM_AMBIENTS			4		// automatic ambient sounds
+
+// leaf 0 is the generic CONTENTS_SOLID leaf, used for all solid areas
+// all other leafs need visibility info
+typedef struct
+{
+	int			contents;
+	int			visofs;				// -1 = no visibility info
+
+	short		mins[3];			// for frustum culling
+	short		maxs[3];
+
+	unsigned short		firstmarksurface;
+	unsigned short		nummarksurfaces;
+
+	byte		ambient_level[NUM_AMBIENTS];
+} dleaf_t;
+
+//============================================================================
+
+#ifndef QUAKE_GAME
+
+// the utilities get to be lazy and just use large static arrays
+
+extern	int			nummodels;
+extern	dmodel_t	dmodels[MAX_MAP_MODELS];
+
+extern	int			visdatasize;
+extern	byte		dvisdata[MAX_MAP_VISIBILITY];
+
+extern	int			lightdatasize;
+extern	byte		dlightdata[MAX_MAP_LIGHTING];
+
+// LordHavoc: stored in .lit file
+extern	int			rgblightdatasize;
+extern	byte		drgblightdata[MAX_MAP_LIGHTING*3];
+
+extern	int			texdatasize;
+extern	byte		dtexdata[MAX_MAP_MIPTEX]; // (dmiptexlump_t)
+
+extern	int			entdatasize;
+extern	char		dentdata[MAX_MAP_ENTSTRING];
+
+extern	int			numleafs;
+extern	dleaf_t		dleafs[MAX_MAP_LEAFS];
+
+extern	int			numplanes;
+extern	dplane_t	dplanes[MAX_MAP_PLANES];
+
+extern	int			numvertexes;
+extern	dvertex_t	dvertexes[MAX_MAP_VERTS];
+
+extern	int			numnodes;
+extern	dnode_t		dnodes[MAX_MAP_NODES];
+
+extern	int			numtexinfo;
+extern	texinfo_t	texinfo[MAX_MAP_TEXINFO];
+
+extern	int			numfaces;
+extern	dface_t		dfaces[MAX_MAP_FACES];
+
+extern	int			numclipnodes;
+extern	dclipnode_t	dclipnodes[MAX_MAP_CLIPNODES];
+
+extern	int			numedges;
+extern	dedge_t		dedges[MAX_MAP_EDGES];
+
+extern	int			nummarksurfaces;
+extern	unsigned short	dmarksurfaces[MAX_MAP_MARKSURFACES];
+
+extern	int			numsurfedges;
+extern	int			dsurfedges[MAX_MAP_SURFEDGES];
+
+typedef struct epair_s
+{
+	struct epair_s	*next;
+	char	*key;
+	char	*value;
+} epair_t;
+
+typedef struct
+{
+	epair_t		*epairs;
+	struct mbrush_s *brushes;
+} entity_t;
+
+extern	int			num_entities;
+extern	entity_t	entities[MAX_MAP_ENTITIES];
+
+void	PrintEntity (entity_t *ent);
+char 	*ValueForKey (entity_t *ent, char *key);
+vec_t	FloatForKey (entity_t *ent, char *key);
+void	SetKeyValue (entity_t *ent, char *key, char *value);
+entity_t *FindEntityWithKeyPair( char *key, char *value );
+void 	GetVectorForKey (entity_t *ent, char *key, vec3_t vec);
+epair_t *ParseEpair (void);
+void	ParseEntities (void);
+void	UnparseEntities (void);
+
+int		CompressVis (byte *vis, byte *dest, int visrow);
+void	DecompressVis(byte *in, byte *out, int size);
+
+void LoadBSPFile (char *filename);
+void WriteBSPFile (char *filename, qboolean litonly);
+void PrintBSPFileSizes (void);
+
+#endif
+
+#endif
+
Index: hmap2/cmdlib.c
diff -u /dev/null hmap2/cmdlib.c:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/cmdlib.c	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,847 @@
+// cmdlib.c
+
+#ifndef WIN32
+#define USEGETTIMEOFDAY 1
+#endif
+
+#define PATHSEPERATOR   '/'
+#define __USE_BSD 1
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef WIN32
+#include <limits.h>
+#include <direct.h>
+#include <windows.h>
+#endif
+#include "cmdlib.h"
+#if USEGETTIMEOFDAY
+#include <sys/time.h>
+#include <unistd.h>
+#endif
+
+#include "mem.h"
+
+// set these before calling CheckParm
+int myargc;
+char **myargv;
+
+char		com_token[1024];
+qboolean	com_eof;
+
+qboolean		archive;
+char			archivedir[1024];
+
+
+/*
+=================
+Error
+
+For abnormal program terminations
+=================
+*/
+void Error (char *error, ...)
+{
+	va_list argptr;
+
+	printf ("************ ERROR ************\n");
+
+	va_start (argptr,error);
+	vprintf (error,argptr);
+	va_end (argptr);
+	printf ("\n");
+
+#if _MSC_VER && _DEBUG
+	printf("press any key\n");
+	getchar();
+#endif
+
+	exit (1);
+}
+
+
+/*
+
+qdir will hold the path up to the quake directory, including the slash
+
+  f:\quake\
+  /raid/quake/
+
+gamedir will hold qdir + the game directory (id1, id2, etc)
+
+  */
+
+char *copystring(char *s)
+{
+  char	*b;
+  b = qmalloc(strlen(s)+1);
+  strcpy (b, s);
+  return b;
+}
+
+
+
+/*
+================
+I_DoubleTime
+================
+*/
+double I_DoubleTime (void)
+{
+#ifdef WIN32
+	static DWORD starttime;
+	static qboolean first = true;
+	DWORD now;
+
+	if (first)
+		timeBeginPeriod (1);
+
+	now = timeGetTime ();
+
+	if (first)
+	{
+		first = false;
+		starttime = now;
+		return 0.0;
+	}
+
+	if (now < starttime)				// wrapped?
+		return (now / 1000.0) + (LONG_MAX - starttime / 1000.0);
+
+	if (now - starttime == 0)
+		return 0.0;
+
+	return (now - starttime) / 1000.0;
+#elif USEGETTIMEOFDAY
+	// LordHavoc: see top of file to disable this cleanly if your system does not support it
+#include <time.h>
+	struct timeval	tp;
+	struct timezone	tzp;
+	static int		secbase;
+
+	gettimeofday(&tp, &tzp);
+
+	if (!secbase)
+	{
+		secbase = tp.tv_sec;
+		return tp.tv_usec/1000000.0;
+	}
+
+	return (tp.tv_sec - secbase) + tp.tv_usec/1000000.0;
+#else
+	time_t	t;
+
+	time (&t);
+
+	return t;
+#endif
+}
+
+/*
+void Q_getwd (char *out)
+{
+#ifdef WIN32
+   _getcwd (out, 256);
+   strcat (out, "\\");
+#else
+   getwd (out);
+   strcat (out, "/");
+#endif
+}
+*/
+/*
+void Q_getwd (char *out)
+{
+  getwd (out);
+}
+*/
+
+
+void Q_mkdir (char *path)
+{
+#ifdef WIN32
+  if (mkdir (path) != -1)
+#else
+  if (mkdir (path, 0777) != -1)
+#endif
+    return;
+  if (errno != EEXIST)
+    Error ("mkdir %s: %s",path, strerror(errno));
+}
+
+/*
+============
+FileTime
+
+returns -1 if not present
+============
+*/
+int	FileTime (char *path)
+{
+  struct	stat	buf;
+
+  if (stat (path,&buf) == -1)
+    return -1;
+
+  return buf.st_mtime;
+}
+
+
+
+/*
+==============
+COM_Parse
+
+Parse a token out of a string
+==============
+*/
+char *COM_Parse (char *data)
+{
+  int		c;
+  int		len;
+
+  len = 0;
+  com_token[0] = 0;
+
+  if (!data)
+    return NULL;
+
+  // skip whitespace
+skipwhite:
+  while ( (c = *data) <= ' ')
+    {
+      if (c == 0)
+	{
+	  com_eof = true;
+	  return NULL;			// end of file;
+	}
+      data++;
+    }
+
+  // skip // comments
+  if (c=='/' && data[1] == '/')
+    {
+      while (*data && *data != '\n')
+	data++;
+      goto skipwhite;
+    }
+
+
+  // handle quoted strings specially
+  if (c == '\"')
+    {
+      data++;
+      do
+	{
+	  c = *data++;
+	  if (c=='\"')
+	    {
+	      com_token[len] = 0;
+	      return data;
+	    }
+	  com_token[len] = c;
+	  len++;
+	} while (1);
+    }
+
+  // parse single characters
+  if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':')
+    {
+      com_token[len] = c;
+      len++;
+      com_token[len] = 0;
+      return data+1;
+    }
+
+  // parse a regular word
+  do
+    {
+      com_token[len] = c;
+      data++;
+      len++;
+      c = *data;
+      if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':')
+	break;
+    } while (c>32);
+
+  com_token[len] = 0;
+  return data;
+}
+
+
+int Q_strncasecmp (char *s1, char *s2, int n)
+{
+	int		c1, c2;
+
+	while (1)
+	{
+		c1 = *s1++;
+		c2 = *s2++;
+
+		if (!n--)
+			return 0;		// strings are equal until end point
+
+		if (c1 != c2)
+		{
+			if (c1 >= 'a' && c1 <= 'z')
+				c1 -= ('a' - 'A');
+			if (c2 >= 'a' && c2 <= 'z')
+				c2 -= ('a' - 'A');
+			if (c1 != c2)
+				return -1;		// strings not equal
+		}
+		if (!c1)
+			return 0;		// strings are equal
+	}
+
+	return -1;
+}
+
+int Q_strcasecmp (char *s1, char *s2)
+{
+  return Q_strncasecmp (s1, s2, 99999);
+}
+
+
+char *Q_strupr (char *start)
+{
+  char	*in;
+  in = start;
+  while (*in)
+    {
+      *in = toupper(*in);
+      in++;
+    }
+  return start;
+}
+
+char *Q_strlower (char *start)
+{
+  char	*in;
+  in = start;
+  while (*in)
+    {
+      *in = tolower(*in);
+      in++;
+    }
+  return start;
+}
+
+
+/*
+=============================================================================
+
+						MISC FUNCTIONS
+
+=============================================================================
+*/
+
+
+/*
+=================
+CheckParm
+
+Checks for the given parameter in the program's command line arguments
+Returns the argument number (1 to argc-1) or 0 if not present
+=================
+*/
+int CheckParm (char *check)
+{
+  int             i;
+
+  for (i = 1;i<myargc;i++)
+    {
+      if ( !Q_strcasecmp(check, myargv[i]) )
+	return i;
+    }
+
+  return 0;
+}
+
+
+
+/*
+================
+Q_filelength
+================
+*/
+int Q_filelength (FILE *f)
+{
+  int		pos;
+  int		end;
+
+  pos = ftell (f);
+  fseek (f, 0, SEEK_END);
+  end = ftell (f);
+  fseek (f, pos, SEEK_SET);
+
+  return end;
+}
+
+
+FILE *SafeOpenWrite (char *filename)
+{
+  FILE	*f;
+
+  f = fopen(filename, "wb");
+
+  if (!f)
+    Error ("Error opening %s: %s",filename,strerror(errno));
+
+  return f;
+}
+
+FILE *SafeOpenRead (char *filename)
+{
+  FILE	*f;
+
+  f = fopen(filename, "rb");
+
+  if (!f)
+    Error ("Error opening %s: %s",filename,strerror(errno));
+
+  return f;
+}
+
+
+void SafeRead (FILE *f, void *buffer, int count)
+{
+  if ( fread (buffer, 1, count, f) != (size_t)count)
+    Error ("File read failure");
+}
+
+
+void SafeWrite (FILE *f, void *buffer, int count)
+{
+  if (fwrite (buffer, 1, count, f) != (size_t)count)
+    Error ("File write failure");
+}
+
+
+
+/*
+==============
+LoadFile
+==============
+*/
+int    LoadFile (char *filename, void **bufferptr)
+{
+  FILE	*f;
+  int    length;
+  void    *buffer;
+
+  f = SafeOpenRead (filename);
+  length = Q_filelength (f);
+  buffer = qmalloc (length+1);
+  ((char *)buffer)[length] = 0;
+  SafeRead (f, buffer, length);
+  fclose (f);
+
+  *bufferptr = buffer;
+  return length;
+}
+
+
+/*
+==============
+SaveFile
+==============
+*/
+void    SaveFile (char *filename, void *buffer, int count)
+{
+  FILE	*f;
+
+  f = SafeOpenWrite (filename);
+  SafeWrite (f, buffer, count);
+  fclose (f);
+}
+
+
+
+void DefaultExtension (char *path, char *extension)
+{
+  char    *src;
+  //
+  // if path doesn't have a .EXT, append extension
+  // (extension should include the .)
+  //
+  src = path + strlen(path) - 1;
+
+  while (*src != PATHSEPERATOR && src != path)
+    {
+      if (*src == '.')
+	return;                 // it has an extension
+      src--;
+    }
+
+  strcat (path, extension);
+}
+
+
+void DefaultPath (char *path, char *basepath)
+{
+  char    temp[128];
+
+  if (path[0] == PATHSEPERATOR)
+    return;                   // absolute path location
+  strcpy (temp,path);
+  strcpy (path,basepath);
+  strcat (path,temp);
+}
+
+
+void ReplaceExtension (char *path, char *extension)
+{
+	char    *src;
+	//
+	// if path has a .EXT, replace it with extension
+	// if path doesn't have a .EXT, append extension
+	// (extension should include the .)
+	//
+	src = path + strlen(path) - 1;
+
+	while (*src != PATHSEPERATOR && src != path)
+	{
+		if (*src == '.')
+		{
+			*src = 0;
+			break;
+		}
+		src--;
+	}
+
+	strcat (path, extension);
+}
+
+
+/*
+====================
+Extract file parts
+====================
+*/
+void ExtractFilePath (char *path, char *dest)
+{
+  char    *src;
+
+  src = path + strlen(path) - 1;
+
+  //
+  // back up until a \ or the start
+  //
+  while (src != path && *(src-1) != PATHSEPERATOR)
+    src--;
+
+  memcpy (dest, path, src-path);
+  dest[src-path] = 0;
+}
+
+void ExtractFileBase (char *path, char *dest)
+{
+  char    *src;
+
+  src = path + strlen(path) - 1;
+
+  //
+  // back up until a \ or the start
+  //
+  while (src != path && *(src-1) != PATHSEPERATOR)
+    src--;
+
+  while (*src && *src != '.')
+    {
+      *dest++ = *src++;
+    }
+  *dest = 0;
+}
+
+void ExtractFileExtension (char *path, char *dest)
+{
+  char    *src;
+
+  src = path + strlen(path) - 1;
+
+  //
+  // back up until a . or the start
+  //
+  while (src != path && *(src-1) != '.')
+    src--;
+  if (src == path)
+    {
+      *dest = 0;	// no extension
+      return;
+    }
+
+  strcpy (dest,src);
+}
+
+
+/*
+==============
+ParseNum / ParseHex
+==============
+*/
+int ParseHex (char *hex)
+{
+  char    *str;
+  int    num;
+
+  num = 0;
+  str = hex;
+
+  while (*str)
+    {
+      num <<= 4;
+      if (*str >= '0' && *str <= '9')
+	num += *str-'0';
+      else if (*str >= 'a' && *str <= 'f')
+	num += 10 + *str-'a';
+      else if (*str >= 'A' && *str <= 'F')
+	num += 10 + *str-'A';
+      else
+	Error ("Bad hex number: %s",hex);
+      str++;
+    }
+
+  return num;
+}
+
+
+int ParseNum (char *str)
+{
+  if (str[0] == '$')
+    return ParseHex (str+1);
+  if (str[0] == '0' && str[1] == 'x')
+    return ParseHex (str+2);
+  return atol (str);
+}
+
+
+
+/*
+============================================================================
+
+					BYTE ORDER FUNCTIONS
+
+============================================================================
+*/
+
+#ifdef _SGI_SOURCE
+#define	__BIG_ENDIAN__
+#endif
+
+#ifdef __BIG_ENDIAN__
+
+short   LittleShort (short l)
+{
+  byte    b1,b2;
+
+  b1 = l&255;
+  b2 = (l>>8)&255;
+
+  return (b1<<8) + b2;
+}
+
+short   BigShort (short l)
+{
+  return l;
+}
+
+
+int    LittleLong (int l)
+{
+  byte    b1,b2,b3,b4;
+
+  b1 = l&255;
+  b2 = (l>>8)&255;
+  b3 = (l>>16)&255;
+  b4 = (l>>24)&255;
+
+  return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4;
+}
+
+int    BigLong (int l)
+{
+  return l;
+}
+
+
+float	LittleFloat (float l)
+{
+  union {byte b[4]; float f;} in, out;
+
+  in.f = l;
+  out.b[0] = in.b[3];
+  out.b[1] = in.b[2];
+  out.b[2] = in.b[1];
+  out.b[3] = in.b[0];
+
+  return out.f;
+}
+
+float	BigFloat (float l)
+{
+  return l;
+}
+
+
+#else
+
+
+short   BigShort (short l)
+{
+  byte    b1,b2;
+
+  b1 = l&255;
+  b2 = (l>>8)&255;
+
+  return (b1<<8) + b2;
+}
+
+short   LittleShort (short l)
+{
+  return l;
+}
+
+
+int    BigLong (int l)
+{
+  byte    b1,b2,b3,b4;
+
+  b1 = l&255;
+  b2 = (l>>8)&255;
+  b3 = (l>>16)&255;
+  b4 = (l>>24)&255;
+
+  return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4;
+}
+
+int    LittleLong (int l)
+{
+  return l;
+}
+
+float	BigFloat (float l)
+{
+  union {byte b[4]; float f;} in, out;
+
+  in.f = l;
+  out.b[0] = in.b[3];
+  out.b[1] = in.b[2];
+  out.b[2] = in.b[1];
+  out.b[3] = in.b[0];
+
+  return out.f;
+}
+
+float	LittleFloat (float l)
+{
+  return l;
+}
+
+
+#endif
+
+
+//=======================================================
+
+
+// FIXME: byte swap?
+
+// this is a 16 bit, non-reflected CRC using the polynomial 0x1021
+// and the initial and final xor values shown below...  in other words, the
+// CCITT standard CRC used by XMODEM
+
+#define CRC_INIT_VALUE	0xffff
+#define CRC_XOR_VALUE	0x0000
+
+static unsigned short crctable[256] =
+{
+  0x0000,	0x1021,	0x2042,	0x3063,	0x4084,	0x50a5,	0x60c6,	0x70e7,
+  0x8108,	0x9129,	0xa14a,	0xb16b,	0xc18c,	0xd1ad,	0xe1ce,	0xf1ef,
+  0x1231,	0x0210,	0x3273,	0x2252,	0x52b5,	0x4294,	0x72f7,	0x62d6,
+  0x9339,	0x8318,	0xb37b,	0xa35a,	0xd3bd,	0xc39c,	0xf3ff,	0xe3de,
+  0x2462,	0x3443,	0x0420,	0x1401,	0x64e6,	0x74c7,	0x44a4,	0x5485,
+  0xa56a,	0xb54b,	0x8528,	0x9509,	0xe5ee,	0xf5cf,	0xc5ac,	0xd58d,
+  0x3653,	0x2672,	0x1611,	0x0630,	0x76d7,	0x66f6,	0x5695,	0x46b4,
+  0xb75b,	0xa77a,	0x9719,	0x8738,	0xf7df,	0xe7fe,	0xd79d,	0xc7bc,
+  0x48c4,	0x58e5,	0x6886,	0x78a7,	0x0840,	0x1861,	0x2802,	0x3823,
+  0xc9cc,	0xd9ed,	0xe98e,	0xf9af,	0x8948,	0x9969,	0xa90a,	0xb92b,
+  0x5af5,	0x4ad4,	0x7ab7,	0x6a96,	0x1a71,	0x0a50,	0x3a33,	0x2a12,
+  0xdbfd,	0xcbdc,	0xfbbf,	0xeb9e,	0x9b79,	0x8b58,	0xbb3b,	0xab1a,
+  0x6ca6,	0x7c87,	0x4ce4,	0x5cc5,	0x2c22,	0x3c03,	0x0c60,	0x1c41,
+  0xedae,	0xfd8f,	0xcdec,	0xddcd,	0xad2a,	0xbd0b,	0x8d68,	0x9d49,
+  0x7e97,	0x6eb6,	0x5ed5,	0x4ef4,	0x3e13,	0x2e32,	0x1e51,	0x0e70,
+  0xff9f,	0xefbe,	0xdfdd,	0xcffc,	0xbf1b,	0xaf3a,	0x9f59,	0x8f78,
+  0x9188,	0x81a9,	0xb1ca,	0xa1eb,	0xd10c,	0xc12d,	0xf14e,	0xe16f,
+  0x1080,	0x00a1,	0x30c2,	0x20e3,	0x5004,	0x4025,	0x7046,	0x6067,
+  0x83b9,	0x9398,	0xa3fb,	0xb3da,	0xc33d,	0xd31c,	0xe37f,	0xf35e,
+  0x02b1,	0x1290,	0x22f3,	0x32d2,	0x4235,	0x5214,	0x6277,	0x7256,
+  0xb5ea,	0xa5cb,	0x95a8,	0x8589,	0xf56e,	0xe54f,	0xd52c,	0xc50d,
+  0x34e2,	0x24c3,	0x14a0,	0x0481,	0x7466,	0x6447,	0x5424,	0x4405,
+  0xa7db,	0xb7fa,	0x8799,	0x97b8,	0xe75f,	0xf77e,	0xc71d,	0xd73c,
+  0x26d3,	0x36f2,	0x0691,	0x16b0,	0x6657,	0x7676,	0x4615,	0x5634,
+  0xd94c,	0xc96d,	0xf90e,	0xe92f,	0x99c8,	0x89e9,	0xb98a,	0xa9ab,
+  0x5844,	0x4865,	0x7806,	0x6827,	0x18c0,	0x08e1,	0x3882,	0x28a3,
+  0xcb7d,	0xdb5c,	0xeb3f,	0xfb1e,	0x8bf9,	0x9bd8,	0xabbb,	0xbb9a,
+  0x4a75,	0x5a54,	0x6a37,	0x7a16,	0x0af1,	0x1ad0,	0x2ab3,	0x3a92,
+  0xfd2e,	0xed0f,	0xdd6c,	0xcd4d,	0xbdaa,	0xad8b,	0x9de8,	0x8dc9,
+  0x7c26,	0x6c07,	0x5c64,	0x4c45,	0x3ca2,	0x2c83,	0x1ce0,	0x0cc1,
+  0xef1f,	0xff3e,	0xcf5d,	0xdf7c,	0xaf9b,	0xbfba,	0x8fd9,	0x9ff8,
+  0x6e17,	0x7e36,	0x4e55,	0x5e74,	0x2e93,	0x3eb2,	0x0ed1,	0x1ef0
+};
+
+void CRC_Init(unsigned short *crcvalue)
+{
+  *crcvalue = CRC_INIT_VALUE;
+}
+
+void CRC_ProcessByte(unsigned short *crcvalue, byte data)
+{
+  *crcvalue = (*crcvalue << 8) ^ crctable[(*crcvalue >> 8) ^ data];
+}
+
+unsigned short CRC_Value(unsigned short crcvalue)
+{
+  return crcvalue ^ CRC_XOR_VALUE;
+}
+//=============================================================================
+
+/*
+============
+COM_CreatePath
+============
+*/
+void	COM_CreatePath (char *path)
+{
+  char	*ofs, c;
+
+  for (ofs = path+1 ; *ofs ; ofs++)
+    {
+      c = *ofs;
+      if (c == '/' || c == '\\')
+	{	// create the directory
+	  *ofs = 0;
+	  Q_mkdir (path);
+	  *ofs = c;
+	}
+    }
+}
+
+
+/*
+============
+COM_CopyFile
+
+  Used to archive source files
+============
+*/
+void COM_CopyFile (char *from, char *to)
+{
+  void	*buffer;
+  int		length;
+
+  length = LoadFile (from, &buffer);
+  COM_CreatePath (to);
+  SaveFile (to, buffer, length);
+  qfree (buffer);
+}
Index: hmap2/cmdlib.h
diff -u /dev/null hmap2/cmdlib.h:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/cmdlib.h	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,105 @@
+// cmdlib.h
+
+#ifndef __CMDLIB__
+#define __CMDLIB__
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <ctype.h>
+#include <time.h>
+#include <stdarg.h>
+//#include <unistd.h>
+
+#ifndef __BYTEBOOL__
+#define __BYTEBOOL__
+typedef enum {false, true} qboolean;
+typedef unsigned char byte;
+#endif
+
+// the dec offsetof macro doesn't work very well...
+#define myoffsetof(type,identifier) ((size_t)&((type *)0)->identifier)
+
+// set these before calling CheckParm
+extern int myargc;
+extern char **myargv;
+
+extern char *Q_strupr (char *in);
+extern char *Q_strlower (char *in);
+extern int Q_strncasecmp (char *s1, char *s2, int n);
+extern int Q_strcasecmp (char *s1, char *s2);
+//extern void Q_getwd (char *out);
+
+extern int Q_filelength (FILE *f);
+extern int	FileTime (char *path);
+
+extern void	Q_mkdir (char *path);
+
+extern char qdir[1024];
+extern char gamedir[1024];
+//extern void SetQdirFromPath (char *path);
+//extern char *ExpandPath (char *path);
+//extern char *ExpandPathAndArchive (char *path);
+
+
+extern double I_DoubleTime (void);
+
+extern void Error (char *error, ...);
+
+extern int CheckParm (char *check);
+
+extern FILE *SafeOpenWrite (char *filename);
+extern FILE *SafeOpenRead (char *filename);
+extern void SafeRead (FILE *f, void *buffer, int count);
+extern void SafeWrite (FILE *f, void *buffer, int count);
+
+extern int LoadFile (char *filename, void **bufferptr);
+extern void SaveFile (char *filename, void *buffer, int count);
+
+extern void DefaultExtension (char *path, char *extension);
+extern void DefaultPath (char *path, char *basepath);
+extern void ReplaceExtension (char *path, char *extension);
+
+extern void ExtractFilePath (char *path, char *dest);
+extern void ExtractFileBase (char *path, char *dest);
+extern void ExtractFileExtension (char *path, char *dest);
+
+extern int ParseNum (char *str);
+
+extern short BigShort (short l);
+extern short LittleShort (short l);
+extern int BigLong (int l);
+extern int LittleLong (int l);
+extern float BigFloat (float l);
+extern float LittleFloat (float l);
+
+
+extern char *COM_Parse (char *data);
+
+extern char com_token[1024];
+extern qboolean com_eof;
+
+extern char *copystring(char *s);
+
+// LordHavoc: increased maximum token length from 128 to 1024
+#define	MAXTOKEN	1024
+
+extern char token[MAXTOKEN];
+extern int	scriptline;
+
+void StartTokenParsing (char *data);
+qboolean GetToken (qboolean crossline);
+void UngetToken (void);
+
+extern void CRC_Init(unsigned short *crcvalue);
+extern void CRC_ProcessByte(unsigned short *crcvalue, byte data);
+extern unsigned short CRC_Value(unsigned short crcvalue);
+
+extern void COM_CreatePath (char *path);
+extern void COM_CopyFile (char *from, char *to);
+
+extern qboolean archive;
+extern char archivedir[1024];
+
+#endif
Index: hmap2/csg4.c
diff -u /dev/null hmap2/csg4.c:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/csg4.c	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,311 @@
+// csg4.c
+
+#include "bsp5.h"
+
+/*
+
+NOTES
+-----
+Brushes that touch still need to be split at the cut point to make a tjunction
+
+*/
+
+face_t	*inside, *outside;
+int		brushfaces;
+int		csgfaces;
+int		csgmergefaces;
+
+/*
+=================
+ClipInside
+
+Clips all of the faces in the inside list, possibly moving them to the
+outside list or spliting it into a piece in each list.
+
+Faces exactly on the plane will stay inside unless overdrawn by later brush
+
+frontside is the side of the plane that holds the outside list
+=================
+*/
+void ClipInside (int splitplane, int frontside, qboolean precedence)
+{
+	face_t	*f, *next;
+	face_t	*frags[2];
+	face_t	*insidelist;
+	plane_t *split;
+
+	split = &mapplanes[splitplane];
+
+	insidelist = NULL;
+	for (f=inside ; f ; f=next)
+	{
+		next = f->next;
+
+		if (f->planenum == splitplane)
+		{	// exactly on, handle special
+			if ( frontside != f->planeside || precedence )
+			{	// allways clip off opposite faceing
+				frags[frontside] = NULL;
+				frags[!frontside] = f;
+			}
+			else
+			{	// leave it on the outside
+				frags[frontside] = f;
+				frags[!frontside] = NULL;
+			}
+		}
+		else
+		{	// proper split
+			SplitFace (f, split, &frags[0], &frags[1]);
+		}
+
+		if (frags[frontside])
+		{
+			frags[frontside]->next = outside;
+			outside = frags[frontside];
+		}
+		if (frags[!frontside])
+		{
+			frags[!frontside]->next = insidelist;
+			insidelist = frags[!frontside];
+		}
+	}
+
+	inside = insidelist;
+}
+
+
+/*
+==================
+SaveOutside
+
+Saves all of the faces in the outside list to the bsp plane list
+==================
+*/
+void SaveOutside (tree_t *tree, qboolean mirror)
+{
+	int		planenum;
+	face_t	*f , *next, *newf;
+
+	for (f=outside ; f ; f=next)
+	{
+		next = f->next;
+		csgfaces++;
+		planenum = f->planenum;
+
+		if (mirror)
+		{
+			newf = NewFaceFromFace (f);
+			newf->planeside = f->planeside ^ 1;	// reverse side
+			newf->contents[0] = f->contents[1];
+			newf->contents[1] = f->contents[0];
+			newf->winding = ReverseWinding( f->winding );
+			tree->validfaces[planenum] = MergeFaceToList_r( newf, tree->validfaces[planenum] );
+		}
+
+		tree->validfaces[planenum] = MergeFaceToList_r( f, tree->validfaces[planenum]);
+		tree->validfaces[planenum] = FreeMergeListScraps( tree->validfaces[planenum] );
+	}
+}
+
+/*
+==================
+FreeInside
+
+Free all the faces that got clipped out
+==================
+*/
+void FreeInside (int contents)
+{
+	face_t	*f, *next;
+
+	for (f=inside ; f ; f=next)
+	{
+		next = f->next;
+
+		if (contents != CONTENTS_SOLID)
+		{
+			f->contents[0] = contents;
+			f->next = outside;
+			outside = f;
+		}
+		else
+			FreeFace (f);
+	}
+}
+
+//==========================================================================
+
+/*
+===========
+AllocSurface
+===========
+*/
+surface_t *AllocSurface( void )
+{
+	surface_t	*s;
+
+	s = qmalloc( sizeof( surface_t ) );
+	memset( s, 0, sizeof( surface_t ) );
+
+	return s;
+}
+
+/*
+===========
+FreeSurface
+===========
+*/
+void FreeSurface( surface_t *s ) {
+	qfree( s );
+}
+
+//==========================================================================
+
+/*
+==================
+BuildSurfaces
+
+Returns a chain of all the external surfaces with one or more visible
+faces.
+==================
+*/
+void BuildSurfaces (tree_t *tree)
+{
+	face_t			**f;
+	face_t			*count;
+	int				i;
+	surface_t		*s;
+	surface_t		*surfhead;
+
+	surfhead = NULL;
+
+	f = tree->validfaces;
+	for (i=0 ; i<nummapplanes ; i++, f++)
+	{
+		if (!*f)
+			continue;	// nothing left on this plane
+
+// create a new surface to hold the faces on this plane
+		s = AllocSurface ();
+		s->planenum = i;
+		s->next = surfhead;
+		surfhead = s;
+		s->faces = *f;
+		for (count = s->faces ; count ; count=count->next)
+			csgmergefaces++;
+		CalcSurfaceInfo (s);	// bounding box and flags
+	}
+
+	tree->surfaces = surfhead;
+}
+
+//==========================================================================
+
+/*
+==================
+CopyFacesToOutside
+==================
+*/
+void CopyFacesToOutside (brush_t *b)
+{
+	face_t		*f, *newf;
+
+	outside = NULL;
+
+	for (f=b->faces ; f ; f=f->next)
+	{
+		if( !f->winding )
+			continue;
+
+		brushfaces++;
+		newf = AllocFace ();
+		newf->texturenum = f->texturenum;
+		newf->planenum = f->planenum;
+		newf->planeside = f->planeside;
+		newf->original = f->original;
+		newf->winding = CopyWinding( f->winding );
+		newf->next = outside;
+		newf->contents[0] = CONTENTS_EMPTY;
+		newf->contents[1] = b->contents;
+		outside = newf;
+	}
+}
+
+/*
+==================
+CSGFaces
+
+Builds a list of surfaces containing all of the faces
+==================
+*/
+void CSGFaces (tree_t *tree)
+{
+	brush_t		*b1, *b2;
+	int			i;
+	qboolean		overwrite;
+	face_t		*f;
+
+	qprintf ("---- CSGFaces ----\n");
+
+	memset (tree->validfaces, 0, sizeof(tree->validfaces));
+
+	csgfaces = brushfaces = csgmergefaces = 0;
+
+//
+// do the solid faces
+//
+	for (b1=tree->brushes ; b1 ; b1 = b1->next)
+	{
+	// set outside to a copy of the brush's faces
+		CopyFacesToOutside (b1);
+
+		overwrite = false;
+
+		for (b2=tree->brushes ; b2 ; b2 = b2->next)
+		{
+		// see if b2 needs to clip a chunk out of b1
+
+			if (b1==b2)
+			{
+				overwrite = true;	// later brushes now overwrite
+				continue;
+			}
+
+		// check bounding box first
+			for (i=0 ; i<3 ; i++)
+				if (b1->mins[i] > b2->maxs[i] || b1->maxs[i] < b2->mins[i])
+					break;
+			if (i<3)
+				continue;
+
+		// divide faces by the planes of the new brush
+
+			inside = outside;
+			outside = NULL;
+
+			for (f=b2->faces ; f ; f=f->next)
+				ClipInside (f->planenum, f->planeside, overwrite);
+
+		// these faces are continued in another brush, so get rid of them
+			if (b1->contents == CONTENTS_SOLID && b2->contents <= CONTENTS_WATER)
+				FreeInside (b2->contents);
+			else
+				FreeInside (CONTENTS_SOLID);
+		}
+
+	// all of the faces left in outside are real surface faces
+		if (b1->contents != CONTENTS_SOLID)
+			SaveOutside (tree, true);	// mirror faces for inside view
+		else
+			SaveOutside (tree, false);
+	}
+
+	BuildSurfaces ( tree );
+
+	qprintf ("%5i brushfaces\n", brushfaces);
+	qprintf ("%5i csgfaces\n", csgfaces);
+	qprintf ("%5i mergedfaces\n", csgmergefaces);
+}
+
+
Index: hmap2/faces.c
diff -u /dev/null hmap2/faces.c:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/faces.c	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,572 @@
+// surfaces.c
+
+#include "bsp5.h"
+
+/*
+
+a surface has all of the faces that could be drawn on a given plane
+the outside filling stage can remove some of them so a better bsp can be generated
+
+*/
+
+//===========================================================================
+
+/*
+===========
+AllocFace
+===========
+*/
+face_t *AllocFace( void )
+{
+	face_t	*f;
+
+	f = qmalloc( sizeof( face_t ) );
+	memset( f, 0, sizeof( face_t ) );
+	f->planenum = -1;
+	f->outputnumber = -1;
+
+	return f;
+}
+
+/*
+===========
+FreeFace
+===========
+*/
+void FreeFace( face_t *f )
+{
+	if( f->winding )
+		FreeWinding( f->winding );
+	qfree( f );
+}
+
+/*
+==================
+NewFaceFromFace
+
+Duplicates the non point information of a face, 
+used by SplitFace and MergeFace.
+==================
+*/
+face_t *NewFaceFromFace( face_t *in )
+{
+	face_t	*newf;
+
+	newf = AllocFace ();
+	newf->planenum = in->planenum;
+	newf->texturenum = in->texturenum;
+	newf->planeside = in->planeside;
+	newf->original = in->original;
+	newf->contents[0] = in->contents[0];
+	newf->contents[1] = in->contents[1];
+
+	return newf;
+}
+
+//===========================================================================
+
+/*
+==================
+SplitFace
+==================
+*/
+void SplitFace( face_t *in, plane_t *split, face_t **front, face_t **back )
+{
+	winding_t	*frontw, *backw;
+
+	if( !in->winding )
+		Error( "SplitFace: freed face" );
+
+	DivideWindingEpsilon( in->winding, split, &frontw, &backw, ON_EPSILON );
+
+	if( !backw ) {
+		*front = in;
+		*back = NULL;
+		return;
+	}
+	if( !frontw ) {
+		*front = NULL;
+		*back = in;
+		return;
+	}
+
+	if( frontw->numpoints < 3 || backw->numpoints < 3 )
+		Error( "SplitFace: numpoints < 3" );
+
+	*front = NewFaceFromFace( in );
+	(*front)->winding = frontw;
+
+	*back = NewFaceFromFace( in );
+	(*back)->winding = backw;
+
+	// free the original face now that is is represented by the fragments
+	FreeFace( in );
+}
+
+/*
+================
+SubdivideFace
+
+If the face is > subdivide_size in either texture direction, 
+carve a valid sized piece off and insert the remainder in the next link
+================
+*/
+int SubdivideFace( face_t *f, face_t **prevptr )
+{
+	int			i, axis;
+	int			subdivides;
+	vec_t		mins, maxs, v;
+	plane_t		plane;
+	winding_t	*w;
+	face_t		*front, *back, *next;
+	texinfo_t	*tex;
+
+	tex = &texinfo[f->texturenum];
+
+	// special (non-surface cached) faces don't need subdivision
+	if ( tex->flags & TEX_SPECIAL )
+		return 0;
+
+	subdivides = 0;
+	for( axis = 0; axis < 2 ; axis++ ) {
+		while( 1 ) {
+			mins = BOGUS_RANGE;
+			maxs = -BOGUS_RANGE;
+
+			w = f->winding;
+			for( i = 0; i < w->numpoints; i++ ) {
+				v = DotProduct( w->points[i], tex->vecs[axis] );
+				if( v < mins )
+					mins = v;
+				if( v > maxs )
+					maxs = v;
+			}
+
+			if( maxs - mins <= subdivide_size )
+				break;
+
+			// split it
+			VectorCopy( tex->vecs[axis], plane.normal );
+			v = VectorNormalize( plane.normal );
+			if( !v )
+				Error( "SubdivideFace: zero length normal" );
+			plane.dist = (mins + subdivide_size - 16) / v;
+
+			next = f->next;
+			SplitFace( f, &plane, &front, &back );
+
+			if( !front || !back )
+				Error( "SubdivideFace: didn't split the polygon" );
+
+			*prevptr = back;
+			back->next = front;
+			front->next = next;
+			f = back;
+			subdivides++;
+		}
+	}
+
+	return subdivides;
+}
+
+//===========================================================================
+
+/*
+=============
+TryMergeFaces
+
+Returns NULL if the faces couldn't be merged, or the new face.
+The originals will NOT be freed.
+=============
+*/
+static face_t *TryMergeFaces( face_t *f1, face_t *f2 )
+{
+	face_t		*newf;
+	vec3_t		planenormal;
+	plane_t		*plane;
+	winding_t	*neww;
+
+	if( !f1->winding || !f2->winding ||
+		f1->planeside != f2->planeside ||
+		f1->texturenum != f2->texturenum ||
+		f1->contents[0] != f2->contents[0] ||
+		f1->contents[1] != f2->contents[1] )
+		return NULL;
+
+	plane = &mapplanes[f1->planenum];
+	VectorCopy( plane->normal, planenormal );
+	if( f1->planeside )
+		VectorNegate( planenormal, planenormal );
+
+	neww = TryMergeWinding( f1->winding, f2->winding, planenormal );
+	if( !neww )
+		return NULL;
+
+	newf = NewFaceFromFace( f1 );
+	newf->winding = neww;
+
+	return newf;
+}
+
+/*
+===============
+MergeFaceToList_r
+===============
+*/
+face_t *MergeFaceToList_r( face_t *face, face_t *list )
+{	
+	face_t	*newf, *f;
+
+	for( f = list; f; f = f->next ) {
+		newf = TryMergeFaces( face, f );
+		if( !newf )
+			continue;
+
+		FreeFace( face );
+		FreeWinding( f->winding );
+		f->winding = NULL;		// merged out
+
+		return MergeFaceToList_r( newf, list );
+    }
+
+	// didn't merge, so add at start
+	face->next = list;
+	return face;
+}
+
+/*
+===============
+FreeMergeListScraps
+===============
+*/
+face_t *FreeMergeListScraps( face_t *merged )
+{
+	face_t	*head, *next;
+
+	for( head = NULL; merged ; merged = next) {
+		next = merged->next;
+
+		if( !merged->winding ) {
+			FreeFace( merged );
+			continue;
+		}
+
+		merged->next = head;
+		head = merged;
+    }
+
+	return head;
+}
+
+/*
+===============
+MergePlaneFaces
+===============
+*/
+static int MergePlaneFaces( surface_t *plane )
+{
+	int		count;
+	face_t	*f1, *next;
+	face_t	*merged;
+
+	merged = NULL;
+	for( f1 = plane->faces; f1; f1 = next ) {
+		next = f1->next;
+		merged = MergeFaceToList_r( f1, merged );
+    }
+
+	// chain all of the non-empty faces to the plane
+	plane->faces = FreeMergeListScraps( merged );
+
+	for( f1 = plane->faces, count = 0; f1; f1 = f1->next, count++ );
+
+	return count;
+}
+
+/*
+============
+MergeTreeFaces
+============
+*/
+void MergeTreeFaces( tree_t *tree )
+{
+	surface_t	*surf;
+	int			mergefaces;
+
+	printf( "---- MergeTreeFaces ----\n" );
+
+	mergefaces = 0; 
+	for( surf = tree->surfaces; surf; surf = surf->next )
+		mergefaces += MergePlaneFaces( surf );
+
+	printf( "%i mergefaces\n", mergefaces );
+}
+
+//===========================================================================
+
+/*
+================
+GatherNodeFaces_r
+================
+*/
+static void GatherNodeFaces_r( node_t *node, face_t **validfaces )
+{
+	face_t	*f, *next;
+
+	if( node->planenum != PLANENUM_LEAF ) {		// decision node
+		for( f = node->faces; f; f = next ) {
+			next = f->next;
+
+			if( !f->winding ) {	// face was removed outside
+				FreeFace( f );
+				continue;
+			}
+
+			f->next = validfaces[f->planenum];
+			validfaces[f->planenum] = f;
+		}
+
+		GatherNodeFaces_r( node->children[0], validfaces );
+		GatherNodeFaces_r( node->children[1], validfaces );
+    }
+
+	FreeNode( node );
+}
+
+/*
+================
+GatherTreeFaces
+
+Frees the current node tree and returns a new chain of 
+the surfaces that have inside faces.
+================
+*/
+void GatherTreeFaces( tree_t *tree )
+{
+	memset( tree->validfaces, 0, sizeof( tree->validfaces ) );
+
+	GatherNodeFaces_r( tree->headnode, tree->validfaces );
+
+	BuildSurfaces( tree );
+}
+
+//===========================================================================
+
+typedef struct hashvert_s
+{
+	vec3_t		point;
+	int			num;
+	int			numplanes;		// for corner determination
+	int			planenums[2];
+	int			numedges;
+	struct hashvert_s *next;
+} hashvert_t;
+
+#define	NUM_HASH		8192
+
+#define	POINT_EPSILON	0.01
+
+static	hashvert_t	hvertex[MAX_MAP_VERTS];
+static	hashvert_t	*hvert_p;
+
+static	hashvert_t	*hashverts[NUM_HASH];
+
+static	face_t		*edgefaces[MAX_MAP_EDGES][2];
+static	int			firstmodeledge;
+
+static	vec3_t		hash_min, hash_scale;
+
+static void InitHash( vec3_t mins, vec3_t maxs )
+{
+	int		i;
+	vec3_t	size;
+	vec_t	scale;
+	int		newsize[2];
+	
+	memset( hashverts, 0, sizeof( hashverts ) );
+
+	for( i = 0; i < 3; i++ ) {
+		hash_min[i] = mins[i];
+		size[i] = maxs[i] - mins[i];
+    }
+	
+	scale = sqrt( size[0] * size[1] / NUM_HASH );
+	
+	newsize[0] = size[0] / scale;
+	newsize[1] = size[1] / scale;
+	
+	hash_scale[0] = newsize[0] / size[0];
+	hash_scale[1] = newsize[1] / size[1];
+	hash_scale[2] = newsize[1];
+	
+	hvert_p = hvertex;
+}
+
+static unsigned HashVec( vec3_t vec )
+{
+	unsigned	h;
+	
+	h =	hash_scale[0] * (vec[0] - hash_min[0]) * hash_scale[2] + hash_scale[1] * (vec[1] - hash_min[1]);
+	return h % NUM_HASH;
+}
+
+//============================================================================
+
+/*
+=============
+GetVertex
+=============
+*/
+static int GetVertex( vec3_t in, int planenum )
+{
+	int			i;
+	int			h;
+	hashvert_t	*hv;
+	vec3_t		vert;
+	
+	for( i = 0; i < 3; i++ ) {
+		h = Q_rint( in[i] );
+
+		if( fabs( in[i] - h ) < 0.001 )
+			vert[i] = h;
+		else
+			vert[i] = in[i];
+    }
+
+	h = HashVec( vert );
+	for( hv = hashverts[h]; hv; hv = hv->next ) {
+		if( fabs( hv->point[0] - vert[0] ) < POINT_EPSILON
+			&& fabs( hv->point[1] - vert[1] ) < POINT_EPSILON
+			&& fabs( hv->point[2] - vert[2] ) < POINT_EPSILON ) {
+			hv->numedges++;
+
+			if( hv->numplanes == 3 )
+				return hv->num;			// allready known to be a corner
+
+			for( i = 0; i < hv->numplanes; i++ ) {
+				if( hv->planenums[i] == planenum )
+					return hv->num;		// allready know this plane
+			}
+
+			if( hv->numplanes != 2 )
+				hv->planenums[hv->numplanes] = planenum;
+			hv->numplanes++;
+			return hv->num;
+		}
+    }
+
+	hv = hvert_p;
+	hv->numedges = 1;
+	hv->numplanes = 1;
+	hv->planenums[0] = planenum;
+	hv->next = hashverts[h];
+	hv->num = numvertexes;
+	VectorCopy( vert, hv->point );
+	hashverts[h] = hv;
+	hvert_p++;
+
+	EmitVertex( vert );
+
+	return hv->num;
+}
+
+/*
+==================
+EmitFaceEdge
+
+Don't allow four way edges
+==================
+*/
+static int EmitFaceEdge( vec3_t p1, vec3_t p2, face_t *f )
+{
+	int		i;
+	int		v1, v2;
+	dedge_t	*edge;
+
+	v1 = GetVertex( p1, f->planenum );
+	v2 = GetVertex( p2, f->planenum );
+	for( i = firstmodeledge; i < numedges; i++ ) {
+		edge = &dedges[i];
+
+		if( v1 == edge->v[1] && v2 == edge->v[0] && !edgefaces[i][1]
+			&& edgefaces[i][0]->contents[0] == f->contents[0] ) {
+			edgefaces[i][1] = f;
+			return -i;
+		}
+    }
+
+	edgefaces[i][0] = f;
+
+	EmitEdge( v1, v2 );
+
+	return i;
+}
+
+/*
+==============
+EmitNodeFaces_r
+==============
+*/
+static void EmitNodeFaces_r( node_t *node )
+{
+	int			i;
+	dface_t		*f;
+	face_t		*face;
+	winding_t	*w;
+
+	if( node->planenum == PLANENUM_LEAF )
+		return;
+
+	node->firstface = numfaces;
+	for( face = node->faces; face; face = face->next ) {
+		if( numfaces == MAX_MAP_FACES )
+			Error( "numfaces == MAX_MAP_FACES" );
+
+		f = &dfaces[numfaces];	// emit a face
+		f->planenum = node->outputplanenum;
+		f->side = face->planeside;
+		f->texinfo = face->texturenum;
+		f->lightofs = -1;
+		for( i = 0; i < MAXLIGHTMAPS; i++ )
+			f->styles[i] = 255;
+
+		// add the face and mergable neighbors to it
+		f->firstedge = numsurfedges;
+		for( i = 0, w = face->winding; i < w->numpoints; i++ ) {
+			if( numsurfedges == MAX_MAP_SURFEDGES )
+				Error( "numsurfedges == MAX_MAP_SURFEDGES" );
+			dsurfedges[numsurfedges++] = EmitFaceEdge( w->points[i], w->points[(i+1) % w->numpoints], face );
+		}
+		f->numedges = numsurfedges - f->firstedge;
+
+		face->outputnumber = numfaces++;
+	}
+
+	node->numfaces = numfaces - node->firstface;
+
+	EmitNodeFaces_r( node->children[0] );
+	EmitNodeFaces_r( node->children[1] );
+}
+
+/*
+================
+EmitNodeFaces
+================
+*/
+void EmitNodeFaces( node_t *headnode )
+{
+	vec_t	radius;
+	vec3_t	maxs, mins;
+
+//	qprintf( "--- EmitNodeFaces ---\n" );
+
+	// origin points won't allways be inside the map, so extend the hash area
+	radius = RadiusFromBounds( headnode->mins, headnode->maxs );
+	VectorSet( maxs, radius, radius, radius );
+	VectorSet( mins, -radius, -radius, -radius );
+
+	InitHash( headnode->mins, headnode->maxs );
+
+	firstmodeledge = numedges;
+	EmitNodeFaces_r( headnode );
+}
Index: hmap2/fast.bat
diff -u /dev/null hmap2/fast.bat:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/fast.bat	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,3 @@
+ at hmap %1
+ at hmap -vis -fast %1
+ at hmap -light %1
Index: hmap2/full.bat
diff -u /dev/null hmap2/full.bat:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/full.bat	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,3 @@
+ at hmap %1
+ at hmap -vis %1
+ at hmap -light -extra8x8 %1
Index: hmap2/hmap2.dsp
diff -u /dev/null hmap2/hmap2.dsp:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/hmap2.dsp	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,234 @@
+# Microsoft Developer Studio Project File - Name="hmap2" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Console Application" 0x0103
+
+CFG=hmap2 - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE 
+!MESSAGE NMAKE /f "hmap2.mak".
+!MESSAGE 
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE 
+!MESSAGE NMAKE /f "hmap2.mak" CFG="hmap2 - Win32 Debug"
+!MESSAGE 
+!MESSAGE Possible choices for configuration are:
+!MESSAGE 
+!MESSAGE "hmap2 - Win32 Release" (based on "Win32 (x86) Console Application")
+!MESSAGE "hmap2 - Win32 Debug" (based on "Win32 (x86) Console Application")
+!MESSAGE 
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+RSC=rc.exe
+
+!IF  "$(CFG)" == "hmap2 - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD CPP /nologo /MD /W3 /GX /O2 /Ob2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
+# ADD LINK32 winmm.lib /nologo /subsystem:console /machine:I386 /out:"hmap2.exe"
+
+!ELSEIF  "$(CFG)" == "hmap2 - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
+# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 winmm.lib /nologo /subsystem:console /debug /machine:I386 /out:"hmap2.exe" /pdbtype:sept
+
+!ENDIF 
+
+# Begin Target
+
+# Name "hmap2 - Win32 Release"
+# Name "hmap2 - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=.\brush.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\bsp2prt.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\bspfile.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cmdlib.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\csg4.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\faces.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\light.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\light_face.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\light_trace.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\map.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\mathlib.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\mem.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\outside.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\portals.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\qbsp.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\scriptlib.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\solidbsp.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\tjunc.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\tree.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\vis.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\vis_flow.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\vis_sound.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\wad.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\winding.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\writebsp.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# Begin Source File
+
+SOURCE=.\bsp5.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\bspfile.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\cmdlib.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\light.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\map.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\mathlib.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\mem.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\vis.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\winding.h
+# End Source File
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
Index: hmap2/hmapreadme.txt
diff -u /dev/null hmap2/hmapreadme.txt:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/hmapreadme.txt	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,83 @@
+-====== hmap2 ======-
+Author: Vic
+Email: vic at quakesrc.org
+
+Additions beyond standard hqbsp+hlight+hvis features:
+
+hmap:
+hqbsp + hlight + hvis + bsp2prt in one exe
+
+general:
+no memory leaks
+general code cleanup
+rewrote many parts of the code to run faster and be easier to read
+bsp2prt allows maps to be re-vised
+
+hqbsp:
+no hull files
+support for maps where "worldspawn" is not the first entity
+support for "func_group" entities
+reduced memory usage
+misc bugfixes
+
+hlight:
+8x8 lightmaps antialiasing (-extra8x8 option)
+
+hvis:
+better vis compression (-reuse option, DO NOT use to revis old maps)
+fixed ambient sounds calculations (they now fade with distance)
+-ambientslime enables the unused slime sound channel (not supported by most (all?) quake engines, normally slime uses the water channel)
+
+-=================-
+
+hmap utilities by Forest "LordHavoc" Hale ( lordhavoc at users.sourceforge.net )
+Based on source code from id Software (Quake utilities) and Ritual Entertainment (Scourge of Armagon mission pack)
+Website: http://darkplaces.gamevisions.com
+
+Run the utilities with no parameters in a DOS prompt or Linux/UNIX terminal to get information about each.
+
+Or read the source code (qbsp.c, vis.c, or light.c, respectively) to see the same usage information.
+
+The sourcecode zip includes a Makefile for GNU make, a Makefile.mingw for mingw (make -f Makefile.mingw), and Microsoft Visual C++ 6.0 project files (open the hmap.dsw workspace).
+
+How to use the new hlight light properties:
+"wait" - affects size of light without affecting it's intensity - 1.0 default, 0.5 = bigger radius, 2 = smaller.
+"_color" - 3 values (red green blue), specifies color of light, the scale of the numbers does not matter ("1 3 2.5" is identical to "1000 3000 2500").
+"_lightradius" - limits light to this radius (and darkens light to make it blend well at the edges), only intended for switchable light styles so they don't cover whole rooms if you don't want them to.
+"light" - can be one of four different formats:
+quake: "light" "100" (intensity) white light but can be colored by "_color", this example is white light of intensity 100
+hlight: "light" "100 50 30" (red green blue) this is the same as using "light" "100" "_color" "1 0.5 0.3", this example is orange light of intensity 100
+halflife: "light" "100 255 255 200" (intensity red green blue) red/green/blue are 0-255 range, this example is yellowish-white light of intensity 100
+
+hqbsp features:
+Most errors report where the problem is (whether by line number in the .map file, or location in the map editor).
+CheckFace: point off plane is now only a warning, and tries to self-correct.
+Rewritten wad loader which should be far less finicky than the original, and allows multiple wads.
+Water/slime/lava is transparent by default (hqbsp -nowater to disable this).
+-darkplaces option to increase subdivide size to 4080 (really huge polygons), can make a map much faster but requires darkplaces engine to be used, or any other engine implementing 256x256 texel lightmaps.
+Support for very large maps (up to +-32767 units, the very limits of the .bsp file format).
+Hipnotic rotation support.
+Greatly increased limits for everything.
+Can vis a leaky map (use hqbsp -noforcevis if you want vis to fail on a leaky map).
+Shows useful information if run with no parameters.
+Can compile .map files using HalfLife WorldCraft texture alignment
+
+hvis features:
+Can disable liquid sounds (-noambient, -noambientwater, -noambientsky, -noambientlava (quake never used ambient lava sounds though)).
+RVis optimization for faster vising (this does cause a 0.001% degradation in vis quality, too small to care about, -norvis to disable).
+Defaults to -level 4 vis quality (which is often faster than vis's default of level 2).
+Shows useful information if run with no parameters.
+
+hlight features:
+More realistic attenuation.  (original light.exe method is not supported in any way, nor are tyrlite modes, this is intentional; only realistic is supported)
+Falloff scaling from arghlite.  (but remember the attenuation is quite different already)
+Colored lighting (.lit file) for enhanced engines while not breaking classic quake.
+16 sample lightmap antialiasing (hlight -extra4x4).
+Better handling of floor-meets-wall seams in lighting, none of those messy problem cases seen in the original light.exe.
+-relight option to make a .lit file for an existing .bsp without modifying the .bsp in any way (this will not add color, unless the lights contain color info already).
+Support for extremely large polygons as produced by hqbsp -darkplaces.
+Hipnotic rotation support.
+Uses vis data to light map faster (-nolightvis to ignore the vis data).
+Shows useful information if run with no parameters.
+
+Probably other features I forgot to mention...
Index: hmap2/light.c
diff -u /dev/null hmap2/light.c:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/light.c	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,595 @@
+// lighting.c
+
+#include "light.h"
+
+/*
+
+NOTES
+-----
+
+*/
+
+dmodel_t	*bspmodel;
+
+qboolean	lightvis;
+qboolean	relight;
+qboolean	verbose;
+
+int			extrasamplesbit; // power of 2 extra sampling (0 = 1x1 sampling, 1 = 2x2 sampling, 2 = 4x4 sampling, etc)
+vec_t		extrasamplesscale; // 1.0 / pointspersample (extrasamples related)
+vec_t		globallightscale;
+
+// filename to write light list to
+char lightsfilename[1024];
+
+byte currentvis[(MAX_MAP_LEAFS + 7) / 8];
+
+int			c_occluded;
+
+int num_directlights;
+directlight_t directlights[MAP_DIRECTLIGHTS];
+
+/*
+==============================================================================
+
+ENTITY FILE PARSING
+
+If a light has a targetname, generate a unique style in the 32-63 range
+==============================================================================
+*/
+
+int		numlighttargets;
+char	lighttargets[32][128];
+
+int LightStyleForTargetname( char *targetname )
+{
+	int		i;
+
+	for( i = 0; i < numlighttargets; i++ ) {
+		if( !strcmp (lighttargets[i], targetname) )
+			return 32 + i;
+	}
+
+	if( numlighttargets == 32 )
+		Error( "LightStyleForTargetname: numlighttargets == 32" );
+
+	strcpy( lighttargets[numlighttargets], targetname );
+	numlighttargets++;
+	return numlighttargets - 1 + 32;
+}
+
+
+/*
+==================
+ParseLightEntities
+==================
+*/
+void ParseLightEntities( void )
+{
+	int	i, j;
+	entity_t *ent;
+	char *value, *targetname, *style;
+	directlight_t *l;
+	double vec[4], color2[3];
+	qboolean isLight;
+
+	num_directlights = 0;
+	for( i = 0, ent = entities; i < num_entities; i++, ent++ ) {
+		value = ValueForKey( ent, "classname" );
+
+		if( strncmp (value, "light", 5) )
+			continue;
+
+		if( !strcmp (value, "light") )
+			isLight = true;
+		else
+			isLight = false;
+
+		if( num_directlights == MAP_DIRECTLIGHTS )
+			Error( "numdirectlights == MAP_DIRECTLIGHTS" );
+
+		l = &directlights[num_directlights++];
+		memset( l, 0, sizeof (*l) );
+		color2[0] = color2[1] = color2[2] = 1.0f;
+		l->color[0] = l->color[1] = l->color[2] = 1.0f;
+		l->lightoffset = LIGHTDISTBIAS;
+		GetVectorForKey( ent, "origin", l->origin );
+
+		l->falloff = FloatForKey( ent, "wait" );
+		if( !l->falloff )
+			l->falloff = DEFAULTFALLOFF;
+		l->falloff *= l->falloff; // square it for lighting calculations
+
+		l->lightradius = FloatForKey( ent, "_lightradius" );
+
+		l->style = FloatForKey( ent, "style" );
+		if( (unsigned)l->style > 254 )
+			Error( "LoadLights: Bad light style %i (must be 0-254)", l->style );
+
+		l->angle = FloatForKey( ent, "angle" );
+
+		value = ValueForKey( ent, "color" );
+		if( !value[0] )
+			value = ValueForKey( ent, "_color" );
+		if( value[0] ) {
+			// scan into doubles, then assign
+			// which makes it vec_t size independent
+			if( sscanf (value, "%lf %lf %lf", &vec[0], &vec[1], &vec[2]) != 3 )
+				Error( "LoadEntities: not 3 values for color" );
+
+			// scale the color to have at least one component at 1.0
+			vec[3] = vec[0];
+			if( vec[1] > vec[3] )
+				vec[3] = vec[1];
+			if( vec[2] > vec[3] )
+				vec[3] = vec[2];
+			if( vec[3] != 0.0 )
+				vec[3] = 1.0 / vec[3];
+			color2[0] = vec[0] * vec[3];
+			color2[1] = vec[1] * vec[3];
+			color2[2] = vec[2] * vec[3];
+		}
+
+		value = ValueForKey( ent, "light" );
+		if( !value[0] )
+			value = ValueForKey( ent, "_light" );
+
+		if( value[0] ) {
+			i = sscanf ( value, "%lf %lf %lf %lf", &vec[0], &vec[1], &vec[2], &vec[3] );
+
+			switch( i ) {
+				case 4:// HalfLife light
+					l->light = (int)vec[3];
+					l->color[0] = vec[0] * (1.0f / 255.0f);
+					l->color[1] = vec[1] * (1.0f / 255.0f);
+					l->color[2] = vec[2] * (1.0f / 255.0f);
+					break;
+				case 3:
+					l->light = 1;
+					l->color[0] = vec[0];
+					l->color[1] = vec[1];
+					l->color[2] = vec[2];
+					break;
+				case 1:
+					l->light = (int)vec[0];
+					l->color[0] = 1.0f;
+					l->color[1] = 1.0f;
+					l->color[2] = 1.0f;
+					break;
+				default:
+					Error( "LoadEntities: _light (or light) key must be 1 (Quake), 4 (HalfLife), or 3 (HLight) values, \"%s\" is not valid\n", value );
+			}
+		}
+
+		if( !l->light )
+			l->light = DEFAULTLIGHTLEVEL;
+
+		// convert to subtraction to the brightness for the whole light, so it will fade nicely, rather than being clipped off
+		l->color[0] *= color2[0] * l->light * /*l->falloff */ 16384.0 * globallightscale;
+		l->color[1] *= color2[1] * l->light * /*l->falloff */ 16384.0 * globallightscale;
+		l->color[2] *= color2[2] * l->light * /*l->falloff */ 16384.0 * globallightscale;
+
+		if( l->lightradius )
+			l->subbrightness = 1.0 / (l->lightradius * l->lightradius * l->falloff + LIGHTDISTBIAS);
+		if( l->subbrightness < (1.0 / 1048576.0) )
+			l->subbrightness = (1.0 / 1048576.0);
+
+		if( isLight ) {
+			value = ValueForKey( ent, "targetname" );
+
+			if( value[0] && !l->style ) {
+				char s[16];
+
+				l->style = LightStyleForTargetname( value );
+
+				memset( s, 0, sizeof(s) );
+				sprintf( s, "%i", l->style );
+				SetKeyValue( ent, "style", s );
+			}
+		}
+
+		value = ValueForKey( ent, "target" );
+		if( !value[0] )
+			continue;
+
+		for( j = 0; j < num_entities; j++ ) {
+			if( i == j )
+				continue;
+
+			targetname = ValueForKey( &entities[j], "targetname" );
+			if( !strcmp (targetname, value) ) {
+				vec3_t origin;
+
+				GetVectorForKey( &entities[j], "origin", origin );
+
+				// set up spotlight values for lighting code to use
+				VectorSubtract( origin, l->origin, l->spotdir );
+				VectorNormalize( l->spotdir );
+
+				if( !l->angle )
+					l->spotcone = -cos( 20 * Q_PI / 180 );
+				else
+					l->spotcone = -cos( l->angle / 2 * Q_PI / 180 );
+
+				if( l->spotcone <= 0 ) {
+					VectorClear( l->spotdir );
+					l->spotcone = 0;
+				}
+				break;
+			}
+		}
+
+		if( j == num_entities )	{
+			printf( "WARNING: light at (%i,%i,%i) has unmatched target\n", (int)l->origin[0], (int)l->origin[1], (int)l->origin[2]);
+			continue;
+		}
+
+		// set the style on the source ent for switchable lights
+		style = ValueForKey( &entities[j], "style" );
+		if( style[0] && atof (style) ) {
+			char s[16];
+
+			l->style = atof( style );
+			if( (unsigned)l->style > 254 )
+				Error( "LoadLights: Bad target light style %i (must be 0-254)", l->style );
+
+			memset( s, 0, sizeof(s) );
+			sprintf( s, "%i", l->style );
+			SetKeyValue( ent, "style", s );
+		}
+	}
+}
+
+void WriteLights( void )
+{
+	int i;
+	FILE *f;
+	directlight_t *l;
+
+	printf ("building .lights file\n");
+
+	f = fopen( lightsfilename, "wb" );
+	for( i = 0, l = directlights; i < num_directlights; i++, l++ )
+		fprintf( f, "%f %f %f %f %f %f %f %f %f %f %f %f %f %d\n", (double)l->origin[0], (double)l->origin[1], (double)l->origin[2], (double)l->falloff, (double)l->color[0], (double)l->color[1], (double)l->color[2], (double)l->subbrightness, (double)l->spotdir[0], (double)l->spotdir[1], (double)l->spotdir[2], (double)l->spotcone, (double)l->lightoffset, l->style );
+	fclose( f );
+}
+
+dleaf_t *Light_PointInLeaf(vec3_t point)
+{
+	int num;
+	num = 0;
+	while (num >= 0)
+		num = dnodes[num].children[DotProduct(point, dplanes[dnodes[num].planenum].normal) < dplanes[dnodes[num].planenum].dist];
+	return dleafs + (-1 - num);
+}
+
+#define LIGHTCHAINS (MAX_MAP_FACES * 32)
+
+lightchain_t *surfacelightchain[MAX_MAP_FACES];
+lightchain_t lightchainbuf[LIGHTCHAINS];
+byte surfacehit[MAX_MAP_FACES];
+directlight_t *novislight[MAX_MAP_ENTITIES];
+directlight_t *alllight[MAX_MAP_ENTITIES];
+int novislights, alllights;
+int lightchainbufindex;
+
+/*
+=============
+LightWorld
+=============
+*/
+extern int dlightdatapos;
+void LightWorld (void)
+{
+	int			i, k, n, m, count;
+	unsigned short	*mark;
+	time_t		lightstarttime, oldtime, newtime;
+	directlight_t *light;
+	dleaf_t		*leaf;
+	int			lightcount = 0, castcount = 0, emptycount = 0, solidcount = 0, watercount = 0, slimecount = 0, lavacount = 0, skycount = 0, misccount = 0, ignorevis;
+	vec3_t		org;
+	char		name[8];
+	entity_t	*ent;
+//	filebase = file_p = dlightdata;
+//	file_end = filebase + MAX_MAP_LIGHTING;
+	if (!relight)
+		lightdatasize = 0;
+	rgblightdatasize = 0;
+	lightstarttime = time(NULL);
+
+	lightchainbufindex = 0;
+	novislights = alllights = 0;
+	memset(surfacelightchain, 0, sizeof(surfacelightchain));
+
+	// LordHavoc: find the right leaf for each entity
+	for (i = 0, light = directlights;i < num_directlights;i++, light++)
+	{
+		lightcount++;
+		alllight[alllights++] = light;
+		leaf = Light_PointInLeaf(light->origin);
+		ignorevis = false;
+		switch (leaf->contents)
+		{
+		case CONTENTS_EMPTY:
+			emptycount++;
+			break;
+		case CONTENTS_SOLID:
+			solidcount++;
+			ignorevis = true;
+			break;
+		case CONTENTS_WATER:
+			watercount++;
+			break;
+		case CONTENTS_SLIME:
+			slimecount++;
+			break;
+		case CONTENTS_LAVA:
+			lavacount++;
+			break;
+		case CONTENTS_SKY:
+			skycount++;
+			ignorevis = true;
+			break;
+		default:
+			misccount++;
+			break;
+		}
+		if (leaf->visofs == -1 || ignorevis || !lightvis)
+		{
+			/*
+			if ((lightchainbufindex + numfaces) > LIGHTCHAINS)
+				Error("LightWorld: ran out of light chains!  complain to maintainer of hlight\n");
+			for (m = 0;m < numfaces;m++)
+			{
+				castcount++;
+				lightchainbuf[lightchainbufindex].light = entity;
+				lightchainbuf[lightchainbufindex].next = surfacelightchain[m];
+				surfacelightchain[m] = &lightchainbuf[lightchainbufindex++];
+			}
+			*/
+			castcount += numfaces;
+			novislight[novislights++] = light;
+		}
+		else
+		{
+			DecompressVis(dvisdata + leaf->visofs, currentvis, (numleafs + 7) >> 3);
+			memset(surfacehit, 0, numfaces);
+			for (n = 0, leaf = dleafs+1;n < numleafs;n++, leaf++) // leafs begin at 1
+			{
+				if (!leaf->nummarksurfaces)
+					continue;
+				if (currentvis[n >> 3] & (1 << (n & 7)))
+				{
+					if ((lightchainbufindex + leaf->nummarksurfaces) > LIGHTCHAINS)
+						Error("LightWorld: ran out of light chains!  complain to maintainer of hlight\n");
+					for (m = 0, mark = dmarksurfaces + leaf->firstmarksurface;m < leaf->nummarksurfaces;m++, mark++)
+					{
+						if (surfacehit[*mark])
+							continue;
+						surfacehit[*mark] = true;
+						castcount++;
+						lightchainbuf[lightchainbufindex].light = light;
+						lightchainbuf[lightchainbufindex].next = surfacelightchain[*mark];
+						surfacelightchain[*mark] = &lightchainbuf[lightchainbufindex++];
+					}
+				}
+			}
+		}
+	}
+
+	printf("%4i lights, %4i air, %4i solid, %4i water, %4i slime, %4i lava, %4i sky, %4i unknown\n", lightcount, emptycount, solidcount, watercount, slimecount, lavacount, skycount, misccount);
+
+	i = 0;
+	for (m = 0;m < numfaces;m++)
+		if (surfacelightchain[m])
+			i++;
+	printf("%5i faces, %5i (%3i%%) may receive light\n", numfaces, i, i * 100 / numfaces);
+
+	if (solidcount || skycount)
+		printf("warning: %4i lights of %4i lights (%3i%%) were found in sky or solid and will not be accelerated using vis, move them out of the solid or sky to accelerate compiling\n", solidcount+skycount, lightcount, (solidcount+skycount) * 100 / lightcount);
+
+	printf("%4i lights will be cast onto %5i surfaces, %10i casts will be performed\n", lightcount, numfaces, castcount);
+
+	// LordHavoc: let there be light
+	count = dmodels[0].numfaces;
+//	k = 1;
+//	j = (int) ((double) count * (double) k * (1.0 / 100.0));
+	org[0] = org[1] = org[2] = 0;
+	oldtime = time(NULL);
+	for (m = 0;m < count;)
+	{
+		LightFace (dfaces + m + dmodels[0].firstface, surfacelightchain[m + dmodels[0].firstface], novislight, novislights, org);
+		m++;
+		newtime = time(NULL);
+		if (newtime != oldtime)
+		{
+			printf ("\rworld face %5i of %5i (%3i%%), estimated time left: %5i ", m, count, (int) (m*100)/count, (int) (((count-m)*(newtime-lightstarttime))/m));
+			fflush(stdout);
+			oldtime = newtime;
+		}
+	}
+	printf ("\n%5i faces done\nlightdatasize: %i\n", numfaces, lightdatasize);
+	printf ("c_occluded: %i\n", c_occluded);
+
+	printf("\nlighting %5i submodels:\n", nummodels);
+	fflush(stdout);
+	// LordHavoc: light bmodels
+	for (k = 1;k < nummodels;k++)
+	{
+		newtime = time(NULL);
+		if (newtime != oldtime)
+		{
+			m = k;
+			count = nummodels;
+			printf ("\rsubmodel %3i of %3i (%3i%%), estimated time left: %5i ", m, count, (int) (m*100)/count, (int) (((count-m)*(newtime-lightstarttime))/m));
+			fflush(stdout);
+			oldtime = newtime;
+		}
+		sprintf(name, "*%d", k);
+		ent = FindEntityWithKeyPair("model", name);
+		if (!ent)
+			Error("FindFaceOffsets: Couldn't find entity for model %s.\n", name);
+
+		org[0] = org[1] = org[2] = 0;
+		if (!strncmp(ValueForKey (ent, "classname"), "rotate_", 7))
+			GetVectorForKey(ent, "origin", org);
+
+		for (m = 0;m < dmodels[k].numfaces;m++)
+			LightFace (dfaces + m + dmodels[k].firstface, NULL, alllight, alllights, org);
+	}
+
+//	lightdatasize = file_p - filebase;
+//	rgblightdatasize = lightdatasize * 3;
+
+	printf ("\n%5i submodels done\nlightdatasize: %i\n", nummodels, lightdatasize);
+	printf ("c_occluded: %i\n", c_occluded);
+}
+
+void CheckLightmaps(void);
+
+/*
+========
+Light_Main
+
+light modelfile
+========
+*/
+int Light_Main (int argc, char **argv)
+{
+	char source[1024];
+	double start, end;
+	int i;
+
+// LordHavoc
+	printf ("hlight 1.04 by LordHavoc\n");
+	printf ("based on id Software's quake light utility source code\n");
+	extrasamplesbit = 0;
+	lightvis = true;
+	relight = false;
+	globallightscale = 1.0;
+
+	for (i=1 ; i<argc ; i++)
+	{
+		if (!strcmp(argv[i],"-extra"))
+		{
+			extrasamplesbit = 1;
+			printf ("2x2 sampling enabled (tip: -extra4x4 is even higher quality)\n");
+		}
+		else if (!strcmp(argv[i],"-extra4x4"))
+		{
+			extrasamplesbit = 2;
+			printf ("4x4 sampling enabled\n");
+		}
+		else if (!strcmp(argv[i],"-extra8x8"))
+		{
+			extrasamplesbit = 3;
+			printf ("8x8 sampling enabled\n");
+		}
+		else if (!strcmp(argv[i],"-nolightvis"))
+		{
+			printf("use of vis data to optimize lighting disabled\n");
+			lightvis = false;
+		}
+		else if (!strcmp(argv[i],"-relight"))
+		{
+			printf("relighting map to create .lit file without modifying .bsp\n");
+			relight = true;
+		}
+		else if (!strcmp(argv[i],"-intensity"))
+		{
+			i++;
+			if (i >= argc)
+				Error("no value was given to -intensity\n");
+			globallightscale = atof(argv[i]);
+			if (globallightscale < 0.01)
+				globallightscale = 0.01;
+		}
+		else if (argv[i][0] == '-')
+			Error ("Unknown option \"%s\"", argv[i]);
+		else
+			break;
+	}
+
+	extrasamplesscale = 1.0f / (1 << (extrasamplesbit * 2));
+
+// LordHavoc
+	if (i != argc - 1)
+		Error ("%s",
+"usage: hlight [options] bspfile\n"
+"Quick usage notes for entities: (place these in key/value pairs)\n"
+"wait - falloff rate (1.0 default, 0.5 = bigger radius, 2 = smaller, affects area the light covers)\n"
+"_color - 3 values (red green blue), specifies color of light, the scale of the numbers does not matter (\"1 3 2.5\" is identical to \"1000 3000 2500\")\n"
+"_lightradius - limits light to this radius (and darkens light to make it blend well at the edges)\n"
+"What the options do:\n"
+"-extra         antialiased lighting (takes much longer, higher quality)\n"
+"-extra4x4      antialiased lighting (even slower and higher quality than -extra)\n"
+"-extra8x8      antialiased lighting (even slower and higher quality than -extra4x4)\n"
+"-nolightvis    disables use of visibility data to optimize lighting\n"
+"-relight       makes a .lit file for an existing map without modifying the .bsp\n"
+		);
+
+	// init memory
+	Q_InitMem ();
+
+	printf ("----- LightFaces ----\n");
+
+//	InitThreads ();
+
+	start = I_DoubleTime ();
+
+	strcpy(source, argv[i]);
+	strcpy(lightsfilename, source);
+	DefaultExtension(source, ".bsp");
+	ReplaceExtension(lightsfilename, ".lights");
+
+	LoadBSPFile (source);
+	memset(dlightdata, 0, sizeof(dlightdata));
+	memset(drgblightdata, 0, sizeof(drgblightdata));
+	//for (i = 0;i < sizeof(dlightdata);i++)
+	//	dlightdata[i] = i;
+	CheckLightmaps();
+
+	if (!visdatasize)
+	{
+		printf("no visibility data found (run -vis before -light to compile faster)\n");
+		lightvis = false;
+	}
+
+	ParseEntities ();
+	printf ("%i entities read\n", num_entities);
+
+	ParseLightEntities ();
+
+	CheckLightmaps();
+
+	MakeTnodes (&dmodels[0]);
+	CheckLightmaps();
+
+	LightWorld ();
+	CheckLightmaps();
+
+	WriteLights ();
+
+	UnparseEntities ();
+
+	CheckLightmaps();
+	WriteBSPFile (source, relight);
+	CheckLightmaps();
+
+	end = I_DoubleTime ();
+	printf ("%5.2f seconds elapsed\n\n", end-start);
+
+	// print memory stats
+	Q_PrintMem ();
+
+#if _MSC_VER && _DEBUG
+	printf("press any key\n");
+	getchar();
+#endif
+
+	// free allocated memory
+	Q_ShutdownMem ();
+
+	return 0;
+}
+
Index: hmap2/light.h
diff -u /dev/null hmap2/light.h:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/light.h	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,68 @@
+
+#include "cmdlib.h"
+#include "mathlib.h"
+#include "bspfile.h"
+#include "mem.h"
+
+#define ON_EPSILON 0.1
+
+#define MAXLIGHTS 1024
+#define LIGHTDISTBIAS 65536.0
+
+#define DEFAULTLIGHTLEVEL	300
+#define DEFAULTFALLOFF	1.0f
+
+extern char lightsfilename[1024];
+
+typedef struct
+{
+	vec3_t	origin;
+	vec_t	angle;
+	int		light;
+	int		style;
+
+	vec_t	falloff;
+	vec_t	lightradius;
+	vec_t	subbrightness;
+	vec_t	lightoffset;
+	vec3_t	color;
+	vec3_t	spotdir;
+	vec_t	spotcone;
+} directlight_t;
+
+typedef struct lightchain_s
+{
+	directlight_t *light;
+	struct lightchain_s *next;
+} lightchain_t;
+
+typedef struct
+{
+	qboolean	startSolid;
+	vec_t		fraction;
+	vec3_t		impact;
+	vec3_t		filter;
+	plane_t	plane;
+} lightTrace_t;
+
+int Light_PointContents( vec3_t p );
+qboolean Light_TraceLine (lightTrace_t *trace, vec3_t start, vec3_t end);
+
+void LightFace (dface_t *f, lightchain_t *lightchain, directlight_t **novislight, int novislights, vec3_t faceorg);
+void LightLeaf (dleaf_t *leaf);
+
+void MakeTnodes (dmodel_t *bm);
+
+extern	int		c_occluded;
+extern	int		c_culldistplane, c_proper;
+
+extern qboolean relight;
+
+#define MAP_DIRECTLIGHTS	MAX_MAP_ENTITIES
+
+extern int num_directlights;
+extern directlight_t directlights[MAP_DIRECTLIGHTS];
+
+extern int extrasamplesbit; // power of 2 extra sampling (0 = 1x1 sampling, 1 = 2x2 sampling, 2 = 4x4 sampling, etc)
+extern vec_t extrasamplesscale; // 1.0 / pointspersample (extrasamples related)
+extern vec_t globallightscale;
Index: hmap2/light_face.c
diff -u /dev/null hmap2/light_face.c:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/light_face.c	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,663 @@
+
+#include "light.h"
+
+/*
+===============================================================================
+
+SAMPLE POINT DETERMINATION
+
+void SetupBlock (dface_t *f) Returns with point[] set
+
+This is a little tricky because the lightmap covers more area than the face.
+If done in the straightforward fashion, some of the
+sample points will be inside walls or on the other side of walls, causing
+false shadows and light bleeds.
+
+To solve this, I only consider a sample point valid if a line can be drawn
+between it and the exact midpoint of the face.  If invalid, it is adjusted
+towards the center until it is valid.
+
+(this doesn't completely work)
+
+===============================================================================
+*/
+
+// LordHavoc: increased from 18x18 samples to 256x256 samples
+#define SINGLEMAP (256*256)
+
+typedef struct
+{
+	vec3_t		v;
+	qboolean	occluded;
+	int			samplepos; // the offset into the lightmap that this point contributes to
+} lightpoint_t;
+
+typedef struct
+{
+	vec3_t c;
+} lightsample_t;
+
+typedef struct
+{
+	vec_t			facedist;
+	vec3_t			facenormal;
+
+	int				numpoints;
+	int				numsamples;
+	// *32 for -extra8x8
+//	lightpoint_t	point[SINGLEMAP*64];
+	lightpoint_t	*point;
+	lightsample_t	sample[MAXLIGHTMAPS][SINGLEMAP];
+
+	vec3_t			texorg;
+	vec3_t			worldtotex[2];	// s = (world - texorg) . worldtotex[0]
+	vec3_t			textoworld[2];	// world = texorg + s * textoworld[0]
+
+	vec_t			exactmins[2], exactmaxs[2];
+
+	int				texmins[2], texsize[2];
+	int				lightstyles[MAXLIGHTMAPS];
+	dface_t			*face;
+} lightinfo_t;
+
+//#define BUGGY_TEST
+
+/*
+================
+CalcFaceVectors
+
+Fills in texorg, worldtotex. and textoworld
+================
+*/
+void CalcFaceVectors (lightinfo_t *l, vec3_t faceorg)
+{
+	texinfo_t	*tex;
+	int			i, j;
+	vec3_t	texnormal;
+	vec_t	distscale;
+	vec_t	dist, len;
+
+	tex = &texinfo[l->face->texinfo];
+
+// convert from float to vec_t
+	for (i=0 ; i<2 ; i++)
+		for (j=0 ; j<3 ; j++)
+			l->worldtotex[i][j] = tex->vecs[i][j];
+
+// calculate a normal to the texture axis.  points can be moved along this
+// without changing their S/T
+	// LordHavoc: this is actually a CrossProduct
+	texnormal[0] = tex->vecs[1][1]*tex->vecs[0][2] - tex->vecs[1][2]*tex->vecs[0][1];
+	texnormal[1] = tex->vecs[1][2]*tex->vecs[0][0] - tex->vecs[1][0]*tex->vecs[0][2];
+	texnormal[2] = tex->vecs[1][0]*tex->vecs[0][1] - tex->vecs[1][1]*tex->vecs[0][0];
+	VectorNormalize (texnormal);
+
+// flip it towards plane normal
+	distscale = DotProduct (texnormal, l->facenormal);
+	if (!distscale)
+		Error ("Texture axis perpendicular to face");
+	if (distscale < 0)
+	{
+		distscale = -distscale;
+		VectorNegate (texnormal, texnormal);
+	}
+
+// distscale is the ratio of the distance along the texture normal to
+// the distance along the plane normal
+	distscale = 1/distscale;
+
+	for (i=0 ; i<2 ; i++)
+	{
+		len = VectorLength (l->worldtotex[i]);
+		dist = DotProduct (l->worldtotex[i], l->facenormal);
+		dist *= distscale;
+		VectorMA (l->worldtotex[i], -dist, texnormal, l->textoworld[i]);
+		VectorScale (l->textoworld[i], (1/len)*(1/len), l->textoworld[i]);
+	}
+
+
+// calculate texorg on the texture plane
+	for (i=0 ; i<3 ; i++)
+		l->texorg[i] = -tex->vecs[0][3] * l->textoworld[0][i] - tex->vecs[1][3] * l->textoworld[1][i];
+
+	VectorAdd(l->texorg, faceorg, l->texorg);
+
+// project back to the face plane
+	dist = DotProduct (l->texorg, l->facenormal) - l->facedist - 1;
+	dist *= distscale;
+	VectorMA (l->texorg, -dist, texnormal, l->texorg);
+}
+
+/*
+================
+CalcFaceExtents
+
+Fills in s->texmins[] and s->texsize[]
+also sets exactmins[] and exactmaxs[]
+================
+*/
+void CalcFaceExtents (lightinfo_t *l)
+{
+	dface_t *s;
+	vec_t	mins[2], maxs[2], val;
+	int		i,j, e;
+	dvertex_t	*v;
+	texinfo_t	*tex;
+
+	s = l->face;
+
+	mins[0] = mins[1] = BOGUS_RANGE;
+	maxs[0] = maxs[1] = -BOGUS_RANGE;
+
+	tex = &texinfo[s->texinfo];
+
+	for (i=0 ; i<s->numedges ; i++)
+	{
+		e = dsurfedges[s->firstedge+i];
+		if (e >= 0)
+			v = dvertexes + dedges[e].v[0];
+		else
+			v = dvertexes + dedges[-e].v[1];
+
+		for (j=0 ; j<2 ; j++)
+		{
+			val = v->point[0] * tex->vecs[j][0] +
+				v->point[1] * tex->vecs[j][1] +
+				v->point[2] * tex->vecs[j][2] +
+				tex->vecs[j][3];
+			if (val < mins[j])
+				mins[j] = val;
+			if (val > maxs[j])
+				maxs[j] = val;
+		}
+	}
+
+	for (i=0 ; i<2 ; i++)
+	{
+		l->exactmins[i] = mins[i];
+		l->exactmaxs[i] = maxs[i];
+
+		mins[i] = floor(mins[i]/16);
+		maxs[i] = ceil(maxs[i]/16);
+
+		l->texmins[i] = mins[i];
+		l->texsize[i] = maxs[i] + 1 - mins[i];
+		if (l->texsize[i] > 256) // LordHavoc: was 17, much much bigger allowed now
+			Error ("Bad surface extents");
+	}
+}
+
+void CalcSamples (lightinfo_t *l)
+{
+	/*
+	int				i, mapnum;
+	lightsample_t	*sample;
+	*/
+
+	l->numsamples = l->texsize[0] * l->texsize[1];
+	// no need to clear because the lightinfo struct was cleared already
+	/*
+	for (mapnum = 0;mapnum < MAXLIGHTMAPS;mapnum++)
+	{
+		for (i = 0, sample = l->sample[mapnum];i < l->numsamples;i++, sample++)
+		{
+			sample->c[0] = 0;
+			sample->c[1] = 0;
+			sample->c[2] = 0;
+		}
+	}
+	*/
+}
+
+/*
+=================
+CalcPoints
+
+For each texture aligned grid point, back project onto the plane
+to get the world xyz value of the sample point
+=================
+*/
+//int c_bad;
+void CalcPoints (lightinfo_t *l)
+{
+	int j, s, t, w, h, realw, realh, stepbit;
+	vec_t starts, startt, us, ut, mids, midt;
+	vec3_t facemid, base;
+#ifndef BUGGY_TEST
+	vec3_t			v;
+#else
+	int i;
+#endif
+	lightTrace_t	tr;
+	lightpoint_t	*point;
+
+//
+// fill in point array
+// the points are biased towards the center of the surface
+// to help avoid edge cases just inside walls
+//
+	mids = (l->exactmaxs[0] + l->exactmins[0]) * 0.5;
+	midt = (l->exactmaxs[1] + l->exactmins[1]) * 0.5;
+
+	VectorAdd( l->texorg, l->facenormal, base );
+
+	for (j = 0;j < 3;j++)
+		facemid[j] = base[j] + l->textoworld[0][j] * mids + l->textoworld[1][j] * midt;
+
+	realw = l->texsize[0];
+	realh = l->texsize[1];
+	starts = l->texmins[0] * 16;
+	startt = l->texmins[1] * 16;
+
+	stepbit = 4 - extrasamplesbit;
+
+	w = realw << extrasamplesbit;
+	h = realh << extrasamplesbit;
+
+	if (stepbit < 4)
+	{
+		starts -= 1 << stepbit;
+		startt -= 1 << stepbit;
+	}
+
+	point = l->point;
+	l->numpoints = w * h;
+	for (t = 0;t < h;t++)
+	{
+		for (s = 0;s < w;s++, point++)
+		{
+			us = starts + (s << stepbit);
+			ut = startt + (t << stepbit);
+			point->occluded = false;
+			point->samplepos = (t >> extrasamplesbit) * realw + (s >> extrasamplesbit);
+
+#ifndef BUGGY_TEST
+			// calculate texture point
+			for (j = 0;j < 3;j++)
+				point->v[j] = base[j] + l->textoworld[0][j] * us + l->textoworld[1][j] * ut;
+
+			if( !Light_TraceLine( &tr, facemid, point->v ) ) {
+				// test failed, adjust to nearest position
+				VectorCopy(tr.impact, point->v);
+				VectorSubtract(facemid, point->v, v);
+				VectorNormalize(v);
+				VectorMA(point->v, 0.5, v, point->v);
+
+				if( Light_PointContents( point->v ) == CONTENTS_SOLID ) {
+					c_occluded++;
+					point->occluded = true;
+				}
+			}
+
+			//VectorSubtract(facemid, point->v, v);
+			//VectorNormalize(v);
+			//VectorMA(point->v, 0.25, v, point->v);
+#else
+			// if a line can be traced from point to facemid, the point is good
+			for (i = 0;i < 6;i++)
+			{
+				// calculate texture point
+				for (j = 0;j < 3;j++)
+					point->v[j] = base[j] + l->textoworld[0][j] * us + l->textoworld[1][j] * ut;
+
+				if (!Light_TraceLine (&tr, facemid, point->v))
+					break;	// got it
+
+				if (i & 1)
+				{
+					if (us > mids)
+					{
+						us -= 8;
+						if (us < mids)
+							us = mids;
+					}
+					else
+					{
+						us += 8;
+						if (us > mids)
+							us = mids;
+					}
+				}
+				else
+				{
+					if (ut > midt)
+					{
+						ut -= 8;
+						if (ut < midt)
+							ut = midt;
+					}
+					else
+					{
+						ut += 8;
+						if (ut > midt)
+							ut = midt;
+					}
+				}
+			}
+			//if (i == 2)
+			//	c_bad++;
+#endif
+		}
+	}
+}
+
+
+/*
+===============================================================================
+
+FACE LIGHTING
+
+===============================================================================
+*/
+
+int		c_culldistplane, c_proper;
+
+/*
+================
+SingleLightFace
+================
+*/
+void SingleLightFace (directlight_t *light, lightinfo_t *l)
+{
+	vec_t			dist, idist;
+	//vec_t			lightbrightness;
+	vec_t			lightsubtract;
+	vec_t			lightfalloff;
+	vec3_t			incoming;
+	vec_t			add;
+	qboolean		hit;
+	int				mapnum;
+	int				i;
+	vec3_t			spotvec;
+	vec_t			spotcone;
+	lightpoint_t	*point;
+	lightsample_t	*sample;
+	lightTrace_t	tr;
+
+	dist = (DotProduct (light->origin, l->facenormal) - l->facedist);
+
+// don't bother with lights behind the surface
+	if (dist < -0.25)
+		return;
+
+	//lightbrightness = light->light * 16384.0;
+	lightfalloff = light->falloff;
+	lightsubtract = light->subbrightness;
+// don't bother with light too far away
+	if (lightsubtract > (1.0 / (dist * dist * lightfalloff + LIGHTDISTBIAS)))
+	{
+		c_culldistplane++;
+		return;
+	}
+
+	for (mapnum = 0;mapnum < MAXLIGHTMAPS;mapnum++)
+	{
+		if (l->lightstyles[mapnum] == light->style)
+			break;
+		if (l->lightstyles[mapnum] == 255)
+		{
+			if (relight)
+				return;
+			memset(l->sample[mapnum], 0, sizeof(lightsample_t) * l->numsamples);
+			break;
+		}
+	}
+
+	if (mapnum == MAXLIGHTMAPS)
+	{
+		printf ("WARNING: Too many light styles on a face\n");
+		return;
+	}
+
+	spotcone = light->spotcone;
+	VectorCopy(light->spotdir, spotvec);
+
+//
+// check it for real
+//
+	hit = relight; // if relighting, the style is already considered used
+	c_proper++;
+
+	for (i = 0, point = l->point;i < l->numpoints;i++, point++)
+	{
+		VectorSubtract (light->origin, point->v, incoming);
+		dist = sqrt(DotProduct(incoming, incoming));
+		if (!dist)
+			continue;
+		idist = 1.0 / dist;
+		VectorScale( incoming, idist, incoming );
+
+		// spotlight
+		if (spotcone > 0 && DotProduct (spotvec, incoming) > spotcone)
+			continue;
+
+		// LordHavoc: changed to be more realistic (entirely different lighting model)
+		// LordHavoc: FIXME: use subbrightness on all lights, simply to have some distance culling
+		add = 1.0 / (dist * dist * lightfalloff + LIGHTDISTBIAS) - lightsubtract; // LordHavoc: added subbrightness
+		if (add <= 0)
+			continue;
+
+		if (point->occluded || !Light_TraceLine(&tr, point->v, light->origin))
+			continue;
+
+		// LordHavoc: FIXME: decide this 0.5 bias based on shader properties (some are dull, some are shiny)
+		add *= (DotProduct (incoming, l->facenormal) * 0.5 + 0.5);
+		add *= extrasamplesscale;
+		sample = &l->sample[mapnum][point->samplepos];
+		sample->c[0] += add * light->color[0] * tr.filter[0];
+		sample->c[1] += add * light->color[1] * tr.filter[1];
+		sample->c[2] += add * light->color[2] * tr.filter[2];
+		// ignore real tiny lights
+		if (!hit && ((sample->c[0]+sample->c[1]+sample->c[2]) >= 1))
+			hit = true;
+	}
+
+	// if the style has some data now, make sure it is in the list
+	if (hit)
+		l->lightstyles[mapnum] = light->style;
+}
+
+/*
+============
+FixMinlight
+============
+*/
+/*
+void FixMinlight (lightinfo_t *l)
+{
+	int				i, j;
+	lightsample_t	*sample;
+
+// if minlight is set, there must be a style 0 light map
+	if (!minlight)
+		return;
+
+	for (i = 0;i < l->numlightstyles;i++)
+	{
+		if (l->lightstyles[i] == 0)
+		{
+			for (j = 0, sample = l->sample[i];j < l->numsamples;j++, sample++)
+			{
+				if (sample->c[0] < minlight)
+					sample->c[0] = minlight;
+				if (sample->c[1] < minlight)
+					sample->c[1] = minlight;
+				if (sample->c[2] < minlight)
+					sample->c[2] = minlight;
+			}
+			return;
+		}
+	}
+
+	if (l->numlightstyles == MAXLIGHTMAPS)
+		return;		// oh well..
+	for (j = 0, sample = l->sample[i];j < l->numsamples;j++, sample++)
+	{
+		sample->c[0] = minlight;
+		sample->c[1] = minlight;
+		sample->c[2] = minlight;
+	}
+	l->lightstyles[i] = 0;
+	l->numlightstyles++;
+}
+*/
+
+int ranout = false;
+
+/*
+============
+LightFace
+============
+*/
+lightinfo_t l; // if this is made multithreaded again, this should be inside the function, but be warned, it's currently 38mb
+void LightFace (dface_t *f, lightchain_t *lightchain, directlight_t **novislight, int novislights, vec3_t faceorg)
+{
+	int				i, j, size;
+	int				red, green, blue, white;
+	byte			*out, *lit;
+	lightsample_t	*sample;
+
+	//memset (&l, 0, sizeof(l));
+	l.face = f;
+	if( !l.point )
+		l.point = qmalloc( SINGLEMAP * (1<<extrasamplesbit)*(1<<extrasamplesbit) );
+
+//
+// some surfaces don't need lightmaps
+//
+
+	if (relight)
+	{
+		if (f->lightofs == -1)
+			return;
+	}
+	else
+	{
+		for (i = 0;i < MAXLIGHTMAPS;i++)
+			f->styles[i] = l.lightstyles[i] = 255;
+		f->lightofs = -1;
+
+		if (texinfo[f->texinfo].flags & TEX_SPECIAL)
+		{
+			// non-lit texture
+			return;
+		}
+	}
+
+//
+// rotate plane
+//
+	VectorCopy (dplanes[f->planenum].normal, l.facenormal);
+	l.facedist = dplanes[f->planenum].dist;
+	if (f->side)
+	{
+		VectorNegate (l.facenormal, l.facenormal);
+		l.facedist = -l.facedist;
+	}
+
+	CalcFaceVectors (&l, faceorg);
+	CalcFaceExtents (&l);
+	CalcSamples (&l);
+	CalcPoints (&l);
+
+	if (l.numsamples > SINGLEMAP)
+		Error ("Bad lightmap size");
+
+	if (relight)
+	{
+		// reserve the correct light styles
+		for (i = 0;i < MAXLIGHTMAPS;i++)
+		{
+			l.lightstyles[i] = f->styles[i];
+			if (l.lightstyles[i] != 255)
+				memset(l.sample[i], 0, sizeof(lightsample_t) * l.numsamples);
+		}
+	}
+
+//
+// cast all lights
+//
+	while (lightchain)
+	{
+		SingleLightFace (lightchain->light, &l);
+		lightchain = lightchain->next;
+	}
+	while (novislights--)
+		SingleLightFace (*novislight++, &l);
+
+	//FixMinlight (&l);
+
+// save out the values
+
+	if (relight)
+	{
+		// relighting an existing map without changing it's lightofs table
+
+		for (i = 0;i < MAXLIGHTMAPS;i++)
+			if (f->styles[i] == 255)
+				break;
+		size = l.numsamples * i;
+
+		if (f->lightofs < 0 || f->lightofs + size > lightdatasize)
+			Error("LightFace: Error while trying to relight map: invalid lightofs value, %i must be >= 0 && < %i\n", f->lightofs, lightdatasize);
+	}
+	else
+	{
+		// creating lightofs table from scratch
+
+		for (i = 0;i < MAXLIGHTMAPS;i++)
+			if (l.lightstyles[i] == 255)
+				break;
+		size = l.numsamples * i;
+
+		if (!size)
+		{
+			// no light styles
+			return;
+		}
+
+		if (lightdatasize + size > MAX_MAP_LIGHTING)
+		{
+			//Error("LightFace: ran out of lightmap dataspace");
+			if (!ranout)
+				printf("LightFace: ran out of lightmap dataspace");
+			ranout = true;
+			return;
+		}
+
+		for (i = 0;i < MAXLIGHTMAPS;i++)
+			f->styles[i] = l.lightstyles[i];
+
+		f->lightofs = lightdatasize;
+		lightdatasize += size;
+	}
+
+	rgblightdatasize = lightdatasize * 3;
+
+	out = dlightdata + f->lightofs;
+	lit = drgblightdata + f->lightofs * 3;
+
+	for (i = 0;i < MAXLIGHTMAPS && f->styles[i] != 255;i++)
+	{
+		for (j = 0, sample = l.sample[i];j < l.numsamples;j++, sample++)
+		{
+			red   = (int) sample->c[0];
+			green = (int) sample->c[1];
+			blue  = (int) sample->c[2];
+			white = (int) ((sample->c[0] + sample->c[1] + sample->c[2]) * (1.0 / 3.0));
+			if (red > 255) red = 255;
+			if (red < 0) red = 0;
+			if (green > 255) green = 255;
+			if (green < 0) green = 0;
+			if (blue > 255) blue = 255;
+			if (blue < 0) blue = 0;
+			if (white > 255) white = 255;
+			if (white < 0) white = 0;
+			*lit++ = red;
+			*lit++ = green;
+			*lit++ = blue;
+			*out++ = white;
+		}
+	}
+}
+
Index: hmap2/light_trace.c
diff -u /dev/null hmap2/light_trace.c:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/light_trace.c	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,239 @@
+// trace.c
+
+#include "light.h"
+
+typedef struct
+{
+	int		type;
+	vec3_t	normal;
+	vec_t	dist;
+	int		children[2];
+} tnode_t;
+
+tnode_t		*tnodes, *tnode_p;
+
+/*
+==============
+MakeTnode
+
+Converts the disk node structure into the efficient tracing structure
+==============
+*/
+void MakeTnode (int nodenum)
+{
+	tnode_t			*t;
+	dplane_t		*plane;
+	int				i;
+	dnode_t 		*node;
+	
+	t = tnode_p++;
+
+	node = dnodes + nodenum;
+	plane = dplanes + node->planenum;
+
+	t->type = plane->type;
+	VectorCopy (plane->normal, t->normal);
+	t->dist = plane->dist;
+
+	for (i=0 ; i<2 ; i++)
+	{
+		if (node->children[i] < 0)
+			t->children[i] = dleafs[-node->children[i] - 1].contents;
+		else
+		{
+			t->children[i] = tnode_p - tnodes;
+			MakeTnode (node->children[i]);
+		}
+	}
+			
+}
+
+
+/*
+=============
+MakeTnodes
+
+Loads the node structure out of a .bsp file to be used for light occlusion
+=============
+*/
+void MakeTnodes (dmodel_t *bm)
+{
+	tnodes = qmalloc((numnodes/*+1*/) * sizeof(tnode_t));
+//	tnodes = (tnode_t *)(((int)tnodes + 31) & ~31);
+	tnode_p = tnodes;
+
+	MakeTnode (0);
+}
+
+
+
+/*
+==============================================================================
+
+LINE TRACING
+
+The major lighting operation is a point to point visibility test, performed
+by recursive subdivision of the line by the BSP tree.
+
+==============================================================================
+*/
+
+int Light_PointContents( vec3_t p )
+{
+	vec_t		d;
+	tnode_t		*tnode;
+	int			num = 0;
+
+	while( num >= 0 ) {
+		tnode = tnodes + num;
+		if( tnode->type < 3 )
+			d = p[tnode->type] - tnode->dist;
+		else
+			d = DotProduct( tnode->normal, p ) - tnode->dist;
+		num = tnode->children[(d < 0)];
+	}
+
+	return num;
+}
+
+#define TESTLINESTATE_BLOCKED 0
+#define TESTLINESTATE_EMPTY 1
+#define TESTLINESTATE_SOLID 2
+
+/*
+==============
+TestLine
+==============
+*/
+// LordHavoc: TestLine returns true if there is no impact,
+// see below for precise definition of impact (important)
+lightTrace_t *trace_trace;
+
+static int RecursiveTestLine (int num, float p1f, float p2f, vec3_t p1, vec3_t p2)
+{
+	int			side, ret;
+	vec_t		t1, t2, frac, midf;
+	vec3_t		mid;
+	tnode_t		*tnode;
+
+	// LordHavoc: this function operates by doing depth-first front-to-back
+	// recursion through the BSP tree, checking at every split for an empty to solid
+	// transition (impact) in the children, and returns false if one is found
+
+	// LordHavoc: note: 'no impact' does not mean it is empty, it occurs when there
+	// is no transition from empty to solid; all solid or a transition from solid
+	// to empty are not considered impacts. (this does mean that tracing is not
+	// symmetrical; point A to point B may have different results than point B to
+	// point A, if either start in solid)
+
+	// check for empty
+loc0:
+	if (num < 0)
+	{
+		if (num == CONTENTS_SOLID)
+		{
+			trace_trace->startSolid = true;
+//			VectorClear( trace_trace->filter );
+			return TESTLINESTATE_SOLID;
+		}
+		else if (num == CONTENTS_WATER)
+		{
+//			trace_trace->filter[0] *= 0.6;
+//			trace_trace->filter[1] *= 0.6;
+		}
+		else if (num == CONTENTS_LAVA)
+		{
+//			trace_trace->filter[1] *= 0.6;
+//			trace_trace->filter[2] *= 0.6;
+		}
+		return TESTLINESTATE_EMPTY;
+	}
+
+	// find the point distances
+	tnode = tnodes + num;
+
+	if (tnode->type < 3)
+	{
+		t1 = p1[tnode->type] - tnode->dist;
+		t2 = p2[tnode->type] - tnode->dist;
+	}
+	else
+	{
+		t1 = DotProduct (tnode->normal, p1) - tnode->dist;
+		t2 = DotProduct (tnode->normal, p2) - tnode->dist;
+	}
+
+	if (t1 >= 0)
+	{
+		if (t2 >= 0)
+		{
+			num = tnode->children[0];
+			goto loc0;
+		}
+		side = 0;
+	}
+	else
+	{
+		if (t2 < 0)
+		{
+			num = tnode->children[1];
+			goto loc0;
+		}
+		side = 1;
+	}
+
+	frac = t1 / (t1 - t2);
+	if (frac < 0)
+		frac = 0;
+	if (frac > 1)
+		frac = 1;
+
+	midf = p1f + (p2f - p1f) * frac;
+	mid[0] = p1[0] + frac * (p2[0] - p1[0]);
+	mid[1] = p1[1] + frac * (p2[1] - p1[1]);
+	mid[2] = p1[2] + frac * (p2[2] - p1[2]);
+
+	// front side first
+	ret = RecursiveTestLine (tnode->children[side], p1f, midf, p1, mid);
+	if (ret != TESTLINESTATE_EMPTY)
+		return ret; // solid or blocked
+
+	ret = RecursiveTestLine (tnode->children[!side], midf, p2f, mid, p2);
+	if (ret != TESTLINESTATE_SOLID)
+		return ret; // empty or blocked
+
+	if (!side)
+	{
+		VectorCopy (tnode->normal, trace_trace->plane.normal);
+		trace_trace->plane.dist = tnode->dist;
+		trace_trace->plane.type = tnode->type;
+	}
+	else
+	{
+		VectorNegate (tnode->normal, trace_trace->plane.normal);
+		trace_trace->plane.dist = -tnode->dist;
+		trace_trace->plane.type = PLANE_ANYX;
+	}
+
+	trace_trace->fraction = midf;
+	VectorCopy( mid, trace_trace->impact );
+
+	return TESTLINESTATE_BLOCKED;
+}
+
+qboolean Light_TraceLine (lightTrace_t *trace, vec3_t start, vec3_t end)
+{
+	static plane_t nullplane;
+
+	if (!trace)
+		return false;
+
+	trace_trace = trace;
+	trace_trace->fraction = 1.0;
+	trace_trace->plane = nullplane;
+	trace_trace->startSolid = false;
+	VectorSet (trace_trace->filter, 1, 1, 1);
+	VectorCopy (end, trace_trace->impact);
+
+	return ( RecursiveTestLine( 0, 0, 1, start, end ) != TESTLINESTATE_BLOCKED );
+}
Index: hmap2/map.c
diff -u /dev/null hmap2/map.c:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/map.c	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,476 @@
+// map.c
+
+#include "bsp5.h"
+
+// just for statistics
+int			nummapbrushfaces;
+
+int			nummapbrushes;
+mbrush_t	mapbrushes[MAX_MAP_BRUSHES];
+
+int			nummapplanes;
+plane_t		mapplanes[MAX_MAP_PLANES];
+
+int			nummiptex;
+char		miptex[MAX_MAP_TEXINFO][16];
+
+//============================================================================
+
+/*
+===============
+FindPlane
+
+Returns a global plane number and the side that will be the front
+===============
+*/
+int	FindPlane( plane_t *dplane, int *side )
+{
+	int			i;
+	plane_t		*dp, pl;
+	vec_t		dot;
+
+	pl = *dplane;
+	NormalizePlane( &pl );
+
+	if( DotProduct( pl.normal, dplane->normal ) > 0 )
+		*side = 0;
+	else
+		*side = 1;
+
+	for( i = 0, dp = mapplanes; i < nummapplanes; i++, dp++ ) {
+		if( DotProduct( dp->normal, pl.normal ) > 1.0 - ANGLEEPSILON && fabs( dp->dist - pl.dist ) < DISTEPSILON )
+			return i; // regular match
+	}
+
+	if( nummapplanes == MAX_MAP_PLANES )
+		Error( "FindPlane: nummapplanes == MAX_MAP_PLANES" );
+
+	dot = VectorLength( dplane->normal );
+	if( dot < 1.0 - ANGLEEPSILON || dot > 1.0 + ANGLEEPSILON )
+		Error( "FindPlane: normalization error (%f %f %f, length %f)", dplane->normal[0], dplane->normal[1], dplane->normal[2], dot );
+
+	mapplanes[nummapplanes] = pl;
+
+	return nummapplanes++;
+}
+
+/*
+===============
+FindMiptex
+===============
+*/
+int FindMiptex( char *name )
+{
+	int		i;
+
+	for( i = 0; i < nummiptex; i++ ) {
+		if( !strcmp( name, miptex[i] ) )
+			return i;
+	}
+
+	if( nummiptex == MAX_MAP_TEXINFO )
+		Error ("nummiptex == MAX_MAP_TEXINFO");
+
+	strcpy( miptex[i], name );
+
+	return nummiptex++;
+}
+
+/*
+===============
+FindTexinfo
+
+Returns a global texinfo number
+===============
+*/
+int	FindTexinfo( texinfo_t *t )
+{
+	int			i, j;
+	texinfo_t	*tex;
+
+	// set the special flag
+	if( miptex[t->miptex][0] == '*' || !Q_strncasecmp (miptex[t->miptex], "sky", 3) )
+		t->flags |= TEX_SPECIAL;
+
+	tex = texinfo;
+	for( i = 0; i < numtexinfo; i++, tex++ ) {
+		if( t->miptex != tex->miptex )
+			continue;
+		if( t->flags != tex->flags )
+			continue;
+
+		for( j = 0; j < 8; j++ ) {
+			if( t->vecs[0][j] != tex->vecs[0][j] )
+				break;
+		}
+		if( j != 8 )
+			continue;
+
+		return i;
+	}
+
+	// allocate a new texture
+	if( numtexinfo == MAX_MAP_TEXINFO )
+		Error( "numtexinfo == MAX_MAP_TEXINFO" );
+
+	texinfo[i] = *t;
+
+	return numtexinfo++;
+}
+
+//============================================================================
+
+
+/*
+==================
+textureAxisFromPlane
+==================
+*/
+vec3_t	baseaxis[18] =
+{
+	{0,0,1}, {1,0,0}, {0,-1,0},			// floor
+	{0,0,-1}, {1,0,0}, {0,-1,0},		// ceiling
+	{1,0,0}, {0,1,0}, {0,0,-1},			// west wall
+	{-1,0,0}, {0,1,0}, {0,0,-1},		// east wall
+	{0,1,0}, {1,0,0}, {0,0,-1},			// south wall
+	{0,-1,0}, {1,0,0}, {0,0,-1}			// north wall
+};
+
+void TextureAxisFromPlane(plane_t *pln, vec3_t xv, vec3_t yv)
+{
+	int		bestaxis;
+	vec_t	dot,best;
+	int		i;
+
+	best = 0;
+	bestaxis = 0;
+
+	for (i=0 ; i<6 ; i++)
+	{
+		dot = DotProduct (pln->normal, baseaxis[i*3]);
+		if (dot > best)
+		{
+			best = dot;
+			bestaxis = i;
+		}
+	}
+
+	VectorCopy (baseaxis[bestaxis*3+1], xv);
+	VectorCopy (baseaxis[bestaxis*3+2], yv);
+}
+
+
+//=============================================================================
+
+
+/*
+=================
+ParseBrush
+=================
+*/
+void ParseBrush (entity_t *ent)
+{
+	int			i, j, sv, tv, hltexdef;
+	vec_t		planepts[3][3], t1[3], t2[3], d, rotate, scale[2], vecs[2][4], ang, sinv, cosv, ns, nt;
+	mbrush_t	*b;
+	mface_t		*f, *f2;
+	plane_t	plane;
+	texinfo_t	tx;
+
+	b = &mapbrushes[nummapbrushes];
+	nummapbrushes++;
+	b->next = ent->brushes;
+	ent->brushes = b;
+
+	do
+	{
+		if (!GetToken (true))
+			break;
+		if (!strcmp (token, "}") )
+			break;
+
+		// read the three point plane definition
+		for (i = 0;i < 3;i++)
+		{
+			if (i != 0)
+				GetToken (true);
+			if (strcmp (token, "(") )
+				Error ("parsing brush on line %d\n", scriptline);
+
+			for (j = 0;j < 3;j++)
+			{
+				GetToken (false);
+				planepts[i][j] = (vec_t)atof(token); // LordHavoc: float coords
+			}
+
+			GetToken (false);
+			if (strcmp (token, ")") )
+				Error ("parsing brush on line %d\n", scriptline);
+		}
+
+		//fflush(stdout);
+
+		// convert points to a plane
+		VectorSubtract(planepts[0], planepts[1], t1);
+		VectorSubtract(planepts[2], planepts[1], t2);
+		CrossProduct(t1, t2, plane.normal);
+		VectorNormalize(plane.normal);
+		plane.dist = DotProduct(planepts[1], plane.normal);
+
+		// read the texturedef
+		memset (&tx, 0, sizeof(tx));
+		GetToken (false);
+		tx.miptex = FindMiptex (token);
+		GetToken (false);
+		if ((hltexdef = !strcmp(token, "[")))
+		{
+			// S vector
+			GetToken(false);
+			vecs[0][0] = (vec_t)atof(token);
+			GetToken(false);
+			vecs[0][1] = (vec_t)atof(token);
+			GetToken(false);
+			vecs[0][2] = (vec_t)atof(token);
+			GetToken(false);
+			vecs[0][3] = (vec_t)atof(token);
+			// ]
+			GetToken(false);
+			// [
+			GetToken(false);
+			// T vector
+			GetToken(false);
+			vecs[1][0] = (vec_t)atof(token);
+			GetToken(false);
+			vecs[1][1] = (vec_t)atof(token);
+			GetToken(false);
+			vecs[1][2] = (vec_t)atof(token);
+			GetToken(false);
+			vecs[1][3] = (vec_t)atof(token);
+			// ]
+			GetToken(false);
+		}
+		else
+		{
+			vecs[0][3] = (vec_t)atof(token); // LordHavoc: float coords
+			GetToken (false);
+			vecs[1][3] = (vec_t)atof(token); // LordHavoc: float coords
+		}
+		GetToken (false);
+		rotate = atof(token);	 // LordHavoc: float coords
+		GetToken (false);
+		scale[0] = (vec_t)atof(token); // LordHavoc: was already float coords
+		GetToken (false);
+		scale[1] = (vec_t)atof(token); // LordHavoc: was already float coords
+
+		// if the three points are all on a previous plane, it is a
+		// duplicate plane
+		for (f2 = b->faces ; f2 ; f2=f2->next)
+		{
+			for (i = 0;i < 3;i++)
+			{
+				d = DotProduct(planepts[i],f2->plane.normal) - f2->plane.dist;
+				if (d < -ON_EPSILON || d > ON_EPSILON)
+					break;
+			}
+			if (i==3)
+				break;
+		}
+		if (f2)
+		{
+			printf ("WARNING: brush with duplicate plane (first point is at %g %g %g, .map file line number %d)\n", planepts[0][0], planepts[0][1], planepts[0][2], scriptline);
+			continue;
+		}
+
+		if (DotProduct(plane.normal, plane.normal) < 0.1)
+		{
+			printf ("WARNING: brush plane with no normal on line %d\n", scriptline);
+			continue;
+		}
+
+		/*
+		// LordHavoc: fix for CheckFace: point off plane errors in some maps (most notably QOOLE ones),
+		// and hopefully preventing most 'portal clipped away' warnings
+		VectorNormalize (plane.normal);
+		for (j = 0;j < 3;j++)
+			plane.normal[j] = (Q_rint((vec_t) plane.normal[j] * (vec_t) 8.0)) * (vec_t) (1.0 / 8.0);
+		VectorNormalize (plane.normal);
+		plane.dist = DotProduct (t3, plane.normal);
+		d = (Q_rint(plane.dist * 8.0)) * (1.0 / 8.0);
+		//if (fabs(d - plane.dist) >= (0.4 / 8.0))
+		//	printf("WARNING: correcting minor math errors in brushface on line %d\n", scriptline);
+		plane.dist = d;
+		*/
+
+		/*
+		VectorNormalize (plane.normal);
+		plane.dist = DotProduct (t3, plane.normal);
+
+		VectorCopy(plane.normal, v);
+		//for (j = 0;j < 3;j++)
+		//	v[j] = (Q_rint((vec_t) v[j] * (vec_t) 32.0)) * (vec_t) (1.0 / 32.0);
+		VectorNormalize (v);
+		d = (Q_rint(DotProduct (t3, v) * 8.0)) * (1.0 / 8.0);
+
+		// if deviation is too high, warn  (frequently happens on QOOLE maps)
+		if (fabs(DotProduct(v, plane.normal) - 1.0) > (0.5 / 32.0)
+		 || fabs(d - plane.dist) >= (0.25 / 8.0))
+			printf("WARNING: minor misalignment of brushface on line %d\n"
+			       "normal     %f %f %f (l: %f d: %f)\n"
+			       "rounded to %f %f %f (l: %f d: %f r: %f)\n",
+			       scriptline,
+			       (vec_t) plane.normal[0], (vec_t) plane.normal[1], (vec_t) plane.normal[2], (vec_t) sqrt(DotProduct(plane.normal, plane.normal)), (vec_t) DotProduct (t3, plane.normal),
+			       (vec_t) v[0], (vec_t) v[1], (vec_t) v[2], (vec_t) sqrt(DotProduct(v, v)), (vec_t) DotProduct(t3, v), (vec_t) d);
+		//VectorCopy(v, plane.normal);
+		//plane.dist = d;
+		*/
+
+		if (!hltexdef)
+		{
+			// fake proper texture vectors from QuakeEd style
+			TextureAxisFromPlane(&plane, vecs[0], vecs[1]);
+		}
+
+		// rotate axis
+		     if (rotate ==  0) {sinv = 0;cosv = 1;}
+		else if (rotate == 90) {sinv = 1;cosv = 0;}
+		else if (rotate == 180) {sinv = 0;cosv = -1;}
+		else if (rotate == 270) {sinv = -1;cosv = 0;}
+		else {ang = rotate * (Q_PI / 180);sinv = sin(ang);cosv = cos(ang);}
+
+		// LordHavoc: I don't quite understand this
+		for (sv = 0;sv < 2 && !vecs[0][sv];sv++);
+		for (tv = 0;tv < 2 && !vecs[1][tv];tv++);
+
+		for (i = 0;i < 2;i++)
+		{
+			// rotate
+			ns = cosv * vecs[i][sv] - sinv * vecs[i][tv];
+			nt = sinv * vecs[i][sv] +  cosv * vecs[i][tv];
+			vecs[i][sv] = ns;
+			vecs[i][tv] = nt;
+			// scale and store into texinfo
+			d = 1.0 / (scale[i] ? scale[i] : 1.0);
+			for (j = 0;j < 3;j++)
+				tx.vecs[i][j] = vecs[i][j] * d;
+			tx.vecs[i][3] = vecs[i][3];
+		}
+
+		f = qmalloc(sizeof(mface_t));
+		f->next = b->faces;
+		b->faces = f;
+		f->plane = plane;
+		f->texinfo = FindTexinfo (&tx);
+		nummapbrushfaces++;
+	}
+	while (1);
+}
+
+/*
+============
+MoveEntityBrushesIntoWorld
+
+Moves entity's brushes into tree.
+============
+*/
+void MoveEntityBrushesIntoWorld( entity_t *ent )
+{
+	entity_t *world;
+	mbrush_t *b, *next;
+
+	world = &entities[0];
+	if( ent == world )
+		return;
+
+	for( b = ent->brushes; b; b = next ) {
+		next = b->next;
+		b->next = world->brushes;
+		world->brushes = b;
+	}
+
+	ent->brushes = NULL;
+}
+
+/*
+================
+ParseEntity
+================
+*/
+qboolean ParseEntity (void)
+{
+	epair_t *e, *next;
+	entity_t *ent;
+
+	if( !GetToken( true ) )
+		return false;
+	if( strcmp( token, "{" ) )
+		Error( "ParseEntity: { not found" );
+
+	if( num_entities == MAX_MAP_ENTITIES )
+		Error( "num_entities == MAX_MAP_ENTITIES" );
+
+	ent = &entities[num_entities++];
+	ent->epairs = NULL;
+
+	do {
+		fflush( stdout );
+
+		if( !GetToken( true ) )
+			Error( "ParseEntity: EOF without closing brace" );
+
+		if( !strcmp (token, "}") )
+			break;
+		else if( !strcmp( token, "{" ) )  {
+			ParseBrush( ent );
+		} else {
+			e = ParseEpair ();
+			e->next = ent->epairs;
+			ent->epairs = e;
+		}
+	} while( 1 );
+
+	if( !strcmp( ValueForKey( ent, "classname" ), "func_group" ) ) {
+		MoveEntityBrushesIntoWorld( ent );
+
+		for( e = ent->epairs; e; e = next ) {
+			next = e->next;
+			qfree( e );
+		}
+
+		num_entities--;
+	}
+
+	return true;
+}
+
+/*
+================
+LoadMapFile
+================
+*/
+void LoadMapFile (char *filename)
+{
+	void	*buf;
+
+	num_entities = 0;
+
+	nummapbrushfaces = 0;
+	nummapbrushes = 0;
+	nummapplanes = 0;
+	nummiptex = 0;
+
+	LoadFile (filename, &buf);
+
+	StartTokenParsing (buf);
+
+	while (ParseEntity ());
+
+	qfree (buf);
+
+	qprintf ("--- LoadMapFile ---\n");
+	qprintf ("%s\n", filename);
+	qprintf ("%5i faces\n", nummapbrushfaces);
+	qprintf ("%5i brushes\n", nummapbrushes);
+	qprintf ("%5i entities\n", num_entities);
+	qprintf ("%5i textures\n", nummiptex);
+	qprintf ("%5i texinfo\n", numtexinfo);
+}
Index: hmap2/map.h
diff -u /dev/null hmap2/map.h:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/map.h	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,33 @@
+
+#define	DISTEPSILON		0.001
+#define	ANGLEEPSILON	0.00001
+
+// LordHavoc: raised MAX_FACES From 32 to 256
+#define	MAX_FACES		256
+
+typedef struct mface_s
+{
+	struct mface_s	*next;
+	plane_t			plane;
+	int				texinfo;
+} mface_t;
+
+typedef struct mbrush_s
+{
+	struct mbrush_s	*next;
+	mface_t *faces;
+} mbrush_t;
+
+extern	int			nummapbrushes;
+extern	mbrush_t	mapbrushes[MAX_MAP_BRUSHES];
+
+extern	int			nummapplanes;
+extern	plane_t		mapplanes[MAX_MAP_PLANES];
+
+extern	int			nummiptex;
+extern	char		miptex[MAX_MAP_TEXINFO][16];
+
+void 	LoadMapFile (char *filename);
+
+int		FindPlane( plane_t *dplane, int *side );
+int		FindMiptex (char *name);
Index: hmap2/mathlib.c
diff -u /dev/null hmap2/mathlib.c:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/mathlib.c	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,190 @@
+// mathlib.c -- math primitives
+
+#include "cmdlib.h"
+#include "mathlib.h"
+
+vec3_t vec3_origin = { 0, 0, 0 };
+
+vec_t VectorNormalize( vec3_t v )
+{
+	vec_t length;
+
+	length = VectorLength( v );
+	if( length ) {
+		vec_t ilength;
+
+		ilength = 1.0 / length;
+		VectorScale( v, ilength, v );
+	}
+
+	return length;
+}
+
+vec_t Q_rint( vec_t n )
+{
+	if (n >= 0)
+		return ( vec_t )(( int )(n + 0.5));
+	else
+		return ( vec_t )(( int )(n - 0.5));
+}
+
+/*
+=================
+ClearBounds
+=================
+*/
+void ClearBounds( vec3_t mins, vec3_t maxs )
+{
+	int		i;
+
+	for( i = 0; i < 3; i++ ) {
+		mins[i] = BOGUS_RANGE;
+		maxs[i] = -BOGUS_RANGE;
+	}
+}
+
+/*
+=================
+AddPointToBounds
+=================
+*/
+void AddPointToBounds( vec3_t v, vec3_t mins, vec3_t maxs )
+{
+	int		i;
+
+	for( i = 0; i < 3; i++ ) {
+		if (v[i] < mins[i])
+			mins[i] = v[i];
+		if (v[i] > maxs[i])
+			maxs[i] = v[i];
+	}
+}
+
+/*
+=================
+RadiusFromBounds
+=================
+*/
+vec_t RadiusFromBounds( vec3_t mins, vec3_t maxs )
+{
+	vec_t mind, maxd;
+
+	if( mins[0] < mins[1] )
+	    mind = mins[0];
+	else
+	    mind = mins[1];
+	if( mins[2] < mind )
+	    mind = mins[2];
+	mind = fabs( mind );
+	
+	if( maxs[0] > maxs[1] )
+	    maxd = maxs[0];
+	else
+	    maxd = maxs[1];
+	if( maxs[2] > maxd )
+	    maxd = maxs[2];
+	maxd = fabs( maxd );
+		    
+	if( mind > maxd )
+		return mind;
+	return maxd;
+}
+
+/*
+=================
+PlaneTypeForNormal
+=================
+*/
+int	PlaneTypeForNormal( vec3_t normal )
+{
+	vec_t	ax, ay, az;
+
+	// NOTE: should these have an epsilon around 1.0?
+	if( normal[0] == 1.0 )
+		return PLANE_X;
+	if( normal[1] == 1.0 )
+		return PLANE_Y;
+	if( normal[2] == 1.0 )
+		return PLANE_Z;
+	if( normal[0] == -1.0 || normal[1] == -1.0 || normal[2] == -1.0 )
+		Error ("PlaneTypeForNormal: not a canonical vector (%f %f %f)", normal[0], normal[1], normal[2]);
+
+	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;
+}
+
+/*
+=================
+NormalizePlane
+=================
+*/
+void NormalizePlane( plane_t *dp )
+{
+	vec_t	ax, ay, az;
+
+	if( dp->normal[0] == -1.0 ) {
+		dp->normal[0] = 1.0;
+		dp->dist = -dp->dist;
+	} else if( dp->normal[1] == -1.0 ) {
+		dp->normal[1] = 1.0;
+		dp->dist = -dp->dist;
+	} else if( dp->normal[2] == -1.0 ) {
+		dp->normal[2] = 1.0;
+		dp->dist = -dp->dist;
+	}
+
+	if( dp->normal[0] == 1.0 ) {
+		dp->type = PLANE_X;
+		return;
+	}
+	if( dp->normal[1] == 1.0 ) {
+		dp->type = PLANE_Y;
+		return;
+	}
+	if( dp->normal[2] == 1.0 ) {
+		dp->type = PLANE_Z;
+		return;
+	}
+
+	ax = fabs( dp->normal[0] );
+	ay = fabs( dp->normal[1] );
+	az = fabs( dp->normal[2] );
+
+	if( ax >= ay && ax >= az )
+		dp->type = PLANE_ANYX;
+	else if( ay >= ax && ay >= az )
+		dp->type = PLANE_ANYY;
+	else
+		dp->type = PLANE_ANYZ;
+
+	if( dp->normal[dp->type - PLANE_ANYX] < 0 ) {
+		VectorNegate( dp->normal, dp->normal );
+		dp->dist = -dp->dist;
+	}
+}
+
+/*
+=================
+PlaneCompare
+=================
+*/
+qboolean PlaneCompare( plane_t *p1, plane_t *p2 )
+{
+	int		i;
+
+	if( fabs( p1->dist - p2->dist ) > 0.01)
+		return false;
+
+	for( i = 0; i < 3; i++ )
+		if( fabs(p1->normal[i] - p2->normal[i] ) > 0.001 )
+			return false;
+
+	return true;
+}
Index: hmap2/mathlib.h
diff -u /dev/null hmap2/mathlib.h:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/mathlib.h	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,89 @@
+// mathlib.h
+
+#ifndef __MATHLIB__
+#define __MATHLIB__
+
+#include <math.h>
+
+#if _MSC_VER
+# pragma warning(disable : 4244)     // MIPS
+# pragma warning(disable : 4136)     // X86
+# pragma warning(disable : 4051)     // ALPHA
+#endif
+
+#define DOUBLEVEC_T
+
+#ifdef DOUBLEVEC_T
+typedef double vec_t;
+#else
+typedef float vec_t;
+#endif
+typedef vec_t vec3_t[3];
+
+extern vec3_t vec3_origin;
+
+typedef struct
+{
+	vec3_t	normal;
+	vec_t	dist;
+	int		type;
+} plane_t;
+
+#define	SIDE_FRONT		0
+#define	SIDE_ON			2
+#define	SIDE_BACK		1
+#define	SIDE_CROSS		-2
+
+#define	Q_PI			3.14159265358979323846
+
+#define	EQUAL_EPSILON	0.001
+
+#ifndef PLANE_X
+
+// 0-2 are axial planes
+# define	PLANE_X			0
+# define	PLANE_Y			1
+# define	PLANE_Z			2
+
+// 3-5 are non-axial planes snapped to the nearest
+# define	PLANE_ANYX		3
+# define	PLANE_ANYY		4
+# define	PLANE_ANYZ		5
+
+#endif
+
+// LordHavoc: moved BOGUS_RANGE from bsp5.h and increased from 18000 to 1000000000
+#define	BOGUS_RANGE		1000000000
+
+#define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2])
+#define VectorSet(a,x,y,z) ((a[0])=(x),(a)[1]=(y),(a)[2]=(z))
+#define VectorCopy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2])
+#define VectorSubtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2])
+#define VectorAdd(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2])
+#define VectorLength(v) (sqrt(((v)[0]*v[0]) + ((v)[1]*v[1]) + ((v)[2]*(v)[2])))
+#define VectorCompare(v1, v2) (fabs((v1)[0]-(v2)[0]) < EQUAL_EPSILON && \
+				fabs((v1)[1]-(v2)[1]) < EQUAL_EPSILON && \
+				fabs((v1)[2]-(v2)[2]) < EQUAL_EPSILON)
+#define VectorMA(va, scale, vb, vc) ((vc)[0]=(va)[0]+(scale)*(vb)[0],(vc)[1]=(va)[1]+(scale)*(vb)[1],(vc)[2]=(va)[2]+(scale)*(vb)[2])
+#define CrossProduct(v1, v2, cross) ((cross)[0]=(v1)[1]*(v2)[2]-(v1)[2]*(v2)[1],(cross)[1]=(v1)[2]*(v2)[0]-(v1)[0]*(v2)[2],(cross)[2]=(v1)[0]*(v2)[1]-(v1)[1]*(v2)[0])
+#define VectorClear(v) ((v)[0]=(v)[1]=(v)[2]=0)
+#define VectorNegate(a,b) ((b)[0]=-(a)[0],(b)[1]=-(a)[1],(b)[2]=-(a)[2])
+#define VectorInverse(v) ((v)[0]=-(v)[0],(v)[1]=-(v)[1],(v)[2]=-(v)[2])
+#define VectorScale(v, scale, out) ((out)[0]=(v)[0]*(scale),(out)[1]=(v)[1]*(scale),(out)[2]=(v)[2]*(scale))
+#define bound(min, value, max) ((value) < (min) ? (min) : ((value) > (max) ? (max) : (value)))
+#define PlaneDiff(point,plane) (((plane)->type < 3 ? (point)[(plane)->type] : DotProduct((point), (plane)->normal)) - (plane)->dist)
+
+vec_t VectorNormalize( vec3_t v );
+
+vec_t Q_rint( vec_t n );
+
+void ClearBounds( vec3_t mins, vec3_t maxs );
+void AddPointToBounds( vec3_t v, vec3_t mins, vec3_t maxs );
+vec_t RadiusFromBounds( vec3_t mins, vec3_t maxs );
+
+int	PlaneTypeForNormal( vec3_t normal );
+void NormalizePlane( plane_t *dp );
+qboolean PlaneCompare( plane_t *p1, plane_t *p2 );
+
+#endif
+
Index: hmap2/mem.c
diff -u /dev/null hmap2/mem.c:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/mem.c	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,120 @@
+#include "cmdlib.h"
+#include "mem.h"
+
+static qboolean mem_initialized = false;
+
+typedef struct chunkheader_s
+{
+	// next and previous memheaders in chain belonging to pool
+	struct chunkheader_s *next;
+	struct chunkheader_s *prev;
+
+	// size of the memory after the header (excluding header and sentinel2)
+	size_t size;
+
+	// immediately followed by data
+} chunkheader_t;
+
+static chunkheader_t *chunks;
+static size_t total_allocated;
+static size_t total_active, total_peakactive;
+
+void Q_InitMem( void )
+{
+	if( mem_initialized )
+		return;
+
+	chunks = NULL;
+	mem_initialized = true;
+	total_allocated = 0;
+	total_active = 0;
+	total_peakactive = 0;
+}
+
+void Q_PrintMem( void )
+{
+	if( !mem_initialized )
+		return;
+
+	printf( "Active memory: %f MB (%i bytes)\n", total_active  / 1048576.0, total_active );
+	printf( "Peak active memory: %f MB (%i bytes)\n", total_peakactive  / 1048576.0, total_peakactive );
+	printf( "Total memory allocated: %f MB (%i bytes)\n", total_allocated  / 1048576.0, total_allocated );
+}
+
+void Q_ShutdownMem( void )
+{
+	size_t total;
+
+	if( !mem_initialized )
+		return;
+
+	total = 0;
+	while( chunks ) {
+		total += chunks->size;
+		qfree( (void *)((unsigned char *)chunks + sizeof(chunkheader_t)) );
+	}
+
+//	printf( "Leaked memory: %f MB (%i bytes)\n", total  / 1048576.0, total );
+//	printf( "Total memory allocated: %f MB (%i bytes)\n", total_allocated  / 1048576.0, total_allocated );
+
+	mem_initialized = false;
+}
+
+void *qmalloc( size_t size )
+{
+	void *data;
+	chunkheader_t *chunk;
+
+	if( size <= 0 )
+		return NULL;
+
+	if( mem_initialized )
+		data = malloc( sizeof(chunkheader_t) + size );
+	else
+		data = malloc( size );
+
+	if( !data )
+		Error( "Failed on allocating %d bytes\n", size );
+
+	if( !mem_initialized )
+		return data;
+
+	chunk = ( chunkheader_t * )data;
+	chunk->size = size;
+	chunk->next = chunks;
+	chunk->prev = NULL;
+	chunks = chunk;
+	if (chunk->next)
+		chunk->next->prev = chunk;
+
+	total_allocated += size;
+	total_active += size;
+	if( total_active > total_peakactive )
+		total_peakactive = total_active;
+
+	return ( void * )(( unsigned char * )data + sizeof(chunkheader_t) );
+}
+
+void qfree( void *data )
+{
+	chunkheader_t *chunk;
+
+	if( !data )
+		return;
+	if( !mem_initialized ) {
+		free( data );
+		return;
+	}
+
+	chunk = ( chunkheader_t * )(( unsigned char * )data - sizeof(chunkheader_t) );
+	if( chunk->prev )
+		chunk->prev->next = chunk->next;
+	else
+		chunks = chunk->next;
+	if( chunk->next )
+		chunk->next->prev = chunk->prev;
+
+	total_active -= chunk->size;
+
+	free( chunk );
+}
Index: hmap2/mem.h
diff -u /dev/null hmap2/mem.h:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/mem.h	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,7 @@
+
+void Q_InitMem( void );
+void Q_PrintMem( void );
+void Q_ShutdownMem( void );
+
+void *qmalloc( size_t size );
+void qfree( void *data );
Index: hmap2/outside.c
diff -u /dev/null hmap2/outside.c:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/outside.c	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,236 @@
+
+#include "bsp5.h"
+
+int		outleafs;
+
+/*
+===========
+PointInLeaf
+
+Vic: rewrote to be faster
+LordHavoc: shortened a little
+===========
+*/
+node_t	*PointInLeaf (node_t *node, vec3_t point)
+{
+	while (!node->contents)
+		node = node->children[PlaneDiff( point, &mapplanes[node->planenum] ) <= 0];
+	return node;
+}
+
+/*
+===========
+PlaceOccupant
+===========
+*/
+qboolean PlaceOccupant (int num, vec3_t point, node_t *headnode)
+{
+	node_t	*n;
+
+	n = PointInLeaf (headnode, point);
+	if (n->contents == CONTENTS_SOLID)
+		return false;
+	n->occupied = num;
+	return true;
+}
+
+
+/*
+==============
+MarkLeakTrail
+==============
+*/
+portal_t	*prevleaknode;
+FILE	*leakfile;
+void MarkLeakTrail (portal_t *n2)
+{
+	int			i;
+	vec3_t		p1, p2, dir;
+	vec_t		len;
+	portal_t	*n1;
+
+	n1 = prevleaknode;
+	prevleaknode = n2;
+
+	if (!n1)
+		return;
+
+	WindingCentre( n2->winding, p1, NULL );
+	WindingCentre( n1->winding, p2, NULL );
+
+	VectorSubtract (p2, p1, dir);
+	len = VectorLength (dir);
+	VectorNormalize (dir);
+
+	if (!leakfile)
+		leakfile = fopen (pointfilename, "w");
+	if (!leakfile)
+		Error ("Couldn't open %s\n", pointfilename);
+
+	while (len > 2)
+	{
+		fprintf (leakfile, "%f %f %f\n", p1[0], p1[1], p1[2]);
+		for (i = 0;i < 3;i++)
+			p1[i] += dir[i] * 2;
+		len -= 2;
+	}
+}
+
+/*
+==================
+RecursiveFillOutside
+
+If fill is false, just check, don't fill
+Returns true if an occupied leaf is reached
+==================
+*/
+int		hit_occupied;
+qboolean RecursiveFillOutside (node_t *l, int hullnum, qboolean fill)
+{
+	portal_t	*p;
+	int			s;
+
+	if (l->contents == CONTENTS_SOLID || l->contents == CONTENTS_SKY)
+		return false;
+
+	if (l->valid == valid)
+		return false;
+
+	if (l->occupied)
+	{
+		hit_occupied = l->occupied; // LordHavoc: this was missing from the released source... odd
+		return true;
+	}
+
+	l->valid = valid;
+
+// fill it and it's neighbors
+	if (fill)
+		l->contents = CONTENTS_SOLID;
+	outleafs++;
+
+	for (p = l->portals;p;)
+	{
+		s = (p->nodes[0] == l);
+
+		if (RecursiveFillOutside (p->nodes[s], hullnum, fill) )
+		{
+			// leaked, so stop filling
+			if (!hullnum)
+				MarkLeakTrail (p);
+			return true;
+		}
+		p = p->next[!s];
+	}
+
+	return false;
+}
+
+/*
+==================
+ClearOutFaces
+
+==================
+*/
+void ClearOutFaces (node_t *node)
+{
+	face_t	**fp;
+
+	if (node->planenum != PLANENUM_LEAF)
+	{
+		ClearOutFaces (node->children[0]);
+		ClearOutFaces (node->children[1]);
+		return;
+	}
+	if (node->contents != CONTENTS_SOLID)
+		return;
+
+	for( fp = node->markfaces; *fp; fp++ ) {
+		// mark all the original faces that are removed
+		FreeWinding( (*fp)->winding );
+		(*fp)->winding = NULL;
+	}
+	node->faces = NULL;
+}
+
+
+//=============================================================================
+
+/*
+===========
+FillOutside
+
+===========
+*/
+qboolean FillOutside (tree_t *tree, int hullnum)
+{
+	int			i;
+	int			s;
+	qboolean	inside;
+	vec3_t		origin;
+
+	qprintf ("----- FillOutside ----\n");
+
+	if (nofill)
+	{
+		printf ("skipped\n");
+		return false;
+	}
+
+	inside = false;
+	for (i=1 ; i<num_entities ; i++)
+	{
+		GetVectorForKey (&entities[i], "origin", origin);
+
+		if (DotProduct (origin, origin) >= 0.1)
+		{
+			if (PlaceOccupant (i, origin, tree->headnode))
+				inside = true;
+		}
+	}
+
+	if (!inside)
+	{
+		printf ("Hullnum %i: No entities in empty space -- no filling performed\n", hullnum);
+		return false;
+	}
+
+	s = !(outside_node.portals->nodes[1] == &outside_node);
+
+// first check to see if an occupied leaf is hit
+	outleafs = 0;
+	valid++;
+
+	prevleaknode = NULL;
+
+	if (RecursiveFillOutside (outside_node.portals->nodes[s], hullnum, false))
+	{
+		if (leakfile)
+			fclose(leakfile);
+		leakfile = NULL;
+		if (!hullnum)
+		{
+			GetVectorForKey (&entities[hit_occupied], "origin", origin);
+
+			qprintf ("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
+			qprintf ("reached occupant at: (%4.0f,%4.0f,%4.0f)\n", origin[0], origin[1], origin[2]);
+			qprintf ("no filling performed\n");
+			qprintf ("leak file written to %s\n", pointfilename);
+			qprintf ("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
+		}
+
+		// remove faces from filled in leafs
+		ClearOutFaces (tree->headnode);
+		return false;
+	}
+
+	// now go back and fill things in
+	valid++;
+	RecursiveFillOutside (outside_node.portals->nodes[s], hullnum, true);
+
+	// remove faces from filled in leafs
+	ClearOutFaces (tree->headnode);
+
+	qprintf ("%4i outleafs\n", outleafs);
+	return true;
+}
Index: hmap2/portals.c
diff -u /dev/null hmap2/portals.c:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/portals.c	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,523 @@
+
+#include "bsp5.h"
+
+
+node_t	outside_node;		// portals outside the world face this
+
+//=============================================================================
+
+/*
+===========
+AllocPortal
+===========
+*/
+portal_t *AllocPortal( void )
+{
+	portal_t	*p;
+
+	p = qmalloc( sizeof( portal_t ) );
+	memset( p, 0, sizeof( portal_t ) );
+
+	return p;
+}
+
+/*
+===========
+FreePortal
+===========
+*/
+void FreePortal( portal_t *p ) {
+	qfree( p );
+}
+
+//=============================================================================
+
+/*
+=============
+AddPortalToNodes
+=============
+*/
+void AddPortalToNodes (portal_t *p, node_t *front, node_t *back)
+{
+	if (p->nodes[0] || p->nodes[1])
+		Error ("AddPortalToNode: allready included");
+
+	p->nodes[0] = front;
+	p->next[0] = front->portals;
+	front->portals = p;
+
+	p->nodes[1] = back;
+	p->next[1] = back->portals;
+	back->portals = p;
+}
+
+
+/*
+=============
+RemovePortalFromNode
+=============
+*/
+void RemovePortalFromNode (portal_t *portal, node_t *l)
+{
+	portal_t	**pp, *t;
+
+	// remove reference to the current portal
+	pp = &l->portals;
+	while (1)
+	{
+		t = *pp;
+		if (!t)
+			Error ("RemovePortalFromNode: portal not in leaf");	
+
+		if ( t == portal )
+			break;
+
+		if (t->nodes[0] == l)
+			pp = &t->next[0];
+		else if (t->nodes[1] == l)
+			pp = &t->next[1];
+		else
+			Error ("RemovePortalFromNode: portal not bounding leaf");
+	}
+
+	if (portal->nodes[0] == l)
+	{
+		*pp = portal->next[0];
+		portal->nodes[0] = NULL;
+	}
+	else if (portal->nodes[1] == l)
+	{
+		*pp = portal->next[1];	
+		portal->nodes[1] = NULL;
+	}
+}
+
+//============================================================================
+
+/*
+================
+CalcNodeBounds
+
+Vic: proper node's bounding box calculation
+================
+*/
+void CalcNodeBounds (node_t *node)
+{
+	int			i;
+	portal_t	*p;
+	winding_t	*w;
+	int			side;
+
+	ClearBounds( node->mins, node->maxs );
+
+	for (p = node->portals ; p ; p = p->next[side])
+	{
+		if (p->nodes[0] == node)
+			side = 0;
+		else if (p->nodes[1] == node)
+			side = 1;
+		else
+		{
+			Error ("CalcNodeBounds: mislinked portal");
+			return; // hush compiler about side uninitialized
+		}
+
+		for (i = 0, w = p->winding; i < w->numpoints; i++)
+			AddPointToBounds( w->points[i], node->mins, node->maxs );
+	}
+}
+
+/*
+================
+MakeHeadnodePortals
+
+The created portals will face the global outside_node
+================
+*/
+void MakeHeadnodePortals (node_t *node, vec3_t mins, vec3_t maxs)
+{
+	vec3_t		bounds[2];
+	int			i, j, n;
+	portal_t	*p, *portals[6];
+	plane_t		bplanes[6], *pl;
+	int			side;
+
+	// pad with some space so there will never be null volume leafs
+	for (i=0 ; i<3 ; i++)
+	{
+		bounds[0][i] = mins[i] - SIDESPACE;
+		bounds[1][i] = maxs[i] + SIDESPACE;
+	}
+
+	outside_node.contents = CONTENTS_SOLID;
+	outside_node.portals = NULL;
+
+	for (i = 0;i < 3;i++)
+	{
+		for (j = 0;j < 2;j++)
+		{
+			n = j*3 + i;
+
+			p = AllocPortal ();
+			portals[n] = p;
+
+			pl = &bplanes[n];
+			memset (pl, 0, sizeof(*pl));
+			if (j)
+			{
+				pl->normal[i] = -1;
+				pl->dist = -bounds[j][i];
+			}
+			else
+			{
+				pl->normal[i] = 1;
+				pl->dist = bounds[j][i];
+			}
+			p->planenum = FindPlane (pl, &side);
+			p->winding = BaseWindingForPlane (pl);
+			if (side)
+				AddPortalToNodes (p, &outside_node, node);
+			else
+				AddPortalToNodes (p, node, &outside_node);
+		}
+	}
+
+	// clip the basewindings by all the other planes
+	for (i = 0;i < 6;i++)
+	{
+		for (j = 0;j < 6;j++)
+		{
+			if (j == i)
+				continue;
+			portals[i]->winding = ClipWinding (portals[i]->winding, &bplanes[j], true);
+		}
+	}
+}
+
+//============================================================================
+
+/*
+================
+CutNodePortals_r
+
+================
+*/
+void CutNodePortals_r (node_t *node)
+{
+	plane_t 	*plane, clipplane;
+	node_t		*f, *b, *other_node;
+	portal_t	*p, *new_portal, *next_portal;
+	winding_t	*w, *frontwinding, *backwinding;
+	int			side;
+
+	// Vic: properly calculate the bounding box
+	CalcNodeBounds (node);
+
+	//
+	// separate the portals on node into it's children	
+	//
+	if (node->contents)
+		return;			// at a leaf, no more dividing
+
+	plane = &mapplanes[node->planenum];
+
+	f = node->children[0];
+	b = node->children[1];
+
+	//
+	// create the new portal by taking the full plane winding for the cutting plane
+	// and clipping it by all of the planes from the other portals
+	//
+	w = BaseWindingForPlane (&mapplanes[node->planenum]);
+	side = 0;	// shut up compiler warning
+	for (p = node->portals ; p ; p = p->next[side])	
+	{
+		clipplane = mapplanes[p->planenum];
+		if (p->nodes[0] == node)
+			side = 0;
+		else if (p->nodes[1] == node)
+		{
+			clipplane.dist = -clipplane.dist;
+			VectorNegate (clipplane.normal, clipplane.normal);
+			side = 1;
+		}
+		else
+			Error ("CutNodePortals_r: mislinked portal");
+
+		w = ClipWinding (w, &clipplane, true);
+		if (!w)
+		{
+			printf ("WARNING: CutNodePortals_r:new portal was clipped away\n");
+			break;
+		}
+	}
+
+	if (w)
+	{
+		// if the plane was not clipped on all sides, there was an error
+		new_portal = AllocPortal ();
+		new_portal->planenum = node->planenum;
+		new_portal->winding = w;	
+		AddPortalToNodes (new_portal, f, b);
+	}
+
+	// partition the portals
+	for (p = node->portals ; p ; p = next_portal)	
+	{
+		if (p->nodes[0] == node)
+			side = 0;
+		else if (p->nodes[1] == node)
+			side = 1;
+		else
+			Error ("CutNodePortals_r: mislinked portal");
+		next_portal = p->next[side];
+
+		other_node = p->nodes[!side];
+		RemovePortalFromNode (p, p->nodes[0]);
+		RemovePortalFromNode (p, p->nodes[1]);
+
+		// cut the portal into two portals, one on each side of the cut plane
+		DivideWindingEpsilon( p->winding, plane, &frontwinding, &backwinding, ON_EPSILON );
+
+		if (!frontwinding)
+		{
+			if (side == 0)
+				AddPortalToNodes (p, b, other_node);
+			else
+				AddPortalToNodes (p, other_node, b);
+			continue;
+		}
+		if (!backwinding)
+		{
+			if (side == 0)
+				AddPortalToNodes (p, f, other_node);
+			else
+				AddPortalToNodes (p, other_node, f);
+			continue;
+		}
+
+		// the winding is split
+		new_portal = AllocPortal ();
+		*new_portal = *p;
+		new_portal->winding = backwinding;
+		FreeWinding (p->winding);
+		p->winding = frontwinding;
+
+		if (side == 0)
+		{
+			AddPortalToNodes (p, f, other_node);
+			AddPortalToNodes (new_portal, b, other_node);
+		}
+		else
+		{
+			AddPortalToNodes (p, other_node, f);
+			AddPortalToNodes (new_portal, other_node, b);
+		}
+	}
+
+	CutNodePortals_r (f);	
+	CutNodePortals_r (b);	
+}
+
+
+/*
+==================
+PortalizeTree
+
+Builds the exact polyhedrons for the nodes and leafs
+==================
+*/
+void PortalizeTree (tree_t *tree)
+{
+	qprintf ("----- portalize ----\n");
+
+	MakeHeadnodePortals (tree->headnode, tree->mins, tree->maxs);
+	CutNodePortals_r (tree->headnode);	
+}
+
+/*
+==================
+FreeNodePortals_r
+==================
+*/
+void FreeNodePortals_r (node_t *node)
+{
+	portal_t	*p, *nextp;
+
+	if (!node->contents)
+	{
+		FreeNodePortals_r (node->children[0]);
+		FreeNodePortals_r (node->children[1]);
+	}
+
+	for (p=node->portals ; p ; p=nextp)
+	{
+		if (p->nodes[0] == node)
+			nextp = p->next[0];
+		else
+			nextp = p->next[1];
+		RemovePortalFromNode (p, p->nodes[0]);
+		RemovePortalFromNode (p, p->nodes[1]);
+		FreeWinding (p->winding);
+		FreePortal (p);
+	}
+}
+
+/*
+==================
+FreeTreePortals
+==================
+*/
+void FreeTreePortals (tree_t *tree) {
+	FreeNodePortals_r( tree->headnode );
+}
+
+/*
+==============================================================================
+
+PORTAL FILE GENERATION
+
+==============================================================================
+*/
+
+#define	PORTALFILE	"PRT1"
+
+FILE	*pf;
+int		num_visleafs;				// leafs the player can be in
+int		num_visportals;
+
+qboolean transwater;
+
+// Vic: proper float output
+void WriteFloatToPortalFile (vec_t f)
+{
+	int i;
+
+	i = Q_rint( f );
+	if( f == i )
+		fprintf( pf, "%i", i );
+	else
+		fprintf( pf, "%f", f );
+}
+
+void WritePortalFile_r (node_t *node)
+{
+	int		i;	
+	portal_t	*p;
+	winding_t	*w;
+	plane_t		*pl, plane2;
+
+	if (!node->contents)
+	{
+		WritePortalFile_r (node->children[0]);
+		WritePortalFile_r (node->children[1]);
+		return;
+	}
+
+	if (node->contents == CONTENTS_SOLID)
+		return;
+
+	for (p = node->portals ; p ; )
+	{
+		w = p->winding;
+		// LordHavoc: transparent water support
+		if (w && p->nodes[0] == node && (p->nodes[0]->contents == p->nodes[1]->contents || (transwater && p->nodes[0]->contents != CONTENTS_SOLID && p->nodes[1]->contents != CONTENTS_SOLID && p->nodes[0]->contents != CONTENTS_SKY && p->nodes[1]->contents != CONTENTS_SKY)))
+		{
+			// write out to the file
+
+			// sometimes planes get turned around when they are very near
+			// the changeover point between different axis. interpret the
+			// plane the same way vis will, and flip the side orders if needed
+			pl = &mapplanes[p->planenum];
+			PlaneFromWinding (w, &plane2);
+			if ( DotProduct (pl->normal, plane2.normal) < 0.99 ) // backwards...
+				fprintf (pf,"%i %i %i ",w->numpoints, p->nodes[1]->visleafnum, p->nodes[0]->visleafnum);
+			else
+				fprintf (pf,"%i %i %i ",w->numpoints, p->nodes[0]->visleafnum, p->nodes[1]->visleafnum);
+
+			// Vic: proper float output
+			for (i=0 ; i<w->numpoints ; i++)
+			{
+				fprintf (pf, "(");
+				WriteFloatToPortalFile (w->points[i][0]);
+				fprintf (pf, " ");
+				WriteFloatToPortalFile (w->points[i][1]);
+				fprintf (pf, " ");
+				WriteFloatToPortalFile (w->points[i][2]);
+				fprintf (pf, ")");
+				fprintf (pf, i == w->numpoints-1 ? "\n" : " ");
+			}
+		}
+
+		if (p->nodes[0] == node)
+			p = p->next[0];
+		else
+			p = p->next[1];
+	}
+}
+
+/*
+================
+NumberLeafs_r
+================
+*/
+void NumberLeafs_r (node_t *node)
+{
+	portal_t	*p;
+
+	if (!node->contents)
+	{	// decision node
+		node->visleafnum = -99;
+		NumberLeafs_r (node->children[0]);
+		NumberLeafs_r (node->children[1]);
+		return;
+	}
+
+	if (node->contents == CONTENTS_SOLID)
+	{	// solid block, viewpoint never inside
+		node->visleafnum = -1;
+		return;
+	}
+
+	node->visleafnum = num_visleafs++;
+
+	for (p = node->portals ; p ; )
+	{
+		if (p->nodes[0] == node)		// only write out from first leaf
+		{
+			if (p->nodes[0]->contents == p->nodes[1]->contents || (transwater && p->nodes[0]->contents != CONTENTS_SOLID && p->nodes[1]->contents != CONTENTS_SOLID && p->nodes[0]->contents != CONTENTS_SKY && p->nodes[1]->contents != CONTENTS_SKY))
+				num_visportals++;
+			p = p->next[0];
+		}
+		else
+			p = p->next[1];		
+	}
+}
+
+
+/*
+================
+WritePortalfile
+================
+*/
+void WritePortalfile (tree_t *tree)
+{
+	// set the visleafnum field in every leaf and count the total number of portals
+	num_visleafs = 0;
+	num_visportals = 0;
+	NumberLeafs_r (tree->headnode);
+
+	// write the file
+	printf ("writing %s\n", portfilename);
+	pf = fopen (portfilename, "w");
+	if (!pf)
+		Error ("Error opening %s", portfilename);
+
+	fprintf (pf, "%s\n", PORTALFILE);
+	fprintf (pf, "%i\n", num_visleafs);
+	fprintf (pf, "%i\n", num_visportals);
+
+	WritePortalFile_r (tree->headnode);
+
+	fclose (pf);
+}
Index: hmap2/qbsp.c
diff -u /dev/null hmap2/qbsp.c:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/qbsp.c	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,313 @@
+// bsp5.c
+
+#include "bsp5.h"
+
+//
+// command line flags
+//
+qboolean nofill;
+qboolean notjunc;
+qboolean noclip;
+qboolean onlyents;
+qboolean verbose;
+qboolean allverbose;
+qboolean transwater;
+qboolean forcevis;
+
+int		subdivide_size;
+
+int		valid;
+
+char	bspfilename[1024];
+char	pointfilename[1024];
+char	portfilename[1024];
+
+int		Vis_Main( int argc, char **argv );
+int		Light_Main( int argc, char **argv );
+int		Bsp2Prt_Main( int argc, char **argv );
+
+//===========================================================================
+
+void qprintf( char *fmt, ... )
+{
+	va_list argptr;
+	extern qboolean verbose;
+
+	if( !verbose )
+		return;		// only print if verbose	
+
+	va_start( argptr, fmt );
+	vprintf( fmt, argptr );
+	va_end( argptr );
+}
+
+//===========================================================================
+
+
+/*
+===============
+ProcessEntity
+===============
+*/
+void ProcessEntity (int entnum, int modnum, int hullnum)
+{
+	entity_t	*ent;
+	tree_t		*tree;
+
+	ent = &entities[entnum];
+	if( !ent->brushes )
+		return;
+
+	tree = Tree_ProcessEntity( ent, modnum, hullnum );
+	EmitNodePlanes( tree->headnode );
+
+	if( hullnum != 0 ) {
+		EmitClipNodes( tree->headnode, modnum, hullnum );
+	} else {
+		EmitNodeFaces( tree->headnode );
+		EmitDrawNodes( tree->headnode );
+	}
+
+	FreeTree( tree );
+}
+
+/*
+=================
+UpdateEntLump
+=================
+*/
+void UpdateEntLump (void)
+{
+	int		m, entnum;
+	char	mod[80];
+
+	m = 1;
+	for (entnum = 1 ; entnum < num_entities ; entnum++)
+    {
+		if (!entities[entnum].brushes)
+			continue;
+		sprintf (mod, "*%i", m);
+		SetKeyValue (&entities[entnum], "model", mod);
+		m++;
+    }
+
+	UnparseEntities();
+}
+
+/*
+=================
+CreateSingleHull
+=================
+*/
+void CreateSingleHull ( int hullnum )
+{
+	int			entnum;
+	int			modnum;
+
+	// for each entity in the map file that has geometry
+	verbose = true;	// print world
+	for( entnum = 0, modnum = 0; entnum < num_entities; entnum++ ) {
+		if( !entities[entnum].brushes )
+			continue;
+
+		ProcessEntity( entnum, modnum++, hullnum );
+		if( !allverbose )
+			verbose = false;	// don't print rest of entities
+	}
+}
+
+/*
+=================
+CreateHulls
+=================
+*/
+void CreateHulls (void)
+{
+	// commanded to ignore the hulls altogether
+	if (noclip)
+    {
+		CreateSingleHull ( 0 );
+		return;
+    }
+
+	// create all the hulls
+
+	// create the hulls sequentially
+	printf ("building hulls sequentially...\n");
+
+	CreateSingleHull ( 0 );
+	CreateSingleHull ( 1 );
+	CreateSingleHull ( 2 );
+}
+
+/*
+=================
+ProcessFile
+=================
+*/
+void ProcessFile (char *sourcebase, char *bspfilename1)
+{
+	// create filenames
+	strcpy(bspfilename, bspfilename1);
+	strcpy(portfilename, bspfilename1);
+	strcpy(pointfilename, bspfilename1);
+	DefaultExtension(bspfilename, ".bsp");
+	ReplaceExtension(portfilename, ".prt");
+	ReplaceExtension(pointfilename, ".pts");
+
+	if (!onlyents)
+	{
+		remove (bspfilename);
+		remove (portfilename);
+		remove (pointfilename);
+	}
+
+	// load brushes and entities
+	LoadMapFile (sourcebase);
+	if (onlyents)
+	{
+		LoadBSPFile (bspfilename);
+		UpdateEntLump ();
+		WriteBSPFile (bspfilename, false);
+		return;
+	}
+
+	// init the tables to be shared by all models
+	BeginBSPFile ();
+
+	// the clipping hulls will be written out to text files by forked processes
+	CreateHulls ();
+
+	UpdateEntLump ();
+
+	WriteMiptex ();
+
+	FinishBSPFile ();
+}
+
+
+/*
+==================
+main
+
+==================
+*/
+int main (int argc, char **argv)
+{
+	int		i = 0;
+	double		start, end;
+	char		sourcename[1024];
+	char		destname[1024];
+
+	//	malloc_debug (15);
+
+	//
+	// check command line flags
+	//
+
+	nofill = false;
+	notjunc = false;
+	noclip = false;
+	onlyents = false;
+	verbose = true;
+	allverbose = false;
+	transwater = true;
+	forcevis = true;
+	subdivide_size = 240;
+
+	if( argc == 1 )
+		goto error;
+
+	if( !strcmp( argv[1], "-bsp2prt" ) )
+		return Bsp2Prt_Main( argc - 1, argv + 1 );
+	else if( !strcmp( argv[1], "-vis" ) )
+		return Vis_Main( argc - 1, argv + 1 );
+	else if( !strcmp( argv[1], "-light" ) )
+		return Light_Main( argc - 1, argv + 1 );
+
+	for (i=1 ; i<argc ; i++)
+	{
+		if (argv[i][0] != '-')
+			break;
+		else if (!strcmp (argv[i],"-nowater"))
+			transwater = false;
+		else if (!strcmp (argv[i],"-notjunc"))
+			notjunc = true;
+		else if (!strcmp (argv[i],"-nofill"))
+			nofill = true;
+		else if (!strcmp (argv[i],"-noclip"))
+			noclip = true;
+		else if (!strcmp (argv[i],"-onlyents"))
+			onlyents = true;
+		else if (!strcmp (argv[i],"-verbose"))
+			allverbose = true;
+		else if (!strcmp (argv[i],"-subdivide"))
+		{
+			subdivide_size = atoi(argv[i+1]);
+			i++;
+		}
+		else if (!strcmp (argv[i],"-darkplaces"))
+		{
+			// produce 256x256 texel lightmaps
+			subdivide_size = 4080;
+		}
+		else if (!strcmp (argv[i],"-noforcevis"))
+			forcevis = false;
+		else
+			Error ("qbsp: Unknown option '%s'", argv[i]);
+	}
+
+	if (i != argc - 2 && i != argc - 1)
+error:
+		Error ("%s",
+"usage: hqbsp [options] sourcefile [destfile]\n"
+"options:\n"
+"-bsp2prt    builds .prt file for .bsp, run -bsp2prt as the first parameter for more\n"
+"-light      lighting utility, run -light as the first parameter for more\n"
+"-vis        vis utility, run -vis as the first parameter for more\n"
+"-nowater    disable watervis; r_wateralpha in glquake will not work right\n"
+"-notjunc    disable tjunction fixing; glquake will have holes between polygons\n"
+"-nofill     disable sealing of map and vis, used for ammoboxes\n"
+"-onlyents   patchs entities in existing .bsp, for relighting\n"
+"-verbose    show more messages\n"
+"-darkplaces allow really big polygons\n"
+"-noforcevis don't make a .prt if the map leaks\n"
+"-fast       build the bsp tree only once, compiles a bit faster (experimental)\n"
+		);
+
+	// argv[i] and argv[arc-1] are the same if only one name is supplied
+	strcpy(sourcename, argv[i]);
+	strcpy(destname, argv[argc-1]);
+	DefaultExtension(sourcename, ".map");
+	DefaultExtension(destname, ".bsp");
+	if (!strcmp(sourcename, destname))
+		ReplaceExtension(destname, ".bsp");
+	printf("inputfile: %s\n", sourcename);
+	printf("outputfile: %s\n", destname);
+	if (!strcmp(sourcename, destname))
+		Error("sourcename == destname\n");
+
+	// init memory
+	Q_InitMem ();
+
+	//
+	// do it!
+	//
+	start = I_DoubleTime ();
+	ProcessFile (sourcename, destname);
+	end = I_DoubleTime ();
+	printf ("%5.1f seconds elapsed\n\n", end-start);
+
+	// print memory stats
+	Q_PrintMem ();
+
+#if _MSC_VER && _DEBUG
+	printf("press any key\n");
+	getchar();
+#endif
+
+	// free allocated memory
+	Q_ShutdownMem ();
+
+	return 0;
+}
Index: hmap2/relight.bat
diff -u /dev/null hmap2/relight.bat:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/relight.bat	Sun Feb 15 21:50:37 2004
@@ -0,0 +1 @@
+ at hmap -light -relight %1
Index: hmap2/revis.bat
diff -u /dev/null hmap2/revis.bat:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/revis.bat	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,2 @@
+ at hmap -bsp2prt %1
+ at hmap -vis -noreuse %1
Index: hmap2/scriptlib.c
diff -u /dev/null hmap2/scriptlib.c:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/scriptlib.c	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,93 @@
+#include "cmdlib.h"
+
+// LordHavoc: increased maximum token length from 128 to 1024
+#define	MAXTOKEN	1024
+
+char	token[MAXTOKEN];
+qboolean	unget;
+char	*script_p;
+int		scriptline;
+
+void StartTokenParsing (char *data)
+{
+	scriptline = 1;
+	script_p = data;
+	unget = false;
+}
+
+qboolean GetToken (qboolean crossline)
+{
+	char    *token_p;
+
+	if (unget)                         // is a token already waiting?
+		return true;
+
+	//
+	// skip space
+	//
+	skipspace:
+	while (*script_p <= 32)
+	{
+		if (!*script_p)
+		{
+			if (!crossline)
+				Error ("Line %i is incomplete",scriptline);
+			return false;
+		}
+		if (*script_p++ == '\n')
+		{
+			if (!crossline)
+				Error ("Line %i is incomplete",scriptline);
+			scriptline++;
+		}
+	}
+
+	if (script_p[0] == '/' && script_p[1] == '/')	// comment field
+	{
+		if (!crossline)
+			Error ("Line %i is incomplete\n",scriptline);
+		while (*script_p++ != '\n')
+			if (!*script_p)
+			{
+				if (!crossline)
+					Error ("Line %i is incomplete",scriptline);
+				return false;
+			}
+		goto skipspace;
+	}
+
+	//
+	// copy token
+	//
+	token_p = token;
+
+	if (*script_p == '"')
+	{
+		script_p++;
+		while ( *script_p != '"' )
+		{
+			if (!*script_p)
+				Error ("EOF inside quoted token");
+			*token_p++ = *script_p++;
+			if (token_p > &token[MAXTOKEN-1])
+				Error ("Token too large on line %i",scriptline);
+		}
+		script_p++;
+	}
+	else while ( *script_p > 32 )
+	{
+		*token_p++ = *script_p++;
+		if (token_p > &token[MAXTOKEN-1])
+			Error ("Token too large on line %i",scriptline);
+	}
+
+	*token_p = 0;
+
+	return true;
+}
+
+void UngetToken (void)
+{
+	unget = true;
+}
+
Index: hmap2/solidbsp.c
diff -u /dev/null hmap2/solidbsp.c:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/solidbsp.c	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,558 @@
+// solidbsp.c
+
+#include "bsp5.h"
+
+int		leaffaces;
+int		c_nodefaces;
+int		c_subdivides;
+int		splitnodes;
+
+int		c_solid, c_empty, c_water, c_slime, c_lava, c_sky;
+
+qboolean		usemidsplit;
+
+//============================================================================
+
+/*
+===========
+AllocNode
+===========
+*/
+node_t *AllocNode( void )
+{
+	node_t	*n;
+
+	n = qmalloc( sizeof( node_t ) );
+	memset( n, 0, sizeof( node_t ) );
+
+	return n;
+}
+
+/*
+===========
+FreeNode
+===========
+*/
+void FreeNode( node_t *n ) {
+	qfree( n );
+}
+
+//============================================================================
+
+/*
+==================
+ChooseMidPlaneFromList
+
+The clipping hull BSP doesn't worry about avoiding splits
+==================
+*/
+surface_t *ChooseMidPlaneFromList( surface_t *surfaces, vec3_t mins, vec3_t maxs )
+{
+	int			j, l;
+	surface_t	*p, *bestsurface;
+	vec_t		bestvalue, value, dist;
+	plane_t		*plane;
+	
+	// pick the plane that splits the least
+	bestvalue = 8*8192*8192;
+	bestsurface = NULL;
+
+	for( p = surfaces; p; p = p->next ) {
+		if( p->onnode )
+			continue;
+
+		plane = &mapplanes[p->planenum];
+
+		// check for axis aligned surfaces
+		l = plane->type;
+		if( l > PLANE_Z )
+			continue;
+
+		// calculate the split metric along axis l, smaller values are better
+		value = 0;
+		dist = plane->dist;
+
+		for( j = 0; j < 3; j++ ) {
+			if( j == l ) {
+				value += (maxs[l] - dist) * (maxs[l] - dist);
+				value += (dist - mins[l]) * (dist - mins[l]);
+				continue;
+			}
+			value += 2 * (maxs[j] - mins[j]) * (maxs[j] - mins[j]);
+		}
+
+		if( value < bestvalue ) { // currently the best!
+			bestvalue = value;
+			bestsurface = p;
+		}
+    }
+
+	if( !bestsurface ) {
+		for( p = surfaces; p; p = p->next ) {
+			if( !p->onnode )
+				return p;		// first valid surface
+		}
+		Error( "ChooseMidPlaneFromList: no valid planes" );
+    }
+
+	return bestsurface;
+}
+
+/*
+==================
+ChoosePlaneFromList
+
+The real BSP hueristic
+==================
+*/
+surface_t *ChoosePlaneFromList( surface_t *surfaces, vec3_t mins, vec3_t maxs )
+{
+	int			j, k, l;
+	surface_t	*p, *p2, *bestsurface;
+	vec_t		bestvalue, bestdistribution, value, dist;
+	plane_t		*plane;
+	face_t		*f;
+
+	// pick the plane that splits the least
+	bestvalue = 8*8192*8192;
+	bestsurface = NULL;
+	bestdistribution = 9e30;
+
+	for( p = surfaces; p; p = p->next ) {
+		if( p->onnode )
+			continue;
+
+		k = 0;
+		plane = &mapplanes[p->planenum];
+
+		for( p2 = surfaces; p2; p2 = p2->next ) {
+			if( p2 == p || p2->onnode )
+				continue;
+
+			for( f = p2->faces; f; f = f->next ) {
+				if( WindingOnPlaneSide( f->winding, plane ) == SIDE_CROSS ) {
+					if( ++k >= bestvalue )
+						break;
+				}
+			}
+			if( k > bestvalue )
+				break;
+		}
+
+		if( k > bestvalue )
+			continue;
+
+		// if equal numbers, axial planes win, then decide on spatial subdivision
+		if( k < bestvalue || (k == bestvalue && plane->type < PLANE_ANYX) ) {
+			// check for axis aligned surfaces
+			l = plane->type;
+
+			if( l <= PLANE_Z ) {	// axial aligned
+				// calculate the split metric along axis l
+				value = 0;
+				dist = plane->dist;
+
+				for( j = 0; j < 3; j++ ) {
+					if( j == l ) {
+						value += (maxs[l] - dist) * (maxs[l] - dist);
+						value += (dist - mins[l]) * (dist - mins[l]);
+						continue;
+					}
+					value += 2 * (maxs[j] - mins[j]) * (maxs[j] - mins[j]);
+				}
+
+				if( value > bestdistribution && k == bestvalue )
+					continue;
+				bestdistribution = value;
+			}
+
+			// currently the best!
+			bestvalue = k;
+			bestsurface = p;
+		}
+	}
+
+	return bestsurface;
+}
+
+
+/*
+==================
+SelectPartition
+
+Selects a surface from a linked list of surfaces to split the group on
+returns NULL if the surface list can not be divided any more (a leaf)
+==================
+*/
+surface_t *SelectPartition( surface_t *surfaces )
+{
+	int			i;
+	vec3_t		mins, maxs;
+	surface_t	*p, *bestsurface;
+
+	// calculate a bounding box of the entire surfaceset
+	ClearBounds( mins, maxs );
+
+	// count onnode surfaces
+	i = 0;
+	bestsurface = NULL;
+	for( p = surfaces; p; p = p->next ) {
+		AddPointToBounds( p->mins, mins, maxs );
+		AddPointToBounds( p->maxs, mins, maxs );
+
+		if( !p->onnode ) {
+			i++;
+			bestsurface = p;
+		}
+	}
+
+	if( !i )
+		return NULL;
+	else if( i == 1 )
+		return bestsurface;	// this is a final split
+
+	if( usemidsplit ) // do fast way for clipping hull
+		return ChooseMidPlaneFromList( surfaces, mins, maxs );
+
+	// do slow way to save poly splits for drawing hull
+	return ChoosePlaneFromList( surfaces, mins, maxs );
+}
+
+//============================================================================
+
+/*
+=================
+CalcSurfaceInfo
+
+Calculates the bounding box
+=================
+*/
+void CalcSurfaceInfo( surface_t *surf )
+{
+	int			i;
+	face_t		*f;
+	winding_t	*w;
+
+	if( !surf->faces )
+		Error( "CalcSurfaceInfo: surface without a face" );
+
+	// calculate a bounding box
+	ClearBounds( surf->mins, surf->maxs );
+
+	for( f = surf->faces; f; f = f->next ) {
+		if( f->contents[0] >= 0 || f->contents[1] >= 0 )
+			Error( "Bad contents" );
+
+		for( i = 0, w = f->winding; i < w->numpoints; i++ )
+			AddPointToBounds( w->points[i], surf->mins, surf->maxs );
+	}
+}
+
+/*
+==================
+DividePlane
+==================
+*/
+void DividePlane( surface_t *in, plane_t *split, surface_t **front, surface_t **back )
+{
+	face_t		*facet, *next;
+	face_t		*frontlist, *backlist;
+	face_t		*frontfrag, *backfrag;
+	surface_t	*news;
+	plane_t		*inplane;
+
+	inplane = &mapplanes[in->planenum];
+
+	// parallel case is easy
+	if( VectorCompare( inplane->normal, split->normal ) ) {
+		// check for exactly on node
+		if( inplane->dist == split->dist ) {	// divide the facets to the front and back sides
+			news = AllocSurface ();
+			*news = *in;
+
+			facet = in->faces;
+			in->faces = NULL;
+			news->faces = NULL;
+			in->onnode = news->onnode = true;
+
+			for( ; facet; facet = next ) {
+				next = facet->next;
+
+				if( facet->planeside ) {
+					facet->next = news->faces;
+					news->faces = facet;
+				} else {
+					facet->next = in->faces;
+					in->faces = facet;
+				}
+			}
+
+			if( in->faces )
+				*front = in;
+			else
+				*front = NULL;
+			if( news->faces )
+				*back = news;
+			else
+				*back = NULL;
+			return;
+		}
+
+		if( inplane->dist > split->dist ) {
+			*front = in;
+			*back = NULL;
+		} else {
+			*front = NULL;
+			*back = in;
+		}
+		return;
+	}
+
+	// do a real split.  may still end up entirely on one side
+	// OPTIMIZE: use bounding box for fast test
+	frontlist = NULL;
+	backlist = NULL;
+
+	for( facet = in->faces; facet; facet = next ) {
+		next = facet->next;
+		SplitFace( facet, split, &frontfrag, &backfrag );
+
+		if( frontfrag ) {
+			frontfrag->next = frontlist;
+			frontlist = frontfrag;
+		}
+		if( backfrag ) {
+			backfrag->next = backlist;
+			backlist = backfrag;
+		}
+	}
+
+	// if nothing actually got split, just move the in plane
+	if( frontlist == NULL ) {
+		*front = NULL;
+		*back = in;
+		in->faces = backlist;
+		return;
+	}
+
+	if( backlist == NULL ) {
+		*front = in;
+		*back = NULL;
+		in->faces = frontlist;
+		return;
+	}
+
+	// stuff got split, so allocate one new plane and reuse in
+	news = AllocSurface ();
+	*news = *in;
+	news->faces = backlist;
+	*back = news;
+
+	in->faces = frontlist;
+	*front = in;
+
+	// recalc bboxes and flags
+	CalcSurfaceInfo( news );
+	CalcSurfaceInfo( in );
+}
+
+/*
+==================
+LinkConvexFaces
+
+Determines the contents of the leaf and creates the final list of
+original faces that have some fragment inside this leaf
+==================
+*/
+void LinkConvexFaces( surface_t *planelist, node_t *leafnode )
+{
+	int			i, count;
+	face_t		*f, *next;
+	surface_t	*surf, *pnext;
+
+	leafnode->faces = NULL;
+	leafnode->contents = 0;
+	leafnode->planenum = -1;
+
+	count = 0;
+	for( surf = planelist; surf; surf = surf->next ) {
+		for( f = surf->faces; f; f = f->next ) {
+			count++;
+			if( !leafnode->contents )
+				leafnode->contents = f->contents[0];
+			else if( leafnode->contents != f->contents[0] )
+				Error ("LinkConvexFaces: Mixed face contents in leafnode");
+		}
+	}
+
+	if( !leafnode->contents )
+		leafnode->contents = CONTENTS_SOLID;
+
+	switch( leafnode->contents ) {
+		case CONTENTS_EMPTY:
+			c_empty++;
+			break;
+		case CONTENTS_SOLID:
+			c_solid++;
+			break;
+		case CONTENTS_WATER:
+			c_water++;
+			break;
+		case CONTENTS_SLIME:
+			c_slime++;
+			break;
+		case CONTENTS_LAVA:
+			c_lava++;
+			break;
+		case CONTENTS_SKY:
+			c_sky++;
+			break;
+		default:
+			Error ("LinkConvexFaces: bad contents number");
+	}
+
+	// write the list of faces, and free the originals
+	leaffaces += count;
+	leafnode->markfaces = qmalloc( sizeof(face_t *) * (count + 1) );
+	for( i = 0, surf = planelist; surf; surf = pnext ) {
+		pnext = surf->next;
+		for( f = surf->faces; f; f = next, i++ ) {
+			next = f->next;
+			leafnode->markfaces[i] = f->original;
+			FreeFace( f );
+		}
+		FreeSurface( surf );
+	}
+	leafnode->markfaces[i] = NULL;	// sentinel
+}
+
+/*
+==================
+LinkNodeFaces
+
+Returns a duplicated list of all faces on surface
+==================
+*/
+face_t *LinkNodeFaces( surface_t *surface )
+{
+	face_t	*f, *list, *newf, **prevptr;
+
+	list = NULL;
+
+	// subdivide
+	prevptr = &surface->faces;
+	while( 1 ) {
+		f = *prevptr;
+		if( !f )
+			break;
+		c_subdivides += SubdivideFace( f, prevptr );
+		f = *prevptr;
+		prevptr = &f->next;
+    }
+
+	// copy
+	for( f = surface->faces; f; f = f->next, c_nodefaces++ ) {
+		newf = NewFaceFromFace( f );
+		newf->winding = CopyWinding( f->winding );
+		f->original = newf;
+		newf->next = list;
+		list = newf;
+    }
+
+	return list;
+}
+
+/*
+==================
+PartitionSurfaces
+==================
+*/
+void PartitionSurfaces( surface_t *surfaces, node_t *node )
+{
+	surface_t	*split, *p, *next;
+	surface_t	*frontlist, *backlist;
+	surface_t	*frontfrag, *backfrag;
+	plane_t		*splitplane;
+	
+	split = SelectPartition (surfaces);
+	if( !split )  {	// this is a leaf node
+		node->planenum = PLANENUM_LEAF;
+		LinkConvexFaces( surfaces, node );
+		return;
+    }
+
+	splitnodes++;
+	node->faces = LinkNodeFaces( split );
+	node->children[0] = AllocNode ();
+	node->children[1] = AllocNode ();
+	node->planenum = split->planenum;
+
+	splitplane = &mapplanes[split->planenum];
+
+	// multiple surfaces, so split all the polysurfaces into front and back lists
+	frontlist = NULL;
+	backlist = NULL;
+
+	for( p = surfaces; p; p = next ) {
+		next = p->next;
+		DividePlane( p, splitplane, &frontfrag, &backfrag );
+
+		if( frontfrag ) {
+			if( !frontfrag->faces )
+				Error( "surface with no faces" );
+			frontfrag->next = frontlist;
+			frontlist = frontfrag;
+		}
+		if( backfrag ) {
+			if( !backfrag->faces )
+				Error ("surface with no faces");
+			backfrag->next = backlist;
+			backlist = backfrag;
+		}
+    }
+
+	PartitionSurfaces( frontlist, node->children[0] );
+	PartitionSurfaces( backlist, node->children[1] );
+}
+
+/*
+==================
+SolidBSP
+==================
+*/
+void SolidBSP( tree_t *tree, qboolean midsplit )
+{
+	int		i;
+	node_t	*headnode;
+
+	qprintf( "----- SolidBSP -----\n" );
+
+	headnode = AllocNode ();
+	usemidsplit = midsplit;
+
+	// calculate a bounding box for the entire model
+	for( i = 0; i < 3; i++ ) {
+		headnode->mins[i] = tree->mins[i] - SIDESPACE;
+		headnode->maxs[i] = tree->maxs[i] + SIDESPACE;
+    }
+
+	// recursively partition everything
+	splitnodes = 0;
+	leaffaces = 0;
+	c_nodefaces = 0;
+	c_solid = c_empty = c_water = c_slime = c_lava = c_sky = 0;
+	c_subdivides = 0;
+
+	PartitionSurfaces( tree->surfaces, tree->headnode = headnode );
+
+	qprintf( "%5i split nodes\n", splitnodes );
+	qprintf( "%5i solid leafs\n", c_solid );
+	qprintf( "%5i empty leafs\n", c_empty );
+	qprintf( "%5i water leafs\n", c_water );
+	qprintf( "%5i slime leafs\n", c_slime );
+	qprintf( "%5i lava  leafs\n", c_lava );
+	qprintf( "%5i sky   leafs\n", c_sky );
+	qprintf( "%5i leaffaces\n",leaffaces );
+	qprintf( "%5i nodefaces\n", c_nodefaces );
+	qprintf( "%5i subdivides\n", c_subdivides );
+}
Index: hmap2/tjunc.c
diff -u /dev/null hmap2/tjunc.c:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/tjunc.c	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,517 @@
+// tjunc.c
+
+#include "bsp5.h"
+
+
+typedef struct wvert_s
+{
+	vec_t	t;
+	struct wvert_s *prev, *next;
+} wvert_t;
+
+typedef struct wedge_s
+{
+	struct wedge_s *next;
+	vec3_t	dir;
+	vec3_t	origin;
+	wvert_t	head;
+} wedge_t;
+
+int		numwedges, numwverts;
+int		c_tjuncs;
+int		c_tjuncfaces;
+int		c_rotated;
+int		c_degenerateEdges;
+int		c_degenerateFaces;
+
+#define	MAX_WVERTS	0x40000
+#define	MAX_WEDGES	0x20000
+
+
+wvert_t	*wverts;
+wedge_t	*wedges;
+
+//============================================================================
+
+#define	NUM_HASH	1024
+
+wedge_t	*wedge_hash[NUM_HASH];
+
+static	vec3_t	hash_min, hash_scale;
+
+static	void InitHash (vec3_t mins, vec3_t maxs)
+{
+	vec3_t	size;
+	vec_t	volume;
+	vec_t	scale;
+	int		newsize[2];
+
+	VectorCopy (mins, hash_min);
+	VectorSubtract (maxs, mins, size);
+	memset (wedge_hash, 0, sizeof(wedge_hash));
+
+	volume = size[0]*size[1];
+
+	scale = sqrt(volume / NUM_HASH);
+
+	newsize[0] = size[0] / scale;
+	newsize[1] = size[1] / scale;
+
+	hash_scale[0] = newsize[0] / size[0];
+	hash_scale[1] = newsize[1] / size[1];
+	hash_scale[2] = newsize[1];
+}
+
+static	unsigned HashVec (vec3_t vec)
+{
+	unsigned	h;
+
+	h =	hash_scale[0] * (vec[0] - hash_min[0]) * hash_scale[2]
+		+ hash_scale[1] * (vec[1] - hash_min[1]);
+	if ( h >= NUM_HASH)
+		return NUM_HASH - 1;
+	return h;
+}
+
+//============================================================================
+
+// Vic: changed this to qboolean
+qboolean CanonicalVector (vec3_t vec)
+{
+	vec_t length;
+
+	length = VectorLength (vec);
+
+	// Vic: ignore degenerate edges
+	if (length < 0.1)
+		return false;
+
+	length = (vec_t)1.0 / length;
+	vec[0] *= length;
+	vec[1] *= length;
+	vec[2] *= length;
+
+	if (vec[0] > EQUAL_EPSILON)
+		return true;
+	else if (vec[0] < -EQUAL_EPSILON)
+	{
+		VectorNegate (vec, vec);
+		return true;
+	}
+	else
+		vec[0] = 0;
+
+	if (vec[1] > EQUAL_EPSILON)
+		return true;
+	else if (vec[1] < -EQUAL_EPSILON)
+	{
+		VectorNegate (vec, vec);
+		return true;
+	}
+	else
+		vec[1] = 0;
+
+	if (vec[2] > EQUAL_EPSILON)
+		return true;
+	else if (vec[2] < -EQUAL_EPSILON)
+	{
+		VectorNegate (vec, vec);
+		return true;
+	}
+	else
+		vec[2] = 0;
+
+	Error ("CanonicalVector: degenerate");
+	return false;
+}
+
+wedge_t	*FindEdge (vec3_t p1, vec3_t p2, vec_t *t1, vec_t *t2)
+{
+	vec3_t	origin;
+	vec3_t	dir;
+	wedge_t	*w;
+	vec_t	temp;
+	int		h;
+
+	VectorSubtract (p2, p1, dir);
+	
+	// ignore degenerate edges
+	if (!CanonicalVector (dir))
+	{
+		c_degenerateEdges++;
+		return NULL;
+	}
+
+	*t1 = DotProduct (p1, dir);
+	*t2 = DotProduct (p2, dir);
+
+	VectorMA (p1, -*t1, dir, origin);
+
+	if (*t1 > *t2)
+	{
+		temp = *t1;
+		*t1 = *t2;
+		*t2 = temp;
+	}
+
+	h = HashVec (origin);
+
+	for (w = wedge_hash[h] ; w ; w=w->next)
+	{
+		temp = w->origin[0] - origin[0];
+		if (temp < -EQUAL_EPSILON || temp > EQUAL_EPSILON)
+			continue;
+		temp = w->origin[1] - origin[1];
+		if (temp < -EQUAL_EPSILON || temp > EQUAL_EPSILON)
+			continue;
+		temp = w->origin[2] - origin[2];
+		if (temp < -EQUAL_EPSILON || temp > EQUAL_EPSILON)
+			continue;
+
+		temp = w->dir[0] - dir[0];
+		if (temp < -EQUAL_EPSILON || temp > EQUAL_EPSILON)
+			continue;
+		temp = w->dir[1] - dir[1];
+		if (temp < -EQUAL_EPSILON || temp > EQUAL_EPSILON)
+			continue;
+		temp = w->dir[2] - dir[2];
+		if (temp < -EQUAL_EPSILON || temp > EQUAL_EPSILON)
+			continue;
+
+		return w;
+	}
+
+	if (numwedges == MAX_WEDGES)
+		Error ("FindEdge: numwedges == MAX_WEDGES");
+	w = &wedges[numwedges];
+	numwedges++;
+
+	w->next = wedge_hash[h];
+	wedge_hash[h] = w;
+
+	VectorCopy (origin, w->origin);
+	VectorCopy (dir, w->dir);
+	w->head.next = w->head.prev = &w->head;
+	w->head.t = 99999;
+	return w;
+}
+
+
+/*
+===============
+AddVert
+
+===============
+*/
+#define	T_EPSILON	0.01
+
+void AddVert (wedge_t *w, vec_t t)
+{
+	wvert_t	*v, *newv;
+
+	v = w->head.next;
+	do
+	{
+		if (fabs(v->t - t) < T_EPSILON)
+			return;
+		if (v->t > t)
+			break;
+		v = v->next;
+	} while (1);
+
+// insert a new wvert before v
+	if (numwverts == MAX_WVERTS)
+		Error ("AddVert: numwverts == MAX_WVERTS");
+
+	newv = &wverts[numwverts];
+	numwverts++;
+
+	newv->t = t;
+	newv->next = v;
+	newv->prev = v->prev;
+	v->prev->next = newv;
+	v->prev = newv;
+}
+
+
+/*
+===============
+AddEdge
+
+===============
+*/
+void AddEdge (vec3_t p1, vec3_t p2)
+{
+	wedge_t	*w;
+	vec_t	t1, t2;
+
+	w = FindEdge(p1, p2, &t1, &t2);
+	if (w)
+	{
+		AddVert (w, t1);
+		AddVert (w, t2);
+	}
+}
+
+//============================================================================
+
+face_t	*newlist;
+
+#define MAX_VERTS_ON_FACE		32
+#define MAX_VERTS_ON_SUPERFACE	8192
+
+typedef struct
+{
+	int numpoints;
+	vec3_t points[MAX_VERTS_ON_SUPERFACE];
+	face_t original;
+} superface_t;
+
+superface_t superface;
+
+void FaceFromSuperface (face_t *original)
+{
+	int			i, numpts;
+	face_t		*newf, *chain;
+	vec3_t		dir, test;
+	vec_t		v;
+	int			firstcorner, lastcorner;
+
+	chain = NULL;
+	do
+	{
+		if( superface.numpoints < 3 ) {
+			c_degenerateFaces++;
+			return;
+		}
+
+		if (superface.numpoints <= MAX_VERTS_ON_FACE)
+		{	// the face is now small enough without more cutting
+			// so copy it back to the original
+			*original = superface.original;
+			original->winding = CopyWindingExt( superface.numpoints, superface.points );
+			original->original = chain;
+			original->next = newlist;
+			newlist = original;
+			return;
+		}
+
+		c_tjuncfaces++;
+
+restart:
+	// find the last corner
+		VectorSubtract (superface.points[superface.numpoints-1], superface.points[0], dir);
+		VectorNormalize (dir);
+		for (lastcorner=superface.numpoints-1 ; lastcorner > 0 ; lastcorner--)
+		{
+			VectorSubtract (superface.points[lastcorner-1], superface.points[lastcorner], test);
+			VectorNormalize (test);
+			v = DotProduct (test, dir);
+			if (v < 0.9999 || v > 1.00001)
+			{
+				break;
+			}
+		}
+
+	// find the first corner
+		VectorSubtract (superface.points[1], superface.points[0], dir);
+		VectorNormalize (dir);
+		for (firstcorner=1 ; firstcorner < superface.numpoints-1 ; firstcorner++)
+		{
+			VectorSubtract (superface.points[firstcorner+1], superface.points[firstcorner], test);
+			VectorNormalize (test);
+			v = DotProduct (test, dir);
+			if (v < 0.9999 || v > 1.00001)
+			{
+				break;
+			}
+		}
+
+		if (firstcorner+2 >= MAX_VERTS_ON_FACE)
+		{
+			c_rotated++;
+		// rotate the point winding
+			VectorCopy (superface.points[0], test);
+			for (i=1 ; i<superface.numpoints ; i++)
+			{
+				VectorCopy (superface.points[i], superface.points[i-1]);
+			}
+			VectorCopy (test, superface.points[superface.numpoints-1]);
+			goto restart;
+		}
+
+
+	// cut off as big a piece as possible, less than MAXPOINTS, and not
+	// past lastcorner
+		newf = NewFaceFromFace (&superface.original);
+		newf->original = chain;
+		chain = newf;
+		newf->next = newlist;
+		newlist = newf;
+
+		if (superface.numpoints - firstcorner <= MAX_VERTS_ON_FACE)
+			numpts = firstcorner + 2;
+		else if (lastcorner+2 < MAX_VERTS_ON_FACE && superface.numpoints - lastcorner <= MAX_VERTS_ON_FACE)
+			numpts = lastcorner + 2;
+		else
+			numpts = MAX_VERTS_ON_FACE;
+
+		if( numpts < 3 ) {
+			c_degenerateFaces++;
+			return;
+		}
+		newf->winding = CopyWindingExt( numpts, superface.points );
+
+		for (i=newf->winding->numpoints-1 ; i<superface.numpoints ; i++)
+			VectorCopy (superface.points[i], superface.points[i-(newf->winding->numpoints-2)]);
+		superface.numpoints -= (newf->winding->numpoints-2);
+	} while (1);
+}
+
+/*
+===============
+FixFaceEdges
+
+===============
+*/
+void FixFaceEdges (face_t *f)
+{
+	int		i, j, k;
+	wedge_t	*w;
+	wvert_t	*v;
+	vec_t	t1, t2;
+
+	if( f->winding->numpoints > MAX_VERTS_ON_SUPERFACE )
+		Error( "FixFaceEdges: f->winding->numpoints > MAX_VERTS_ON_SUPERFACE" );
+
+	superface.original = *f;
+	superface.numpoints = f->winding->numpoints;
+	memcpy( superface.points, f->winding->points, sizeof(vec3_t)*f->winding->numpoints );
+	FreeWinding( f->winding );
+
+	// LordHavoc: FIXME: rewrite this mess to find points on edges,
+	// rather than finding edges that run into polygons
+restart:
+	for (i=0 ; i < superface.numpoints ; i++)
+	{
+		j = (i+1)%superface.numpoints;
+
+		w = FindEdge (superface.points[i], superface.points[j], &t1, &t2);
+		if (!w)
+			continue;
+
+		for (v=w->head.next ; v->t < t1 + T_EPSILON ; v = v->next)
+		{
+		}
+
+		if (v->t < t2-T_EPSILON)
+		{
+			c_tjuncs++;
+		// insert a new vertex here
+			for (k = superface.numpoints ; k> j ; k--)
+				VectorCopy (superface.points[k-1], superface.points[k]);
+			VectorMA (w->origin, v->t, w->dir, superface.points[j]);
+			superface.numpoints++;
+			goto restart;
+		}
+	}
+
+	// the face might be split into multiple faces because of too many edges
+	FaceFromSuperface( f );
+}
+
+//============================================================================
+
+void tjunc_find_r (node_t *node)
+{
+	int			i, j;
+	face_t		*f;
+	winding_t	*w;
+
+	if (node->planenum == PLANENUM_LEAF)
+		return;
+
+	for (f=node->faces ; f ; f=f->next) {
+		w = f->winding;
+		for( i = 0, j = 1; i < w->numpoints; i++, j = (i + 1) % w->numpoints )
+			 AddEdge( w->points[i], w->points[j] );
+	}
+
+	tjunc_find_r (node->children[0]);
+	tjunc_find_r (node->children[1]);
+}
+
+void tjunc_fix_r (node_t *node)
+{
+	face_t	*f, *next;
+
+	if (node->planenum == PLANENUM_LEAF)
+		return;
+		
+	newlist = NULL;
+	
+	for (f=node->faces ; f ; f=next)
+	{
+		next = f->next;
+		FixFaceEdges (f);
+	}
+
+	node->faces = newlist;
+
+	tjunc_fix_r (node->children[0]);
+	tjunc_fix_r (node->children[1]);
+}
+
+/*
+===========
+FixTJunctions
+===========
+*/
+void FixTJunctions(tree_t *tree)
+{
+	vec_t	radius;
+	vec3_t	maxs, mins;
+
+	qprintf ("---- tjunc ----\n");
+	
+	if (notjunc)
+		return;
+
+//
+// identify all points on common edges
+//
+
+// origin points won't allways be inside the map, so extend the hash area
+	radius = RadiusFromBounds( tree->mins, tree->maxs );
+	VectorSet( maxs, radius, radius, radius );
+	VectorSet( mins, -radius, -radius, -radius );
+
+	InitHash (mins, maxs);
+
+	numwedges = numwverts = 0;
+	wverts = qmalloc( sizeof(*wverts) * MAX_WVERTS );
+	wedges = qmalloc( sizeof(*wverts) * MAX_WEDGES );
+
+	tjunc_find_r (tree->headnode);
+
+	qprintf ("%i world edges  %i edge points\n", numwedges, numwverts);
+
+//
+// add extra vertexes on edges where needed
+//
+	c_tjuncs = c_tjuncfaces = c_degenerateEdges = c_degenerateFaces = c_rotated = 0;
+
+	tjunc_fix_r (tree->headnode);
+
+	qfree( wverts );
+	qfree( wedges );
+
+	// Vic: report number of degenerate edges
+	qprintf ("%i degenerate edges\n", c_degenerateEdges);
+	qprintf ("%i degenerate faces\n", c_degenerateFaces);
+	qprintf ("%i edges added by tjunctions\n", c_tjuncs);
+	qprintf ("%i faces added by tjunctions\n", c_tjuncfaces);
+	qprintf ("%i naturally ordered\n", c_tjuncfaces - c_rotated);
+	qprintf ("%i rotated orders\n", c_rotated);
+}
+
Index: hmap2/tree.c
diff -u /dev/null hmap2/tree.c:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/tree.c	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,122 @@
+#include "bsp5.h"
+
+//============================================================================
+
+/*
+===========
+AllocTree
+===========
+*/
+tree_t *AllocTree( void )
+{
+	tree_t	*t;
+
+	t = qmalloc( sizeof( tree_t ) );
+	memset( t, 0, sizeof( tree_t ) );
+	ClearBounds( t->mins, t->maxs );
+
+	return t;
+}
+
+/*
+===========
+FreeTree
+===========
+*/
+void FreeTree( tree_t *t ) {
+	brush_t		*b, *next;
+
+	for( b = t->brushes; b; b = next ) {
+		next = b->next;
+		FreeBrush( b );
+	}
+	qfree( t );
+}
+
+//============================================================================
+
+/*
+===============
+Tree_ProcessEntity
+===============
+*/
+tree_t *Tree_ProcessEntity( entity_t *ent, int modnum, int hullnum )
+{
+	tree_t		*tree;
+	qboolean	worldmodel;
+
+	if( !strcmp( ValueForKey( ent, "classname" ), "worldspawn" ) ) {
+		worldmodel = true;
+	} else {
+		worldmodel = false;
+
+		if( verbose )
+			PrintEntity( ent );
+	}
+
+	// allocate a tree to hold our entity
+	tree = AllocTree ();
+
+	// take the brushes and clip off all overlapping and contained faces,
+	// leaving a perfect skin of the model with no hidden faces
+	Brush_LoadEntity( ent, tree, hullnum );
+	if( !tree->brushes ) {
+		PrintEntity( ent );
+		Error( "Entity with no valid brushes" );
+	}
+
+	CSGFaces( tree );
+
+	if( hullnum != 0 ) {
+		SolidBSP( tree, true );
+
+		// assume non-world bmodels are simple
+		if( worldmodel && !nofill ) {
+			PortalizeTree( tree );
+
+			if( FillOutside( tree, hullnum ) ) {
+				GatherTreeFaces( tree );
+				SolidBSP( tree, false );	// make a really good tree
+			}
+			FreeTreePortals( tree );
+		}
+	} else {
+		// if not the world, make a good tree first
+		// the world is just going to make a bad tree
+		// because the outside filling will force a regeneration later
+		SolidBSP( tree, worldmodel );	// SolidBSP generates a node tree
+
+		// build all the portals in the bsp tree
+		// some portals are solid polygons, and some are paths to other leafs
+
+		// assume non-world bmodels are simple
+		if( worldmodel && (!nofill || forcevis) ) {
+			PortalizeTree( tree );
+
+			if( FillOutside( tree, 0 ) || forcevis ) {
+				FreeTreePortals( tree );
+
+				// get the remaining faces together into surfaces again
+				GatherTreeFaces( tree );
+
+				// merge polygons
+				MergeTreeFaces( tree );
+
+				// make a really good tree
+				SolidBSP( tree, false );
+
+				// make the real portals for vis tracing
+				PortalizeTree( tree );
+
+				// save portal file for vis tracing
+				WritePortalfile( tree );
+
+				// fix tjunctions
+				FixTJunctions( tree );
+			}
+			FreeTreePortals( tree );
+		}
+	}
+
+	return tree;
+}
Index: hmap2/vis.c
diff -u /dev/null hmap2/vis.c:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/vis.c	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,728 @@
+// vis.c
+
+#include "vis.h"
+
+#define	MAX_THREADS		4
+
+int numportals;
+int portalleafs;
+
+portal_t *portals;
+leaf_t *leafs;
+
+int c_portaltest, c_portalpass, c_portalcheck;
+int c_reused;
+
+int c_cnt;
+
+// past visfile
+byte *vismap, *vismap_p, *vismap_end;
+int originalvismapsize;
+
+// [bitbytes*portalleafs]
+byte *uncompressed;
+
+// (portalleafs+63)>>3
+int bitbytes;
+int bitlongs;
+
+int numthreads = 4;
+
+qboolean fastvis;
+qboolean verbose;
+qboolean rvis;
+qboolean noreuse;
+
+// LordHavoc: default to level 4 vis
+int testlevel = 4;
+// LordHavoc: optional ambient sounds
+qboolean noambient, noambientwater, noambientslime, noambientlava, noambientsky;
+
+//=============================================================================
+
+/*
+=============
+GetNextPortal
+
+Returns the next portal for a thread to work on
+Returns the portals from the least complex, so the later ones can reuse
+the earlier information.
+=============
+*/
+portal_t *GetNextPortal (void)
+{
+	int		j;
+	portal_t	*p, *tp;
+	int		min;
+
+	min = 99999;
+	p = NULL;
+
+	for (j=0, tp = portals ; j<numportals*2 ; j++, tp++)
+	{
+		if (tp->nummightsee < min && tp->status == stat_none)
+		{
+			min = tp->nummightsee;
+			p = tp;
+		}
+	}
+
+
+	if (p)
+		p->status = stat_working;
+
+	return p;
+}
+
+long portalizestarttime, portalschecked;
+
+/*
+==============
+LeafThread
+==============
+*/
+void *LeafThread (int thread)
+{
+	time_t oldtime, newtime;
+	portal_t *p;
+
+//printf ("Begining LeafThread: %i\n",(int)thread);
+	oldtime = time(NULL);
+	do
+	{
+		p = GetNextPortal ();
+		if (!p)
+			break;
+
+		PortalFlow (p);
+
+		portalschecked++;
+		if (verbose)
+			printf ("portal %4i of %4i mightsee:%4i  cansee:%4i\n", (int) portalschecked, (int) numportals * 2, (int) p->nummightsee, (int) p->numcansee);
+		else
+		{
+			newtime = time(NULL);
+			if (newtime != oldtime)
+			{
+				printf("\rportal %4i of %4i (%3i%%), estimated time left: %i seconds", (int) portalschecked, (int) numportals * 2, (int) (portalschecked*100/(numportals*2)), (int) ((numportals*2-portalschecked)*(newtime-portalizestarttime)/portalschecked));
+				fflush(stdout);
+				oldtime = newtime;
+			}
+		}
+	} while (1);
+	printf("\n");
+
+//printf ("Completed LeafThread: %i\n",(int)thread);
+
+	return NULL;
+}
+
+/*
+===============
+LeafFlow
+
+Builds the entire visibility list for a leaf
+===============
+*/
+int		totalvis;
+
+void LeafFlow (int leafnum)
+{
+	leaf_t		*leaf;
+	byte		*outbuffer;
+	byte		compressed[MAX_MAP_LEAFS/8];
+	int			i, j;
+	int			numvis;
+	byte		*dest;
+	portal_t	*p;
+
+//
+// flow through all portals, collecting visible bits
+//
+	outbuffer = uncompressed + leafnum*bitbytes;
+	leaf = &leafs[leafnum];
+	for (i=0 ; i<leaf->numportals ; i++)
+	{
+		p = leaf->portals[i];
+		if (p->status != stat_done)
+			Error ("portal not done");
+		for (j=0 ; j<bitbytes ; j++)
+			outbuffer[j] |= p->visbits[j];
+	}
+
+	if (outbuffer[leafnum>>3] & (1<<(leafnum&7)))
+		Error ("Leaf portals saw into leaf");
+
+	outbuffer[leafnum>>3] |= (1<<(leafnum&7));
+
+	numvis = 0;
+	for (i=0 ; i<portalleafs ; i++)
+		if (outbuffer[i>>3] & (1<<(i&3)))
+			numvis++;
+
+//
+// compress the bit string
+//
+	if (verbose)
+		printf ("leaf %4i : %4i visible\n", leafnum, numvis);
+	totalvis += numvis;
+
+	if( !noreuse ) {	// Vic: reuse old vis data
+		int		i;
+		byte	*data;
+
+		data = uncompressed;
+		for( i = 0; i < leafnum; i++, data += bitbytes ) {
+			if( !memcmp( data, outbuffer, bitbytes ) ) {
+				c_reused++;
+				dleafs[leafnum+1].visofs = dleafs[i+1].visofs;
+				return;
+			}
+		}
+	}
+
+#if 0
+	i = bitbytes;
+	memcpy (compressed, outbuffer, bitbytes);
+#else
+	i = CompressVis (outbuffer, compressed, bitbytes);
+#endif
+
+
+	dest = vismap_p;
+	vismap_p += i;
+
+	if (vismap_p > vismap_end)
+		Error ("Vismap expansion overflow");
+
+	dleafs[leafnum+1].visofs = dest-vismap;	// leaf 0 is a common solid
+
+	memcpy (dest, compressed, i);
+}
+
+
+/*
+==================
+CalcPortalVis
+==================
+*/
+void CalcPortalVis (void)
+{
+	int		i;
+
+// fastvis just uses mightsee for a very loose bound
+	if (fastvis)
+	{
+		for (i=0 ; i<numportals*2 ; i++)
+		{
+			portals[i].visbits = portals[i].mightsee;
+			portals[i].status = stat_done;
+		}
+		return;
+	}
+
+	portalizestarttime = time(NULL);
+
+	LeafThread (0);
+
+	if (verbose)
+	{
+		printf ("portalcheck: %i  portaltest: %i  portalpass: %i\n",c_portalcheck, c_portaltest, c_portalpass);
+		printf ("c_vistest: %i  c_mighttest: %i\n",c_vistest, c_mighttest);
+	}
+}
+
+/*
+==================
+CalcVis
+==================
+*/
+void CalcVis (void)
+{
+	int		i;
+
+	BasePortalVis ();
+
+	CalcPortalVis ();
+
+//
+// assemble the leaf vis lists by oring and compressing the portal lists
+//
+	for (i=0 ; i<portalleafs ; i++)
+		LeafFlow (i);
+
+	printf ("average leafs visible: %i\n", totalvis / portalleafs);
+	fflush(stdout);
+}
+
+/*
+==============================================================================
+
+PASSAGE CALCULATION (not used yet...)
+
+==============================================================================
+*/
+
+int		count_sep;
+
+sep_t *Findpassages (viswinding_t *source, viswinding_t *pass)
+{
+	int			i, j, k, l;
+	plane_t		plane;
+	vec3_t		v1, v2;
+	double		d;
+	double		length;
+	int			counts[3];
+	qboolean	fliptest;
+	sep_t		*sep, *list;
+
+	list = NULL;
+
+// check all combinations
+	for (i=0 ; i<source->numpoints ; i++)
+	{
+		l = (i+1)%source->numpoints;
+		VectorSubtract (source->points[l] , source->points[i], v1);
+
+	// fing a vertex of pass that makes a plane that puts all of the
+	// vertexes of pass on the front side and all of the vertexes of
+	// source on the back side
+		for (j=0 ; j<pass->numpoints ; j++)
+		{
+			VectorSubtract (pass->points[j], source->points[i], v2);
+			CrossProduct(v1, v2, plane.normal);
+
+		// if points don't make a valid plane, skip it
+			length = DotProduct( plane.normal, plane.normal );
+
+			if (length < ON_EPSILON)
+				continue;
+
+			length = 1/sqrt(length);
+			VectorScale( plane.normal, length, plane.normal );
+			plane.dist = DotProduct (pass->points[j], plane.normal);
+
+		//
+		// find out which side of the generated seperating plane has the
+		// source portal
+		//
+			fliptest = false;
+			for (k=0 ; k<source->numpoints ; k++)
+			{
+				if (k == i || k == l)
+					continue;
+				d = DotProduct (source->points[k], plane.normal) - plane.dist;
+				if (d < -ON_EPSILON)
+				{	// source is on the negative side, so we want all
+					// pass and target on the positive side
+					fliptest = false;
+					break;
+				}
+				else if (d > ON_EPSILON)
+				{	// source is on the positive side, so we want all
+					// pass and target on the negative side
+					fliptest = true;
+					break;
+				}
+			}
+			if (k == source->numpoints)
+				continue;		// planar with source portal
+
+		//
+		// flip the normal if the source portal is backwards
+		//
+			if (fliptest)
+			{
+				VectorNegate (plane.normal, plane.normal);
+				plane.dist = -plane.dist;
+			}
+
+		//
+		// if all of the pass portal points are now on the positive side,
+		// this is the seperating plane
+		//
+			counts[0] = counts[1] = counts[2] = 0;
+			for (k=0 ; k<pass->numpoints ; k++)
+			{
+				if (k==j)
+					continue;
+				d = DotProduct (pass->points[k], plane.normal) - plane.dist;
+				if (d < -ON_EPSILON)
+					break;
+				else if (d > ON_EPSILON)
+					counts[0]++;
+				else
+					counts[2]++;
+			}
+			if (k != pass->numpoints)
+				continue;	// points on negative side, not a seperating plane
+
+			if (!counts[0])
+				continue;	// planar with pass portal
+
+		//
+		// save this out
+		//
+			count_sep++;
+
+			sep = qmalloc(sizeof(*sep));
+			sep->next = list;
+			list = sep;
+			sep->plane = plane;
+		}
+	}
+
+	return list;
+}
+
+
+
+/*
+============
+CalcPassages
+============
+*/
+void CalcPassages (void)
+{
+	int		i, j, k;
+	int		count, count2;
+	leaf_t	*l;
+	portal_t	*p1, *p2;
+	sep_t		*sep;
+	passage_t		*passages;
+
+	printf ("building passages...\n");
+
+	count = count2 = 0;
+	for (i=0 ; i<portalleafs ; i++)
+	{
+		l = &leafs[i];
+
+		for (j=0 ; j<l->numportals ; j++)
+		{
+			p1 = l->portals[j];
+			for (k=0 ; k<l->numportals ; k++)
+			{
+				if (k==j)
+					continue;
+
+				count++;
+				p2 = l->portals[k];
+
+			// definately can't see into a coplanar portal
+				if (PlaneCompare (&p1->plane, &p2->plane) )
+					continue;
+
+				count2++;
+
+				sep = Findpassages (p1->winding, p2->winding);
+				if (!sep)
+				{
+//					Error ("No seperating planes found in portal pair");
+					count_sep++;
+					sep = qmalloc(sizeof(*sep));
+					sep->next = NULL;
+					sep->plane = p1->plane;
+				}
+				passages = qmalloc(sizeof(*passages));
+				passages->planes = sep;
+				passages->from = p1->leaf;
+				passages->to = p2->leaf;
+				passages->next = l->passages;
+				l->passages = passages;
+			}
+		}
+	}
+
+	printf ("numpassages: %i (%i)\n", count2, count);
+	printf ("total passages: %i\n", count_sep);
+}
+
+//=============================================================================
+
+/*
+============
+LoadPortals
+============
+*/
+void LoadPortals (char *name)
+{
+	int			i, j;
+	portal_t	*p;
+	leaf_t		*l;
+	char		magic[80];
+	FILE		*f;
+	int			numpoints;
+	viswinding_t *w;
+	int			leafnums[2];
+	plane_t		plane;
+
+	if (!strcmp(name,"-"))
+		f = stdin;
+	else
+	{
+		f = fopen(name, "r");
+		if (!f)
+		{
+			printf ("LoadPortals: couldn't read %s\n",name);
+			printf ("No vising performed.\n");
+			exit (1);
+		}
+	}
+
+	if (fscanf (f,"%79s\n%i\n%i\n",magic, &portalleafs, &numportals) != 3)
+		Error ("LoadPortals: failed to read header");
+	if (strcmp(magic,PORTALFILE))
+		Error ("LoadPortals: not a portal file");
+
+	printf ("%4i portalleafs\n", portalleafs);
+	printf ("%4i numportals\n", numportals);
+
+	bitbytes = ((portalleafs+63)&~63)>>3;
+	bitlongs = bitbytes/sizeof(long);
+
+// each file portal is split into two memory portals
+	portals = qmalloc(2*numportals*sizeof(portal_t));
+	memset (portals, 0, 2*numportals*sizeof(portal_t));
+
+	leafs = qmalloc(portalleafs*sizeof(leaf_t));
+	memset (leafs, 0, portalleafs*sizeof(leaf_t));
+
+	originalvismapsize = portalleafs*((portalleafs+7)/8);
+
+	vismap = vismap_p = dvisdata;
+	vismap_end = vismap + MAX_MAP_VISIBILITY;
+
+	for (i=0, p=portals ; i<numportals ; i++)
+	{
+		if (fscanf (f, "%i %i %i ", &numpoints, &leafnums[0], &leafnums[1])
+			!= 3)
+			Error ("LoadPortals: reading portal %i", i);
+		if (numpoints > MAX_POINTS_ON_VISWINDING)
+			Error ("LoadPortals: portal %i has too many points", i);
+		if ((unsigned)leafnums[0] > (unsigned)portalleafs
+		 || (unsigned)leafnums[1] > (unsigned)portalleafs)
+			Error ("LoadPortals: reading portal %i", i);
+
+		w = p->winding = NewVisWinding (numpoints);
+		w->original = true;
+		w->numpoints = numpoints;
+
+		for (j=0 ; j<numpoints ; j++)
+		{
+			double	v[3];
+			int		k;
+
+			// scanf into double, then assign to vec_t
+			if (fscanf (f, "(%lf %lf %lf ) ", &v[0], &v[1], &v[2]) != 3)
+				Error ("LoadPortals: reading portal %i", i);
+			for (k=0 ; k<3 ; k++)
+				w->points[j][k] = v[k];
+		}
+		fscanf (f, "\n");
+
+	// calc plane
+		PlaneFromVisWinding (w, &plane);
+
+	// create forward portal
+		l = &leafs[leafnums[0]];
+		if (l->numportals == MAX_PORTALS_ON_LEAF)
+			Error ("Leaf with too many portals");
+		l->portals[l->numportals] = p;
+		l->numportals++;
+
+		p->winding = w;
+		VectorNegate (plane.normal, p->plane.normal);
+		p->plane.dist = -plane.dist;
+		if (p->plane.normal[0] == 1)
+			p->plane.type = PLANE_X;
+		else if (p->plane.normal[1] == 1)
+			p->plane.type = PLANE_Y;
+		else if (p->plane.normal[2] == 1)
+			p->plane.type = PLANE_Z;
+		else
+			p->plane.type = PLANE_ANYX;
+		p->leaf = leafnums[1];
+		p++;
+
+	// create backwards portal
+		l = &leafs[leafnums[1]];
+		if (l->numportals == MAX_PORTALS_ON_LEAF)
+			Error ("Leaf with too many portals");
+		l->portals[l->numportals] = p;
+		l->numportals++;
+
+		p->winding = w;
+		p->plane = plane;
+		if (p->plane.normal[0] == 1)
+			p->plane.type = PLANE_X;
+		else if (p->plane.normal[1] == 1)
+			p->plane.type = PLANE_Y;
+		else if (p->plane.normal[2] == 1)
+			p->plane.type = PLANE_Z;
+		else
+			p->plane.type = PLANE_ANYX;
+		p->leaf = leafnums[0];
+		p++;
+
+	}
+
+	fclose (f);
+}
+
+
+/*
+===========
+Vis_Main
+===========
+*/
+int Vis_Main( int argc, char **argv )
+{
+	int			i;
+	char		portalfile[1024];
+	char		source[1024];
+	double		start, end;
+
+	printf ("---- vis ----\n");
+
+	fastvis = false;
+	verbose = false;
+	rvis = true;
+	noambientslime = true;
+	noreuse = false;
+
+	for (i=1 ; i<argc ; i++)
+	{
+		if (!strcmp(argv[i], "-norvis"))
+		{
+			rvis = false;
+			printf ("rvis optimization disabled\n");
+		}
+		else if (!strcmp(argv[i],"-threads"))
+		{
+			numthreads = atoi (argv[i+1]);
+			i++;
+		}
+		else if (!strcmp(argv[i], "-fast"))
+		{
+			printf ("fastvis = true\n");
+			fastvis = true;
+		}
+		else if (!strcmp(argv[i], "-level"))
+		{
+			testlevel = atoi(argv[i+1]);
+			printf ("testlevel = %i\n", testlevel);
+			i++;
+		}
+		else if (!strcmp(argv[i], "-v"))
+		{
+			printf ("verbose = true\n");
+			verbose = true;
+		}
+		else if (!strcmp(argv[i], "-noreuse"))
+		{
+			printf ("vis rows reusage disabled\n");
+			noreuse = true;
+		}
+		else if (!strcmp(argv[i], "-noambient"))
+		{
+			noambient = true;
+			printf ("all ambient sounds disabled\n");
+		}
+		else if (!strcmp(argv[i], "-noambientwater"))
+		{
+			noambientwater = true;
+			printf ("ambient water sounds disabled\n");
+		}
+		else if (!strcmp(argv[i], "-ambientslime"))
+		{
+			noambientslime = false;
+			printf ("ambient slime sounds enabled\n");
+		}
+		else if (!strcmp(argv[i], "-noambientlava"))
+		{
+			noambientlava = true;
+			printf ("ambient lava sounds disabled\n");
+		}
+		else if (!strcmp(argv[i], "-noambientsky"))
+		{
+			noambientsky = true;
+			printf ("ambient sky sounds disabled\n");
+		}
+		else if (argv[i][0] == '-')
+			Error ("Unknown option \"%s\"", argv[i]);
+		else
+			break;
+	}
+
+	if (i != argc - 1)
+	{
+		Error ("%s",
+"usage: hvis [options] bspfile"
+"options:\n"
+"-level 0-4      quality, default 4\n"
+"-fast           fast but bad quality vis\n"
+"-v              verbose\n"
+"-norvis         disable rvis optimization, 0.001% better quality and 30% slower\n"
+"-ambientslime   enables ambient slime sounds (reserve a channel, do not default to water)\n"
+"-noambient      disable ambient sounds (water bubbling, wind, etc)\n"
+"-noambientwater disable ambient water sounds (water)\n"
+"-noambientsky   disable ambient sky sounds (wind)\n"
+"-noambientlava  disable ambient lava sounds (unused by quake)\n"
+		);
+	}
+
+	// init memory
+	Q_InitMem ();
+
+	start = I_DoubleTime ();
+
+	strcpy(source, argv[i]);
+	strcpy(portalfile, source);
+	DefaultExtension(source, ".bsp");
+	ReplaceExtension(portalfile, ".prt");
+
+	LoadBSPFile (source);
+	LoadPortals (portalfile);
+
+	uncompressed = qmalloc(bitbytes*portalleafs);
+	memset (uncompressed, 0, bitbytes*portalleafs);
+
+//	CalcPassages ();
+
+	CalcVis ();
+
+	printf ("row size: %i\n",bitbytes);
+	printf ("c_reused: %i\n",c_reused);
+	printf ("c_chains: %i\n",c_chains);
+
+	visdatasize = vismap_p - dvisdata;
+	printf ("reused bytes: %i\n",c_reused*bitbytes);
+	printf ("visdatasize:%i compressed from %i\n", visdatasize, originalvismapsize);
+
+	if (!noambient)
+		CalcAmbientSounds ();
+
+	WriteBSPFile (source, false);
+
+//	unlink (portalfile);
+
+	printf( "%i\n", c_cnt );
+
+	end = I_DoubleTime ();
+	printf ("%5.2f seconds elapsed\n\n", end-start);
+
+	// print memory stats
+	Q_PrintMem ();
+
+#if _MSC_VER && _DEBUG
+	printf("press any key\n");
+	getchar();
+#endif
+
+	// free allocated memory
+	Q_ShutdownMem ();
+
+	return 0;
+}
+
Index: hmap2/vis.h
diff -u /dev/null hmap2/vis.h:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/vis.h	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,114 @@
+// vis.h
+
+#include "cmdlib.h"
+#include "mathlib.h"
+#include "bspfile.h"
+#include "mem.h"
+
+#define	MAX_PORTALS	32768
+
+#define	PORTALFILE	"PRT1"
+
+#define	ON_EPSILON	0.1
+
+typedef struct
+{
+	qboolean	original;			// don't free, it's part of the portal
+	int			numpoints;
+	vec3_t		points[8];			// variable sized
+} viswinding_t;
+
+#define MAX_POINTS_ON_VISWINDING	64
+
+viswinding_t	*NewVisWinding (int points);
+void			FreeVisWinding (viswinding_t *w);
+viswinding_t	*ClipVisWinding (viswinding_t *in, plane_t *split, qboolean keepon);
+viswinding_t	*CopyVisWinding (viswinding_t *w);
+void			PlaneFromVisWinding (viswinding_t *w, plane_t *plane);
+void			VisWindingCentre( viswinding_t *w, vec3_t centre, vec_t *radius );
+
+
+typedef enum {stat_none, stat_working, stat_done} vstatus_t;
+typedef struct
+{
+	plane_t			plane;		// normal pointing into neighbor
+	int				leaf;		// neighbor
+	viswinding_t	*winding;
+	vstatus_t		status;
+	byte			*visbits;
+	byte			*mightsee;
+	int				nummightsee;
+	int				numcansee;
+} portal_t;
+
+typedef struct seperating_plane_s
+{
+	struct seperating_plane_s *next;
+	plane_t			plane;		// from portal is on positive side
+} sep_t;
+
+
+typedef struct passage_s
+{
+	struct passage_s	*next;
+	int				from, to;	// leaf numbers
+	sep_t			*planes;
+} passage_t;
+
+// LordHavoc: a friend ran into this limit (was 128)
+#define	MAX_PORTALS_ON_LEAF		512
+typedef struct leaf_s
+{
+	int				numportals;
+	passage_t		*passages;
+	portal_t		*portals[MAX_PORTALS_ON_LEAF];
+} leaf_t;
+
+	
+typedef struct pstack_s
+{
+	struct pstack_s	*next;
+	leaf_t			*leaf;
+	portal_t		*portal;		// portal exiting
+	viswinding_t	*source, *pass;
+	plane_t			portalplane;
+	byte			*mightsee;		// bit string
+} pstack_t;
+
+typedef struct
+{
+	byte			*leafvis;		// bit string
+	portal_t		*base;
+	pstack_t		pstack_head;
+} threaddata_t;
+
+extern	int			numportals;
+extern	int			portalleafs;
+
+extern	portal_t	*portals;
+extern	leaf_t		*leafs;
+
+extern	int			c_portaltest, c_portalpass, c_portalcheck;
+extern	int			c_portalskip, c_leafskip;
+extern	int			c_vistest, c_mighttest;
+extern	int			c_chains;
+extern	int			c_reused;
+
+extern	byte	*vismap, *vismap_p, *vismap_end;	// past visfile
+
+extern	int			testlevel;
+
+extern	qboolean	rvis;
+extern	qboolean	noreuse;
+extern	qboolean	noambientwater, noambientslime, noambientlava, noambientsky;
+
+extern	byte		*uncompressed;
+extern	int			bitbytes;
+extern	int			bitlongs;
+
+void LeafFlow (int leafnum);
+void BasePortalVis (void);
+
+void PortalFlow (portal_t *p);
+
+void CalcAmbientSounds (void);
Index: hmap2/vis_flow.c
diff -u /dev/null hmap2/vis_flow.c:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/vis_flow.c	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,697 @@
+#include "vis.h"
+
+int c_chains;
+int c_portalskip, c_leafskip;
+int c_vistest, c_mighttest;
+
+int active;
+
+//=============================================================================
+
+/*
+==================
+NewVisWinding
+==================
+*/
+viswinding_t *NewVisWinding (int points)
+{
+	viswinding_t	*w;
+	int			size;
+	
+	if (points > MAX_POINTS_ON_VISWINDING)
+		Error ("NewWinding: %i points", points);
+	
+	size = (int)((viswinding_t *)0)->points[points];
+	w = qmalloc (size);
+	memset (w, 0, size);
+	
+	return w;
+}
+
+/*
+==================
+FreeVisWinding
+==================
+*/
+void FreeVisWinding (viswinding_t *w)
+{
+	if (!w->original)
+		qfree (w);
+}
+
+/*
+==================
+CopyVisWinding
+==================
+*/
+viswinding_t *CopyVisWinding (viswinding_t *w)
+{
+	int			size;
+	viswinding_t	*c;
+
+	size = (int)((viswinding_t *)0)->points[w->numpoints];
+	c = qmalloc (size);
+	memcpy (c, w, size);
+	c->original = false;
+	return c;
+}
+
+/*
+==================
+ClipVisWinding
+
+Clips the winding to the plane, returning the new winding on the positive side
+Frees the input winding.
+If keepon is true, an exactly on-plane winding will be saved, otherwise
+it will be clipped away.
+==================
+*/
+viswinding_t *ClipVisWinding (viswinding_t *in, plane_t *split, qboolean keepon)
+{
+	int				i, j;
+	vec_t			dists[MAX_POINTS_ON_VISWINDING + 1];
+	int				sides[MAX_POINTS_ON_VISWINDING + 1];
+	int				counts[3];
+	vec_t			dot;
+	vec_t			*p1, *p2;
+	vec3_t			mid;
+	viswinding_t	*neww;
+	int				maxpts;
+
+	counts[0] = counts[1] = counts[2] = 0;
+
+// determine sides for each point
+	for (i=0 ; i<in->numpoints ; i++)
+	{
+		dists[i] = dot = DotProduct (in->points[i], split->normal) - split->dist;
+		if (dot > ON_EPSILON) sides[i] = SIDE_FRONT;
+		else if (dot < -ON_EPSILON) sides[i] = SIDE_BACK;
+		else sides[i] = SIDE_ON;
+		counts[sides[i]]++;
+	}
+	sides[i] = sides[0];
+	dists[i] = dists[0];
+	
+	if (keepon && !counts[0] && !counts[1])
+		return in;
+
+	if (!counts[0])
+	{
+		FreeVisWinding (in);
+		return NULL;
+	}
+	if (!counts[1])
+		return in;
+	
+	maxpts = in->numpoints+4;	// can't use counts[0]+2 because
+								// of fp grouping errors
+	neww = NewVisWinding (maxpts);
+		
+	for (i=0 ; i<in->numpoints ; i++)
+	{
+		p1 = in->points[i];
+		
+		if (sides[i] == SIDE_ON)
+		{
+			VectorCopy (p1, neww->points[neww->numpoints]);
+			neww->numpoints++;
+			continue;
+		}
+	
+		if (sides[i] == SIDE_FRONT)
+		{
+			VectorCopy (p1, neww->points[neww->numpoints]);
+			neww->numpoints++;
+		}
+		
+		if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i])
+			continue;
+			
+	// generate a split point
+		p2 = in->points[(i+1)%in->numpoints];
+		
+		dot = dists[i] / (dists[i]-dists[i+1]);
+		for (j=0 ; j<3 ; j++)
+		{	// avoid round off error when possible
+			if (split->normal[j] == 1)
+				mid[j] = split->dist;
+			else if (split->normal[j] == -1)
+				mid[j] = -split->dist;
+			else
+				mid[j] = p1[j] + dot*(p2[j]-p1[j]);
+		}
+			
+		VectorCopy (mid, neww->points[neww->numpoints]);
+		neww->numpoints++;
+	}
+	
+	if (neww->numpoints > maxpts)
+		Error ("ClipVisWinding: points exceeded estimate");
+		
+// free the original winding
+	FreeVisWinding (in);
+	
+	return neww;
+}
+
+void PlaneFromVisWinding (viswinding_t *w, plane_t *plane)
+{
+	vec3_t		v1, v2;
+
+// calc plane
+	VectorSubtract (w->points[2], w->points[1], v1);
+	VectorSubtract (w->points[0], w->points[1], v2);
+	CrossProduct (v2, v1, plane->normal);
+	VectorNormalize (plane->normal);
+	plane->dist = DotProduct (w->points[0], plane->normal);
+}
+
+/*
+=================
+VisWindingCentre
+=================
+*/
+void VisWindingCentre( viswinding_t *w, vec3_t centre, vec_t *radius )
+{
+	int			i;
+	vec3_t		edge;
+	vec_t		dist, best;
+
+	if( !w->numpoints )
+		Error( "WindingCentre: no points" );
+
+	VectorCopy( w->points[0], centre );
+	for( i = 1; i < w->numpoints; i++ )
+		VectorAdd( centre, w->points[i], centre );
+	VectorScale( centre, (vec_t)1.0/w->numpoints, centre );
+
+	if( radius ) {
+		best = 0;
+		for( i = 0; i < w->numpoints; i++ ) {
+			VectorSubtract( centre, w->points[i], edge );
+			dist = VectorLength( edge );
+			if( dist > best )
+				best = dist;
+		}
+
+	#ifdef PARANOID
+		if( best == 0 )
+			Error( "WindingCentre: winding has zero radius" );
+	#endif
+
+		*radius = best;
+	}
+}
+
+void CheckStack (leaf_t *leaf, threaddata_t *thread)
+{
+	pstack_t	*p;
+
+	for (p=thread->pstack_head.next ; p ; p=p->next)
+		if (p->leaf == leaf)
+			Error ("CheckStack: leaf recursion");
+}
+
+
+/*
+==============
+ClipToSeperators
+
+Source, pass, and target are an ordering of portals.
+
+Generates separating planes candidates by taking two points from source and one
+point from pass, and clips target by them.
+
+If target is totally clipped away, that portal can not be seen through.
+
+Normal clip keeps target on the same side as pass, which is correct if the
+order goes source, pass, target.  If the order goes pass, source, target then
+flipclip should be set.
+==============
+*/
+viswinding_t *ClipToSeperators (viswinding_t *source, viswinding_t *pass, viswinding_t *target, qboolean flipclip)
+{
+	int			i, j, k, l;
+	plane_t		plane;
+	vec3_t		v1, v2;
+	vec_t		d, epcompare;
+	vec_t		length;
+//	int			counts[3];
+	qboolean		fliptest;
+
+// check all combinations	
+	for (i=0 ; i<source->numpoints ; i++)
+	{
+		l = (i+1)%source->numpoints;
+		VectorSubtract (source->points[l] , source->points[i], v1);
+
+	// find a vertex of pass that makes a plane that puts all of the
+	// vertexes of pass on the front side and all of the vertexes of
+	// source on the back side
+		for (j=0 ; j<pass->numpoints ; j++)
+		{
+			VectorSubtract (pass->points[j], source->points[i], v2);
+			CrossProduct(v1, v2, plane.normal);
+
+		// if points don't make a valid plane, skip it
+			length = DotProduct(plane.normal, plane.normal);
+
+			if (length < ON_EPSILON)
+				continue;
+
+			length = 1/sqrt(length);
+			VectorScale( plane.normal, length, plane.normal );			
+			plane.dist = DotProduct (pass->points[j], plane.normal);
+
+		//
+		// find out which side of the generated seperating plane has the
+		// source portal
+		//
+			fliptest = false;
+			for (k=0 ; k<source->numpoints ; k++)
+			{
+				if (k == i || k == l)
+					continue;
+				d = DotProduct (source->points[k], plane.normal) - plane.dist;
+				if (d < -ON_EPSILON)
+				{	// source is on the negative side, so we want all
+					// pass and target on the positive side
+					fliptest = false;
+					break;
+				}
+				else if (d > ON_EPSILON)
+				{	// source is on the positive side, so we want all
+					// pass and target on the negative side
+					fliptest = true;
+					break;
+				}
+			}
+			if (k == source->numpoints)
+				continue;		// planar with source portal
+
+		//
+		// flip the normal if the source portal is backwards
+		//
+			if (fliptest)
+			{
+				VectorInverse (plane.normal);
+				plane.dist = -plane.dist;
+			}
+			
+		//
+		// if all of the pass portal points are now on the positive side,
+		// this is the seperating plane
+		//
+			// LordHavoc: rewrote this to be faster
+			for (k=0;k<j;k++)
+			{
+				d = DotProduct (pass->points[k], plane.normal) - plane.dist;
+				if (d < -ON_EPSILON) goto cliptoseperatorscontinue;
+				else if (d > ON_EPSILON) {epcompare = plane.dist - ON_EPSILON;goto cliptoseperatorsloop1;}
+			}
+			for (k=j+1;k<pass->numpoints;k++)
+			{
+				d = DotProduct (pass->points[k], plane.normal) - plane.dist;
+				if (d < -ON_EPSILON) goto cliptoseperatorscontinue;
+				else if (d > ON_EPSILON) {epcompare = plane.dist - ON_EPSILON;goto cliptoseperatorsloop2;}
+			}
+			continue;
+			for (;k<j;k++)
+			{
+				if (DotProduct(pass->points[k], plane.normal) < epcompare) goto cliptoseperatorscontinue;
+cliptoseperatorsloop1:
+				;
+			}
+			for (k=j+1;k<pass->numpoints;k++)
+			{
+				if (DotProduct(pass->points[k], plane.normal) < epcompare) goto cliptoseperatorscontinue;
+cliptoseperatorsloop2:
+				;
+			}
+			/*
+			// original code
+			counts[0] = counts[1] = counts[2] = 0;
+			for (k=0 ; k<pass->numpoints ; k++)
+			{
+				if (k==j)
+					continue;
+				d = DotProduct (pass->points[k], plane.normal) - plane.dist;
+				if (d < -ON_EPSILON)
+					break;
+				else if (d > ON_EPSILON)
+					counts[0]++;
+				else
+					counts[2]++;
+			}
+			if (k != pass->numpoints)
+				continue;	// points on negative side, not a seperating plane
+
+			if (!counts[0])
+			{
+				continue;	// planar with seperating plane
+			}
+			*/
+
+		//
+		// flip the normal if we want the back side
+		//
+			if (flipclip)
+			{
+				VectorInverse(plane.normal);
+				plane.dist = -plane.dist;
+			}
+
+		//
+		// clip target by the seperating plane
+		//
+			target = ClipVisWinding (target, &plane, false);
+			if (!target)
+				return NULL;		// target is not visible
+
+			if (rvis)
+				break;
+cliptoseperatorscontinue:
+			;
+		}
+	}
+
+	return target;
+}
+
+
+
+/*
+==================
+RecursiveLeafFlow
+
+Flood fill through the leafs
+If src_portal is NULL, this is the originating leaf
+==================
+*/
+void RecursiveLeafFlow (int leafnum, threaddata_t *thread, pstack_t *prevstack)
+{
+	pstack_t	stack;
+	portal_t	*p;
+	plane_t		backplane;
+	viswinding_t *source, *target;
+	leaf_t 		*leaf;
+	int			i, j;
+	long		*test, *might, *vis;
+	qboolean		more;
+	
+	c_chains++;
+
+	leaf = &leafs[leafnum];
+	CheckStack (leaf, thread);
+	
+// mark the leaf as visible
+	if (! (thread->leafvis[leafnum>>3] & (1<<(leafnum&7)) ) )
+	{
+		thread->leafvis[leafnum>>3] |= 1<<(leafnum&7);
+		thread->base->numcansee++;
+	}
+	
+	prevstack->next = &stack;
+	stack.next = NULL;
+	stack.leaf = leaf;
+	stack.portal = NULL;
+	stack.mightsee = qmalloc(bitbytes);
+	might = (long *)stack.mightsee;
+	vis = (long *)thread->leafvis;
+	
+// check all portals for flowing into other leafs	
+	for (i=0 ; i<leaf->numportals ; i++)
+	{
+		p = leaf->portals[i];
+
+		if ( ! (prevstack->mightsee[p->leaf>>3] & (1<<(p->leaf&7)) ) )
+		{
+			c_leafskip++;
+			continue;	// can't possibly see it
+		}
+
+	// if the portal can't see anything we haven't allready seen, skip it
+		if (p->status == stat_done)
+		{
+			c_vistest++;
+			test = (long *)p->visbits;
+		}
+		else
+		{
+			c_mighttest++;
+			test = (long *)p->mightsee;
+		}
+		more = false;
+		for (j=0 ; j<bitlongs ; j++)
+		{
+			might[j] = ((long *)prevstack->mightsee)[j] & test[j];
+			if (might[j] & ~vis[j])
+				more = true;
+		}
+		
+		if (!more)
+		{	// can't see anything new
+			c_portalskip++;
+			continue;
+		}
+		
+// get plane of portal, point normal into the neighbor leaf
+		stack.portalplane = p->plane;
+		VectorNegate (p->plane.normal, backplane.normal);
+		backplane.dist = -p->plane.dist;
+			
+		if (VectorCompare (prevstack->portalplane.normal, backplane.normal) )
+			continue;	// can't go out a coplanar face
+	
+		c_portalcheck++;
+		
+		stack.portal = p;
+		stack.next = NULL;
+		
+		target = ClipVisWinding (p->winding, &thread->pstack_head.portalplane, false);
+		if (!target)
+			continue;
+			
+		if (!prevstack->pass)
+		{	// the second leaf can only be blocked if coplanar
+		
+			stack.source = prevstack->source;
+			stack.pass = target;
+			RecursiveLeafFlow (p->leaf, thread, &stack);
+			FreeVisWinding (target);
+			continue;
+		}
+
+		target = ClipVisWinding (target, &prevstack->portalplane, false);
+		if (!target)
+			continue;
+		
+		source = CopyVisWinding (prevstack->source);
+
+		source = ClipVisWinding (source, &backplane, false);
+		if (!source)
+		{
+			FreeVisWinding (target);
+			continue;
+		}
+
+		c_portaltest++;
+
+		if (testlevel > 0)
+		{
+			target = ClipToSeperators (source, prevstack->pass, target, false);
+			if (!target)
+			{
+				FreeVisWinding (source);
+				continue;
+			}
+		}
+		
+		if (testlevel > 1)
+		{
+			target = ClipToSeperators (prevstack->pass, source, target, true);
+			if (!target)
+			{
+				FreeVisWinding (source);
+				continue;
+			}
+		}
+		
+		if (testlevel > 2)
+		{
+			source = ClipToSeperators (target, prevstack->pass, source, false);
+			if (!source)
+			{
+				FreeVisWinding (target);
+				continue;
+			}
+		}
+		
+		if (testlevel > 3)
+		{
+			source = ClipToSeperators (prevstack->pass, target, source, true);
+			if (!source)
+			{
+				FreeVisWinding (target);
+				continue;
+			}
+		}
+
+		stack.source = source;
+		stack.pass = target;
+
+		c_portalpass++;
+	
+	// flow through it for real
+		RecursiveLeafFlow (p->leaf, thread, &stack);
+		
+		FreeVisWinding (source);
+		FreeVisWinding (target);
+	}
+	
+	qfree (stack.mightsee);
+}
+
+
+/*
+===============
+PortalFlow
+
+===============
+*/
+void PortalFlow (portal_t *p)
+{
+	threaddata_t	data;
+
+	if (p->status != stat_working)
+		Error ("PortalFlow: reflowed");
+	p->status = stat_working;
+	
+	p->visbits = qmalloc (bitbytes);
+	memset (p->visbits, 0, bitbytes);
+
+	memset (&data, 0, sizeof(data));
+	data.leafvis = p->visbits;
+	data.base = p;
+	
+	data.pstack_head.portal = p;
+	data.pstack_head.source = p->winding;
+	data.pstack_head.portalplane = p->plane;
+	data.pstack_head.mightsee = p->mightsee;
+		
+	RecursiveLeafFlow (p->leaf, &data, &data.pstack_head);
+
+	p->status = stat_done;
+}
+
+
+/*
+===============================================================================
+
+This is a rough first-order aproximation that is used to trivially reject some
+of the final calculations.
+
+===============================================================================
+*/
+
+byte	portalsee[MAX_PORTALS];
+int		c_leafsee, c_portalsee;
+
+void SimpleFlood (portal_t *srcportal, int leafnum)
+{
+	int		i;
+	leaf_t	*leaf;
+	portal_t	*p;
+	
+	if (srcportal->mightsee[leafnum>>3] & (1<<(leafnum&7)) )
+		return;
+	srcportal->mightsee[leafnum>>3] |= (1<<(leafnum&7));
+	c_leafsee++;
+	
+	leaf = &leafs[leafnum];
+	
+	for (i=0 ; i<leaf->numportals ; i++)
+	{
+		p = leaf->portals[i];
+		if ( !portalsee[ p - portals ] )
+			continue;
+		SimpleFlood (srcportal, p->leaf);
+	}
+}
+
+
+/*
+==============
+BasePortalVis
+==============
+*/
+void BasePortalVis (void)
+{
+	int i, j, k;
+	portal_t *tp, *p;
+	plane_t *plane;
+	vec_t d;
+	viswinding_t *w;
+	time_t oldtime, newtime;
+
+	oldtime = time(NULL);
+	for (i=0, p = portals ; i<numportals*2 ; i++, p++)
+	{
+		p->mightsee = qmalloc (bitbytes);
+		memset (p->mightsee, 0, bitbytes);
+		
+		c_portalsee = 0;
+		memset (portalsee, 0, numportals*2);
+
+		for (j=0, tp = portals ; j<numportals*2 ; j++, tp++)
+		{
+			if (j == i)
+				continue;
+			w = tp->winding;
+			plane = &p->plane;
+			for (k=0 ; k<w->numpoints ; k++)
+			{
+				if (plane->type < 3)
+					d = w->points[k][plane->type] - plane->dist;
+				else
+					d = DotProduct (w->points[k], plane->normal) - plane->dist;
+				if (d > ON_EPSILON)
+					break;
+			}
+			if (k == w->numpoints)
+				continue;	// no points on front
+
+			w = p->winding;
+			plane = &tp->plane;
+			for (k=0 ; k<w->numpoints ; k++)
+			{
+				if (plane->type < 3)
+					d = w->points[k][plane->type] - plane->dist;
+				else
+					d = DotProduct (w->points[k], plane->normal) - plane->dist;
+				if (d < -ON_EPSILON)
+					break;
+			}
+			if (k == w->numpoints)
+				continue;	// no points on front
+
+			portalsee[j] = 1;		
+			c_portalsee++;
+			
+		}
+		
+		c_leafsee = 0;
+		SimpleFlood (p, p->leaf);
+		p->nummightsee = c_leafsee;
+//		printf ("portal:%4i  c_leafsee:%4i \n", i, c_leafsee);
+		newtime = time(NULL);
+		if (newtime != oldtime)
+		{
+			printf ("\rbasevis: portal%5i of%5i", i, numportals*2);
+			fflush(stdout);
+			newtime = oldtime;
+		}
+	}
+	printf ("\rbasevis done                \n");
+	fflush(stdout);
+}
Index: hmap2/vis_sound.c
diff -u /dev/null hmap2/vis_sound.c:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/vis_sound.c	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,177 @@
+
+#include "vis.h"
+
+/*
+
+Some textures (sky, water, slime, lava) are considered ambient sound emiters.
+Find an aproximate distance to the nearest emiter of each class for each leaf.
+
+*/
+
+/*
+====================
+SurfaceBBox
+====================
+*/
+void SurfaceBBox (dface_t *s, vec3_t mins, vec3_t maxs)
+{
+	int		i;
+	int		e;
+	int		vi;
+	vec3_t	v;
+
+	ClearBounds( mins, maxs );
+
+	// Vic: old buggy code
+//	mins[0] = mins[1] = BOGUS_RANGE;
+//	maxs[0] = maxs[1] = -BOGUS_RANGE;
+
+	for (i=0 ; i<s->numedges ; i++)
+	{
+		e = dsurfedges[s->firstedge+i];
+		if (e >= 0)
+			vi = dedges[e].v[0];
+		else
+			vi = dedges[-e].v[1];
+
+		VectorCopy( dvertexes[vi].point, v );
+		AddPointToBounds( v, mins, maxs );
+	}
+}
+
+/*
+====================
+CalcAmbientSounds
+====================
+*/
+void CalcAmbientSounds (void)
+{
+	int		i, j, k, l;
+	dleaf_t	*leaf, *hit;
+	byte	*vis;
+	dface_t	*surf;
+	vec3_t	mins, maxs;
+	vec_t	d, maxd;
+	int		ambient_type;
+	texinfo_t	*info;
+	miptex_t	*miptex;
+	int		ofs;
+	vec_t	dists[NUM_AMBIENTS];
+	vec_t	vol;
+
+	printf ("---- ambient ----\n");
+
+	if (noambientslime)
+		printf ("defaulting slime to water\n");
+
+	for (i=0 ; i< portalleafs ; i++)
+	{
+		leaf = &dleafs[i+1];
+
+	//
+	// clear ambients
+	//
+		for (j=0 ; j<NUM_AMBIENTS ; j++)
+			dists[j] = BOGUS_RANGE;
+
+		vis = &uncompressed[i*bitbytes];
+
+		for (j=0 ; j< portalleafs ; j++)
+		{
+			if ( !(vis[j>>3] & (1<<(j&7))) )
+				continue;
+
+		//
+		// check this leaf for sound textures
+		//	
+			hit = &dleafs[j+1];
+
+			for (k=0 ; k< hit->nummarksurfaces ; k++)
+			{
+				surf = &dfaces[dmarksurfaces[hit->firstmarksurface + k]];
+				info = &texinfo[surf->texinfo];
+				ofs = ((dmiptexlump_t *)dtexdata)->dataofs[info->miptex];
+				miptex = (miptex_t *)(&dtexdata[ofs]);
+
+				// LordHavoc: optimized this to reduce wasted Q_strncasecmp calls
+				if ((miptex->name[0] == 's' || miptex->name[0] == 'S') && (miptex->name[1] == 'k' || miptex->name[1] == 'K') && (miptex->name[2] == 'y' || miptex->name[2] == 'Y'))
+				{
+					if (noambientsky)
+						continue;
+					ambient_type = AMBIENT_SKY;
+				}
+				else if (miptex->name[0] == '*')
+				{
+					if ( !Q_strncasecmp (miptex->name, "*water", 6) )
+					{
+						if (noambientwater)
+							continue;
+						ambient_type = AMBIENT_WATER;
+					}
+					else if ( !Q_strncasecmp (miptex->name, "*slime", 6) )
+					{
+						if (noambientwater)
+							continue;
+						if (noambientslime)
+							ambient_type = AMBIENT_WATER;
+						else
+							ambient_type = AMBIENT_SLIME;
+					}
+					else if ( !Q_strncasecmp (miptex->name, "*lava", 6) )
+					{
+						if (noambientlava)
+							continue;
+						ambient_type = AMBIENT_LAVA;
+					}
+					else if ( !Q_strncasecmp (miptex->name, "*04water", 8) )
+					{
+						if (noambientwater)
+							continue;
+						ambient_type = AMBIENT_WATER;
+					}
+					else
+						continue;
+				}
+				else
+					continue;
+
+			// find distance from source leaf to polygon
+				SurfaceBBox (surf, mins, maxs);
+				maxd = 0;
+				for (l=0 ; l<3 ; l++)
+				{
+					if (mins[l] > leaf->maxs[l])
+						d = mins[l] - leaf->maxs[l];
+					else if (maxs[l] < leaf->mins[l])
+						d = leaf->mins[l] - mins[l];
+					else
+						d = 0;
+					if (d > maxd)
+						maxd = d;
+				}
+
+				// Vic: old buggy code
+//				maxd = 0.25;
+				if (maxd < dists[ambient_type])
+					dists[ambient_type] = maxd;
+			}
+		}
+
+		for (j=0 ; j<NUM_AMBIENTS ; j++)
+		{
+			if (dists[j] < 100)
+				vol = 1.0;
+			else
+			{
+				// Vic: old buggy code
+//				vol = 1.0 - dists[2]*0.002;
+				vol = 1.0 - dists[j]*0.002;
+				if (vol < 0)
+					vol = 0;
+				else if (vol > 1.0)
+					vol = 1.0;		// Vic
+			}
+			leaf->ambient_level[j] = vol*255;
+		}
+	}
+}
Index: hmap2/wad.c
diff -u /dev/null hmap2/wad.c:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/wad.c	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,329 @@
+
+#include "bsp5.h"
+
+typedef struct
+{
+	char	identification[4]; // "WAD2"
+	int		numlumps;
+	int		infotableofs;
+}
+wadinfo_t;
+
+typedef struct
+{
+	int		filepos;
+	int		disksize;
+	int		size; // uncompressed
+	char	type;
+	char	compression;
+	char	pad1, pad2;
+	char	name[16]; // must be null terminated
+}
+lumpinfo_t;
+
+#define	TYP_NONE		0
+#define	TYP_LABEL		1
+
+#define	TYP_LUMPY		64				// 64 + grab command number
+#define	TYP_PALETTE		64
+#define	TYP_QTEX		65
+#define	TYP_QPIC		66
+#define	TYP_SOUND		67
+#define	TYP_MIPTEX		68
+
+typedef struct
+{
+	FILE *file;
+	int filepos;
+	int size;
+	char name[16];
+}
+miptexfile_t;
+
+#define MAX_MIPTEXFILES 32768
+
+miptexfile_t miptexfile[MAX_MIPTEXFILES];
+int nummiptexfiles = 0;
+
+void CleanupName (char *in, char *out)
+{
+	int		i;
+
+	for (i = 0;i < 15 && in[i];i++)
+		out[i] = toupper(in[i]);
+
+	for (;i < 16;i++)
+		out[i] = 0;
+}
+
+int MipTexUsed(char *texname)
+{
+	int i;
+	char name[16];
+	CleanupName(texname, name);
+
+	for (i = 0;i < nummiptex;i++)
+		if (!strcmp(miptex[i], name))
+			return true;
+	return false;
+}
+
+miptexfile_t *FindMipTexFile(char *texname)
+{
+	int i;
+	char name[16];
+	CleanupName(texname, name);
+
+	for (i = 0;i < nummiptexfiles;i++)
+		if (!strcmp(miptexfile[i].name, name))
+			return miptexfile + i;
+	return NULL;
+}
+
+void AddAnimatingTextures (void)
+{
+	int		base;
+	int		i, j;
+	char	name[32];
+
+	base = nummiptex;
+
+	for (i = 0;i < base;i++)
+	{
+		if (miptex[i][0] != '+')
+			continue;
+		CleanupName (miptex[i], name);
+
+		for (j = 0;j < 20;j++)
+		{
+			if (j < 10)
+				name[1] = j + '0';
+			else
+				name[1] = j + 'A' - 10;		// alternate animation
+
+			if (!MipTexUsed(name) && FindMipTexFile(name))
+				FindMiptex (name);
+		}
+	}
+}
+
+void getwadname(char *out, char **inpointer)
+{
+	char *in;
+	in = *inpointer;
+	while (*in && (*in == ';' || *in <= ' '))
+		in++;
+	if (*in == '"')
+	{
+		// quoted name
+		in++;
+		while (*in && *in != '"')
+			*out++ = *in++;
+		if (*in == '"')
+			in++;
+		// FIXME: error if quoted string is not properly terminated?
+	}
+	else
+	{
+		// normal name
+		while (*in && *in != ';')
+			*out++ = *in++;
+	}
+	*out++ = 0;
+	*inpointer = in;
+}
+
+int loadwad(char *filename)
+{
+	int i, oldnummiptexfiles = nummiptexfiles;
+	wadinfo_t wadinfo;
+	lumpinfo_t lumpinfo;
+	FILE *file;
+	file = fopen(filename, "rb");
+	if (!file)
+	{
+		printf("unable to open wad \"%s\"\n", filename);
+		return -1;
+	}
+	if (fread(&wadinfo, sizeof(wadinfo_t), 1, file) < 1)
+	{
+		printf("error reading wad header in \"%s\"\n", filename);
+		fclose(file);
+		return -1;
+	}
+	// FIXME: support halflife WAD3?
+	if (memcmp(wadinfo.identification, "WAD2", 4))
+	{
+		printf("\"%s\" is not a quake WAD2 archive\n", filename);
+		fclose(file);
+		return -1;
+	}
+
+	wadinfo.numlumps = LittleLong(wadinfo.numlumps);
+	wadinfo.infotableofs = LittleLong(wadinfo.infotableofs);
+	if (fseek(file, wadinfo.infotableofs, SEEK_SET))
+	{
+		printf("error seeking in wad \"%s\"\n", filename);
+		fclose(file);
+		return -1;
+	}
+
+	for (i = 0;i < wadinfo.numlumps;i++)
+	{
+		if (fread(&lumpinfo, sizeof(lumpinfo_t), 1, file) < 1)
+		{
+			printf("error reading lump header in wad \"%s\"\n", filename);
+			fclose(file);
+			return -1;
+		}
+		lumpinfo.filepos = LittleLong(lumpinfo.filepos);
+		lumpinfo.disksize = LittleLong(lumpinfo.disksize);
+		lumpinfo.size = LittleLong(lumpinfo.size);
+		CleanupName(lumpinfo.name, lumpinfo.name);
+		// LordHavoc: some wad editors write 0 size
+		// disabled the size check because some wad editors write garbage for the lump size
+		if (lumpinfo.compression)// || (lumpinfo.size && lumpinfo.size != lumpinfo.disksize))
+		{
+			printf("lump \"%s\" in wad \"%s\" is compressed??\n", lumpinfo.name, filename);
+			continue;
+		}
+		if (lumpinfo.type == TYP_MIPTEX)
+		{
+			miptexfile[nummiptexfiles].file = file;
+			miptexfile[nummiptexfiles].filepos = lumpinfo.filepos;
+			miptexfile[nummiptexfiles].size = lumpinfo.disksize;
+			memcpy(miptexfile[nummiptexfiles].name, lumpinfo.name, 16);
+			nummiptexfiles++;
+		}
+	}
+	printf("loaded wad \"%s\" (%i miptex found)\n", filename, nummiptexfiles - oldnummiptexfiles);
+	return nummiptexfiles - oldnummiptexfiles;
+}
+
+int ReadMipTexFile(miptexfile_t *m, byte *out)
+{
+	if (fseek(m->file, m->filepos, SEEK_SET))
+	{
+		printf("error seeking to data for lump \"%s\"\n", m->name);
+		return true;
+	}
+	if (fread(out, m->size, 1, m->file) < 1)
+	{
+		printf("error reading data for lump \"%s\"\n", m->name);
+		return true;
+	}
+	return false;
+}
+
+static char wadname[2048], wadfilename[2048];
+void WriteMiptex (void)
+{
+	int i, success;
+	byte *miptex_data;
+	dmiptexlump_t *miptex_lumps;
+	char *path, *currentpath;
+	miptexfile_t *m;
+
+	path = ValueForKey (&entities[0], "_wad");
+	if (!path || !path[0])
+	{
+		path = ValueForKey (&entities[0], "wad");
+		if (!path || !path[0])
+		{
+			printf ("WriteMiptex: no wads specified in \"wad\" key in worldspawn\n");
+			texdatasize = 0;
+			return;
+		}
+	}
+
+	nummiptexfiles = 0;
+
+	currentpath = path;
+	while (*currentpath)
+	{
+		getwadname(wadname, &currentpath);
+		if (wadname[0])
+		{
+			success = false;
+			// try prepending each -wadpath on the commandline to the wad name
+			for (i = 1;i < myargc;i++)
+			{
+				if (!Q_strcasecmp("-wadpath", myargv[i]))
+				{
+					i++;
+					if (i < myargc)
+					{
+						sprintf(wadfilename, "%s%s", myargv[i], wadname);
+						if ((success = loadwad(wadfilename) >= 0))
+							break;
+					}
+				}
+			}
+			if (!success)
+			{
+				// if the map name has a path, we can try loading the wad from there
+				ExtractFilePath(bspfilename, wadfilename);
+				if (wadfilename[0])
+				{
+					strcat(wadfilename, wadname);
+					if (!(success = loadwad(wadfilename) >= 0))
+					{
+						// try the parent directory
+						ExtractFilePath(bspfilename, wadfilename);
+						strcat(wadfilename, "../");
+						strcat(wadfilename, wadname);
+						success = loadwad(wadfilename) >= 0;
+					}
+				}
+			}
+			if (!success)
+			{
+				// try the wadname itself
+				success = loadwad(wadname) >= 0;
+			}
+			if (!success)
+				printf("Could not find wad \"%s\" using -wadpath options or in the same directory as the map or it's parent directory, so there!\n", wadname);
+		}
+	}
+
+	for (i = 0;i < nummiptex;i++)
+		CleanupName(miptex[i], miptex[i]);
+
+	AddAnimatingTextures();
+
+	miptex_lumps = (dmiptexlump_t *)dtexdata;
+	miptex_data = (byte *)&miptex_lumps->dataofs[nummiptex];
+	miptex_lumps->nummiptex = nummiptex;
+	for (i=0 ; i < nummiptex ; i++)
+	{
+		printf("miptex used: %s", miptex[i]);
+		m = FindMipTexFile(miptex[i]);
+		if (m)
+		{
+			if (miptex_data + m->size - dtexdata >= MAX_MAP_MIPTEX)
+			{
+				miptex_lumps->dataofs[i] = -1;
+				printf(" (MAX_MAP_MIPTEX exceeded)\n");
+			}
+			else if (ReadMipTexFile(m, miptex_data))
+			{
+				miptex_lumps->dataofs[i] = -1;
+				printf(" (READ ERROR)\n");
+			}
+			else
+			{
+				miptex_lumps->dataofs[i] = miptex_data - (byte *)miptex_lumps;
+				printf("\n");
+				miptex_data += m->size;
+			}
+		}
+		else
+		{
+			miptex_lumps->dataofs[i] = -1;
+			printf(" (NOT FOUND)\n");
+		}
+	}
+
+	texdatasize = miptex_data - dtexdata;
+}
+
Index: hmap2/winding.c
diff -u /dev/null hmap2/winding.c:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/winding.c	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,681 @@
+#include "cmdlib.h"
+#include "mathlib.h"
+#include "mem.h"
+#include "winding.h"
+
+#define WINDING_EPSILON		0.05
+
+//===========================================================================
+
+/*
+==================
+AllocWinding
+==================
+*/
+winding_t *AllocWinding( int points )
+{
+	winding_t	*w;
+	size_t		size;
+
+	if( points > MAX_POINTS_ON_WINDING )
+		Error( "AllocWinding: %i points", points );
+
+	size = ( size_t )((winding_t *)0)->points[points];
+	w = qmalloc( size );
+	memset( w, 0, size );
+
+	return w;
+}
+
+/*
+==================
+FreeWinding
+==================
+*/
+void FreeWinding( winding_t *w )
+{
+	qfree( w );
+}
+
+/*
+==================
+CopyWinding
+==================
+*/
+winding_t *CopyWinding( winding_t *w )
+{
+	winding_t	*c;
+	size_t		size;
+
+	size = ( size_t )((winding_t *)0)->points[w->numpoints];
+	c = qmalloc( size );
+	memcpy( c, w, size );
+
+	return c;
+}
+
+/*
+==================
+CopyWindingExt
+==================
+*/
+winding_t *CopyWindingExt( int numpoints, vec3_t *points )
+{
+	winding_t	*c;
+	size_t		size;
+
+	if( numpoints > MAX_POINTS_ON_WINDING )
+		Error( "CopyWinding: %i points", numpoints );
+
+	size = ( size_t )((winding_t *)0)->points[numpoints];
+	c = malloc( size );
+	c->numpoints = numpoints;
+	memcpy( c->points, points, sizeof(*points)*numpoints );
+
+	return c;
+}
+
+/*
+==================
+ReverseWinding
+==================
+*/
+winding_t *ReverseWinding( winding_t *w )
+{
+	int		i;
+	winding_t *neww;
+
+	if( w->numpoints > MAX_POINTS_ON_WINDING )
+		Error( "ReverseWinding: %i points", w->numpoints );
+
+	neww = AllocWinding( w->numpoints );
+	neww->numpoints = w->numpoints;
+	for( i = 0; i < w->numpoints; i++ )		// add points backwards
+		VectorCopy( w->points[w->numpoints - 1 - i], neww->points[i] );
+
+#ifdef PARANOID
+	CheckWinding( neww );
+#endif
+
+	return neww;
+}
+
+//===========================================================================
+
+/*
+=================
+BaseWindingForPlane
+=================
+*/
+winding_t *BaseWindingForPlane( plane_t *p )
+{
+	int			i, x;
+	vec_t		max, v;
+	vec3_t		org, vright, vup;
+	winding_t	*w;
+
+	// find the major axis
+	max = -BOGUS_RANGE;
+	x = -1;
+	for( i = 0; i < 3; i++ ) {
+		v = fabs( p->normal[i] );
+		if( v > max ) {
+			x = i;
+			max = v;
+		}
+	}
+
+	if( x == -1 )
+		Error( "BaseWindingForPlane: no axis found" );
+
+	VectorClear( vup );
+	switch( x )
+	{
+		case 0:
+		case 1:
+			vup[2] = 1;
+			break;
+		default:
+			vup[0] = 1;
+			break;
+	}
+
+	v = DotProduct( vup, p->normal );
+	VectorMA( vup, -v, p->normal, vup );
+	VectorNormalize( vup );
+
+	VectorScale( p->normal, p->dist, org );
+
+	CrossProduct( vup, p->normal, vright );
+
+	VectorScale( vup, BOGUS_RANGE, vup );
+	VectorScale( vright, BOGUS_RANGE, vright );
+
+	// project a really big	axis aligned box onto the plane
+	w = AllocWinding( 4 );
+
+	VectorSubtract( org, vright, w->points[0] );
+	VectorAdd( w->points[0], vup, w->points[0] );
+
+	VectorAdd( org, vright, w->points[1] );
+	VectorAdd( w->points[1], vup, w->points[1] );
+
+	VectorAdd( org, vright, w->points[2] );
+	VectorSubtract( w->points[2], vup, w->points[2] );
+
+	VectorSubtract( org, vright, w->points[3] );
+	VectorSubtract( w->points[3], vup, w->points[3] );
+
+	w->numpoints = 4;
+
+	return w;
+}
+
+
+/*
+==================
+CheckWinding
+
+Check for possible errors
+==================
+*/
+void CheckWinding( winding_t *w )
+{
+	int		i, j;
+	vec_t	*p1, *p2;
+	vec_t	d, edgedist;
+	vec3_t	dir, edgenormal;
+	plane_t	facenormal;
+
+	PlaneFromWinding ( w, &facenormal );
+
+	for( i = 0; i < w->numpoints; i++ ) {
+		p1 = w->points[i];
+		p2 = w->points[(i + 1) % w->numpoints];
+
+		for( j = 0; j < 3; j++ ) {
+			if( p1[j] > BOGUS_RANGE || p1[j] < -BOGUS_RANGE )
+				Error( "CheckWinding: BOGUS_RANGE: %f %f %f", p1[0], p1[1], p1[2] );
+		}
+
+		// check the point is on the face plane
+		d = DotProduct( p1, facenormal.normal ) - facenormal.dist;
+		if( d < -WINDING_EPSILON || d > WINDING_EPSILON )
+			printf( "CheckWinding: point off plane at %f %f %f, correcting", p1[0], p1[1], p1[2] );
+
+		// check the edge isn't degenerate
+		VectorSubtract( p2, p1, dir );
+
+		if( VectorLength( dir ) < WINDING_EPSILON )
+			Error( "CheckWinding: degenerate edge at %f %f %f", p2[0], p2[1], p2[2] );
+
+		CrossProduct( facenormal.normal, dir, edgenormal );
+		VectorNormalize( edgenormal );
+		edgedist = DotProduct( p1, edgenormal );
+		edgedist += WINDING_EPSILON;
+
+		// all other points must be on front side
+		for( j = 0; j < w->numpoints; j++ ) {
+			if( j == i )
+				continue;
+
+			d = DotProduct( w->points[j], edgenormal );
+			if( d > edgedist )
+				Error( "CheckWinding: non-convex at %f %f %f", w->points[j][0], w->points[j][1], w->points[j][2] );
+		}
+	}
+}
+
+/*
+==================
+WindingIsTiny
+==================
+*/
+qboolean WindingIsTiny( winding_t *w )
+{
+	int		i, cnt;
+	vec3_t	edge;
+	vec_t	len;
+
+#ifdef PARANOID
+	if( !w || !w->numpoints )
+		return true;
+#endif
+
+	cnt = 0;
+	for( i = 0; i < w->numpoints; i++ ) {
+		VectorSubtract( w->points[i], w->points[(i+1)%w->numpoints], edge );
+		len = VectorLength( edge );
+		if( len > 0.3 ) {
+			if( ++cnt == 3 )
+				return false;
+		}
+	}
+
+	return true;
+}
+
+/*
+=================
+WindingArea
+=================
+*/
+vec_t WindingArea( winding_t *w )
+{
+	int		i;
+	vec_t	total;
+	vec3_t	v1, v2, cross;
+
+	total = 0;
+	for( i = 2; i < w->numpoints; i++ ) {
+		VectorSubtract( w->points[i-1], w->points[0], v1 );
+		VectorSubtract( w->points[i  ], w->points[0], v2 );
+		CrossProduct( v1, v2, cross );
+		total += VectorLength( cross ) * 0.5;
+	}
+
+	return total;
+}
+
+/*
+=================
+WindingCentre
+=================
+*/
+void WindingCentre( winding_t *w, vec3_t centre, vec_t *radius )
+{
+	int			i;
+	vec3_t		edge;
+	vec_t		dist, best;
+
+	if( !w->numpoints )
+		Error( "WindingCentre: no points" );
+
+	VectorCopy( w->points[0], centre );
+	for( i = 1; i < w->numpoints; i++ )
+		VectorAdd( centre, w->points[i], centre );
+	VectorScale( centre, (vec_t)1.0/w->numpoints, centre );
+
+	if( radius ) {
+		best = 0;
+		for( i = 0; i < w->numpoints; i++ ) {
+			VectorSubtract( centre, w->points[i], edge );
+			dist = VectorLength( edge );
+			if( dist > best )
+				best = dist;
+		}
+
+	#ifdef PARANOID
+		if( best == 0 )
+			Error( "WindingCentre: winding has zero radius" );
+	#endif
+
+		*radius = best;
+	}
+}
+
+/*
+=================
+PlaneFromWinding
+=================
+*/
+void PlaneFromWinding( winding_t *w, plane_t *plane )
+{
+	vec3_t	v1, v2;
+
+	if( w->numpoints < 3 )
+		Error( "PlaneFromWinding: %i points at %f %f %f", w->numpoints, w->points[0], w->points[1], w->points[2] );
+
+	// calc plane
+	VectorSubtract( w->points[2], w->points[1], v1 );
+	VectorSubtract( w->points[0], w->points[1], v2 );
+	CrossProduct( v2, v1, plane->normal );
+	VectorNormalize( plane->normal );
+	plane->dist = DotProduct( w->points[0], plane->normal );
+}
+
+/*
+==================
+WindingOnPlaneSide
+==================
+*/
+int WindingOnPlaneSide( winding_t *w, plane_t *plane )
+{
+	int i;
+	vec_t dist;
+	qboolean back, front;
+
+	back = front = false;
+	for( i = 0; i < w->numpoints; i++ ) {
+		dist = DotProduct( w->points[i], plane->normal ) - plane->dist;
+
+		if( dist < -WINDING_EPSILON ) {
+			if( front )
+				return SIDE_CROSS;
+			back = true;
+		} else if( dist > WINDING_EPSILON ) {
+			if( back )
+				return SIDE_CROSS;
+			front = true;
+		}
+	}
+
+	if( back )
+		return SIDE_BACK;
+	if ( front )
+		return SIDE_FRONT;
+	return SIDE_ON;
+}
+
+/*
+==================
+ClipWindingEpsilon
+==================
+*/
+winding_t *ClipWindingEpsilon( winding_t *in, plane_t *split, vec_t epsilon, qboolean keepon )
+{
+	int			i, j;
+	vec_t		dists[MAX_POINTS_ON_WINDING + 1];
+	int			sides[MAX_POINTS_ON_WINDING + 1];
+	int			counts[3];
+	vec_t		dot;
+	vec_t		*p1, *p2;
+	vec3_t		mid;
+	winding_t	*neww;
+	int			maxpts;
+
+	counts[0] = counts[1] = counts[2] = 0;
+
+	// determine sides for each point
+	for( i = 0; i < in->numpoints; i++ )
+	{
+		dists[i] = dot = DotProduct( in->points[i], split->normal ) - split->dist;
+		if( dot > epsilon )
+			sides[i] = SIDE_FRONT;
+		else if( dot < -epsilon )
+			sides[i] = SIDE_BACK;
+		else
+			sides[i] = SIDE_ON;
+		counts[sides[i]]++;
+	}
+	sides[i] = sides[0];
+	dists[i] = dists[0];
+
+	if( keepon && !counts[SIDE_FRONT] && !counts[SIDE_BACK] )
+		return in;
+
+	if( !counts[SIDE_FRONT] ) {
+		FreeWinding( in );
+		return NULL;
+	}
+	if( !counts[SIDE_BACK] )
+		return in;
+
+	maxpts = in->numpoints + 4;	// can't use counts[0]+2 because of fp grouping errors
+	neww = AllocWinding( maxpts );
+
+	for( i = 0; i < in->numpoints; i++ ) {
+		p1 = in->points[i];
+
+		if( sides[i] == SIDE_ON ) {
+			VectorCopy( p1, neww->points[neww->numpoints] );
+			neww->numpoints++;
+			continue;
+		}
+
+		if( sides[i] == SIDE_FRONT ) {
+			VectorCopy( p1, neww->points[neww->numpoints] );
+			neww->numpoints++;
+		}
+
+		if( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] )
+			continue;
+
+		// generate a split point
+		p2 = in->points[(i+1)%in->numpoints];
+
+		dot = dists[i] / (dists[i] - dists[i+1]);
+		for( j = 0; j < 3; j++ ) {
+			// avoid round off error when possible
+			if( split->normal[j] == 1 )
+				mid[j] = split->dist;
+			else if( split->normal[j] == -1 )
+				mid[j] = -split->dist;
+			else
+				mid[j] = p1[j] + dot*(p2[j]-p1[j]);
+		}
+
+		VectorCopy( mid, neww->points[neww->numpoints] );
+		neww->numpoints++;
+	}
+
+	if( neww->numpoints > maxpts )
+		Error( "ClipWinding: points exceeded estimate" );
+
+	// free the original winding
+	FreeWinding( in );
+
+	return neww;
+}
+
+/*
+==================
+ClipWinding
+==================
+*/
+winding_t *ClipWinding( winding_t *in, plane_t *split, qboolean keepon ) {
+	return ClipWindingEpsilon( in, split, WINDING_EPSILON, keepon );
+}
+
+/*
+==================
+DivideWindingEpsilon
+
+Divides a winding by a plane, producing one or two windings. The
+original winding is not damaged or freed. If only on one side, the
+returned winding will be the input winding. If on both sides, two
+new windings will be created.
+==================
+*/
+void DivideWindingEpsilon( winding_t *in, plane_t *split, winding_t **front, winding_t **back, vec_t epsilon )
+{
+	int			i, j;
+	vec_t		dists[MAX_POINTS_ON_WINDING + 1];
+	int			sides[MAX_POINTS_ON_WINDING + 1];
+	int			counts[3];
+	vec_t		dot;
+	vec_t		*p1, *p2;
+	vec3_t		mid;
+	winding_t	*f, *b;
+	int			maxpts;
+
+	counts[0] = counts[1] = counts[2] = 0;
+
+	// determine sides for each point
+	for( i = 0; i < in->numpoints; i++ ) {
+		dists[i] = dot = DotProduct (in->points[i], split->normal) - split->dist;
+		if( dot > epsilon )
+			sides[i] = SIDE_FRONT;
+		else if( dot < -epsilon )
+			sides[i] = SIDE_BACK;
+		else
+			sides[i] = SIDE_ON;
+		counts[sides[i]]++;
+	}
+	sides[i] = sides[0];
+	dists[i] = dists[0];
+
+	*front = *back = NULL;
+
+	if( !counts[SIDE_FRONT] ) {
+		*back = in;
+		return;
+	}
+	if( !counts[SIDE_BACK] ) {
+		*front = in;
+		return;
+	}
+
+	maxpts = in->numpoints + 4;	// can't use counts[0]+2 because of fp grouping errors
+
+	*front = f = AllocWinding( maxpts );
+	*back = b = AllocWinding( maxpts );
+
+	for( i = 0; i < in->numpoints; i++ ) {
+		p1 = in->points[i];
+
+		if( sides[i] == SIDE_ON ) {
+			VectorCopy( p1, f->points[f->numpoints] );
+			f->numpoints++;
+			VectorCopy( p1, b->points[b->numpoints] );
+			b->numpoints++;
+			continue;
+		}
+
+		if( sides[i] == SIDE_FRONT ) {
+			VectorCopy( p1, f->points[f->numpoints] );
+			f->numpoints++;
+		} else if( sides[i] == SIDE_BACK ) {
+			VectorCopy( p1, b->points[b->numpoints] );
+			b->numpoints++;
+		}
+
+		if( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] )
+			continue;
+
+		// generate a split point
+		p2 = in->points[(i+1) % in->numpoints];
+
+		dot = dists[i] / (dists[i] - dists[i+1]);
+		for( j = 0;j  < 3; j++) {
+			// avoid round off error when possible
+			if( split->normal[j] == 1 )
+				mid[j] = split->dist;
+			else if( split->normal[j] == -1 )
+				mid[j] = -split->dist;
+			else
+				mid[j] = p1[j] + dot * (p2[j] - p1[j]);
+		}
+
+		VectorCopy( mid, f->points[f->numpoints] );
+		f->numpoints++;
+		VectorCopy( mid, b->points[b->numpoints] );
+		b->numpoints++;
+	}
+
+	if( f->numpoints > maxpts || b->numpoints > maxpts )
+		Error ("ClipWinding: points exceeded estimate");
+}
+
+/*
+==================
+DivideWinding
+==================
+*/
+void DivideWinding( winding_t *in, plane_t *split, winding_t **front, winding_t **back ) {
+	DivideWindingEpsilon( in, split, front, back, WINDING_EPSILON );
+}
+
+/*
+=============
+TryMergeWinding
+
+If two polygons share a common edge and the edges that meet at the
+common points are both inside the other polygons, merge them
+
+Returns NULL if the windings couldn't be merged, or the new winding.
+The originals will NOT be freed.
+=============
+*/
+#define CONTINUOUS_EPSILON	0.001
+
+winding_t *TryMergeWinding( winding_t *w1, winding_t *w2, vec3_t planenormal )
+{
+	int			i, j = 0, k, l;
+	vec_t		*p1 = NULL, *p2 = NULL, *p3, *p4, *back;
+	winding_t	*neww;
+	vec3_t		normal, delta;
+	vec_t		dot;
+	qboolean	keep1, keep2;
+
+	//
+	// find a common edge
+	//	
+	for( i = 0; i < w1->numpoints; i++ ) {
+		p1 = w1->points[i];
+		p2 = w1->points[(i + 1) % w1->numpoints];
+
+		for( j = 0; j < w2->numpoints; j++ ) {
+			p3 = w2->points[j];
+			p4 = w2->points[(j + 1) % w2->numpoints];
+
+			for( k = 0; k < 3; k++ ) {
+				if( fabs( p1[k] - p4[k] ) > EQUAL_EPSILON ||
+					fabs( p2[k] - p3[k] ) > EQUAL_EPSILON )
+					break;
+			}
+
+			if( k == 3 )
+				break;
+		}
+
+		if( j < w2->numpoints )
+			break;
+    }
+
+	if( i == w1->numpoints )
+		return NULL;			// no matching edges
+
+	//
+	// check slope of connected lines
+	// if the slopes are colinear, the point can be removed
+	//
+	back = w1->points[(i + w1->numpoints - 1) % w1->numpoints];
+	VectorSubtract( p1, back, delta );
+	CrossProduct( planenormal, delta, normal );
+	VectorNormalize( normal );
+
+	back = w2->points[(j + 2) % w2->numpoints];
+	VectorSubtract( back, p1, delta );
+	dot = DotProduct( delta, normal );
+	if( dot > CONTINUOUS_EPSILON )
+		return NULL;			// not a convex polygon
+	keep1 = dot < -CONTINUOUS_EPSILON;
+
+	back = w1->points[(i + 2) % w1->numpoints];
+	VectorSubtract( back, p2, delta );
+	CrossProduct( planenormal, delta, normal );
+	VectorNormalize( normal );
+
+	back = w2->points[(j + w2->numpoints - 1) % w2->numpoints];
+	VectorSubtract( back, p2, delta );
+	dot = DotProduct( delta, normal );
+	if( dot > CONTINUOUS_EPSILON )
+		return NULL;			// not a convex polygon
+	keep2 = dot < -CONTINUOUS_EPSILON;
+
+	//
+	// build the new polygon
+	//
+	neww = AllocWinding( w1->numpoints + w2->numpoints - 4 + keep1 + keep2 );
+
+	// copy first polygon
+	for( k = (i + 1) % w1->numpoints; k != i; k = (k + 1)%w1->numpoints ) {
+		if( k == (i + 1) % w1->numpoints && !keep2 )
+			continue;
+		
+		VectorCopy( w1->points[k], neww->points[neww->numpoints] );
+		neww->numpoints++;
+    }
+
+	// copy second polygon
+	for( l = (j + 1) % w2->numpoints; l != j; l = (l + 1) % w2->numpoints ) {
+		if( l == (j + 1) % w2->numpoints && !keep1 )
+			continue;
+
+		VectorCopy( w2->points[l], neww->points[neww->numpoints] );
+		neww->numpoints++;
+    }
+
+	return neww;
+}
Index: hmap2/winding.h
diff -u /dev/null hmap2/winding.h:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/winding.h	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,30 @@
+#ifndef __WINDING_H__
+#define __WINDING_H__
+
+typedef struct
+{
+	int		numpoints;
+	vec3_t	points[8];			// variable sized
+} winding_t;
+
+#define MAX_POINTS_ON_WINDING	64
+
+winding_t	*AllocWinding (int points);
+void		FreeWinding (winding_t *w);
+winding_t	*CopyWinding (winding_t *w);
+winding_t	*CopyWindingExt( int numpoints, vec3_t *points );
+winding_t	*ReverseWinding( winding_t *w );
+winding_t	*BaseWindingForPlane( plane_t *p );
+void		CheckWinding( winding_t *w );
+qboolean	WindingIsTiny( winding_t *w );
+vec_t		WindingArea( winding_t *w );
+void		WindingCentre( winding_t *w, vec3_t centre, vec_t *radius );
+void		PlaneFromWinding( winding_t *w, plane_t *plane );
+int			WindingOnPlaneSide( winding_t *w, plane_t *plane );
+winding_t	*ClipWinding( winding_t *in, plane_t *split, qboolean keepon );
+winding_t	*ClipWindingEpsilon( winding_t *in, plane_t *split, vec_t epsilon, qboolean keepon );
+void		DivideWinding( winding_t *in, plane_t *split, winding_t **front, winding_t **back );
+void		DivideWindingEpsilon( winding_t *in, plane_t *split, winding_t **front, winding_t **back, vec_t epsilon );
+winding_t	*TryMergeWinding( winding_t *w1, winding_t *w2, vec3_t planenormal );
+
+#endif //__WINDING_H__
Index: hmap2/writebsp.c
diff -u /dev/null hmap2/writebsp.c:1.1
--- /dev/null	Sun Feb 15 21:50:47 2004
+++ hmap2/writebsp.c	Sun Feb 15 21:50:37 2004
@@ -0,0 +1,308 @@
+
+#include "bsp5.h"
+
+int		firstface;
+int		*planemapping;
+
+//===========================================================================
+
+/*
+==================
+FindFinalPlane
+
+Used to find plane index numbers for clip nodes read from child processes
+==================
+*/
+int FindFinalPlane( vec3_t normal, vec_t dist, int type )
+{
+	int			i;
+	dplane_t	*dplane;
+
+	for( i = 0, dplane = dplanes; i < numplanes; i++, dplane++ ) {
+		if( dplane->type != type 
+			|| dplane->dist != dist
+			|| dplane->normal[0] != normal[0]
+			|| dplane->normal[1] != normal[1]
+			|| dplane->normal[2] != normal[2])
+			continue;
+		return i;
+	}
+
+	// new plane
+	if( numplanes == MAX_MAP_PLANES )
+		Error( "numplanes == MAX_MAP_PLANES" );
+
+	dplane->type = type;
+	dplane->dist = dist;
+	VectorCopy( normal, dplane->normal );
+
+	return numplanes++;
+}
+
+/*
+==================
+EmitNodePlanes
+==================
+*/
+static void EmitNodePlanes_r( node_t *node )
+{
+	if( node->planenum == PLANENUM_LEAF )
+		return;
+
+	if( planemapping[node->planenum] == -1 ) {	// a new plane
+		plane_t *plane;
+		
+		plane = &mapplanes[node->planenum];
+		planemapping[node->planenum] = FindFinalPlane( plane->normal, plane->dist, plane->type );
+	}
+
+	node->outputplanenum = planemapping[node->planenum];
+
+	EmitNodePlanes_r( node->children[0] );
+	EmitNodePlanes_r( node->children[1] );
+}
+
+/*
+==================
+EmitNodePlanes
+==================
+*/
+void EmitNodePlanes( node_t *nodes )
+{
+//	qprintf( "--- EmitNodePlanes ---\n" );
+
+	EmitNodePlanes_r( nodes );
+}
+
+//===========================================================================
+
+/*
+==================
+EmitClipNodes_r
+==================
+*/
+static int EmitClipNodes_r( node_t *node )
+{
+	int			i, c;
+	dclipnode_t	*cn;
+	int			num;
+
+	// FIXME: free more stuff?
+	if( node->planenum == PLANENUM_LEAF ) {
+		num = node->contents;
+		FreeNode( node );
+		return num;
+	}
+
+	c = numclipnodes;
+	cn = &dclipnodes[numclipnodes++];	// emit a clipnode
+	cn->planenum = node->outputplanenum;
+	for( i = 0; i < 2; i++ )
+		cn->children[i] = EmitClipNodes_r( node->children[i] );
+
+	FreeNode( node );
+
+	return c;
+}
+
+/*
+==================
+EmitClipNodes
+
+Called after the clipping hull is completed. Generates a disk format
+representation and frees the original memory.
+==================
+*/
+void EmitClipNodes( node_t *nodes, int modnum, int hullnum )
+{
+//	qprintf( "--- EmitClipNodes ---\n" );
+
+	dmodels[modnum].headnode[hullnum] = numclipnodes;
+	EmitClipNodes_r( nodes );
+}
+
+//===========================================================================
+
+/*
+==================
+EmitVertex
+==================
+*/
+void EmitVertex( vec3_t point )
+{
+	dvertex_t	*vert;
+
+	if( numvertexes == MAX_MAP_VERTS )
+		Error( "numvertexes == MAX_MAP_VERTS" );
+
+	vert = &dvertexes[numvertexes++];	// emit a vertex
+	VectorCopy( point, vert->point );
+}
+
+/*
+==================
+EmitEdge
+==================
+*/
+void EmitEdge( int v1, int v2 )
+{
+	dedge_t		*edge;
+
+	if( numedges == MAX_MAP_EDGES )
+		Error( "numedges == MAX_MAP_EDGES" );
+
+	edge = &dedges[numedges++];	// emit an edge
+	edge->v[0] = v1;
+	edge->v[1] = v2;
+}
+
+//===========================================================================
+
+/*
+==================
+EmitLeaf
+==================
+*/
+static void EmitLeaf( node_t *node )
+{
+	face_t		**fp, *f;
+	dleaf_t		*leaf_p;
+
+	if( numleafs == MAX_MAP_LEAFS )
+		Error( "numleafs == MAX_MAP_LEAFS" );
+
+	leaf_p = &dleafs[numleafs++];	// emit a leaf
+	leaf_p->contents = node->contents;
+	leaf_p->visofs = -1;			// no vis info yet
+
+	// write bounding box info
+	VectorCopy( node->mins, leaf_p->mins );
+	VectorCopy( node->maxs, leaf_p->maxs );
+
+	// write the marksurfaces
+	leaf_p->firstmarksurface = nummarksurfaces;
+
+	for( fp = node->markfaces; *fp ; fp++ ) {
+		for( f = *fp; f; f = f->original ) {	// grab tjunction split faces
+			if( nummarksurfaces == MAX_MAP_MARKSURFACES )
+				Error( "nummarksurfaces == MAX_MAP_MARKSURFACES" );
+			if( f->outputnumber == -1 )
+				Error( "f->outputnumber == -1" );
+			dmarksurfaces[nummarksurfaces++] = f->outputnumber;		// emit a marksurface
+		}
+	}
+
+	leaf_p->nummarksurfaces = nummarksurfaces - leaf_p->firstmarksurface;
+}
+
+/*
+==================
+EmitDrawNodes_r
+==================
+*/
+void EmitDrawNodes_r( node_t *node )
+{
+	int			i;
+	dnode_t		*n;
+
+	if( numnodes == MAX_MAP_NODES )
+		Error( "numnodes == MAX_MAP_NODES" );
+
+	n = &dnodes[numnodes++];		// emit a node
+	n->planenum = node->outputplanenum;
+	n->firstface = node->firstface;
+	n->numfaces = node->numfaces;
+	VectorCopy( node->mins, n->mins );
+	VectorCopy( node->maxs, n->maxs );
+
+	// recursively output the other nodes
+	for( i = 0; i < 2; i++ ) {
+		if( node->children[i]->planenum == PLANENUM_LEAF ) {
+			n->children[i] = -1;
+
+			if( node->children[i]->contents != CONTENTS_SOLID ) {
+				n->children[i] = -(numleafs + 1);
+				EmitLeaf( node->children[i] );
+			}
+		} else {
+			n->children[i] = numnodes;	
+			EmitDrawNodes_r( node->children[i] );
+		}
+	}
+}
+
+/*
+==================
+EmitDrawNodes
+==================
+*/
+void EmitDrawNodes( node_t *headnode )
+{
+	int			i;
+	dmodel_t	*bm;
+	int			firstleaf;
+
+//	qprintf( "--- EmitDrawNodes ---\n" );
+
+	if( nummodels == MAX_MAP_MODELS )
+		Error( "nummodels == MAX_MAP_MODELS" );
+
+	bm = &dmodels[nummodels++];		// emit a model
+	bm->headnode[0] = numnodes;
+	bm->firstface = firstface;
+	bm->numfaces = numfaces - firstface;
+	firstface = numfaces;
+
+	firstleaf = numleafs;
+	if( headnode->contents < 0 )
+		EmitLeaf( headnode );
+	else
+		EmitDrawNodes_r( headnode );
+	bm->visleafs = numleafs - firstleaf;
+
+	for( i = 0; i < 3; i++ ) {
+		bm->mins[i] = headnode->mins[i] + SIDESPACE + 1;	// remove the padding
+		bm->maxs[i] = headnode->maxs[i] - SIDESPACE - 1;
+	}
+}
+
+//=============================================================================
+
+/*
+==================
+BeginBSPFile
+==================
+*/
+void BeginBSPFile( void )
+{
+	qprintf( "--- BeginBSPFile ---\n" );
+
+	planemapping = qmalloc( sizeof( *planemapping ) * MAX_MAP_PLANES );
+	memset( planemapping, -1, sizeof( *planemapping ) * MAX_MAP_PLANES );
+
+	// edge 0 is not used, because 0 can't be negated
+	numedges = 1;
+
+	// leaf 0 is common solid with no faces
+	numleafs = 1;
+	dleafs[0].contents = CONTENTS_SOLID;
+	dleafs[0].visofs = -1; // thanks to Vic for suggesting this line
+
+	firstface = 0;
+}
+
+/*
+==================
+FinishBSPFile
+==================
+*/
+void FinishBSPFile( void )
+{
+	printf( "--- BSP file information ---\n" );
+	PrintBSPFileSizes ();
+
+	qprintf( "--- WriteBSPFile: %s ---\n", bspfilename );
+	WriteBSPFile( bspfilename, false );
+
+	qfree( planemapping );
+}


More information about the twilight-commits mailing list