#include "QF/script.h"
#include "QF/sys.h"

#include "SetBrush.h"
#include "Entity.h"
#include "EntityClass.h"
#include "Map.h"
#include "Preferences.h"
#include "XYView.h"
#include "ZView.h"
#include "CameraView.h"
#include "Clipper.h"
#include "QuakeEd.h"

@implementation SetBrush
/*
==================
textureAxisFromPlane
==================
*/
#if 1
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
};
#else
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
};
#endif

float
TextureAxisFromPlane (plane_t *pln, float *xv, float *yv)
{
	int     bestaxis;
	float   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);

	return lightaxis[bestaxis >> 1];
}

#define BOGUS_RANGE 18000

/*
=================
CheckFace

Note: this will not catch 0 area polygons
=================
*/
void
CheckFace (face_t * f)
{
	int         i, j;
	float       *p1, *p2;
	float       d, edgedist;
	vec3_t      dir, edgenormal;
	winding_t   *w;

	w = f->w;
	if (!w)
		Sys_Error ("CheckFace: no winding");

	if (w->numpoints < 3)
		Sys_Error ("CheckFace: %i points", w->numpoints);

	for (i = 0; i < w->numpoints; i++) {
		p1 = w->points[i];

		for (j = 0; j < 3; j++) {
			if (p1[j] > BOGUS_RANGE || p1[j] < -BOGUS_RANGE)
				Sys_Error ("CheckFace: BUGUS_RANGE: %f", p1[j]);
		}

		j = i + 1 == w->numpoints ? 0 : i + 1;

		// check the point is on the face plane
		d = DotProduct (p1, f->plane.normal) - f->plane.dist;
		if (d < -ON_EPSILON || d > ON_EPSILON)
			Sys_Error ("CheckFace: point off plane");

		// check the edge isn't degenerate
		p2 = w->points[j];
		VectorSubtract (p2, p1, dir);

		if (VectorLength (dir) < ON_EPSILON)
			Sys_Error ("CheckFace: degenerate edge");

		CrossProduct (f->plane.normal, dir, edgenormal);
		VectorNormalize (edgenormal);
		edgedist = DotProduct (p1, edgenormal);
		edgedist += ON_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)
				Sys_Error ("CheckFace: non-convex");
		}
	}
}

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

            TURN PLANES INTO GROUPS OF FACES

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

/*
==================
NewWinding
==================
*/
winding_t *
NewWinding (int points)
{
	winding_t   *w;
	size_t      size;

	if (points > MAX_POINTS_ON_WINDING)
		Sys_Error ("NewWinding: %i points", points);

	size = (size_t) ((winding_t *) 0)->points[points];
	w = malloc (size);
	memset (w, 0, size);

	return w;
}

/*
==================
CopyWinding
==================
*/
winding_t *
CopyWinding (winding_t * w)
{
	size_t      size;
	winding_t   *c;

	size = (size_t) ((winding_t *) 0)->points[w->numpoints];
	c = malloc (size);
	memcpy (c, w, size);
	return c;
}

/*
==================
ClipWinding

Clips the winding to the plane, returning the new winding on the positive side
Frees the input winding.
==================
*/
winding_t *
ClipWinding (winding_t * in, plane_t *split)
{
	float       dists[MAX_POINTS_ON_WINDING];
	int         sides[MAX_POINTS_ON_WINDING];
	int         counts[3];
	float       dot;
	int         i, j;
	float       *p1, *p2, *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++) {
		dot = DotProduct (in->points[i], split->normal);
		dot -= split->dist;
		dists[i] = dot;
		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 (!counts[0] && !counts[1])
		return in;
	if (!counts[0]) {
		free (in);
		return NULL;
	}
	if (!counts[1])
		return in;

	// can't use counts[0]+2 because of fp grouping errors
	maxpts = in->numpoints + 4;
	neww = NewWinding (maxpts);

	for (i = 0; i < in->numpoints; i++) {
		p1 = in->points[i];

		mid = neww->points[neww->numpoints];

		if (sides[i] == SIDE_FRONT || sides[i] == SIDE_ON) {
			VectorCopy (p1, mid);
			mid[3] = p1[3];
			mid[4] = p1[4];
			neww->numpoints++;
			if (sides[i] == SIDE_ON)
				continue;
			mid = neww->points[neww->numpoints];
		}

		if (sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i])
			continue;

		// generate a split point
		if (i == in->numpoints - 1)
			p2 = in->points[0];
		else
			p2 = p1 + 5;

		neww->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;
			mid[j] = p1[j] + dot * (p2[j] - p1[j]);
		}
		mid[3] = p1[3] + dot * (p2[3] - p1[3]);
		mid[4] = p1[4] + dot * (p2[4] - p1[4]);
	}

	if (neww->numpoints > maxpts)
		Sys_Error ("ClipWinding: points exceeded estimate");

	// free the original winding
	free (in);

	return neww;
}

/*
=================
BasePolyForPlane

There has GOT to be a better way of doing this...
=================
*/
winding_t *
BasePolyForPlane (face_t * f)
{
	int             i, x;
	float           max, v;
	vec3_t          org, vright, vup;
	vec3_t          xaxis, yaxis;
	winding_t       *w;
	texturedef_t    *td;
	plane_t         *p;
	float           ang, sinv, cosv;
	float           s, t, ns, nt;

	p = &f->plane;

	// 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)
		Sys_Error ("BasePolyForPlane: no axis found");

	VectorCopy (vec3_origin, vup);
	switch (x) {
		case 0:
		case 1:
			vup[2] = 1;
			break;

		case 2:
			vup[0] = 1;
			break;
	}

	v = DotProduct (vup, p->normal);
	VectorMultAdd (vup, -v, p->normal, vup);
	VectorNormalize (vup);

	VectorScale (p->normal, p->dist, org);

	CrossProduct (vup, p->normal, vright);

	VectorScale (vup, 8192, vup);
	VectorScale (vright, 8192, vright);

	// project a really big axis-aligned box onto the plane
	w = NewWinding (4);
	w->numpoints = 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]);

	// set texture values
	f->light = TextureAxisFromPlane (&f->plane, xaxis, yaxis);
	td = &f->texture;

	// rotate axis
	ang = td->rotate / 180 * M_PI;
	sinv = sin (ang);
	cosv = cos (ang);

	if (!(td->scale[0]))
		td->scale[0] = 1;

	if (!(td->scale[1]))
		td->scale[1] = 1;

	for (i = 0; i < 4; i++) {
		s = DotProduct (w->points[i], xaxis);
		t = DotProduct (w->points[i], yaxis);

		ns = cosv * s - sinv * t;
		nt = sinv * s + cosv * t;

		w->points[i][3] = ns / td->scale[0] + td->shift[0];
		w->points[i][4] = nt / td->scale[1] + td->shift[1];
	}

	return w;
}

/*
===========
calcWindings

recalc the faces and mins / maxs from the planes
If a face has a NULL winding, it is an overconstraining plane and
can be removed.
===========
*/
- (id) calcWindings
{
	int         i, j, k;
	float       v;
	face_t      *f;
	winding_t   *w;
	plane_t     plane;
	vec3_t      t1, t2, t3;
	BOOL        useplane[MAX_FACES];

	bmins[0] = bmins[1] = bmins[2] = 99999;
	bmaxs[0] = bmaxs[1] = bmaxs[2] = -99999;
	invalid = NO;

	[self freeWindings];

	for (i = 0; i < MAX_FACES; i++) {
		f = &faces[i];

		// calc a plane from the points
		for (j = 0; j < 3; j++) {
			t1[j] = f->planepts[0][j] - f->planepts[1][j];
			t2[j] = f->planepts[2][j] - f->planepts[1][j];
			t3[j] = f->planepts[1][j];
		}

		CrossProduct (t1, t2, f->plane.normal);
		if (VectorCompare (f->plane.normal, vec3_origin)) {
			useplane[i] = NO;
			break;
		}
		VectorNormalize (f->plane.normal);
		f->plane.dist = DotProduct (t3, f->plane.normal);

		// if the plane duplicates another plane, ignore it
		// (assume it is a brush being edited that will be fixed)
		useplane[i] = YES;
		for (j = 0; j < i; j++) {
			if (f->plane.normal[0] == faces[j].plane.normal[0]
			    && f->plane.normal[1] == faces[j].plane.normal[1]
			    && f->plane.normal[2] == faces[j].plane.normal[2]
			    && f->plane.dist == faces[j].plane.dist) {
				useplane[i] = NO;
				break;
			}
		}
	}

	for (i = 0; i < numfaces; i++) {
		if (!useplane[i])           // duplicate plane
			continue;

		f = &faces[i];

		w = BasePolyForPlane (f);
		for (j = 0; j < numfaces && w; j++) {
			if (j == i)
				continue;

			// flip the plane, because we want to keep the back side
			VectorSubtract (vec3_origin, faces[j].plane.normal, plane.normal);
			plane.dist = -faces[j].plane.dist;

			w = ClipWinding (w, &plane);
		}
		f->w = w;
		if (w) {
			CheckFace (f);
			for (j = 0; j < w->numpoints; j++) {
				for (k = 0; k < 3; k++) {
					v = w->points[j][k];
					if (fabs (v - rint (v)) < FP_EPSILON)
						v = w->points[j][k] = rint (v);
					if (v < bmins[k])
						bmins[k] = v;
					if (v > bmaxs[k])
						bmaxs[k] = v;
				}
			}
		}
	}

	if (bmins[0] == 99999) {
		invalid = YES;
		VectorCopy (vec3_origin, bmins);
		VectorCopy (vec3_origin, bmaxs);
		return nil;
	}

	return self;
}

// ============================================================================

/*
===========
initOwner:::
===========
*/
- (SetBrush *) initOwner: own
   mins: (float *)mins
   maxs: (float *)maxs
   texture: (texturedef_t *)tex
{
	[super init];

	parent = own;

	[self setTexturedef: tex];
	[self setMins: mins maxs: maxs];
	return self;
}

- (id) setMins: (float *)
   mins maxs: (float *)maxs
{
	int     i, j;
	vec3_t  pts[4][2];

	for (i = 0; i < 3; i++) {
		if (maxs[i] - mins[i] <= 0) {
			VectorCopy (mins, bmins);
			VectorCopy (maxs, bmaxs);
			invalid = YES;
			numfaces = 0;
			return self;
		}
	}

	pts[0][0][0] = mins[0];
	pts[0][0][1] = mins[1];

	pts[1][0][0] = mins[0];
	pts[1][0][1] = maxs[1];

	pts[2][0][0] = maxs[0];
	pts[2][0][1] = maxs[1];

	pts[3][0][0] = maxs[0];
	pts[3][0][1] = mins[1];

	for (i = 0; i < 4; i++) {
		pts[i][0][2] = mins[2];
		pts[i][1][0] = pts[i][0][0];
		pts[i][1][1] = pts[i][0][1];
		pts[i][1][2] = maxs[2];
	}

	numfaces = 6;
	for (i = 0; i < 4; i++) {
		j = (i + 1) % 4;
		faces[i].planepts[0][0] = pts[j][1][0];
		faces[i].planepts[0][1] = pts[j][1][1];
		faces[i].planepts[0][2] = pts[j][1][2];

		faces[i].planepts[1][0] = pts[i][1][0];
		faces[i].planepts[1][1] = pts[i][1][1];
		faces[i].planepts[1][2] = pts[i][1][2];

		faces[i].planepts[2][0] = pts[i][0][0];
		faces[i].planepts[2][1] = pts[i][0][1];
		faces[i].planepts[2][2] = pts[i][0][2];
	}

	faces[4].planepts[0][0] = pts[0][1][0];
	faces[4].planepts[0][1] = pts[0][1][1];
	faces[4].planepts[0][2] = pts[0][1][2];

	faces[4].planepts[1][0] = pts[1][1][0];
	faces[4].planepts[1][1] = pts[1][1][1];
	faces[4].planepts[1][2] = pts[1][1][2];

	faces[4].planepts[2][0] = pts[2][1][0];
	faces[4].planepts[2][1] = pts[2][1][1];
	faces[4].planepts[2][2] = pts[2][1][2];

	faces[5].planepts[0][0] = pts[2][0][0];
	faces[5].planepts[0][1] = pts[2][0][1];
	faces[5].planepts[0][2] = pts[2][0][2];

	faces[5].planepts[1][0] = pts[1][0][0];
	faces[5].planepts[1][1] = pts[1][0][1];
	faces[5].planepts[1][2] = pts[1][0][2];

	faces[5].planepts[2][0] = pts[0][0][0];
	faces[5].planepts[2][1] = pts[0][0][1];
	faces[5].planepts[2][2] = pts[0][0][2];

	[self calcWindings];
	return self;
}

- (id) parent
{
	return parent;
}

- (id) setParent: (id)p
{
	parent = p;
	return self;
}

- (id) setEntityColor: (vec3_t)color
{
	VectorCopy (color, entitycolor);
	return self;
}

- (void) freeWindings
{
	int  i;

	for (i = 0; i < MAX_FACES; i++) {
		if (faces[i].w) {
			free (faces[i].w);
			faces[i].w = NULL;
		}
	}
}

- (void) dealloc
{
	[self freeWindings];
	return [super dealloc];
}

/*
===========
initOwner: fromTokens
===========
*/
int  numsb;

static vec3_t *
ParseVerts (script_t *script, int *n_verts)
{
	vec3_t     *verts;
	int         i;

	if (strcmp (Script_Token (script), ":"))
		Sys_Error ("parsing map file");
	Script_GetToken (script, false);
	*n_verts = atoi (Script_Token (script));
	verts = malloc (sizeof (vec3_t) * *n_verts);

	for (i = 0; i < *n_verts; i++) {
		Script_GetToken (script, true);
		verts[i][0] = atof (Script_Token (script));
		Script_GetToken (script, true);
		verts[i][1] = atof (Script_Token (script));
		Script_GetToken (script, true);
		verts[i][2] = atof (Script_Token (script));
	}
	return verts;
}

- (id) initFromScript: (script_t *)script owner: own
{
	face_t  *f;
	vec3_t  *verts = 0;
	int     n_verts = 0;
	int     i, j;

	[self init];

	parent = own;

	f = faces;
	numfaces = 0;

	if (!Script_GetToken (script, true))
		return self;

	if (strcmp (Script_Token (script), "(")) {
		verts = ParseVerts (script, &n_verts);
	} else {
		Script_UngetToken (script);
	}

	do {
		if (!Script_GetToken (script, true))
			break;
		if (!strcmp (Script_Token (script), "}"))
			break;

		if (verts) {
			int     n_v, v;
			n_v = atoi (Script_Token (script));
			Script_GetToken (script, false);
			for (i = 0; i < n_v; i++) {
				Script_GetToken (script, false);
				v = atoi (Script_Token (script));
				if (i < 3)
					VectorCopy (verts[v], f->planepts[i]);
			}
			printf ("\n");
			Script_GetToken (script, false);
		} else {
			for (i = 0; i < 3; i++) {
				if (i != 0)
					Script_GetToken (script, true);
				if (strcmp (Script_Token (script), "("))
					Sys_Error ("parsing map file");

				for (j = 0; j < 3; j++) {
					Script_GetToken (script, false);
					f->planepts[i][j] = atoi (Script_Token (script));
				}
				Script_GetToken (script, false);

				if (strcmp (Script_Token (script), ")"))
					Sys_Error ("parsing map file");
			}
		}

		Script_GetToken (script, false);
		strcpy (f->texture.texture, Script_Token (script));
		Script_GetToken (script, false);
		f->texture.shift[0] = atof (Script_Token (script));
		Script_GetToken (script, false);
		f->texture.shift[1] = atof (Script_Token (script));
		Script_GetToken (script, false);
		f->texture.rotate = atof (Script_Token (script));
		Script_GetToken (script, false);
		f->texture.scale[0] = atof (Script_Token (script));
		Script_GetToken (script, false);
		f->texture.scale[1] = atof (Script_Token (script));

		while (Script_TokenAvailable (script, false)) {
			Script_GetToken (script, false);
			if (!strcmp (Script_Token (script), "detail"))
				; // XXX implement
			else
				Sys_Error ("parsing map file");
		}

#if 0
		flags = atoi (Script_Token (script));

		flags &= 7;

		f->texture.rotate = 0;
		f->texture.scale[0] = 1;
		f->texture.scale[1] = 1;

# define TEX_FLIPAXIS   1
# define TEX_FLIPS      2
# define TEX_FLIPT      4

		if (flags & TEX_FLIPAXIS) {
			f->texture.rotate = 90;
			if (!(flags & TEX_FLIPT))
				f->texture.scale[0] = -1;
			if (flags & TEX_FLIPS)
				f->texture.scale[1] = -1;
		} else {
			if (flags & TEX_FLIPS)
				f->texture.scale[0] = -1;
			if (flags & TEX_FLIPT)
				f->texture.scale[1] = -1;
		}
#endif /* if 0 */
		f++;
		numfaces++;
	} while (1);

	numsb++;

	[self calcWindings];

	return self;
}

/*
===========
writeToFILE
===========
*/
- (void) writeToFILE: (FILE *)f
   region: (BOOL)reg
{
	int  i, j;
	face_t          *fa;
	texturedef_t    *td;

	if (reg && regioned)
		return;
	fprintf (f, "{\n");
	for (i = 0; i < numfaces; i++) {
		fa = &faces[i];
		for (j = 0; j < 3; j++) {
			fprintf (f, "( %d %d %d ) ", (int) fa->planepts[j][0],
			         (int) fa->planepts[j][1], (int) fa->planepts[j][2]);
		}
		td = &fa->texture;
		fprintf (f, "%s %d %d %d %f %f\n", td->texture, (int) td->shift[0],
		         (int) td->shift[1], (int) td->rotate, td->scale[0],
		         td->scale[1]);
	}
	fprintf (f, "}\n");

	return;
}

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

INTERACTION

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

- (void) getMins: (vec3_t)mins
   maxs: (vec3_t)maxs
{
	VectorCopy (bmins, mins);
	VectorCopy (bmaxs, maxs);
}

- (BOOL) selected
{
	return selected;
}

- (void) setSelected: (BOOL)s
{
	selected = s;
}

- (BOOL) regioned
{
	return regioned;
}

- (void) setRegioned: (BOOL)s
{
	regioned = s;
}

/*
===========
setTexturedef
===========
*/
- (void) setTexturedef: (texturedef_t *)tex
{
	int  i;

	for (i = 0; i < MAX_FACES; i++) {
		faces[i].texture = *tex;
		faces[i].qtexture = NULL;   // recache next render
	}

	[self calcWindings];            // in case texture coords changed
}

- (void) setTexturedef: (texturedef_t *)tex
   forFace: (int)f
{
	if (f > numfaces)
		Sys_Error ("setTexturedef:forFace: bad face number %i", f);
	faces[f].texture = *tex;
	faces[f].qtexture = NULL;       // recache next render

	[self calcWindings];            // in case texture coords changed
}

/*
===========
texturedef
===========
*/
- (texturedef_t *) texturedef
{
	return &faces[0].texture;
}

- (texturedef_t *) texturedefForFace: (int)f
{
	return &faces[f].texture;
}

/*
===========
removeIfInvalid

So created veneers don't stay around
===========
*/
- (id) removeIfInvalid
{
	int  i, j;

	for (i = 0; i < numfaces; i++) {
		if (faces[i].w)
			continue;
		for (j = i + 1; j < numfaces; j++)
			faces[j - 1] = faces[j];

		i--;
		numfaces--;
	}
	for ( ; i < MAX_FACES; i++)
		faces[i].w = NULL;

	if (numfaces < 4) {
		invalid = YES;
		[self remove];
		return nil;
	}
	return self;
}

/*
===========
containsPoint

===========
*/
- (BOOL) containsPoint: (vec3_t)pt
{
	int  i;

	for (i = 0; i < numfaces; i++) {
		if (DotProduct (faces[i].plane.normal, pt) >= faces[i].plane.dist)
			return NO;
	}
	return YES;
}

/*
===========
clipRay::::::

===========
*/
- (id) clipRay: (vec3_t)p1
   : (vec3_t)p2
   : (vec3_t)frontpoint
   : (int *)f_face
   : (vec3_t)backpoint
   : (int *)b_face
{
	int     frontface, backface;
	int     i, j;
	face_t  *f;
	float   d1, d2, m;
	float   *start;

	start = p1;

	frontface = -2;
	backface = -2;

	f = faces;
	for (i = 0; i < numfaces; i++, f++) {
		if (!f->w)
			continue;               // clipped off plane
		d1 = DotProduct (p1, f->plane.normal) - f->plane.dist;
		d2 = DotProduct (p2, f->plane.normal) - f->plane.dist;
		if (d1 >= 0 && d2 >= 0) {   // the whole ray is in front of the polytope
			*f_face = -1;
			*b_face = -1;
			return self;
		}
		if (d1 > 0 && d2 < 0) {     // new front plane
			frontface = i;
			m = d1 / (d1 - d2);
			for (j = 0; j < 3; j++)
				frontpoint[j] = p1[j] + m * (p2[j] - p1[j]);

			p1 = frontpoint;
		}
		if (d1 < 0 && d2 > 0) {     // new back plane
			backface = i;
			m = d1 / (d1 - d2);
			for (j = 0; j < 3; j++)
				backpoint[j] = p1[j] + m * (p2[j] - p1[j]);

			p2 = backpoint;
		}
	}

	*f_face = frontface;
	*b_face = backface;

	return self;
}

/*
===========
hitByRay::::

===========
*/
- (void) hitByRay: (vec3_t)p1
   : (vec3_t)p2
   : (float *)time
   : (int *)face
{
	vec3_t  frontpoint, backpoint, dir;
	int     frontface, backface;

	if (regioned) {
		*time = -1;
		*face = -1;
		return;
	}

	[self clipRay: p1 : p2 : frontpoint : &frontface : backpoint : &backface];

	if (frontface == -2 && backface == -2) {
		// entire ray is inside the brush, select first face
		*time = 0;
		*face = 0;
		return;
	}

	if (frontface < 0) {    // ray started inside the polytope, don't select it
		*time = -1;
		*face = -1;
		return;
	}

	VectorSubtract (p2, p1, dir);
	VectorNormalize (dir);
	VectorSubtract (frontpoint, p1, frontpoint);
	*time = DotProduct (frontpoint, dir);

	if (*time < 0)
		Sys_Error ("hitByRay: negative t");

	*face = frontface;
}

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

DRAWING ROUTINES

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

BOOL  fakebrush;

- (id) drawConnections
{
	id          obj;
	int         c, i;
	vec3_t      dest, origin;
	vec3_t      mid;
	vec3_t      forward, right;
	const char  *targname;
	vec3_t      min, max, temp;
	const char  *targ;

	targ = [parent valueForQKey: "target"];

	if (!targ || !targ[0])
		return self;

	origin[0] = (bmins[0] + bmaxs[0]) / 2;
	origin[1] = (bmins[1] + bmaxs[1]) / 2;

	c = [map_i count];
	for (i = 0; i < c; i++) {
		obj = [map_i objectAtIndex: i];
		targname = [obj valueForQKey: "targetname"];
		if (strcmp (targ, targname))
			continue;

		[[obj objectAtIndex: 0] getMins: min maxs: max];
		dest[0] = (min[0] + max[0]) / 2;
		dest[1] = (min[1] + max[1]) / 2;

		XYmoveto (origin);
		XYlineto (dest);

		forward[0] = dest[0] - origin[0];
		forward[1] = dest[1] - origin[1];
		forward[2] = 0;

		if (!forward[0] && !forward[1])
			continue;
		VectorNormalize (forward);
		forward[0] = 8 * forward[0];
		forward[1] = 8 * forward[1];
		right[0] = forward[1];
		right[1] = -forward[0];

		mid[0] = (dest[0] + origin[0]) / 2;
		mid[1] = (dest[1] + origin[1]) / 2;

		temp[0] = mid[0] + right[0] - forward[0];
		temp[1] = mid[1] + right[1] - forward[1];
		XYmoveto (temp);
		XYlineto (mid);
		temp[0] = mid[0] - right[0] - forward[0];
		temp[1] = mid[1] - right[1] - forward[1];
		XYlineto (temp);
	}

	return self;
}

- (BOOL) fakeBrush: (SEL)call
{
	id      copy;
	face_t  face;

	if (!selected || fakebrush)
		return NO;

	if (![clipper_i getFace: &face])
		return NO;

	fakebrush = YES;
	copy = [self copy];
	copy = [copy addFace: &face];
	if (copy) {
		[copy performSelector: call];
		[copy dealloc];
	}
	fakebrush = NO;
	return YES;
}

/*
===========
XYDrawSelf
===========
*/
- (void) XYDrawSelf
{
	int         i, j;
	winding_t   *w;
	vec3_t      mid, end, s1, s2;
	const char  *val;
	float       ang;
	id          worldent, currentent;
	BOOL        keybrush;

	if ([self fakeBrush: @selector (XYDrawSelf)])
		return;

	[xyview_i addToScrollRange: bmins[0] : bmins[1]];
	[xyview_i addToScrollRange: bmaxs[0] : bmaxs[1]];

	worldent = [map_i objectAtIndex: 0];
	currentent = [map_i currentEntity];

	if (parent != worldent && self == [parent objectAtIndex: 0])
		keybrush = YES;
	else
		keybrush = NO;

	if (parent != worldent && worldent == currentent)
		linecolor (entitycolor[0], entitycolor[1], entitycolor[2]);
	else if (selected)              // selected
		linecolor (1, 0, 0);
	else if (parent == currentent)  // unselected, but in same entity
		linecolor (0, 0, 0);
	else                            // other entity green
		linecolor (0, 0.5, 0);

	if (keybrush)
		[self drawConnections];     // target line

	if (!selected &&
	    (bmaxs[0] < xy_draw_rect.origin.x
	     || bmaxs[1] < xy_draw_rect.origin.y
	     || bmins[0] > xy_draw_rect.origin.x + xy_draw_rect.size.width
	     || bmins[1] > xy_draw_rect.origin.y + xy_draw_rect.size.height))
		return;                     // off view, don't bother

	for (i = 0; i < numfaces; i++) {
		w = faces[i].w;
		if (!w)
			continue;
		if (DotProduct (faces[i].plane.normal, xy_viewnormal) > -VECTOR_EPSILON)
			continue;

		XYmoveto (w->points[w->numpoints - 1]);
		for (j = 0; j < w->numpoints; j++)
			XYlineto (w->points[j]);
	}

	if (keybrush) {
		val = [parent valueForQKey: "angle"];   // angle arrow
		if (val && val[0]) {
			ang = atof (val) * M_PI / 180;
			if (ang > 0) {          // negative values are up/down flags
				mid[0] = (bmins[0] + bmaxs[0]) / 2;
				mid[1] = (bmins[1] + bmaxs[1]) / 2;

				end[0] = mid[0] + 16 * cos (ang);
				end[1] = mid[1] + 16 * sin (ang);

				s1[0] = mid[0] + 12 * cos (ang + 0.4);
				s1[1] = mid[1] + 12 * sin (ang + 0.4);

				s2[0] = mid[0] + 12 * cos (ang - 0.4);
				s2[1] = mid[1] + 12 * sin (ang - 0.4);

				XYmoveto (mid);
				XYlineto (end);
				XYmoveto (s1);
				XYlineto (end);
				XYlineto (s2);
			}
		}
	}

	return;
}

/*
===========
ZDrawSelf
===========
*/
- (void) ZDrawSelf
{
	int         i;
	NSPoint     p;
	vec3_t      p1, p2;
	vec3_t      frontpoint, backpoint;
	int         frontface, backface;
	qtexture_t  *q;

	if ([self fakeBrush: @selector (ZDrawSelf)])
		return;

	[zview_i addToHeightRange: bmins[2]];
	[zview_i addToHeightRange: bmaxs[2]];

	if (selected) {
		[[NSColor redColor] set];
		NSFrameRect (NSMakeRect (1, bmins[2], 24, bmaxs[2] - bmins[2]));
	}

	[zview_i getPoint: &p];
	p1[0] = p.x;
	p1[1] = p.y;
	for (i = 0; i < 2; i++) {
		if (bmins[i] >= p1[i] || bmaxs[i] <= p1[i])
			return;
	}

	p1[2] = 4096;
	p2[0] = p1[0];
	p2[1] = p1[1];
	p2[2] = -4096;

	[self clipRay: p1 : p2 : frontpoint : &frontface : backpoint : &backface];

	if (frontface == -1 || backface == -1)
		return;

	q = TEX_ForName (faces[frontface].texture.texture);

	[[NSColor colorWithCalibratedRed: q->flatcolor.chan[0] / 255.0
	                           green: q->flatcolor.chan[1] / 255.0
	                            blue: q->flatcolor.chan[2] / 255.0
	                           alpha: 1.0] set];
	NSRectFill (NSMakeRect (-8, backpoint[2],
	                        17, frontpoint[2] - backpoint[2] + 1));
	[[NSColor blackColor] set];
	NSFrameRect (NSMakeRect (-12, backpoint[2],
	                         25, frontpoint[2] - backpoint[2] + 1));

	return;
}

/*
===========
CameraDrawSelf
===========
*/
- (void) CameraDrawSelf
{
	int         i, j;
	winding_t   *w;
	id          worldent, currentent;

	if ([self fakeBrush: @selector (CameraDrawSelf)])
		return;

	worldent = [map_i objectAtIndex: 0];
	currentent = [map_i currentEntity];

	if (parent != worldent && worldent == currentent)
		linecolor (entitycolor[0], entitycolor[1], entitycolor[2]);
	else if (selected)
		linecolor (1, 0, 0);
	else if (parent == [map_i currentEntity])
		linecolor (0, 0, 0);
	else
		linecolor (0, 0.5, 0);

	for (i = 0; i < numfaces; i++) {
		w = faces[i].w;
		if (!w)
			continue;
		CameraMoveto (w->points[w->numpoints - 1]);
		for (j = 0; j < w->numpoints; j++)
			CameraLineto (w->points[j]);
	}
	return;
}

/*
===========
XYRenderSelf
===========
*/
- (void) XYRenderSelf
{
	int  i;

	if ([self fakeBrush: @selector (XYRenderSelf)])
		return;

	for (i = 0; i < numfaces; i++)
		REN_DrawXYFace (&faces[i]);

	return;
}

/*
===========
CameraRenderSelf
===========
*/
- (void) CameraRenderSelf
{
	int     i;
	BOOL    olddraw;
	extern qtexture_t   badtex;
	pixel32_t           p;

	if ([self fakeBrush: @selector (CameraRenderSelf)])
		return;

	// hack to draw entity boxes as single flat color
	if (![parent modifiable]) {
		olddraw = r_drawflat;
		r_drawflat = YES;

		p = badtex.flatcolor;

		badtex.flatcolor.chan[0] = entitycolor[0] * 255;
		badtex.flatcolor.chan[1] = entitycolor[1] * 255;
		badtex.flatcolor.chan[2] = entitycolor[2] * 255;

		for (i = 0; i < numfaces; i++)
			REN_DrawCameraFace (&faces[i]);

		badtex.flatcolor = p;
		r_drawflat = olddraw;
	} else {
		for (i = 0; i < numfaces; i++)
			REN_DrawCameraFace (&faces[i]);
	}

	return;
}

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

SINGLE BRUSH ACTIONS

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

face_t  *dragface, *dragface2;

int     numcontrolpoints;
float   *controlpoints[MAX_FACES * 3];

- (BOOL) checkModifiable
{
//  int     i;

	if ([parent modifiable])
		return YES;

	// don't stretch spawned entities, move all points
#if 0
	numcontrolpoints = numfaces * 3;

	for (i = 0; i < numcontrolpoints; i++)
		controlpoints[i] = faces[i / 3].planepts[i % 3];
#endif

	return NO;
}

- (void) getZdragface: (vec3_t)dragpoint
{
	int     i, j;
	float   d;

	if (![self checkModifiable])
		return;

	numcontrolpoints = 0;

	for (i = 0; i < numfaces; i++) {
		if (!faces[i].w)
			continue;

		if (faces[i].plane.normal[2] == 1)
			d = dragpoint[2] - faces[i].plane.dist;
		else if (faces[i].plane.normal[2] == -1)
			d = -faces[i].plane.dist - dragpoint[2];
		else
			continue;

		if (d <= 0)
			continue;

		for (j = 0; j < 3; j++) {
			controlpoints[numcontrolpoints] = faces[i].planepts[j];
			numcontrolpoints++;
		}
	}
}

- (void) getXYdragface: (vec3_t)dragpoint
{
	int     i, j;
	float   d;

	numcontrolpoints = 0;

	if (![self checkModifiable])
		return;

	for (i = 0; i < numfaces; i++) {
		if (!faces[i].w)
			continue;

		if (faces[i].plane.normal[2])
			continue;

		d = DotProduct (faces[i].plane.normal, dragpoint) - faces[i].plane.dist;
		if (d <= 0)
			continue;

		for (j = 0; j < 3; j++) {
			controlpoints[numcontrolpoints] = faces[i].planepts[j];
			numcontrolpoints++;
		}
	}
}

- (void) getXYShearPoints: (vec3_t)dragpoint
{
	int         i, j, k;
	int         facectl;
	float       d;
	int         numdragplanes;
	BOOL        dragplane[MAX_FACES];
	winding_t   *w;
	face_t      *f;
	BOOL        onplane[MAX_POINTS_ON_WINDING];

	if (![self checkModifiable])
		return;
	numcontrolpoints = 0;
	numdragplanes = 0;
	for (i = 0; i < numfaces; i++) {
		dragplane[i] = NO;
		if (!faces[i].w)
			continue;
//		if (faces[i].plane.normal[2])
//			continue;

		d = DotProduct (faces[i].plane.normal, dragpoint) - faces[i].plane.dist;
		if (d <= -ON_EPSILON)
			continue;
		dragplane[i] = YES;
		numdragplanes++;
	}

	// find faces that just share an edge with a drag plane
	for (i = 0; i < numfaces; i++) {
		f = &faces[i];
		w = f->w;
		if (!w)
			continue;

		if (dragplane[i] && numdragplanes == 1) {
			for (j = 0; j < 3; j++) {
				controlpoints[numcontrolpoints] = faces[i].planepts[j];
				numcontrolpoints++;
			}
			continue;
		}

		if (!dragplane[i] && numdragplanes > 1)
			continue;

		facectl = 0;
		for (j = 0; j < w->numpoints; j++) {
			onplane[j] = NO;
			for (k = 0; k < numfaces; k++) {
				if (!dragplane[k])
					continue;
				if (k == i)
					continue;
				d = DotProduct (w->points[j], faces[k].plane.normal)
				    - faces[k].plane.dist;
				if (fabs (d) > ON_EPSILON)
					continue;
				onplane[j] = YES;
				facectl++;
				break;
			}
		}

		if (facectl == 0)
			continue;

		// find one or two static points to go with the controlpoints
		// and change the plane points
		k = 0;
		for (j = 0; j < w->numpoints; j++) {
			if (!onplane[j])
				continue;
			if (facectl >= 2 && !onplane[(j + 1) % w->numpoints])
				continue;
			if (facectl == 3 && !onplane[(j + 2) % w->numpoints])
				continue;
			VectorCopy (w->points[j], f->planepts[k]);
			controlpoints[numcontrolpoints] = f->planepts[k];
			numcontrolpoints++;
			k++;

			if (facectl >= 2) {
				VectorCopy (w->points[(j + 1) % w->numpoints], f->planepts[k]);
				controlpoints[numcontrolpoints] = f->planepts[k];
				numcontrolpoints++;
				k++;
			}
			if (facectl == 3) {
				VectorCopy (w->points[(j + 2) % w->numpoints], f->planepts[k]);
				controlpoints[numcontrolpoints] = f->planepts[k];
				numcontrolpoints++;
				k++;
			}
			break;
		}

		for ( ; j < w->numpoints && k != 3; j++) {
			if (!onplane[j]) {
				VectorCopy (w->points[j], f->planepts[k]);
				k++;
			}
		}

		for (j = 0; j < w->numpoints && k != 3; j++) {
			if (!onplane[j]) {
				VectorCopy (w->points[j], f->planepts[k]);
				k++;
			}
		}

		if (k != 3) {
//          Sys_Error ("getXYShearPoints: didn't get three points on plane");
			numcontrolpoints = 0;
			return;
		}

		for (j = 0; j < 3; j++) {
			for (k = 0; k < 3; k++)
				f->planepts[j][k] = rint (f->planepts[j][k]);
		}
	}
}

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

MULTIPLE BRUSH ACTIONS

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

vec3_t  region_min, region_max;

/*
===========
newRegion

Set the regioned flag based on if the object is containted in region_min/max
===========
*/
- (id) newRegion
{
	int         i;
	const char  *name;

// filter away entities
	if (parent != [map_i objectAtIndex: 0]) {
		if (filter_entities) {
			regioned = YES;
			return self;
		}

		name = [parent valueForQKey: "classname"];

		if ((filter_light && !strncmp (name, "light", 5))
		    || (filter_path && !strncmp (name, "path", 4))) {
			regioned = YES;
			return self;
		}
	} else if (filter_world) {
		regioned = YES;
		return self;
	}

	if (filter_clip_brushes && !strcasecmp (faces[0].texture.texture, "clip")) {
		regioned = YES;
		return self;
	}

	if (filter_water_brushes && faces[0].texture.texture[0] == '*') {
		regioned = YES;
		return self;
	}

	for (i = 0; i < 3; i++) {
		if (region_min[i] >= bmaxs[i] || region_max[i] <= bmins[i]) {
			if (selected)
				[self deselect];
			regioned = YES;
			return self;
		}
	}

	regioned = NO;
	return self;
}

vec3_t  select_min, select_max;

- (void) selectPartial
{
	int  i;

	for (i = 0; i < 3; i++) {
		if (select_min[i] >= bmaxs[i] || select_max[i] <= bmins[i])
			return;
	}
	selected = YES;
}

- (void) selectComplete
{
	int  i;

	for (i = 0; i < 3; i++) {
		if (select_min[i] > bmins[i] || select_max[i] < bmaxs[i])
			return;
	}
	selected = YES;
}

- (void) regionPartial
{
	int  i;

	for (i = 0; i < 3; i++) {
		if (select_min[i] >= bmaxs[i] || select_max[i] <= bmins[i])
			return;
	}
	selected = YES;
}

- (void) regionComplete
{
	int  i;

	for (i = 0; i < 3; i++) {
		if (select_min[i] > bmins[i] || select_max[i] < bmaxs[i])
			return;
	}
	selected = YES;
}

id  sb_newowner;

- (void) moveToEntity
{
	id      eclass;
	float   *c;

	[parent removeObject: self];
	parent = sb_newowner;

// hack to allow them to be copied to another map
	if ([parent respondsToSelector: @selector (valueForQKey:)]) {
		eclass = [entity_classes_i classForName: [parent valueForQKey: "classname"]];
		c = [eclass drawColor];
		[self setEntityColor: c];
	}

	[parent addObject: self];
}

vec3_t  sb_translate;

- (void) translate
{
	int  i, j;

// move the planes
	for (i = 0; i < numfaces; i++) {
		for (j = 0; j < 3; j++)
			VectorAdd (faces[i].planepts[j], sb_translate, faces[i].planepts[j]);
	}

	[self calcWindings];
}

vec3_t  sb_mins, sb_maxs;

- (void) addToBBox
{
	int  k;

	if (numfaces < 4)
		return;

	for (k = 0; k < 3; k++) {
		if (bmins[k] < sb_mins[k])
			sb_mins[k] = bmins[k];
		if (bmaxs[k] > sb_maxs[k])
			sb_maxs[k] = bmaxs[k];
	}
}

- (void) flushTextures
{                                   // call when texture palette changes
	int  i;

	for (i = 0; i < MAX_FACES; i++)
		faces[i].qtexture = NULL;

	[self calcWindings];
}

- (void) select
{
	[map_i setCurrentEntity: parent];
	selected = YES;
}

- (void) deselect
{
	selected = NO;

// the last selected brush determines
	if (invalid)
		printf ("WARNING: deselected invalid brush\n");
	[map_i setCurrentMinZ: bmins[2]];
	[map_i setCurrentMaxZ: bmaxs[2]];
}

- (void) remove
{
// the last selected brush determines
	if (!invalid) {
		[map_i setCurrentMinZ: bmins[2]];
		[map_i setCurrentMaxZ: bmaxs[2]];
	}

	[parent removeObject: self];
	[self release];
}

vec3_t  sel_x, sel_y, sel_z;
vec3_t  sel_org;

- (void) transform
{
	int     i, j;
	vec3_t  old;
	float   *p;

	for (i = 0; i < numfaces; i++) {
		for (j = 0; j < 3; j++) {
			p = faces[i].planepts[j];
			VectorCopy (p, old);
			VectorSubtract (old, sel_org, old);
			p[0] = DotProduct (old, sel_x);
			p[1] = DotProduct (old, sel_y);
			p[2] = DotProduct (old, sel_z);
			VectorAdd (p, sel_org, p);
		}
	}

	[self calcWindings];
}

- (void) flipNormals                // used after an inside-out transform
                                    // (flip x/y/z)
{
	int     i;
	vec3_t  temp;

	for (i = 0; i < numfaces; i++) {
		VectorCopy (faces[i].planepts[0], temp);
		VectorCopy (faces[i].planepts[2], faces[i].planepts[0]);
		VectorCopy (temp, faces[i].planepts[2]);
	}
	[self calcWindings];
}

- (void) carveByClipper
{
	face_t  face;

	if (![clipper_i getFace: &face])
		return;

	[self addFace: &face];
}

- (void) takeCurrentTexture
{
	texturedef_t  td;

	[texturepalette_i getTextureDef: &td];
	[self setTexturedef: &td];
}

float  sb_floor_dir, sb_floor_dist;

- (void) feetToFloor
{
	float   oldz;
	vec3_t  p1, p2;
	int     frontface, backface;
	vec3_t  frontpoint, backpoint;
	float   dist;

	[cameraview_i getOrigin: p1];
	VectorCopy (p1, p2);
	oldz = p1[2] - 48;

	p1[2] = 4096;
	p2[2] = -4096;

	[self clipRay: p1 : p2 : frontpoint : &frontface : backpoint : &backface];
	if (frontface == -1)
		return;

	dist = frontpoint[2] - oldz;

	if (sb_floor_dir == 1) {
		if (dist > 0 && dist < sb_floor_dist)
			sb_floor_dist = dist;
		else if (dist < 0 && dist > sb_floor_dist)
			sb_floor_dist = dist;
	}
}

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

BRUSH SUBTRACTION

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

vec3_t  carvemin, carvemax;
int     numcarvefaces;
face_t  *carvefaces;
id      carve_in, carve_out;

// returns the new brush formed after the addition of the given plane
// nil is returned if it faced all of the original setbrush
- (id) addFace: (face_t *)f
{
	if (numfaces == MAX_FACES)
		Sys_Error ("addFace: numfaces == MAX_FACES");
	faces[numfaces] = *f;
	faces[numfaces].texture = faces[0].texture;
	faces[numfaces].qtexture = NULL;
	faces[numfaces].w = NULL;
	numfaces++;
	[self calcWindings];

// remove any degenerate faces
	return [self removeIfInvalid];
}

- (void) clipByFace: (face_t *)fa
   front: (id *)f
   back: (id *)b
{
	id      front, back;
	face_t  fb;
	vec3_t  temp;

	fb = *fa;
	VectorCopy (fb.planepts[0], temp);
	VectorCopy (fb.planepts[2], fb.planepts[0]);
	VectorCopy (temp, fb.planepts[2]);

	front = [self copy];
	back = [self copy];

	*b = [back addFace: fa];
	*f = [front addFace: &fb];
}

- (id) carve
{
	int     i;
	id      front, back;

#if 0
	if ((i = NSMallocCheck ()))
		Sys_Error ("MallocCheck failure");
#endif

	// check bboxes
	for (i = 0; i < 3; i++) {
		if (bmins[i] >= carvemax[i] || bmaxs[i] <= carvemin[i]) {
			[carve_out addObject: self];
			return self;
		}
	}

	// carve by the planes
	back = self;
	for (i = 0; i < numcarvefaces; i++) {
		[back clipByFace: &carvefaces[i] front: &front back: &back];
		if (front)
			[carve_out addObject: front];
		if (!back)
			return nil;             // nothing completely inside
	}

	[carve_in addObject: back];
	return self;
}

/*
==================
setCarveVars
==================
*/
- (void) setCarveVars
{
	VectorCopy (bmins, carvemin);
	VectorCopy (bmaxs, carvemax);
	numcarvefaces = numfaces;
	carvefaces = faces;
}

- (int) getNumBrushFaces
{
	return numfaces;
}

- (face_t *) getBrushFace: (int)which
{
	return &faces[which];
}

@end