ftp.nice.ch/pub/next/games/action/QuakeEd.s.tar.gz#/QuakeEd/SetBrush.m

This is SetBrush.m in view mode; [Download] [Up]

#import "qedefs.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)
		Error ("CheckFace: no winding");
		
	if (w->numpoints < 3)
		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)
				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)
			Error ("CheckFace: point off plane");
	
	// check the edge isn't degenerate
		p2 = w->points[j];
		VectorSubtract (p2, p1, dir);
		
		if (VectorLength (dir) < ON_EPSILON)
			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)
				Error ("CheckFace: non-convex");
		}
	}
}


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

			TURN PLANES INTO GROUPS OF FACES

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


/*
==================
NewWinding
==================
*/
winding_t *NewWinding (int points)
{
	winding_t	*w;
	int			size;
	
	if (points > MAX_POINTS_ON_WINDING)
		Error ("NewWinding: %i points", points);
	
	size = (int)((winding_t *)0)->points[points];
	w = malloc (size);
	memset (w, 0, size);
	
	return w;
}


/*
==================
CopyWinding
==================
*/
winding_t	*CopyWinding (winding_t *w)
{
	int			size;
	winding_t	*c;
	
	size = (int)((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;
	
	maxpts = in->numpoints+4;	// can't use counts[0]+2 because
								// of fp grouping errors
	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)
		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)
		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);
	VectorMA (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.
===========
*/
- 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])
			continue;			// duplicate plane
			
		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:::
===========
*/
- 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;
}

- 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;
}

- parent
{
	return parent;
}

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

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

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

- copyFromZone:(NXZone *)zone
{
	id	new;
	
	[self freeWindings];
	new = [super copyFromZone: zone];
	
	[self calcWindings];
	[new calcWindings];
	
	return new;
}

- free
{
	[self freeWindings];
	return [super free];
}

/*
===========
initOwner: fromTokens
===========
*/
int		numsb;
- initFromTokens: own
{
	face_t	*f;
	int		i,j;

	[self init];
	
	parent = own;
	
	f = faces;
	numfaces = 0;
	do
	{
		if (!GetToken (true))
			break;
		if (!strcmp (token, "}") )
			break;
			
		for (i=0 ; i<3 ; i++)
		{
			if (i != 0)
				GetToken (true);
			if (strcmp (token, "(") )
				Error ("parsing map file");
			
			for (j=0 ; j<3 ; j++)
			{
				GetToken (false);
				f->planepts[i][j] = atoi(token);
			}
			
			GetToken (false);
			if (strcmp (token, ")") )
				Error ("parsing map file");
		}

		GetToken (false);
		strcpy (f->texture.texture, token);
		GetToken (false);
		f->texture.shift[0] = atof(token);
		GetToken (false);
		f->texture.shift[1] = atof(token);
		GetToken (false);
		f->texture.rotate = atof(token);
		GetToken (false);
		f->texture.scale[0] = atof(token);
		GetToken (false);
		f->texture.scale[1] = atof(token);
		
#if 0
		flags = atoi(token);
		
		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		
		f++;
		numfaces++;
	} while (1);
	
	numsb++;

	[self calcWindings];

	return self;
}

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

	if (reg && regioned)
		return self;

	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 self;
}



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

INTERACTION

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

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


- (BOOL)selected
{
	return selected;
}

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

- (BOOL)regioned
{
	return regioned;
}

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


/*
===========
setTexturedef
===========
*/
- 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
	return self;
}

- setTexturedef: (texturedef_t *)tex forFace:(int)f
{
	if ( (unsigned)f > numfaces)
		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
	return self;
}

/*
===========
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
===========
*/
- 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

===========
*/
- 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 entire 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

===========
*/
- 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;
	}
	
	[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 self;
	}
	
	
	if (frontface < 0)
	{	// ray started inside the polytope, don't select it
		*time = -1;
		*face = -1;
		return self;
	}
	
	VectorSubtract (p2, p1, dir);
	VectorNormalize (dir);
	VectorSubtract (frontpoint, p1, frontpoint);
	*time = DotProduct (frontpoint, dir);

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

	*face = frontface;
		
	return self;
}


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

DRAWING ROUTINES

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

BOOL	fakebrush;

- drawConnections
{
	id	obj;
	int	c, i;
	vec3_t	dest, origin;
	vec3_t	mid;
	vec3_t	forward, right;
	char	*targname;	
	vec3_t	min, max, temp;
	char	targ[64];
	
	strcpy (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 objectAt: i];
		targname = [obj valueForQKey: "targetname"];
		if (strcmp (targ, targname))
			continue;
			
		[[obj objectAt: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 perform:call];
		[copy free];
	}
	fakebrush = NO;
	return YES;
}

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

	if ([self fakeBrush: @selector(XYDrawSelf)])
		return self;
	
	[xyview_i addToScrollRange: bmins[0] : bmins[1]];
	[xyview_i addToScrollRange: bmaxs[0] : bmaxs[1]];

	worldent = [map_i objectAt: 0];
	currentent = [map_i currentEntity];
	
	if (parent != worldent && self == [parent objectAt: 0])
		keybrush = YES;
	else
		keybrush = NO;

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

	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 self;	// 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)
	{
// angle arrow
		val = [parent valueForQKey: "angle"];
		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 self;
}

/*
===========
ZDrawSelf
===========
*/
- ZDrawSelf
{
	int			i;
	vec3_t		p1, p2;
	vec3_t		frontpoint, backpoint;
	int			frontface, backface;
	qtexture_t	*q;
	
	if ([self fakeBrush: @selector(ZDrawSelf)])
		return self;

	[zview_i addToHeightRange: bmins[2]];
	[zview_i addToHeightRange: bmaxs[2]];
	
	if (selected)
	{		
		PSmoveto (1, bmaxs[2]);
		PSlineto (23, bmaxs[2]);
		PSlineto (23, bmins[2]);
		PSlineto (1, bmins[2]);
		PSlineto (1, bmaxs[2]);
		PSsetrgbcolor (1,0,0);
		PSstroke ();
	}

	[zview_i getPoint: (NXPoint *)p1];
	
	for (i=0 ; i<2 ; i++)
		if (bmins[i] >= p1[i] || bmaxs[i] <= p1[i])
			return self;
	
	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 self;
		
	q = TEX_ForName (faces[frontface].texture.texture);
	
	PSmoveto (-8, frontpoint[2]);
	PSlineto (8, frontpoint[2]);
	PSlineto (8, backpoint[2]);
	PSlineto (-8, backpoint[2]);
	PSlineto (-8, frontpoint[2]);
	
	PSsetrgbcolor (q->flatcolor.chan[0]/255.0
			, q->flatcolor.chan[1]/255.0
			, q->flatcolor.chan[2]/255.0);
	PSfill ();

	PSmoveto (-12, frontpoint[2]);
	PSlineto (12, frontpoint[2]);
	PSlineto (12, backpoint[2]);
	PSlineto (-12, backpoint[2]);
	PSlineto (-12, frontpoint[2]);
	
	PSsetrgbcolor (0,0,0);
	PSstroke ();
			
	return self;
}

/*
===========
CameraDrawSelf
===========
*/
- CameraDrawSelf
{
	int		i, j;
	winding_t	*w;
	id		worldent, currentent;
	
	if ([self fakeBrush: @selector(CameraDrawSelf)])
		return self;
	
	worldent = [map_i objectAt: 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 self;
}


/*
===========
XYRenderSelf
===========
*/
- XYRenderSelf
{
	int		i;
	
	if ([self fakeBrush: @selector(XYRenderSelf)])
		return self;
	
	for (i=0 ; i<numfaces ; i++)
		REN_DrawXYFace (&faces[i]);
		
	return self;
}

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

	if ([self fakeBrush: @selector(CameraRenderSelf)])
		return self;
// 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 self;
}

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

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;
}

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

	if (![self checkModifiable])
		return self;

	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++;
		}
	}
	
	return self;
}

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

	numcontrolpoints = 0;	

	if (![self checkModifiable])
		return self;
		
	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++;
		}
	}
	
	return self;
}

- 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 self;

	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)
		{
//			Error ("getXYShearPoints: didn't get three points on plane");
			numcontrolpoints = 0;
			return self;
		}
			
		for (j=0 ; j<3 ; j++)
			for (k=0 ; k<3 ; k++)
				f->planepts[j][k] = rint(f->planepts[j][k]);
	}
		
	return self;
}

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

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
===========
*/
- newRegion
{
	int		i;
	char	*name;
	
// filter away entities
	if (parent != [map_i objectAt: 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;
- selectPartial
{
	int		i;
	for (i=0 ; i<3 ; i++)
		if (select_min[i] >= bmaxs[i] || select_max[i] <= bmins[i])
			return self;
	selected = YES;
	return self;
}

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


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

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


id	sb_newowner;
- moveToEntity
{
	id		eclass;
	float	*c;
	
	[parent removeObject: self];
	parent = sb_newowner;
	
// hack to allow them to be copied to another map
	if ( [parent respondsTo:@selector(valueForQKey:)])
	{
		eclass = [entity_classes_i classForName: [parent valueForQKey: "classname"]];
		c = [eclass drawColor];
		[self setEntityColor: c];
	}
	
	[parent addObject: self];
	return self;
}

vec3_t	sb_translate;

- 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];
	
	return self;
}

vec3_t	sb_mins, sb_maxs;
- addToBBox
{
	int			k;
	
	if (numfaces < 4)
		return self;

	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];
	}

	return self;
}

- flushTextures
{	// call when texture palette changes
	int		i;
	
	for (i=0 ; i<MAX_FACES ; i++)
		faces[i].qtexture = NULL;
	
	[self calcWindings];

	return self;
}

- select
{
	[map_i setCurrentEntity: parent];
	selected = YES;
	return self;
}

- 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]];
	
	return self;
}

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

	[parent removeObject: self];
	[self free];

	return nil;
}


vec3_t	sel_x, sel_y, sel_z;
vec3_t	sel_org;
- 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];

	return self;
}

- 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];
	return self;
}

- carveByClipper
{
	face_t face;
	
	if (![clipper_i getFace: &face])
		return self;
		
	[self addFace: &face];
	
	return self;
}


- takeCurrentTexture
{
	texturedef_t	td;
	
	[texturepalette_i getTextureDef: &td];
	[self setTexturedef: &td];
	
	return self;
}


float	sb_floor_dir, sb_floor_dist;
- 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 self;

	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;	
	}
	return self;
}


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

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
- addFace: (face_t *)f
{
	if (numfaces == MAX_FACES)
		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];
}

- 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];
	
	return self;
}

- carve
{
	int		i;
	id		front, back;
		
#if 0
	if ( (i = NXMallocCheck()) )
		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
==================
*/
- setCarveVars
{
	VectorCopy (bmins, carvemin);
	VectorCopy (bmaxs, carvemax);
	numcarvefaces = numfaces;
	carvefaces = faces;
	
	return self;
}

- (int) getNumBrushFaces
{
	return numfaces;
}

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

@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.