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

Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.

This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").

Doom 3 Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Doom 3 Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.

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

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

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

#include "tools/edit_gui_common.h"


#include "qe3.h"
#include "DialogInfo.h"
#include "CapDialog.h"

// externs
extern void MemFile_fprintf( CMemFile *pMemFile,const char *pText,... );
extern face_t *Face_Alloc( void );
void _Write3DMatrix( FILE *f,int y,int x,int z,float *m );
void _Write3DMatrix( CMemFile *f,int y,int x,int z,float *m );
extern void SamplePatch( float ctrl[3][3][5],int baseCol,int baseRow,int width,int horzSub,int vertSub,idDrawVert *outVerts,idDrawVert *drawVerts );
patchMesh_t *Patch_GenerateGeneric( int width,int height,int orientation,const idVec3 &mins,const idVec3 &maxs );



patchMesh_t * MakeNewPatch( int width,int height ) {
	patchMesh_t	*pm	= reinterpret_cast< patchMesh_t*>(Mem_ClearedAlloc(sizeof(patchMesh_t)));
	pm->horzSubdivisions = DEFAULT_CURVE_SUBDIVISION;
	pm->vertSubdivisions = DEFAULT_CURVE_SUBDIVISION;
	pm->explicitSubdivisions = false;
	pm->width = width;
	pm->height = height;
	pm->verts = reinterpret_cast< idDrawVert*>(Mem_ClearedAlloc(sizeof(idDrawVert) * width * height));
	//pm->ctrl = reinterpret_cast<idDrawVert*>(Mem_ClearedAlloc(sizeof(idDrawVert) * width * height));
	//pm->ctrl = &pm->verts;
	return pm;
}


void Patch_AdjustSize( patchMesh_t *p,int wadj,int hadj ) {
	idDrawVert	*newverts	= reinterpret_cast< idDrawVert*>(Mem_ClearedAlloc(sizeof(idDrawVert) * (p->width + wadj) * (p->height + hadj)));
	int			copyWidth	= (wadj < 0) ? p->width + wadj : p->width;
	int			copyHeight	= (hadj < 0) ? p->height + hadj : p->height;
	int			copysize	= copyWidth *copyHeight * sizeof(idDrawVert);

	for ( int i = 0; i < p->width; i++ ) {
		for ( int j = 0; j < p->height; j++ ) {
			newverts[j * (p->width + wadj) + i] = p->ctrl(i, j);
		}
	}

	p->width += wadj;
	p->height += hadj;
	Mem_Free(p->verts);
	p->verts = newverts;
}

// algorithm from Journal of graphics tools, 2(1):21-28, 1997
bool RayIntersectsTri( const idVec3 &origin,const idVec3 &direction,const idVec3 &vert0,const idVec3 &vert1,const idVec3 &vert2,float &scale ) {
	idVec3	edge1, edge2, tvec, pvec, qvec;
	float	det, inv_det;
	scale = 0;

	/* find vectors for two edges sharing vert0 */
	edge1 = vert1 - vert0;
	edge2 = vert2 - vert0;

	/* begin calculating determinant - also used to calculate U parameter */
	pvec.Cross(direction, edge2);

	/* if determinant is near zero, ray lies in plane of triangle */
	det = edge1 * pvec;

	if ( det > -VECTOR_EPSILON && det < VECTOR_EPSILON ) {
		return false;
	}

	inv_det = 1.0f / det;

	/* calculate distance from vert0 to ray origin */
	tvec = origin - vert0;

	/* calculate U parameter and test bounds */
	float	u	= (tvec *pvec) * inv_det;
	if ( u < 0.0f || u> 1.0f ) {
		return false;
	}

	/* prepare to test V parameter */
	qvec.Cross(tvec, edge1);

	/* calculate V parameter and test bounds */
	float	v	= (direction *qvec) * inv_det;
	if ( v < 0.0f || u + v> 1.0f ) {
		return false;
	}

	scale = tvec.Length();
	return true;
}

bool Patch_Intersect( patchMesh_t *pm,idVec3 origin,idVec3 direction,float &scale ) {
	int				i, j;
	//float scale;
	idSurface_Patch	cp	(pm->width * 6, pm->height * 6);

	cp.SetSize(pm->width, pm->height);
	for ( i = 0; i < pm->width; i++ ) {
		for ( j = 0; j < pm->height; j++ ) {
			(cp)[j * cp.GetWidth() + i].xyz = pm->ctrl(i, j).xyz;
			(cp)[j * cp.GetWidth() + i].st = pm->ctrl(i, j).st;
		}
	}

	if ( pm->explicitSubdivisions ) {
		cp.SubdivideExplicit(pm->horzSubdivisions, pm->vertSubdivisions, false);
	} else {
		cp.Subdivide(DEFAULT_CURVE_MAX_ERROR, DEFAULT_CURVE_MAX_ERROR, DEFAULT_CURVE_MAX_LENGTH, false);
	}

	if ( cp.RayIntersection(origin, direction, scale) ) {
		return true;
	} else {
		return false;
	}

	/*
	int width = cp.GetWidth();
	int height = cp.GetHeight();
	for ( i = 0 ; i < width - 1; i++ ) {
		for ( j = 0 ; j < height - 1; j++ ) {
			// v1-v2-v3-v4 makes a quad
			int v1, v2, v3, v4;
			v1 = j * width + i;
			v2 = v1 + 1;
			v3 = v1 + width + 1;
			v4 = v1 + width;
			if (RayIntersectsTri(origin, direction, (cp)[v1].xyz, (cp)[v2].xyz, (cp)[v3].xyz)) {
				return true;
			}
			if (RayIntersectsTri(origin, direction, (cp)[v3].xyz, (cp)[v4].xyz, (cp)[v1].xyz)) {
				return true;
			}
		}
	}
	return false;
	*/
}

patchMesh_t * Patch_MakeNew( patchMesh_t *p,int newWidth,int newHeight ) {
	patchMesh_t	*newPatch	= MakeNewPatch(newWidth, newHeight);
	newPatch->d_texture = p->d_texture;
	newPatch->horzSubdivisions = p->horzSubdivisions;
	newPatch->vertSubdivisions = p->vertSubdivisions;
	newPatch->explicitSubdivisions = p->explicitSubdivisions;
	return newPatch;
}

void Patch_Combine( patchMesh_t *p,patchMesh_t *p2,int sourceCol1,int sourceCol2,int sourceRow1,int sourceRow2,bool invert1,bool invert2 ) {
	int			i, j, out;
	patchMesh_t	*newPatch	= NULL;
	if ( sourceCol1 >= 0 ) {
		// adding width
		if ( sourceCol2 >= 0 ) {
			// from width
			newPatch = Patch_MakeNew(p, p->width + p2->width - 1, p->height);
			int	adj1	= 1;
			int	adj2	= 1;
			int	col1	= 0;
			int	col2	= 1;
			if ( sourceCol1 != 0 ) {
				adj1 = -1;
				col1 = p->width - 1;
			}
			if ( sourceCol2 != 0 ) {
				adj2 = -1;
				col2 = p2->width - 2;
			}

			out = 0;
			for ( i = 0; i < p->width; i++, col1 += adj1 ) {
				int	in	= (invert1) ? p->height - 1 : 0;
				for ( j = 0; j < p->height; j++ ) {
					newPatch->ctrl(out, j) = p->ctrl(col1, in);
					in += (invert1) ? -1 : 1;
				}
				out++;
			}

			for ( i = 1; i < p2->width; i++, col2 += adj2 ) {
				int	in	= (invert2) ? p2->height - 1 : 0;
				for ( j = 0; j < p2->height; j++ ) {
					newPatch->ctrl(out, j) = p2->ctrl(col2, in);
					in += (invert2) ? -1 : 1;
				}
				out++;
			}
		} else {
			// from height
			newPatch = Patch_MakeNew(p, p->width + p2->height - 1, p->height);
			int	adj1	= 1;
			int	adj2	= 1;
			int	col1	= 0;
			int	row2	= 1;
			if ( sourceCol1 != 0 ) {
				adj1 = -1;
				col1 = p->width - 1;
			}
			if ( sourceRow2 != 0 ) {
				adj2 = -1;
				row2 = p2->height - 2;
			}

			out = 0;
			for ( i = 0; i < p->width; i++, col1 += adj1 ) {
				int	in	= (invert1) ? p->height - 1 : 0;
				for ( j = 0; j < p->height; j++ ) {
					newPatch->ctrl(out, j) = p->ctrl(col1, in);
					in += (invert1) ? -1 : 1;
				}
				out++;
			}

			for ( i = 1; i < p2->height; i++, row2 += adj2 ) {
				int	in	= (invert2) ? p2->width - 1 : 0;
				for ( j = 0; j < p2->width; j++ ) {
					newPatch->ctrl(out, j) = p2->ctrl(in, row2);
					in += (invert2) ? -1 : 1;
				}
				out++;
			}
		}
	} else {
		// adding height
		if ( sourceRow1 >= 0 ) {
			// from height
			newPatch = Patch_MakeNew(p, p->width, p->height + p2->height - 1);
			int	adj1	= 1;
			int	adj2	= 1;
			int	row1	= 0;
			int	row2	= 0;
			if ( sourceRow1 != 0 ) {
				adj1 = -1;
				row1 = p->height - 1;
			}
			if ( sourceRow2 != 0 ) {
				adj2 = -1;
				row2 = p2->height - 2;
			}

			out = 0;
			for ( i = 0; i < p->height; i++, row1 += adj1 ) {
				int	in	= (invert1) ? p->width - 1 : 0;
				for ( j = 0; j < p->width; j++ ) {
					newPatch->ctrl(j, out) = p->ctrl(in, row1);
					in += (invert1) ? -1 : 1;
				}
				out++;
			}

			for ( i = 1; i < p2->height; i++, row2 += adj2 ) {
				int	in	= (invert2) ? p->width - 1 : 0;
				for ( j = 0; j < p2->width; j++ ) {
					newPatch->ctrl(j, out) = p2->ctrl(in, row2);
					in += (invert1) ? -1 : 1;
				}
				out++;
			}
		} else {
			// from width
			newPatch = Patch_MakeNew(p, p->width, p->height + p2->width - 1);
			int	adj1	= 1;
			int	adj2	= 1;
			int	row1	= 0;
			int	col2	= 0;
			if ( sourceRow1 != 0 ) {
				adj1 = -1;
				row1 = p->height - 1;
			}
			if ( sourceCol2 != 0 ) {
				adj2 = -1;
				col2 = p2->width - 2;
			}

			out = 0;
			for ( i = 0; i < p->height; i++, row1 += adj1 ) {
				int	in	= (invert1) ? p->width - 1 : 0;
				for ( j = 0; j < p->width; j++ ) {
					newPatch->ctrl(j, out) = p->ctrl(in, row1);
					in += (invert1) ? -1 : 1;
				}
				out++;
			}

			for ( i = 1; i < p2->width; i++, col2 += adj2 ) {
				int	in	= (invert2) ? p->height - 1 : 0;
				for ( j = 0; j < p2->height; j++ ) {
					newPatch->ctrl(j, out) = p2->ctrl(col2, in);
					in += (invert1) ? -1 : 1;
				}
				out++;
			}
		}
	}
	if ( newPatch ) {
		AddBrushForPatch(newPatch, true);
		Brush_Free(p->pSymbiot, true);
		Brush_Free(p2->pSymbiot, true);
		Patch_Naturalize(newPatch, true, true);
	}
}

#define WELD_EPSILON	0.001f

void Patch_Weld( patchMesh_t *p,patchMesh_t *p2 ) {
	// check against all 4 edges of p2
	// could roll this up but left it out for some semblence of clarity
	//

	if ( p->width == p2->width ) {
		int	row		= 0;
		int	row2	= 0;
		while ( 1 ) {
			bool	match	= true;

			// need to see if any of the corners match then run down or up based
			// on the match edges
			int		col1	= 0;
			int		col2	= 0;
			int		adj1	= 1;
			int		adj2	= 1;
			if ( p->ctrl(0, row).xyz.Compare(p2->ctrl(0, row2).xyz, WELD_EPSILON) ) {
			} else if ( p->ctrl(0, row).xyz.Compare(p2->ctrl(p2->width - 1, row2).xyz, WELD_EPSILON) ) {
				col2 = p2->width - 1;
				adj2 = -1;
			} else if ( p->ctrl(p->width - 1, row).xyz.Compare(p2->ctrl(p2->width - 1, row2).xyz, WELD_EPSILON) ) {
				col2 = p2->width - 1;
				adj2 = -1;
				col1 = p->width - 1;
				adj1 = -1;
			} else if ( p->ctrl(p->width - 1, row).xyz.Compare(p2->ctrl(0, row2).xyz, WELD_EPSILON) ) {
				col1 = p->width - 1;
				adj1 = -1;
			} else {
				adj1 = 0;
			}

			if ( adj1 ) {
				for ( int col = 0; col < p->width; col++, col2 += adj2, col1 += adj1 ) {
					if ( !p->ctrl(col1, row).xyz.Compare(p2->ctrl(col2, row2).xyz, WELD_EPSILON) ) {
						match = false;
						break;
					}
				}
			} else {
				match = false;
			}

			if ( match ) {
				// have a match weld these edges
				common->Printf("Welding row %i with row %i\n", row, row2);
				row = (row == 0) ? p->height - 1 : 0;
				Patch_Combine(p, p2, -1, -1, row, row2, (adj1 == -1), (adj2 == -1));
				return;
			} else if ( row2 == 0 ) {
				row2 = p2->height - 1;
			} else if ( row == 0 ) {
				row = p->height - 1;
				row2 = 0;
			} else {
				break;
			}
		}
	}

	if ( p->width == p2->height ) {
		int	row		= 0;
		int	col2	= 0;
		while ( 1 ) {
			bool	match	= true;

			int		col1	= 0;
			int		adj1	= 1;
			int		row2	= 0;
			int		adj2	= 1;
			if ( p->ctrl(0, row).xyz.Compare(p2->ctrl(col2, 0).xyz, WELD_EPSILON) ) {
			} else if ( p->ctrl(0, row).xyz.Compare(p2->ctrl(col2, p2->height - 1).xyz, WELD_EPSILON) ) {
				row2 = p2->height - 1;
				adj2 = -1;
			} else if ( p->ctrl(p->width - 1, row).xyz.Compare(p2->ctrl(col2, p2->height - 1).xyz, WELD_EPSILON) ) {
				row2 = p2->height - 1;
				adj2 = -1;
				col1 = p->width - 1;
				adj1 = -1;
			} else if ( p->ctrl(p->width - 1, row).xyz.Compare(p2->ctrl(col2, 0).xyz, WELD_EPSILON) ) {
				col1 = p->width - 1;
				adj1 = -1;
			} else {
				adj1 = 0;
			}

			if ( adj1 ) {
				for ( int col = 0; col < p->width; col++, col1 += adj1, row2 += adj2 ) {
					if ( !p->ctrl(col1, row).xyz.Compare(p2->ctrl(col2, row2).xyz, WELD_EPSILON) ) {
						match = false;
						break;
					}
				}
			} else {
				match = false;
			}

			if ( match ) {
				// have a match weld these edges
				common->Printf("Welding row %i with col %i\n", row, col2);
				row = (row == 0) ? p->height - 1 : 0;
				Patch_Combine(p, p2, -1, col2, row, -1, (adj1 == -1), (adj2 == -1));
				return;
			} else if ( col2 == 0 ) {
				col2 = p2->width - 1;
			} else if ( row == 0 ) {
				row = p->height - 1;
				col2 = 0;
			} else {
				break;
			}
		}
	}

	if ( p->height == p2->width ) {
		int	col		= 0;
		int	row2	= 0;
		while ( 1 ) {
			bool	match	= true;


			int		row1	= 0;
			int		adj1	= 1;
			int		col2	= 0;
			int		adj2	= 1;
			if ( p->ctrl(col, 0).xyz.Compare(p2->ctrl(0, row2).xyz, WELD_EPSILON) ) {
			} else if ( p->ctrl(col, 0).xyz.Compare(p2->ctrl(p2->width - 1, row2).xyz, WELD_EPSILON) ) {
				col2 = p2->width - 1;
				adj2 = -1;
			} else if ( p->ctrl(col, p->height - 1).xyz.Compare(p2->ctrl(p2->width - 1, row2).xyz, WELD_EPSILON) ) {
				col2 = p2->width - 1;
				adj2 = -1;
				row1 = p2->height - 1;
				adj2 = -1;
			} else if ( p->ctrl(col, p->height - 1).xyz.Compare(p2->ctrl(0, row2).xyz, WELD_EPSILON) ) {
				row1 = p2->height - 1;
				adj2 = -1;
			} else {
				adj1 = 0;
			}

			if ( adj1 ) {
				for ( int row = 0; row < p->height; row++, row1 += adj1, col2 += adj2 ) {
					if ( !p->ctrl(col, row1).xyz.Compare(p2->ctrl(col2, row2).xyz, WELD_EPSILON) ) {
						match = false;
						break;
					}
				}
			} else {
				match = false;
			}

			if ( match ) {
				// have a match weld these edges
				common->Printf("Welding col %i with row %i\n", col, row2);
				col = (col == 0) ? p->width - 1 : 0;
				Patch_Combine(p, p2, col, -1, -1, row2, (adj1 == -1), (adj2 == -1));
				return;
			} else if ( row2 == 0 ) {
				row2 = p2->height - 1;
			} else if ( col == 0 ) {
				col = p->width - 1;
				row2 = 0;
			} else {
				break;
			}
		}
	}

	if ( p->height == p2->height ) {
		int	col		= 0;
		int	col2	= 0;
		while ( 1 ) {
			bool	match	= true;


			int		row1	= 0;
			int		adj1	= 1;
			int		row2	= 0;
			int		adj2	= 1;
			if ( p->ctrl(col, 0).xyz.Compare(p2->ctrl(col2, 0).xyz, WELD_EPSILON) ) {
			} else if ( p->ctrl(col, 0).xyz.Compare(p2->ctrl(col2, p2->height - 1).xyz, WELD_EPSILON) ) {
				row2 = p2->height - 1;
				adj2 = -1;
			} else if ( p->ctrl(col, p2->height - 1).xyz.Compare(p2->ctrl(col2, p2->height - 1).xyz, WELD_EPSILON) ) {
				row2 = p2->height - 1;
				adj2 = -1;
				row1 = p->height - 1;
				adj1 = -1;
			} else if ( p->ctrl(col, p2->height - 1).xyz.Compare(p2->ctrl(col2, 0).xyz, WELD_EPSILON) ) {
				row1 = p->height - 1;
				adj1 = -1;
			} else {
				adj1 = 0;
			}

			if ( adj1 ) {
				for ( int row = 0; row < p->height; row++, row1 += adj1, row2 += adj2 ) {
					if ( !p->ctrl(col, row1).xyz.Compare(p2->ctrl(col2, row2).xyz, WELD_EPSILON) ) {
						match = false;
						break;
					}
				}
			} else {
				match = false;
			}

			if ( match ) {
				// have a match weld these edges
				common->Printf("Welding col %i with col %i\n", col, col2);
				col = (col == 0) ? p->width - 1 : 0;
				Patch_Combine(p, p2, col, col2, -1, -1, (adj1 == -1), (adj2 == -1));
				return;
			} else if ( col2 == 0 ) {
				col2 = p2->width - 1;
			} else if ( col == 0 ) {
				col = p->width - 1;
				col2 = 0;
			} else {
				break;
			}
		}
	}


	Sys_Status("Unable to weld patches, no common sized edges.\n");
}


// used for a save spot
patchMesh_t	*patchSave				= NULL;

// Tracks the selected patch for point manipulation/update. FIXME: Need to revert back to a generalized
// brush approach
//--int  g_nSelectedPatch = -1;

// HACK: for tracking which view generated the click
// as we dont want to deselect a point on a same point
// click if it is from a different view
int			g_nPatchClickedView	= -1;
bool		g_bSameView			= false;


// globals
bool		g_bPatchShowBounds	= false;
bool		g_bPatchWireFrame	= false;
bool		g_bPatchWeld		= true;
bool		g_bPatchDrillDown	= true;
bool		g_bPatchInsertMode	= false;
bool		g_bPatchBendMode	= false;
int			g_nPatchBendState	= -1;
int			g_nPatchInsertState	= -1;
int			g_nBendOriginIndex	= 0;
idVec3		g_vBendOrigin;

bool		g_bPatchAxisOnRow	= true;
int			g_nPatchAxisIndex	= 0;
bool		g_bPatchLowerEdge	= true;

// BEND states
enum {
	BEND_SELECT_ROTATION	= 0,
	BEND_SELECT_ORIGIN,
	BEND_SELECT_EDGE,
	BEND_BENDIT,
	BEND_STATE_COUNT
};

const char	*g_pBendStateMsg[]	= {
	"Use TAB to cycle through available bend axis. Press ENTER when the desired one is highlighted.", "Use TAB to cycle through available rotation axis. This will LOCK around that point. You may also use Shift + Middle Click to select an arbitrary point. Press ENTER when the desired one is highlighted", "Use TAB to choose which side to bend. Press ENTER when the desired one is highlighted.", "Use the MOUSE to bend the patch. It uses the same ui rules as Free Rotation. Press ENTER to accept the bend, press ESC to abandon it and exit Bend mode", ""
};

// INSERT states
enum {
	INSERT_SELECT_EDGE		= 0,
	INSERT_STATE_COUNT
};

const char	*g_pInsertStateMsg[]	= {
	"Use TAB to cycle through available rows/columns for insertion/deletion. Press INS to insert at the highlight, DEL to remove the pair"
};


float		*g_InversePoints[1024];

const float	fFullBright			= 1.0f;
const float	fLowerLimit			= 0.5f;
const float	fDec				= 0.05f;


void Patch_SetType( patchMesh_t *p,int nType ) {
	p->type = (p->type & PATCH_STYLEMASK) | nType;
}

void Patch_SetStyle( patchMesh_t *p,int nStyle ) {
	p->type = (p->type & PATCH_TYPEMASK) | nStyle;
}

/*
==================
Patch_MemorySize
==================
*/
int Patch_MemorySize( patchMesh_t *p ) {
	return (sizeof(patchMesh_t) + p->width * p->height * sizeof(idDrawVert));
}



/*
===============
InterpolateInteriorPoints
===============
*/
void InterpolateInteriorPoints( patchMesh_t *p ) {
	int	i, j, k;
	int	next, prev;

	for ( i = 0 ; i < p->width ; i += 2 ) {
		next = (i == p->width - 1) ? 1 : (i + 1) % p->width;
		prev = (i == 0) ? p->width - 2 : i - 1;
		for ( j = 0 ; j < p->height ; j++ ) {
			for ( k = 0 ; k < 3 ; k++ ) {
				p->ctrl(i, j).xyz[k] = (p->ctrl(next, j).xyz[k] + p->ctrl(prev, j).xyz[k]) * 0.5;
			}
		}
	}
}

/*
=================
MakeMeshNormals

=================
*/
int	neighbors[8][2]	= {
	{0,1}, {1,1}, {1,0}, {1, -1}, {0, -1}, { - 1, -1}, { - 1,0}, { - 1,1}
};

void Patch_MeshNormals( patchMesh_t *in ) {
	int			i, j, k, dist;
	idVec3		normal;
	idVec3		sum;
	int			count;
	idVec3		base;
	idVec3		delta;
	int			x, y;
	idDrawVert	*dv;
	idVec3		around[8], temp;
	bool		good[8];
	bool		wrapWidth, wrapHeight;
	float		len;

	wrapWidth = false;
	for ( i = 0 ; i < in->height ; i++ ) {
		VectorSubtract(in->ctrl(0, i).xyz, in->ctrl(in->width - 1, i).xyz, delta);
		len = delta.Length();
		if ( len > 1.0f ) {
			break;
		}
	}
	if ( i == in->height ) {
		wrapWidth = true;
	}

	wrapHeight = false;
	for ( i = 0 ; i < in->width ; i++ ) {
		VectorSubtract(in->ctrl(i, 0).xyz, in->ctrl(i, in->height - 1).xyz, delta);
		len = delta.Length();
		if ( len > 1.0f ) {
			break;
		}
	}
	if ( i == in->width ) {
		wrapHeight = true;
	}


	for ( i = 0 ; i < in->width ; i++ ) {
		for ( j = 0 ; j < in->height ; j++ ) {
			count = 0;
			//--dv = reinterpret_cast<idDrawVert*>(in.ctrl[j*in.width+i]);
			dv = &in->ctrl(i, j);
			VectorCopy(dv->xyz, base);
			for ( k = 0 ; k < 8 ; k++ ) {
				around[k] = vec3_origin;
				good[k] = false;

				for ( dist = 1 ; dist <= 3 ; dist++ ) {
					x = i + neighbors[k][0] * dist;
					y = j + neighbors[k][1] * dist;
					if ( wrapWidth ) {
						if ( x < 0 ) {
							x = in->width - 1 + x;
						} else if ( x >= in->width ) {
							x = 1 + x - in->width;
						}
					}
					if ( wrapHeight ) {
						if ( y < 0 ) {
							y = in->height - 1 + y;
						} else if ( y >= in->height ) {
							y = 1 + y - in->height;
						}
					}

					if ( x < 0 || x >= in->width || y < 0 || y >= in->height ) {
						break;					// edge of patch
					}
					//--VectorSubtract( in.ctrl[y*in.width+x]->xyz, base, temp );
					VectorSubtract(in->ctrl(x, y).xyz, base, temp);
					if ( temp.Normalize() == 0 ) {
						continue;				// degenerate edge, get more dist
					} else {
						good[k] = true;
						VectorCopy(temp, around[k]);
						break;					// good edge
					}
				}
			}

			sum = vec3_origin;
			for ( k = 0 ; k < 8 ; k++ ) {
				if ( !good[k] || !good[(k + 1) & 7] ) {
					continue;	// didn't get two points
				}
				normal = around[(k + 1) & 7].Cross(around[k]);
				if ( normal.Normalize() == 0 ) {
					continue;
				}
				VectorAdd(normal, sum, sum);
				count++;
			}
			if ( count == 0 ) {
				//printf("bad normal\n");
				count = 1;
				//continue;
			}
			dv->normal = sum;
			dv->normal.Normalize();
		}
	}
}

void Patch_MakeDirty( patchMesh_t *p ) {
	assert(p);
	p->nListID = -1;
	p->nListIDCam = -1;
	p->nListSelected = -1;
}


/*
==================
Patch_CalcBounds
==================
*/
void Patch_CalcBounds( patchMesh_t *p,idVec3 &vMin,idVec3 &vMax ) {
	vMin[0] = vMin[1] = vMin[2] = 999999;
	vMax[0] = vMax[1] = vMax[2] = -999999;

	Patch_MakeDirty(p);
	for ( int w = 0; w < p->width; w++ ) {
		for ( int h = 0; h < p->height; h++ ) {
			for ( int j = 0; j < 3; j++ ) {
				float	f	= p->ctrl(w, h).xyz[j];
				if ( f < vMin[j] )
					vMin[j] = f;
				if ( f > vMax[j] )
					vMax[j] = f;
			}
		}
	}
}

/*
==================
Brush_RebuildBrush
==================
*/
void Brush_RebuildBrush( brush_t *b,idVec3 vMins,idVec3 vMaxs,bool patch ) {
	//
	// Total hack job
	// Rebuilds a brush
	int			i, j;
	face_t		*f, *next;
	idVec3		pts[4][2];
	texdef_t	texdef;
	// free faces

	for ( j = 0; j < 3; j++ ) {
		if ( (int) vMins[j] == (int) vMaxs[j] ) {
			vMins[j] -= 4;
			vMaxs[j] += 4;
		}
	}


	for ( f = b->brush_faces ; f ; f = next ) {
		next = f->next;
		if ( f ) {
			texdef = f->texdef;
		}
		Face_Free(f);
	}

	b->brush_faces = NULL;

	// left the last face so we can use its texdef

	for ( i = 0 ; i < 3 ; i++ ) {
		if ( vMaxs[i] < vMins[i] ) {
			Error("Brush_RebuildBrush: backwards");
		}
	}

	pts[0][0][0] = vMins[0];
	pts[0][0][1] = vMins[1];

	pts[1][0][0] = vMins[0];
	pts[1][0][1] = vMaxs[1];

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

	pts[3][0][0] = vMaxs[0];
	pts[3][0][1] = vMins[1];

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

	for ( i = 0 ; i < 4 ; i++ ) {
		f = Face_Alloc();
		f->texdef = texdef;
		f->next = b->brush_faces;
		b->brush_faces = f;
		j = (i + 1) % 4;

		VectorCopy(pts[j][1], f->planepts[0]);
		VectorCopy(pts[i][1], f->planepts[1]);
		VectorCopy(pts[i][0], f->planepts[2]);
	}

	f = Face_Alloc();
	f->texdef = texdef;
	f->next = b->brush_faces;
	b->brush_faces = f;

	VectorCopy(pts[0][1], f->planepts[0]);
	VectorCopy(pts[1][1], f->planepts[1]);
	VectorCopy(pts[2][1], f->planepts[2]);

	f = Face_Alloc();
	f->texdef = texdef;
	f->next = b->brush_faces;
	b->brush_faces = f;

	VectorCopy(pts[2][0], f->planepts[0]);
	VectorCopy(pts[1][0], f->planepts[1]);
	VectorCopy(pts[0][0], f->planepts[2]);

	Brush_Build(b);
}

void WINAPI Patch_Rebuild( patchMesh_t *p ) {
	idVec3	vMin, vMax;
	Patch_CalcBounds(p, vMin, vMax);
	Brush_RebuildBrush(p->pSymbiot, vMin, vMax);
	Patch_MakeDirty(p);
}

/*
==================
AddBrushForPatch
==================
 adds a patch brush and ties it to this patch id
*/
brush_t * AddBrushForPatch( patchMesh_t *pm,bool bLinkToWorld ) {
	// find the farthest points in x,y,z
	idVec3	vMin, vMax;
	Patch_CalcBounds(pm, vMin, vMax);

	for ( int j = 0; j < 3; j++ ) {
		if ( idMath::Fabs(vMin[j] - vMax[j]) <= VECTOR_EPSILON ) {
			vMin[j] -= 4;
			vMax[j] += 4;
		}
	}

	texdef_t	td;
	//td.SetName(pm->d_texture->getName());
	brush_t		*b	= Brush_Create(vMin, vMax, &td);
	//brush_t *b = Brush_Create(vMin, vMax, &g_qeglobals.d_texturewin.texdef);

	// FIXME: this entire type of linkage needs to be fixed
	b->pPatch = pm;
	pm->pSymbiot = b;
	pm->bSelected = false;
	pm->bOverlay = false;
	pm->nListID = -1;
	pm->nListIDCam = -1;

	if ( bLinkToWorld ) {
		Brush_AddToList(b, &active_brushes);
		Entity_LinkBrush(world_entity, b);
		Brush_Build(b);
	}

	return b;
}

void Patch_SetPointIntensities( int n ) {
#if 0
		patchMesh_t	*p = patchMeshes[n];
	for (int i = 0; i < p->width; i++) {
		for (int j = 0; j < p->height; j++) {

		}
	}
#endif
}

// very approximate widths and heights

/*
==================
Patch_Width
==================
*/
float Patch_Width( patchMesh_t *p ) {
	float	f	= 0;
	for ( int j = 0; j < p->height - 1; j++ ) {
		float	t	= 0;
		for ( int i = 0; i < p->width - 1; i++ ) {
			idVec3	vTemp;
			vTemp = p->ctrl(i, j).xyz - p->ctrl(i + 1, j).xyz;
			t += vTemp.Length();
		}
		if ( f < t ) {
			f = t;
		}
	}
	return f;
}

/*
==================
Patch_Height
==================
*/
float Patch_Height( patchMesh_t *p ) {
	float	f	= 0;
	for ( int j = 0; j < p->width - 1; j++ ) {
		float	t	= 0;
		for ( int i = 0; i < p->height - 1; i++ ) {
			idVec3	vTemp;
			vTemp = p->ctrl(j, i).xyz - p->ctrl(j, i + 1).xyz;
			t += vTemp.Length();
		}
		if ( f < t ) {
			f = t;
		}
	}
	return f;
}

/*
==================
Patch_WidthDistanceTo
==================
*/
float Patch_WidthDistanceTo( patchMesh_t *p,int j ) {
	float	f	= 0;
	for ( int i = 0; i < j ; i++ ) {
		idVec3	vTemp;
		vTemp = p->ctrl(i, 0).xyz - p->ctrl(i + 1, 0).xyz;
		f += vTemp.Length();
	}
	return f;
}

/*
==================
Patch_HeightDistanceTo
==================
*/
float Patch_HeightDistanceTo( patchMesh_t *p,int j ) {
	float	f	= 0;
	for ( int i = 0; i < j ; i++ ) {
		idVec3	vTemp;
		vTemp = p->ctrl(0, i).xyz - p->ctrl(0, i + 1).xyz;
		f += vTemp.Length();
	}
	return f;
}



/*
==================
Patch_Naturalize
==================
texture = TotalTexture * LengthToThisControlPoint / TotalControlPointLength

dist( this control point to first control point ) / dist ( last control pt to first)
*/
void Patch_Naturalize( patchMesh_t *p,bool horz,bool vert,bool alt ) {
	int		i, j;

	int		nWidth		= p->d_texture->GetEditorImage()->uploadWidth * 0.5;
	int		nHeight		= p->d_texture->GetEditorImage()->uploadHeight * 0.5;
	float	fPWidth		= Patch_Width(p);
	float	fPHeight	= Patch_Height(p);
	float	xAccum		= 0;
	for ( i = 0 ; i < ((alt) ? p->height : p->width) ; i++ ) {
		float	yAccum	= 0;
		for ( j = 0 ; j < ((alt) ? p->width : p->height) ; j++ ) {
			int	r	= ((alt) ? j : i);
			int	c	= ((alt) ? i : j);
			p->ctrl(r, c).st[0] = (fPWidth / nWidth) * xAccum / fPWidth;
			p->ctrl(r, c).st[1] = (fPHeight / nHeight) * yAccum / fPHeight;
			if ( alt ) {
				yAccum = Patch_WidthDistanceTo(p, j + 1);
			} else {
				yAccum = Patch_HeightDistanceTo(p, j + 1);
			}
		}
		if ( alt ) {
			xAccum = Patch_HeightDistanceTo(p, i + 1);
		} else {
			xAccum = Patch_WidthDistanceTo(p, i + 1);
		}
	}

	Patch_MakeDirty(p);
}

/*
  if (bIBevel)
  {
	VectorCopy(p->ctrl(1,0], p->ctrl(1,1]);
  }

  if (bIEndcap)
  {
	VectorCopy(p->ctrl(3,0], p->ctrl(4,1]);
	VectorCopy(p->ctrl(2,0], p->ctrl(3,1]);
	VectorCopy(p->ctrl(2,0], p->ctrl(2,1]);
	VectorCopy(p->ctrl(2,0], p->ctrl(1,1]);
	VectorCopy(p->ctrl(1,0], p->ctrl(0,1]);
	VectorCopy(p->ctrl(1,0], p->ctrl(0,2]);
	VectorCopy(p->ctrl(1,0], p->ctrl(1,2]);
	VectorCopy(p->ctrl(2,0], p->ctrl(2,2]);
	VectorCopy(p->ctrl(3,0], p->ctrl(3,2]);
	VectorCopy(p->ctrl(3,0], p->ctrl(4,2]);
  }
*/

int	Index3By[][2]		= {
	{0,0}, {1,0}, {2,0}, {2,1}, {2,2}, {1,2}, {0,2}, {0,1}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}
};

int	Index5By[][2]		= {
	{0,0}, {1,0}, {2,0}, {3,0}, {4,0}, {4,1}, {4,2}, {4,3}, {4,4}, {3,4}, {2,4}, {1,4}, {0,4}, {0,3}, {0,2}, {0,1}
};



int	Interior3By[][2]	= {
	{1,1}
};

int	Interior5By[][2]	= {
	{1,1}, {2,1}, {3,1}, {1,2}, {2,2}, {3,2}, {1,3}, {2,3}, {3,3}
};

int	Interior3ByCount	= sizeof(Interior3By) / sizeof(int[2]);
int	Interior5ByCount	= sizeof(Interior5By) / sizeof(int[2]);

face_t * Patch_GetAxisFace( patchMesh_t *p ) {
	face_t	*f	= NULL;
	idVec3	vTemp;
	brush_t	*b	= p->pSymbiot;

	for ( f = b->brush_faces ; f ; f = f->next ) {
		vTemp = (*f->face_winding)[1].ToVec3() - (*f->face_winding)[0].ToVec3();
		int	nScore	= 0;

		// default edge faces on caps are 8 high so
		// as soon as we hit one that is bigger it should be on the right axis
		for ( int j = 0; j < 3; j++ ) {
			if ( vTemp[j] > 8 )
				nScore++;
		}

		if ( nScore > 0 ) {
			break;
		}
	}

	if ( f == NULL ) {
		f = b->brush_faces;
	}
	return f;
}

int	g_nFaceCycle	= 0;

face_t * nextFace( patchMesh_t *p ) {
	brush_t	*b	= p->pSymbiot;
	face_t	*f	= NULL;
	int		n	= 0;
	for ( f = b->brush_faces ; f && n <= g_nFaceCycle; f = f->next ) {
		n++;
	}

	g_nFaceCycle++;

	if ( g_nFaceCycle > 5 ) {
		g_nFaceCycle = 0;
		f = b->brush_faces;
	}

	return f;
}


void Patch_CapTexture( patchMesh_t *p,bool bFaceCycle = false,bool alt = false ) {
	Patch_MeshNormals(p);
	face_t	*f	= (bFaceCycle) ? nextFace(p) : Patch_GetAxisFace(p);
	idVec3	vSave;
	VectorCopy(f->plane, vSave);
	float	fRotate	= f->texdef.rotate;
	f->texdef.rotate = 0;
	float	fScale[2];
	fScale[0] = f->texdef.scale[0];
	fScale[1] = f->texdef.scale[1];
	f->texdef.scale[0] = (float) p->d_texture->GetEditorImage()->uploadWidth / 32.0f;
	f->texdef.scale[1] = (float) p->d_texture->GetEditorImage()->uploadHeight / 32.0f;
	float	fShift[2];
	fShift[0] = f->texdef.shift[0];
	fShift[1] = f->texdef.shift[1];
	f->texdef.shift[0] = 0;
	f->texdef.shift[1] = 0;

	for ( int i = 0 ; i < p->width; i++ ) {
		for ( int j = 0 ; j < p->height ; j++ ) {
			if ( !bFaceCycle ) {
				VectorCopy(p->ctrl(i, j).normal, f->plane);
			}
			idVec5	temp;
			temp.x = p->ctrl(i, j).xyz.x;
			temp.y = p->ctrl(i, j).xyz.y;
			temp.z = p->ctrl(i, j).xyz.z;
			EmitTextureCoordinates(temp, f->d_texture, f, true);
			p->ctrl(i, j).st.x = temp.s;
			p->ctrl(i, j).st.y = temp.t;
		}
	}

	VectorCopy(vSave, f->plane);
	f->texdef.rotate = fRotate;
	f->texdef.scale[0] = fScale[0];
	f->texdef.scale[1] = fScale[1];
	f->texdef.shift[0] = fShift[0];
	f->texdef.shift[1] = fShift[1];
	Patch_ScaleTexture(p, 1.0f, -1.0f, false);
	Patch_MakeDirty(p);
}

void FillPatch( patchMesh_t *p,idVec3 v ) {
	for ( int i = 0; i < p->width; i++ ) {
		for ( int j = 0; j < p->height; j++ ) {
			VectorCopy(v, p->ctrl(i, j).xyz);
		}
	}
}

brush_t * Cap( patchMesh_t *pParent,bool bByColumn,bool bFirst ) {
	brush_t		*b;
	patchMesh_t	*p;
	idVec3		vMin, vMax;
	int			i, j;

	bool		bSmall	= true;
	// make a generic patch
	if ( pParent->width <= 9 ) {
		b = Patch_GenericMesh(3, 3, 2, false, false, pParent);
	} else {
		b = Patch_GenericMesh(5, 5, 2, false, false, pParent);
		bSmall = false;
	}

	if ( !b ) {
		Sys_Status("Unable to cap. You may need to ungroup the patch.\n");
		return NULL;
	}

	p = b->pPatch;
	p->type |= PATCH_CAP;

	vMin[0] = vMin[1] = vMin[2] = 99999;
	vMax[0] = vMax[1] = vMax[2] = -99999;

	// we seam the column edge, FIXME: this might need to be able to seem either edge
	//
	int	nSize	= (bByColumn) ? pParent->width : pParent->height;
	int	nIndex	= (bFirst) ? 0 : (bByColumn) ? pParent->height - 1 : pParent->width - 1;

	FillPatch(p, pParent->ctrl(0, nIndex).xyz);

	for ( i = 0; i < nSize; i++ ) {
		if ( bByColumn ) {
			if ( bSmall ) {
				VectorCopy(pParent->ctrl(i, nIndex).xyz, p->ctrl(Index3By[i][0], Index3By[i][1]).xyz);
			} else {
				VectorCopy(pParent->ctrl(i, nIndex).xyz, p->ctrl(Index5By[i][0], Index5By[i][1]).xyz);
			}
		} else {
			if ( bSmall ) {
				VectorCopy(pParent->ctrl(nIndex, i).xyz, p->ctrl(Index3By[i][0], Index3By[i][1]).xyz);
			} else {
				VectorCopy(pParent->ctrl(nIndex, i).xyz, p->ctrl(Index5By[i][0], Index5By[i][1]).xyz);
			}
		}

		for ( j = 0; j < 3; j++ ) {
			float	f	= (bSmall) ? p->ctrl(Index3By[i][0], Index3By[i][1]).xyz[j] : p->ctrl(Index5By[i][0], Index5By[i][1]).xyz[j];
			if ( f < vMin[j] )
				vMin[j] = f;
			if ( f > vMax[j] )
				vMax[j] = f;
		}
	}

	idVec3	vTemp;
	for ( j = 0; j < 3; j++ ) {
		vTemp[j] = vMin[j] + abs((vMax[j] - vMin[j]) * 0.5);
	}

	int	nCount	= (bSmall) ? Interior3ByCount : Interior5ByCount;
	for ( j = 0; j < nCount; j++ ) {
		if ( bSmall ) {
			VectorCopy(vTemp, p->ctrl(Interior3By[j][0], Interior3By[j][1]).xyz);
		} else {
			VectorCopy(vTemp, p->ctrl(Interior5By[j][0], Interior5By[j][1]).xyz);
		}
	}

	if ( bFirst ) {
		idDrawVert	vertTemp;
		for ( i = 0; i < p->width; i++ ) {
			for ( j = 0; j < p->height / 2; j++ ) {
				memcpy(&vertTemp, &p->ctrl(i, p->height - 1 - j), sizeof(idDrawVert));
				memcpy(&p->ctrl(i, p->height - 1 - j), &p->ctrl(i, j), sizeof(idDrawVert));
				memcpy(&p->ctrl(i, j), &vertTemp, sizeof(idDrawVert));
			}
		}
	}

	Patch_Rebuild(p);
	Patch_CapTexture(p);
	return p->pSymbiot;
}

brush_t * CapSpecial( patchMesh_t *pParent,int nType,bool bFirst ) {
	brush_t		*b;
	patchMesh_t	*p;
	idVec3		vMin, vMax, vTemp;
	int			i, j;

	if ( nType == CCapDialog::IENDCAP ) {
		b = Patch_GenericMesh(5, 3, 2, false, false, pParent);
	} else {
		b = Patch_GenericMesh(3, 3, 2, false, false, pParent);
	}

	if ( !b ) {
		Sys_Status("Unable to cap. Make sure you ungroup before re-capping.");
		return NULL;
	}

	p = b->pPatch;
	p->type |= PATCH_CAP;

	vMin[0] = vMin[1] = vMin[2] = 99999;
	vMax[0] = vMax[1] = vMax[2] = -99999;

	int	nSize	= pParent->width;
	int	nIndex	= (bFirst) ? 0 : pParent->height - 1;

	// parent bounds are used for some things
	Patch_CalcBounds(pParent, vMin, vMax);

	for ( j = 0; j < 3; j++ ) {
		vTemp[j] = vMin[j] + abs((vMax[j] - vMin[j]) * 0.5);
	}

	if ( nType == CCapDialog::IBEVEL ) {
		VectorCopy(pParent->ctrl(0, nIndex).xyz, p->ctrl(0, 0).xyz);
		VectorCopy(pParent->ctrl(2, nIndex).xyz, p->ctrl(0, 2).xyz);
		VectorCopy(pParent->ctrl(1, nIndex).xyz, p->ctrl(0, 1).xyz);
		VectorCopy(pParent->ctrl(1, nIndex).xyz, p->ctrl(2, 2).xyz);
		VectorCopy(pParent->ctrl(1, nIndex).xyz, p->ctrl(1, 0).xyz);
		VectorCopy(pParent->ctrl(1, nIndex).xyz, p->ctrl(1, 1).xyz);
		VectorCopy(pParent->ctrl(1, nIndex).xyz, p->ctrl(1, 2).xyz);
		VectorCopy(pParent->ctrl(1, nIndex).xyz, p->ctrl(2, 0).xyz);
		VectorCopy(pParent->ctrl(1, nIndex).xyz, p->ctrl(2, 1).xyz);
	} else if ( nType == CCapDialog::BEVEL ) {
		idVec3	p1, p2, p3, p4, temp, dir;

		VectorCopy(pParent->ctrl(0, nIndex).xyz, p3);
		VectorCopy(pParent->ctrl(1, nIndex).xyz, p1);
		VectorCopy(pParent->ctrl(2, nIndex).xyz, p2);

		VectorSubtract(p3, p2, dir);
		dir.Normalize();
		VectorSubtract(p1, p2, temp);
		float	dist	= DotProduct(temp, dir);

		VectorScale(dir, dist, temp);

		VectorAdd(p2, temp, temp);

		VectorSubtract(temp, p1, temp);
		VectorScale(temp, 2, temp);
		VectorAdd(p1, temp, p4);

		VectorCopy(p4, p->ctrl(0, 0).xyz);
		VectorCopy(p4, p->ctrl(1, 0).xyz);
		VectorCopy(p4, p->ctrl(0, 1).xyz);
		VectorCopy(p4, p->ctrl(1, 1).xyz);
		VectorCopy(p4, p->ctrl(0, 2).xyz);
		VectorCopy(p4, p->ctrl(1, 2).xyz);
		VectorCopy(p3, p->ctrl(2, 0).xyz);
		VectorCopy(p1, p->ctrl(2, 1).xyz);
		VectorCopy(p2, p->ctrl(2, 2).xyz);
	} else if ( nType == CCapDialog::ENDCAP ) {
		VectorAdd(pParent->ctrl(4, nIndex).xyz, pParent->ctrl(0, nIndex).xyz, vTemp);
		VectorScale(vTemp, 0.5, vTemp);
		VectorCopy(pParent->ctrl(0, nIndex).xyz, p->ctrl(0, 0).xyz);
		VectorCopy(vTemp, p->ctrl(1, 0).xyz);
		VectorCopy(pParent->ctrl(4, nIndex).xyz, p->ctrl(2, 0).xyz);

		VectorCopy(pParent->ctrl(2, nIndex).xyz, p->ctrl(0, 2).xyz);
		VectorCopy(pParent->ctrl(2, nIndex).xyz, p->ctrl(1, 2).xyz);
		VectorCopy(pParent->ctrl(2, nIndex).xyz, p->ctrl(2, 2).xyz);
		VectorCopy(pParent->ctrl(2, nIndex).xyz, p->ctrl(1, 1).xyz);

		VectorCopy(pParent->ctrl(1, nIndex).xyz, p->ctrl(0, 1).xyz);
		VectorCopy(pParent->ctrl(3, nIndex).xyz, p->ctrl(2, 1).xyz);
	} else {
		VectorCopy(pParent->ctrl(0, nIndex).xyz, p->ctrl(0, 0).xyz);
		VectorCopy(pParent->ctrl(1, nIndex).xyz, p->ctrl(1, 0).xyz);
		VectorCopy(pParent->ctrl(2, nIndex).xyz, p->ctrl(2, 0).xyz);
		VectorCopy(pParent->ctrl(3, nIndex).xyz, p->ctrl(3, 0).xyz);
		VectorCopy(pParent->ctrl(4, nIndex).xyz, p->ctrl(4, 0).xyz);

		VectorCopy(pParent->ctrl(1, nIndex).xyz, p->ctrl(0, 1).xyz);
		VectorCopy(pParent->ctrl(1, nIndex).xyz, p->ctrl(1, 1).xyz);
		VectorCopy(pParent->ctrl(2, nIndex).xyz, p->ctrl(2, 1).xyz);
		VectorCopy(pParent->ctrl(3, nIndex).xyz, p->ctrl(3, 1).xyz);
		VectorCopy(pParent->ctrl(3, nIndex).xyz, p->ctrl(4, 1).xyz);

		VectorCopy(pParent->ctrl(1, nIndex).xyz, p->ctrl(0, 2).xyz);
		VectorCopy(pParent->ctrl(1, nIndex).xyz, p->ctrl(1, 2).xyz);
		VectorCopy(pParent->ctrl(2, nIndex).xyz, p->ctrl(2, 2).xyz);
		VectorCopy(pParent->ctrl(3, nIndex).xyz, p->ctrl(3, 2).xyz);
		VectorCopy(pParent->ctrl(3, nIndex).xyz, p->ctrl(4, 2).xyz);
	}


	bool	bEndCap	= (nType == CCapDialog::ENDCAP || nType == CCapDialog::IENDCAP);
	if ( (!bFirst && !bEndCap) || (bFirst && bEndCap) ) {
		idDrawVert	vertTemp;
		for ( i = 0; i < p->width; i++ ) {
			for ( j = 0; j < p->height / 2; j++ ) {
				memcpy(&vertTemp, &p->ctrl(i, p->height - 1 - j), sizeof(idDrawVert));
				memcpy(&p->ctrl(i, p->height - 1 - j), &p->ctrl(i, j), sizeof(idDrawVert));
				memcpy(&p->ctrl(i, j), &vertTemp, sizeof(idDrawVert));
			}
		}
	}

	//--Patch_CalcBounds(p, vMin, vMax);
	//--Brush_RebuildBrush(p->pSymbiot, vMin, vMax);
	Patch_Rebuild(p);
	Patch_CapTexture(p);
	return p->pSymbiot;
}


void Patch_CapCurrent( bool bInvertedBevel,bool bInvertedEndcap ) {
	patchMesh_t	*pParent	= NULL;
	brush_t		*b[4];
	brush_t		*pCap		= NULL;
	b[0] = b[1] = b[2] = b[3] = NULL;
	int	nIndex	= 0;

	if ( !QE_SingleBrush() ) {
		Sys_Status("Cannot cap multiple selection. Please select a single patch.\n");
		return;
	}


	for ( brush_t*pb = selected_brushes.next ; pb != NULL && pb != &selected_brushes ; pb = pb->next ) {
		if ( pb->pPatch ) {
			pParent = pb->pPatch;
			// decide which if any ends we are going to cap
			// if any of these compares hit, it is a closed patch and as such
			// the generic capping will work.. if we do not find a closed edge
			// then we need to ask which kind of cap to add
			if ( pParent->ctrl(0, 0).xyz.Compare(pParent->ctrl(pParent->width - 1, 0).xyz) ) {
				pCap = Cap(pParent, true, false);
				if ( pCap != NULL ) {
					b[nIndex++] = pCap;
				}
			}
			if ( pParent->ctrl(0, pParent->height - 1).xyz.Compare(pParent->ctrl(pParent->width - 1, pParent->height - 1).xyz) ) {
				pCap = Cap(pParent, true, true);
				if ( pCap != NULL ) {
					b[nIndex++] = pCap;
				}
			}
			if ( pParent->ctrl(0, 0).xyz.Compare(pParent->ctrl(0, pParent->height - 1).xyz) ) {
				pCap = Cap(pParent, false, false);
				if ( pCap != NULL ) {
					b[nIndex++] = pCap;
				}
			}
			if ( pParent->ctrl(pParent->width - 1, 0).xyz.Compare(pParent->ctrl(pParent->width - 1, pParent->height - 1).xyz) ) {
				pCap = Cap(pParent, false, true);
				if ( pCap != NULL ) {
					b[nIndex++] = pCap;
				}
			}
		}
	}

	if ( pParent ) {
		// if we did not cap anything with the above tests
		if ( nIndex == 0 ) {
			CCapDialog	dlg;
			if ( dlg.DoModal() == IDOK ) {
				b[nIndex++] = CapSpecial(pParent, dlg.getCapType(), false);
				b[nIndex++] = CapSpecial(pParent, dlg.getCapType(), true);
			}
		}

		if ( nIndex > 0 ) {
			while ( nIndex > 0 ) {
				nIndex--;
				if ( b[nIndex] ) {
					Select_Brush(b[nIndex]);
				}
			}
			eclass_t*pecNew	= Eclass_ForName("func_static", false);
			if ( pecNew ) {
				entity_t*e	= Entity_Create(pecNew);
				SetKeyValue(e, "type", "patchCapped");
			}
		}
	}
}


//FIXME: Table drive all this crap
//
void GenerateEndCaps( brush_t *brushParent,bool bBevel,bool bEndcap,bool bInverted ) {
	brush_t		*b, *b2;
	patchMesh_t	*p, *p2, *pParent;
	idVec3		vTemp, vMin, vMax;
	int			i, j;

	pParent = brushParent->pPatch;

	Patch_CalcBounds(pParent, vMin, vMax);
	// basically generate two endcaps, place them, and link the three brushes with a func_group

	if ( pParent->width > 9 ) {
		b = Patch_GenericMesh(5, 3, 2, false, false, pParent);
	} else {
		b = Patch_GenericMesh(3, 3, 2, false, false, pParent);
	}
	p = b->pPatch;

	vMin[0] = vMin[1] = vMin[2] = 99999;
	vMax[0] = vMax[1] = vMax[2] = -99999;

	for ( i = 0; i < pParent->width; i++ ) {
		VectorCopy(pParent->ctrl(i, 0).xyz, p->ctrl(Index3By[i][0], Index3By[i][1]).xyz);
		for ( j = 0; j < 3; j++ ) {
			if ( pParent->ctrl(i, 0).xyz[j] < vMin[j] )
				vMin[j] = pParent->ctrl(i, 0).xyz[j];
			if ( pParent->ctrl(i, 0).xyz[j] > vMax[j] )
				vMax[j] = pParent->ctrl(i, 0).xyz[j];
		}
	}

	for ( j = 0; j < 3; j++ ) {
		vTemp[j] = vMin[j] + abs((vMax[j] - vMin[j]) * 0.5);
	}

	for ( i = 0; i < Interior3ByCount; i++ ) {
		VectorCopy(vTemp, p->ctrl(Interior3By[i][0], Interior3By[i][1]).xyz);
	}

	Patch_CalcBounds(p, vMin, vMax);
	Brush_RebuildBrush(p->pSymbiot, vMin, vMax);
	Select_Brush(p->pSymbiot);
	return;

	bool	bCreated	= false;

	if ( bInverted ) {
		if ( bBevel ) {
			b = Patch_GenericMesh(3, 3, 2, false, false, pParent);
			p = b->pPatch;
			VectorCopy(p->ctrl(2, 2).xyz, p->ctrl(1, 2).xyz);
			VectorCopy(p->ctrl(2, 2).xyz, p->ctrl(2, 1).xyz);
			VectorCopy(p->ctrl(2, 2).xyz, p->ctrl(0, 1).xyz);
			VectorCopy(p->ctrl(2, 2).xyz, p->ctrl(1, 0).xyz);
			VectorCopy(p->ctrl(2, 2).xyz, p->ctrl(1, 1).xyz);
			VectorCopy(p->ctrl(2, 0).xyz, p->ctrl(0, 0).xyz);

			b2 = Patch_GenericMesh(3, 3, 2, false, false, pParent);
			p2 = b2->pPatch;
			VectorCopy(p2->ctrl(2, 2).xyz, p2->ctrl(1, 2).xyz);
			VectorCopy(p2->ctrl(2, 2).xyz, p2->ctrl(2, 1).xyz);
			VectorCopy(p2->ctrl(2, 2).xyz, p2->ctrl(0, 1).xyz);
			VectorCopy(p2->ctrl(2, 2).xyz, p2->ctrl(1, 0).xyz);
			VectorCopy(p2->ctrl(2, 2).xyz, p2->ctrl(1, 1).xyz);
			VectorCopy(p2->ctrl(2, 0).xyz, p2->ctrl(0, 0).xyz);


			bCreated = true;
		} else if ( bEndcap ) {
			b = Patch_GenericMesh(5, 5, 2, false, false, pParent);
			p = b->pPatch;
			VectorCopy(p->ctrl(4, 4).xyz, p->ctrl(4, 3).xyz);
			VectorCopy(p->ctrl(0, 4).xyz, p->ctrl(1, 4).xyz);
			VectorCopy(p->ctrl(0, 4).xyz, p->ctrl(2, 4).xyz);
			VectorCopy(p->ctrl(0, 4).xyz, p->ctrl(3, 4).xyz);

			VectorCopy(p->ctrl(4, 0).xyz, p->ctrl(4, 1).xyz);
			VectorCopy(p->ctrl(0, 0).xyz, p->ctrl(1, 0).xyz);
			VectorCopy(p->ctrl(0, 0).xyz, p->ctrl(2, 0).xyz);
			VectorCopy(p->ctrl(0, 0).xyz, p->ctrl(3, 0).xyz);

			for ( i = 1; i < 4; i++ ) {
				for ( j = 0; j < 4; j++ ) {
					VectorCopy(p->ctrl(4, i).xyz, p->ctrl(j, i).xyz);
				}
			}


			b2 = Patch_GenericMesh(5, 5, 2, false, false, pParent);
			p2 = b2->pPatch;
			VectorCopy(p2->ctrl(4, 4).xyz, p2->ctrl(4, 3).xyz);
			VectorCopy(p2->ctrl(0, 4).xyz, p2->ctrl(1, 4).xyz);
			VectorCopy(p2->ctrl(0, 4).xyz, p2->ctrl(2, 4).xyz);
			VectorCopy(p2->ctrl(0, 4).xyz, p2->ctrl(3, 4).xyz);

			VectorCopy(p2->ctrl(4, 0).xyz, p2->ctrl(4, 1).xyz);
			VectorCopy(p2->ctrl(0, 0).xyz, p2->ctrl(1, 0).xyz);
			VectorCopy(p2->ctrl(0, 0).xyz, p2->ctrl(2, 0).xyz);
			VectorCopy(p2->ctrl(0, 0).xyz, p2->ctrl(3, 0).xyz);

			for ( i = 1; i < 4; i++ ) {
				for ( j = 0; j < 4; j++ ) {
					VectorCopy(p2->ctrl(4, i).xyz, p2->ctrl(j, i).xyz);
				}
			}


			bCreated = true;
		}
	} else {
		if ( bBevel ) {
			b = Patch_GenericMesh(3, 3, 2, false, false, pParent);
			p = b->pPatch;
			VectorCopy(p->ctrl(2, 0).xyz, p->ctrl(2, 1).xyz);
			VectorCopy(p->ctrl(0, 0).xyz, p->ctrl(1, 0).xyz);
			VectorCopy(p->ctrl(0, 0).xyz, p->ctrl(2, 0).xyz);

			b2 = Patch_GenericMesh(3, 3, 2, false, false, pParent);
			p2 = b2->pPatch;
			VectorCopy(p2->ctrl(2, 0).xyz, p2->ctrl(2, 1).xyz);
			VectorCopy(p2->ctrl(0, 0).xyz, p2->ctrl(1, 0).xyz);
			VectorCopy(p2->ctrl(0, 0).xyz, p2->ctrl(2, 0).xyz);
			bCreated = true;
		} else if ( bEndcap ) {
			b = Patch_GenericMesh(5, 5, 2, false, false, pParent);
			p = b->pPatch;
			VectorCopy(p->ctrl(0, 0).xyz, p->ctrl(1, 0).xyz);
			VectorCopy(p->ctrl(0, 0).xyz, p->ctrl(2, 0).xyz);
			VectorCopy(p->ctrl(0, 0).xyz, p->ctrl(3, 0).xyz);
			VectorCopy(p->ctrl(4, 0).xyz, p->ctrl(4, 1).xyz);
			VectorCopy(p->ctrl(0, 0).xyz, p->ctrl(4, 0).xyz);

			VectorCopy(p->ctrl(0, 4).xyz, p->ctrl(1, 4).xyz);
			VectorCopy(p->ctrl(0, 4).xyz, p->ctrl(2, 4).xyz);
			VectorCopy(p->ctrl(0, 4).xyz, p->ctrl(3, 4).xyz);
			VectorCopy(p->ctrl(4, 4).xyz, p->ctrl(4, 3).xyz);
			VectorCopy(p->ctrl(0, 4).xyz, p->ctrl(4, 4).xyz);

			b2 = Patch_GenericMesh(5, 5, 2, false, false, pParent);
			p2 = b2->pPatch;
			VectorCopy(p2->ctrl(0, 0).xyz, p2->ctrl(1, 0).xyz);
			VectorCopy(p2->ctrl(0, 0).xyz, p2->ctrl(2, 0).xyz);
			VectorCopy(p2->ctrl(0, 0).xyz, p2->ctrl(3, 0).xyz);
			VectorCopy(p2->ctrl(4, 0).xyz, p2->ctrl(4, 1).xyz);
			VectorCopy(p2->ctrl(0, 0).xyz, p2->ctrl(4, 0).xyz);

			VectorCopy(p2->ctrl(0, 4).xyz, p2->ctrl(1, 4).xyz);
			VectorCopy(p2->ctrl(0, 4).xyz, p2->ctrl(2, 4).xyz);
			VectorCopy(p2->ctrl(0, 4).xyz, p2->ctrl(3, 4).xyz);
			VectorCopy(p2->ctrl(4, 4).xyz, p2->ctrl(4, 3).xyz);
			VectorCopy(p2->ctrl(0, 4).xyz, p2->ctrl(4, 4).xyz);
			bCreated = true;
		} else {
			b = Patch_GenericMesh(3, 3, 2, false, false, pParent);
			p = b->pPatch;

			VectorCopy(p->ctrl(0, 1).xyz, vTemp);
			VectorCopy(p->ctrl(0, 2).xyz, p->ctrl(0, 1).xyz);
			VectorCopy(p->ctrl(1, 2).xyz, p->ctrl(0, 2).xyz);
			VectorCopy(p->ctrl(2, 2).xyz, p->ctrl(1, 2).xyz);
			VectorCopy(p->ctrl(2, 1).xyz, p->ctrl(2, 2).xyz);
			VectorCopy(p->ctrl(2, 0).xyz, p->ctrl(2, 1).xyz);
			VectorCopy(p->ctrl(1, 0).xyz, p->ctrl(2, 0).xyz);
			VectorCopy(p->ctrl(0, 0).xyz, p->ctrl(1, 0).xyz);
			VectorCopy(vTemp, p->ctrl(0, 0).xyz);

			b2 = Patch_GenericMesh(3, 3, 2, false, false, pParent);
			p2 = b2->pPatch;
			VectorCopy(p2->ctrl(0, 1).xyz, vTemp);
			VectorCopy(p2->ctrl(0, 2).xyz, p2->ctrl(0, 1).xyz);
			VectorCopy(p2->ctrl(1, 2).xyz, p2->ctrl(0, 2).xyz);
			VectorCopy(p2->ctrl(2, 2).xyz, p2->ctrl(1, 2).xyz);
			VectorCopy(p2->ctrl(2, 1).xyz, p2->ctrl(2, 2).xyz);
			VectorCopy(p2->ctrl(2, 0).xyz, p2->ctrl(2, 1).xyz);
			VectorCopy(p2->ctrl(1, 0).xyz, p2->ctrl(2, 0).xyz);
			VectorCopy(p2->ctrl(0, 0).xyz, p2->ctrl(1, 0).xyz);
			VectorCopy(vTemp, p2->ctrl(0, 0).xyz);
			bCreated = true;
		}
	}

	if ( bCreated ) {
		idDrawVert	vertTemp;
		for ( i = 0; i < p->width; i++ ) {
			for ( j = 0; j < p->height; j++ ) {
				p->ctrl(i, j).xyz[2] = vMin[2];
				p2->ctrl(i, j).xyz[2] = vMax[2];
			}

			for ( j = 0; j < p->height / 2; j++ ) {
				memcpy(&vertTemp, &p->ctrl(i, p->height - 1 - j), sizeof(idDrawVert));
				memcpy(&p->ctrl(i, p->height - 1 - j), &p->ctrl(i, j), sizeof(idDrawVert));
				memcpy(&p->ctrl(i, j), &vertTemp, sizeof(idDrawVert));
			}
		}
		//Select_Delete();

		Patch_CalcBounds(p, vMin, vMax);
		Brush_RebuildBrush(p->pSymbiot, vMin, vMax);
		Patch_CalcBounds(p2, vMin, vMax);
		Brush_RebuildBrush(p2->pSymbiot, vMin, vMax);
		Select_Brush(p->pSymbiot);
		Select_Brush(p2->pSymbiot);
	} else {
		Select_Delete();
	}
	//Select_Brush(brushParent);

}


/*
===============
BrushToPatchMesh
===============
*/
void Patch_BrushToMesh( bool bCone,bool bBevel,bool bEndcap,bool bSquare,int nHeight ) {
	brush_t		*b;
	patchMesh_t	*p;
	int			i, j;

	int			width	= 9;
	if ( bBevel & !bSquare ) {
		width = 3;
	} else if ( bEndcap & !bSquare ) {
		width = 5;
	}

	if ( !QE_SingleBrush() ) {
		return;
	}

	b = selected_brushes.next;

	p = MakeNewPatch(width, nHeight);

	p->d_texture = b->brush_faces->d_texture;

	p->type = PATCH_CYLINDER;
	if ( bBevel & !bSquare ) {
		p->type = PATCH_BEVEL;
		int	nStep	= (b->maxs[2] - b->mins[2]) / (p->height - 1);
		int	nStart	= b->mins[2];
		for ( i = 0; i < p->height; i++ ) {
			p->ctrl(0, i).xyz[0] = b->mins[0];
			p->ctrl(0, i).xyz[1] = b->mins[1];
			p->ctrl(0, i).xyz[2] = nStart;

			p->ctrl(1, i).xyz[0] = b->maxs[0];
			p->ctrl(1, i).xyz[1] = b->mins[1];
			p->ctrl(1, i).xyz[2] = nStart;

			p->ctrl(2, i).xyz[0] = b->maxs[0];
			p->ctrl(2, i).xyz[1] = b->maxs[1];
			p->ctrl(2, i).xyz[2] = nStart;
			nStart += nStep;
		}
	} else if ( bEndcap & !bSquare ) {
		p->type = PATCH_ENDCAP;
		int	nStep	= (b->maxs[2] - b->mins[2]) / (p->height - 1);
		int	nStart	= b->mins[2];
		for ( i = 0; i < p->height; i++ ) {
			p->ctrl(0, i).xyz[0] = b->mins[0];
			p->ctrl(0, i).xyz[1] = b->mins[1];
			p->ctrl(0, i).xyz[2] = nStart;

			p->ctrl(1, i).xyz[0] = b->mins[0];
			p->ctrl(1, i).xyz[1] = b->maxs[1];
			p->ctrl(1, i).xyz[2] = nStart;

			p->ctrl(2, i).xyz[0] = b->mins[0] + ((b->maxs[0] - b->mins[0]) * 0.5);
			p->ctrl(2, i).xyz[1] = b->maxs[1];
			p->ctrl(2, i).xyz[2] = nStart;

			p->ctrl(3, i).xyz[0] = b->maxs[0];
			p->ctrl(3, i).xyz[1] = b->maxs[1];
			p->ctrl(3, i).xyz[2] = nStart;

			p->ctrl(4, i).xyz[0] = b->maxs[0];
			p->ctrl(4, i).xyz[1] = b->mins[1];
			p->ctrl(4, i).xyz[2] = nStart;
			nStart += nStep;
		}
	} else {
		p->ctrl(1, 0).xyz[0] = b->mins[0];
		p->ctrl(1, 0).xyz[1] = b->mins[1];

		p->ctrl(3, 0).xyz[0] = b->maxs[0];
		p->ctrl(3, 0).xyz[1] = b->mins[1];

		p->ctrl(5, 0).xyz[0] = b->maxs[0];
		p->ctrl(5, 0).xyz[1] = b->maxs[1];

		p->ctrl(7, 0).xyz[0] = b->mins[0];
		p->ctrl(7, 0).xyz[1] = b->maxs[1];

		for ( i = 1 ; i < p->width - 1 ; i += 2 ) {
			p->ctrl(i, 0).xyz[2] = b->mins[2];

			VectorCopy(p->ctrl(i, 0).xyz, p->ctrl(i, 2).xyz);

			p->ctrl(i, 2).xyz[2] = b->maxs[2];

			p->ctrl(i, 1).xyz[0] = (p->ctrl(i, 0).xyz[0] + p->ctrl(i, 2).xyz[0]) * 0.5;
			p->ctrl(i, 1).xyz[1] = (p->ctrl(i, 0).xyz[1] + p->ctrl(i, 2).xyz[1]) * 0.5;
			p->ctrl(i, 1).xyz[2] = (p->ctrl(i, 0).xyz[2] + p->ctrl(i, 2).xyz[2]) * 0.5;
		}
		InterpolateInteriorPoints(p);

		if ( bSquare ) {
			if ( bBevel || bEndcap ) {
				if ( bBevel ) {
					for ( i = 0; i < p->height; i++ ) {
						VectorCopy(p->ctrl(1, i).xyz, p->ctrl(2, i).xyz);
						VectorCopy(p->ctrl(7, i).xyz, p->ctrl(6, i).xyz);
					}
				} else {
					for ( i = 0; i < p->height; i++ ) {
						VectorCopy(p->ctrl(5, i).xyz, p->ctrl(4, i).xyz);
						VectorCopy(p->ctrl(1, i).xyz, p->ctrl(2, i).xyz);
						VectorCopy(p->ctrl(7, i).xyz, p->ctrl(6, i).xyz);
						VectorCopy(p->ctrl(8, i).xyz, p->ctrl(7, i).xyz);
					}
				}
			} else {
				for ( i = 0; i < p->width - 1; i ++ ) {
					for ( j = 0; j < p->height; j++ ) {
						VectorCopy(p->ctrl(i + 1, j).xyz, p->ctrl(i, j).xyz);
					}
				}
				for ( j = 0; j < p->height; j++ ) {
					VectorCopy(p->ctrl(0, j).xyz, p->ctrl(8, j).xyz);
				}
			}
		}
	}


	Patch_Naturalize(p);

	if ( bCone ) {
		p->type = PATCH_CONE;
		float	xc	= (b->maxs[0] + b->mins[0]) * 0.5;
		float	yc	= (b->maxs[1] + b->mins[1]) * 0.5;

		for ( i = 0 ; i < p->width ; i ++ ) {
			p->ctrl(i, 2).xyz[0] = xc;
			p->ctrl(i, 2).xyz[1] = yc;
		}
	}
	b = AddBrushForPatch(p);

	Select_Delete();
	Select_Brush(b);
}

patchMesh_t * Patch_GenerateGeneric( int width,int height,int orientation,const idVec3 &mins,const idVec3 &maxs ) {
	patchMesh_t	*p	= MakeNewPatch(width, height);
	p->d_texture = Texture_ForName(g_qeglobals.d_texturewin.texdef.name);

	p->type = PATCH_GENERIC;

	int	nFirst	= 0;
	int	nSecond	= 1;
	if ( orientation == 0 ) {
		nFirst = 1;
		nSecond = 2;
	} else if ( orientation == 1 ) {
		nSecond = 2;
	}


	int		xStep	= mins[nFirst];
	float	xAdj	= abs((maxs[nFirst] - mins[nFirst]) / (width - 1));
	float	yAdj	= abs((maxs[nSecond] - mins[nSecond]) / (height - 1));

	for ( int i = 0; i < width; i++ ) {
		int	yStep	= mins[nSecond];
		for ( int j = 0; j < height; j++ ) {
			p->ctrl(i, j).xyz[nFirst] = xStep;
			p->ctrl(i, j).xyz[nSecond] = yStep;
			p->ctrl(i, j).xyz[orientation] = g_qeglobals.d_new_brush_bottom[orientation];
			yStep += yAdj;
		}
		xStep += xAdj;
	}

	return p;
}

/*
==================
Patch_GenericMesh
==================
*/
brush_t * Patch_GenericMesh( int width,int height,int orientation,bool bDeleteSource,bool bOverride,patchMesh_t *parent ) {
	if ( height < 3 || height> 15 || width < 3 || width> 15 ) {
		Sys_Status("Invalid patch width or height.\n");
		return NULL;
	}

	if ( !bOverride && !QE_SingleBrush() ) {
		Sys_Status("Cannot generate a patch from multiple selections.\n");
		return NULL;
	}

	brush_t		*b	= selected_brushes.next;

	patchMesh_t	*p	= Patch_GenerateGeneric(width, height, orientation, b->mins, b->maxs);

	if ( parent ) {
		p->explicitSubdivisions = parent->explicitSubdivisions;
		p->horzSubdivisions = parent->horzSubdivisions;
		p->vertSubdivisions = parent->vertSubdivisions;
	}

	Patch_Naturalize(p);

	b = AddBrushForPatch(p);

	if ( bDeleteSource ) {
		Select_Delete();
		Select_Brush(b);
	}

	return b;
}

/*
==================
PointInMoveList
==================
*/
int PointInMoveList( idVec3 *pf ) {
	for ( int i = 0; i < g_qeglobals.d_num_move_points; i++ ) {
		if ( pf == g_qeglobals.d_move_points[i] ) {
			return i;
		}
	}
	return -1;
}

/*
==================
PointValueInMoveList
==================
*/
static int PointValueInMoveList( idVec3 v ) {
	for ( int i = 0; i < g_qeglobals.d_num_move_points; i++ ) {
		if ( v.Compare(*g_qeglobals.d_move_points[i]) ) {
			return i;
		}
	}
	return -1;
}

/*
==================
RemovePointFromMoveList
==================
*/
void RemovePointFromMoveList( idVec3 v ) {
	int	n;
	while ( (n = PointValueInMoveList(v)) >= 0 ) {
		for ( int i = n; i < g_qeglobals.d_num_move_points - 1; i++ ) {
			g_qeglobals.d_move_points[i] = g_qeglobals.d_move_points[i + 1];
		}
		g_qeglobals.d_num_move_points--;
	}
}

/*
==================
ColumnSelected
==================
*/
bool ColumnSelected( patchMesh_t *p,int nCol ) {
	for ( int i = 0; i < p->height; i++ ) {
		if ( PointInMoveList(&p->ctrl(nCol, i).xyz) == -1 ) {
			return false;
		}
	}
	return true;
}


/*
==================
AddPoint
==================
*/
static void AddPoint( patchMesh_t *p,idVec3 *v,bool bWeldOrDrill = true ) {
	int	nDim1	= (g_pParentWnd->ActiveXY()->GetViewType() == YZ) ? 1 : 0;
	int	nDim2	= (g_pParentWnd->ActiveXY()->GetViewType() == XY) ? 1 : 2;
	g_qeglobals.d_move_points[g_qeglobals.d_num_move_points++] = v;
	if ( (g_bPatchWeld || g_bPatchDrillDown) && bWeldOrDrill ) {
		for ( int i = 0 ; i < p->width ; i++ ) {
			for ( int j = 0 ; j < p->height ; j++ ) {
				if ( g_bPatchWeld ) {
					if ( (*v).Compare(p->ctrl(i, j).xyz) && PointInMoveList(&p->ctrl(i, j).xyz) == -1 ) {
						g_qeglobals.d_move_points[g_qeglobals.d_num_move_points++] = &p->ctrl(i, j).xyz;
						continue;
					}
				}
				if ( g_bPatchDrillDown && g_nPatchClickedView != W_CAMERA ) {
					if ( (idMath::Fabs((*v)[nDim1] - p->ctrl(i, j).xyz[nDim1]) <= VECTOR_EPSILON) && (idMath::Fabs((*v)[nDim2] - p->ctrl(i, j).xyz[nDim2]) <= VECTOR_EPSILON) ) {
						if ( PointInMoveList(&p->ctrl(i, j).xyz) == -1 ) {
							g_qeglobals.d_move_points[g_qeglobals.d_num_move_points++] = &p->ctrl(i, j).xyz;
							continue;
						}
					}
#if 0
		  int l = 0;
					for ( int k = 0; k < 2; k++ ) {
						if (idMath::Fabs(v[k] - p->ctrl(i,j).xyz[k]) > VECTOR_EPSILON)
						continue;
						l++;
					}
					if (l >= 2 && PointInMoveList(&p->ctrl(i,j).xyz) == -1) {
						g_qeglobals.d_move_points[g_qeglobals.d_num_move_points++] = p->ctrl(i,j).xyz;
						continue;
					}
#endif
				}
			}
		}
	}
#if 0
		if (g_qeglobals.d_num_move_points == 1) {
		// single point selected
		// FIXME: the two loops can probably be reduced to one
		for ( int i = 0 ; i < p->width ; i++ ) {
			for ( int j = 0 ; j < p->height ; j++ ) {
				int n = PointInMoveList(v);
				if (n >= 0) {
					if (((i & 0x01) && (j & 0x01)) == 0) {
						// put any sibling fixed points
						// into the inverse list
						int p1, p2, p3, p4;
						p1 = i + 2;
						p2 = i - 2;
						p3 = j + 2;
						p4 = j - 2;
						if (p1 < p->width) {

						}
						if (p2 >= 0) {
						}
						if (p3 < p->height) {
						}
						if (p4 >= 0) {
						}
					}
				}
			}
		}
	}
#endif
}

/*
==================
SelectRow
==================
*/
void SelectRow( patchMesh_t *p,int nRow,bool bMulti ) {
	if ( !bMulti ) {
		g_qeglobals.d_num_move_points = 0;
	}
	for ( int i = 0; i < p->width; i++ ) {
		AddPoint(p, &p->ctrl(i, nRow).xyz, false);
	}
	//common->Printf("Selected Row %d\n", nRow);
}

/*
==================
SelectColumn
==================
*/
void SelectColumn( patchMesh_t *p,int nCol,bool bMulti ) {
	if ( !bMulti ) {
		g_qeglobals.d_num_move_points = 0;
	}
	for ( int i = 0; i < p->height; i++ ) {
		AddPoint(p, &p->ctrl(nCol, i).xyz, false);
	}
	//common->Printf("Selected Col %d\n", nCol);
}


/*
==================
AddPatchMovePoint
==================
*/
void AddPatchMovePoint( idVec3 v,bool bMulti,bool bFull ) {
	if ( !g_bSameView && !bMulti && !bFull ) {
		g_bSameView = true;
		return;
	}

	for ( brush_t*pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next ) {
		if ( pb->pPatch ) {
			patchMesh_t	*p	= pb->pPatch;
			for ( int i = 0 ; i < p->width ; i++ ) {
				for ( int j = 0 ; j < p->height ; j++ ) {
					if ( v.Compare(p->ctrl(i, j).xyz) ) {
						if ( PointInMoveList(&p->ctrl(i, j).xyz) == -1 ) {
							if ( bFull )		// if we want the full row/col this is on
							{
								SelectColumn(p, i, bMulti);
							} else {
								if ( !bMulti )
									g_qeglobals.d_num_move_points = 0;
								AddPoint(p, &p->ctrl(i, j).xyz);
								//common->Printf("Selected col:row %d:%d\n", i, j);
							}
							//--if (!bMulti)
							return;
						} else {
							if ( bFull ) {
								if ( ColumnSelected(p, i) ) {
									SelectRow(p, j, bMulti);
								} else {
									SelectColumn(p, i, bMulti);
								}
								return;
							}
							if ( g_bSameView ) {
								RemovePointFromMoveList(v);
								return;
							}
						}
					}
				}
			}
		}
	}
}
/*
==================
Patch_UpdateSelected
==================
*/
void Patch_UpdateSelected( idVec3 vMove ) {
	int	i, j;
	for ( i = 0 ; i < g_qeglobals.d_num_move_points ; i++ ) {
		VectorAdd(*g_qeglobals.d_move_points[i], vMove, *g_qeglobals.d_move_points[i]);
		if ( g_qeglobals.d_num_move_points == 1 ) {
		}
	}

	//--patchMesh_t* p = &patchMeshes[g_nSelectedPatch];
	for ( brush_t*pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next ) {
		if ( pb->pPatch ) {
			patchMesh_t	*p	= pb->pPatch;


			g_qeglobals.d_numpoints = 0;
			for ( i = 0 ; i < p->width ; i++ ) {
				for ( j = 0 ; j < p->height ; j++ ) {
					VectorCopy(p->ctrl(i, j).xyz, g_qeglobals.d_points[g_qeglobals.d_numpoints]);
					if ( g_qeglobals.d_numpoints < MAX_POINTS - 1 ) {
						g_qeglobals.d_numpoints++;
					}
				}
			}

			idVec3	vMin, vMax;
			Patch_CalcBounds(p, vMin, vMax);
			Brush_RebuildBrush(p->pSymbiot, vMin, vMax);
		}
	}
	//Brush_Free(p->pSymbiot);
	//Select_Brush(AddBrushForPatch(g_nSelectedPatch));
}


void Patch_AdjustSubdivisions( float hadj,float vadj ) {
	brush_t	*pb;
	for ( pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next ) {
		if ( pb->pPatch ) {
			patchMesh_t	*p	= pb->pPatch;
			p->horzSubdivisions += hadj;
			p->vertSubdivisions += vadj;
			Patch_MakeDirty(p);
		}
	}
	Sys_UpdateWindows(W_ALL);
}

extern float ShadeForNormal( idVec3 normal );

/*
=================
DrawPatchMesh
=================
*/
//FIXME: this routine needs to be reorganized.. should be about 1/4 the size and complexity
void DrawPatchMesh( patchMesh_t *pm,bool bPoints,int *list,bool bShade = false ) {
	int		i, j;

	bool	bOverlay	= pm->bOverlay;
	int		nDrawMode	= g_pParentWnd->GetCamera()->Camera().draw_mode;

	// patches use two display lists, one for camera one for xy
	if ( *list <= 0 ) {
		if ( *list <= 0 ) {
			*list = qglGenLists(1);
		}

		if ( *list > 0 ) {
			qglNewList(*list, GL_COMPILE_AND_EXECUTE);
		}

		//FIXME: finish consolidating all the patch crap
		idSurface_Patch	*cp	= new idSurface_Patch(pm->width * 6, pm->height * 6);
		cp->SetSize(pm->width, pm->height);
		for ( i = 0; i < pm->width; i++ ) {
			for ( j = 0; j < pm->height; j++ ) {
				(*cp)[j * cp->GetWidth() + i].xyz = pm->ctrl(i, j).xyz;
				(*cp)[j * cp->GetWidth() + i].st = pm->ctrl(i, j).st;
			}
		}

		if ( pm->explicitSubdivisions ) {
			cp->SubdivideExplicit(pm->horzSubdivisions, pm->vertSubdivisions, true);
		} else {
			cp->Subdivide(DEFAULT_CURVE_MAX_ERROR, DEFAULT_CURVE_MAX_ERROR, DEFAULT_CURVE_MAX_LENGTH, true);
		}


		int			width	= cp->GetWidth();
		int			height	= cp->GetHeight();
		/*
					for (i = 0; i < width; i++) {
						for (j = 0; j < height; j++) {
							qglBegin(GL_POINTS);
							int index = j * width + i;
							qglVertex3fv((*cp)[index].xyz);
							qglEnd();
							char msg[64];
							sprintf(msg, "(%0.3f, %0.3f, %0.3f)(%0.3f, %0.3f)", (*cp)[index].xyz.x, (*cp)[index].xyz.y, (*cp)[index].xyz.z, (*cp)[index].st.x, (*cp)[index].st.y);
							qglRasterPos3f((*cp)[index].xyz.x + 1, (*cp)[index].xyz.y + 1, (*cp)[index].xyz.z + 1);
							qglCallLists (strlen(msg), GL_UNSIGNED_BYTE, msg);
						}
					}
			*/
#ifdef TEST_SURFACE_CLIPPING
		int			n;
		idSurface	*surf	= cp;
		idSurface	*front, *back;

		surf->Split(idPlane(1, 0, 0, 0), 0.1f, &front, &back);
		if ( front && back ) {
			front->TranslateSelf(idVec3(10, 10, 10));
			(*front) += (*back);
			surf = front;
		} else {
			surf = cp;
		}
		//		surf->ClipInPlace( idPlane( 1, 0, 0, 0 ), 0.1f, true );

		qglBegin(GL_TRIANGLES);
		for ( i = 0; i < surf->GetNumIndexes(); i += 3 ) {
			n = surf->GetIndexes()[i + 0];
			qglTexCoord2fv((*surf)[n].st.ToFloatPtr());
			qglVertex3fv((*surf)[n].xyz.ToFloatPtr());
			n = surf->GetIndexes()[i + 1];
			qglTexCoord2fv((*surf)[n].st.ToFloatPtr());
			qglVertex3fv((*surf)[n].xyz.ToFloatPtr());
			n = surf->GetIndexes()[i + 2];
			qglTexCoord2fv((*surf)[n].st.ToFloatPtr());
			qglVertex3fv((*surf)[n].xyz.ToFloatPtr());
		}
		qglEnd();

		if ( front ) {
			delete front;
		}
		if ( back ) {
			delete back;
		}
#else
		for ( i = 0 ; i < width - 1; i++ ) {
			qglBegin(GL_QUAD_STRIP);
			for ( j = 0 ; j < height; j++ ) {
				// v1-v2-v3-v4 makes a quad
				int		v1, v2;
				float	f;
				v1 = j * width + i;
				v2 = v1 + 1;
				if ( bShade ) {
					f = ShadeForNormal((*cp)[v2].normal);
					qglColor3f(f, f, f);
				}
				qglTexCoord2fv((*cp)[v2].st.ToFloatPtr());
				qglVertex3fv((*cp)[v2].xyz.ToFloatPtr());
				if ( bShade ) {
					f = ShadeForNormal((*cp)[v1].normal);
					qglColor3f(f, f, f);
				}
				qglTexCoord2fv((*cp)[v1].st.ToFloatPtr());
				qglVertex3fv((*cp)[v1].xyz.ToFloatPtr());
			}
			qglEnd();
		}
#endif

		if ( list == &pm->nListSelected ) {
			globalImages->BindNull();
			qglPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
			qglColor3f(1.0f, 1.0f, 1.0f);
			for ( i = 0 ; i < width - 1; i++ ) {
				qglBegin(GL_QUAD_STRIP);
				for ( j = 0 ; j < height; j++ ) {
					int	v1, v2;
					v1 = j * width + i;
					v2 = v1 + 1;
					qglVertex3fv((*cp)[v2].xyz.ToFloatPtr());
					qglVertex3fv((*cp)[v1].xyz.ToFloatPtr());
				}
				qglEnd();
			}
		}

		delete cp;

		if ( *list > 0 ) {
			qglEndList();
		}
	} else {
		qglCallList(*list);
	}

	idVec3	*pSelectedPoints[256];
	int		nIndex	= 0;

	// FIXME: this bend painting code needs to be rolled up significantly as it is a mess right now
	if ( bPoints && (g_qeglobals.d_select_mode == sel_curvepoint || g_qeglobals.d_select_mode == sel_area || g_bPatchBendMode || g_bPatchInsertMode) ) {
		bOverlay = false;

		// bending or inserting
		if ( g_bPatchBendMode || g_bPatchInsertMode ) {
			qglPointSize(6);
			if ( g_bPatchAxisOnRow ) {
				qglColor3f(1, 0, 1);
				qglBegin(GL_POINTS);
				for ( i = 0; i < pm->width; i++ ) {
					qglVertex3fv(reinterpret_cast< float(*)>(&pm->ctrl(i, g_nPatchAxisIndex).xyz));
				}
				qglEnd();

				// could do all of this in one loop but it was pretty messy
				if ( g_bPatchInsertMode ) {
					qglColor3f(0, 0, 1);
					qglBegin(GL_POINTS);
					for ( i = 0; i < pm->width; i++ ) {
						qglVertex3fv(reinterpret_cast< float(*)>(&pm->ctrl(i, g_nPatchAxisIndex).xyz));
						qglVertex3fv(reinterpret_cast< float(*)>(&pm->ctrl(i, g_nPatchAxisIndex + 1).xyz));
					}
					qglEnd();
				} else {
					if ( g_nPatchBendState == BEND_SELECT_EDGE || g_nPatchBendState == BEND_BENDIT || g_nPatchBendState == BEND_SELECT_ORIGIN ) {
						qglColor3f(0, 0, 1);
						qglBegin(GL_POINTS);
						if ( g_nPatchBendState == BEND_SELECT_ORIGIN ) {
							qglVertex3fv(g_vBendOrigin.ToFloatPtr());
						} else {
							for ( i = 0; i < pm->width; i++ ) {
								if ( g_bPatchLowerEdge ) {
									for ( j = 0; j < g_nPatchAxisIndex; j++ ) {
										qglVertex3fv(reinterpret_cast< float(*)>(&pm->ctrl(i, j).xyz));
									}
								} else {
									for ( j = pm->height - 1; j > g_nPatchAxisIndex; j-- ) {
										qglVertex3fv(reinterpret_cast< float(*)>(&pm->ctrl(i, j).xyz));
									}
								}
							}
						}
						qglEnd();
					}
				}
			} else {
				qglColor3f(1, 0, 1);
				qglBegin(GL_POINTS);
				for ( i = 0; i < pm->height; i++ ) {
					qglVertex3fv(reinterpret_cast< float(*)>(&pm->ctrl(g_nPatchAxisIndex, i).xyz));
				}
				qglEnd();

				// could do all of this in one loop but it was pretty messy
				if ( g_bPatchInsertMode ) {
					qglColor3f(0, 0, 1);
					qglBegin(GL_POINTS);
					for ( i = 0; i < pm->height; i++ ) {
						qglVertex3fv(reinterpret_cast< float(*)>(&pm->ctrl(g_nPatchAxisIndex, i).xyz));
						qglVertex3fv(reinterpret_cast< float(*)>(&pm->ctrl(g_nPatchAxisIndex + 1, i).xyz));
					}
					qglEnd();
				} else {
					if ( g_nPatchBendState == BEND_SELECT_EDGE || g_nPatchBendState == BEND_BENDIT || g_nPatchBendState == BEND_SELECT_ORIGIN ) {
						qglColor3f(0, 0, 1);
						qglBegin(GL_POINTS);
						for ( i = 0; i < pm->height; i++ ) {
							if ( g_nPatchBendState == BEND_SELECT_ORIGIN ) {
								qglVertex3fv(reinterpret_cast< float(*)>(&pm->ctrl(g_nBendOriginIndex, i).xyz));
							} else {
								if ( g_bPatchLowerEdge ) {
									for ( j = 0; j < g_nPatchAxisIndex; j++ ) {
										qglVertex3fv(reinterpret_cast< float(*)>(&pm->ctrl(j, i).xyz));
									}
								} else {
									for ( j = pm->width - 1; j > g_nPatchAxisIndex; j-- ) {
										qglVertex3fv(reinterpret_cast< float(*)>(&pm->ctrl(j, i).xyz));
									}
								}
							}
						}
						qglEnd();
					}
				}
			}
		} else {
			qglPointSize(6);
			for ( i = 0 ; i < pm->width ; i++ ) {
				for ( j = 0 ; j < pm->height ; j++ ) {
					qglBegin(GL_POINTS);
					// FIXME: need to not do loop lookups inside here
					int	n	= PointValueInMoveList(pm->ctrl(i, j).xyz);
					if ( n >= 0 ) {
						pSelectedPoints[nIndex++] = &pm->ctrl(i, j).xyz;
					}

					if ( i & 0x01 || j & 0x01 ) {
						qglColor3f(1, 0, 1);
					} else {
						qglColor3f(0, 1, 0);
					}
					qglVertex3fv(pm->ctrl(i, j).xyz.ToFloatPtr());
					qglEnd();
				}
			}
		}

		if ( nIndex > 0 ) {
			qglBegin(GL_POINTS);
			qglColor3f(0, 0, 1);
			while ( nIndex-- > 0 ) {
				qglVertex3fv((*pSelectedPoints[nIndex]).ToFloatPtr());
			}
			qglEnd();
		}
	}
	if ( bOverlay ) {
		qglPointSize(6);
		qglColor3f(0.5, 0.5, 0.5);
		for ( i = 0 ; i < pm->width ; i++ ) {
			qglBegin(GL_POINTS);
			for ( j = 0 ; j < pm->height ; j++ ) {
				if ( i & 0x01 || j & 0x01 ) {
					qglColor3f(0.5, 0, 0.5);
				} else {
					qglColor3f(0, 0.5, 0);
				}
				qglVertex3fv(pm->ctrl(i, j).xyz.ToFloatPtr());
			}
			qglEnd();
		}
	}
}

/*
==================
Patch_DrawXY
==================
*/
void Patch_DrawXY( patchMesh_t *pm ) {
	qglPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	if ( pm->bSelected ) {
		qglColor3fv(g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES].ToFloatPtr());
		//qglDisable (GL_LINE_STIPPLE);
		//qglLineWidth (1);
	} else {
		qglColor3fv(g_qeglobals.d_savedinfo.colors[COLOR_BRUSHES].ToFloatPtr());
	}

	DrawPatchMesh(pm, pm->bSelected, &pm->nListID);
	qglPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	if ( pm->bSelected ) {
		//qglLineWidth (2);
		//qglEnable (GL_LINE_STIPPLE);
	}
}

/*
==================
Patch_DrawCam
==================
*/
void Patch_DrawCam( patchMesh_t *pm,bool selected ) {

	int nDrawMode = g_pParentWnd->GetCamera()->Camera().draw_mode;

	if ( !selected ) {
		qglColor3f(1, 1, 1);
	}

	if ( g_bPatchWireFrame || nDrawMode == cd_wire ) {
		qglDisable(GL_CULL_FACE);
		qglPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
		globalImages->BindNull();
		DrawPatchMesh(pm, pm->bSelected, &pm->nListIDCam, true);
		qglEnable(GL_CULL_FACE);
	} else {
		qglEnable(GL_CULL_FACE);
		qglCullFace(GL_FRONT);
		qglPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

		if ( nDrawMode == cd_texture || nDrawMode == cd_light ) {
			pm->d_texture->GetEditorImage()->Bind();
		}

		if ( !selected && pm->d_texture->GetEditorAlpha() != 1.0f ) {
			qglEnable(GL_BLEND);
			qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		}

		DrawPatchMesh(pm, pm->bSelected, &pm->nListIDCam, true);

		if ( !selected && pm->d_texture->GetEditorAlpha() != 1.0f ) {
			qglDisable(GL_BLEND);
		}

		globalImages->BindNull();

		if ( !selected ) {
			qglCullFace(GL_BACK);
			qglPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
			qglDisable(GL_BLEND);
		} else {
			qglEnable(GL_BLEND);
			qglColor4f(g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES][0], g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES][1], g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES][2], 0.25);
			qglDisable(GL_CULL_FACE);
		}
		DrawPatchMesh(pm, pm->bSelected, (selected) ? &pm->nListSelected : &pm->nListIDCam, !selected);
		qglEnable(GL_CULL_FACE);
	}

#if 0 // this paints normal indicators on the ctrl points
		//--qglDisable (GL_DEPTH_TEST);
	qglColor3f (1,1,1);
	for (int i = 0; i < pm->width; i++) {
		for (int j = 0; j < pm->height; j++) {
			idVec3 temp;
			qglBegin (GL_LINES);
			qglVertex3fv (pm->ctrl(i,j).xyz);
			VectorMA (pm->ctrl(i,j).xyz, 8, pm->ctrl(i,j].normal, temp);
			qglVertex3fv (temp);
			qglEnd ();
		}
	}
	//--qglEnable (GL_DEPTH_TEST);
#endif

}




void ConvexHullForSection( float section[2][4][7] ) {
}

void BrushesForSection( float section[2][4][7] ) {
}


/*
==================
Patch_Move
==================
*/
void Patch_Move( patchMesh_t *pm,const idVec3 vMove,bool bRebuild ) {
	Patch_MakeDirty(pm);
	for ( int w = 0; w < pm->width; w++ ) {
		for ( int h = 0; h < pm->height; h++ ) {
			VectorAdd(pm->ctrl(w, h).xyz, vMove, pm->ctrl(w, h).xyz);
		}
	}
	if ( bRebuild ) {
		idVec3	vMin, vMax;
		Patch_CalcBounds(pm, vMin, vMax);
		//Brush_RebuildBrush(patchMeshes[n].pSymbiot, vMin, vMax);
	}
	UpdatePatchInspector();
}

/*
==================
Patch_ApplyMatrix
==================
*/
void Patch_ApplyMatrix( patchMesh_t *p,const idVec3 vOrigin,const idMat3 matrix,bool bSnap ) {
	idVec3	vTemp;

	for ( int w = 0; w < p->width; w++ ) {
		for ( int h = 0; h < p->height; h++ ) {
			if ( (g_qeglobals.d_select_mode == sel_curvepoint || g_bPatchBendMode) && PointInMoveList(&p->ctrl(w, h).xyz) == -1 ) {
				continue;
			}
			vTemp = p->ctrl(w, h).xyz - vOrigin;
			vTemp *= matrix;
			p->ctrl(w, h).xyz = vTemp + vOrigin;
		}
	}
	idVec3	vMin, vMax;
	Patch_CalcBounds(p, vMin, vMax);
	Brush_RebuildBrush(p->pSymbiot, vMin, vMax);
}

/*
==================
Patch_EditPatch
==================
*/
void Patch_EditPatch() {
	//--patchMesh_t* p = &patchMeshes[n];
	g_qeglobals.d_numpoints = 0;
	g_qeglobals.d_num_move_points = 0;

	for ( brush_t*pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next ) {
		if ( pb->pPatch ) {
			patchMesh_t	*p	= pb->pPatch;
			for ( int i = 0 ; i < p->width ; i++ ) {
				for ( int j = 0 ; j < p->height ; j++ ) {
					VectorCopy(p->ctrl(i, j).xyz, g_qeglobals.d_points[g_qeglobals.d_numpoints]);
					if ( g_qeglobals.d_numpoints < MAX_POINTS - 1 ) {
						g_qeglobals.d_numpoints++;
					}
				}
			}
		}
	}
	g_qeglobals.d_select_mode = sel_curvepoint;
	//--g_nSelectedPatch = n;
}



/*
==================
Patch_Deselect
==================
*/
//FIXME: need all sorts of asserts throughout a lot of this crap
void Patch_Deselect() {
	//--g_nSelectedPatch = -1;
	g_qeglobals.d_select_mode = sel_brush;

	for ( brush_t*b = selected_brushes.next ; b != &selected_brushes ; b = b->next ) {
		if ( b->pPatch ) {
			b->pPatch->bSelected = false;
		}
	}

	if ( g_bPatchBendMode ) {
		Patch_BendToggle();
	}

	if ( g_bPatchInsertMode ) {
		Patch_InsDelToggle();
	}
}


/*
==================
Patch_Select
==================
*/
void Patch_Select( patchMesh_t *p ) {
	// maintained for point manip.. which i need to fix as this
	// is pf error prone
	//--g_nSelectedPatch = n;
	p->bSelected = true;
}


/*
==================
Patch_Deselect
==================
*/
void Patch_Deselect( patchMesh_t *p ) {
	p->bSelected = false;
}


/*
==================
Patch_Delete
==================
*/
void Patch_Delete( patchMesh_t *p ) {
	if ( p->pSymbiot ) {
		p->pSymbiot->pPatch = NULL;
	}

	Mem_Free(p->verts);
	if ( p->epairs ) {
		delete p->epairs;
	}
	Mem_Free(p);

	p = NULL;

	UpdatePatchInspector();
}


/*
==================
Patch_Scale
==================
*/
void Patch_Scale( patchMesh_t *p,const idVec3 vOrigin,const idVec3 vAmt,bool bRebuild ) {
	for ( int w = 0; w < p->width; w++ ) {
		for ( int h = 0; h < p->height; h++ ) {
			if ( g_qeglobals.d_select_mode == sel_curvepoint && PointInMoveList(&p->ctrl(w, h).xyz) == -1 )
				continue;
			for ( int i = 0 ; i < 3 ; i++ ) {
				p->ctrl(w, h).xyz[i] -= vOrigin[i];
				p->ctrl(w, h).xyz[i] *= vAmt[i];
				p->ctrl(w, h).xyz[i] += vOrigin[i];
			}
		}
	}
	if ( bRebuild ) {
		idVec3	vMin, vMax;
		Patch_CalcBounds(p, vMin, vMax);
		Brush_RebuildBrush(p->pSymbiot, vMin, vMax);
	}
	UpdatePatchInspector();
}


/*
==================
Patch_Cleanup
==================
*/
void Patch_Cleanup() {
	//--g_nSelectedPatch = -1;
	//numPatchMeshes = 0;
}



/*
==================
Patch_SetView
==================
*/
void Patch_SetView( int n ) {
	g_bSameView = (n == g_nPatchClickedView);
	g_nPatchClickedView = n;
}


/*
==================
Patch_SetTexture
==================
*/
// FIXME: need array validation throughout
void Patch_SetTexture( patchMesh_t *p,texdef_t *tex_def ) {
	p->d_texture = Texture_ForName(tex_def->name);
	UpdatePatchInspector();
}

/*
==================
Patch_SetTexture
==================
*/
// FIXME: need array validation throughout
void Patch_SetTextureName( patchMesh_t *p,const char *name ) {
	p->d_texture = Texture_ForName(name);
	UpdatePatchInspector();
}


/*
==================
Patch_DragScale
==================
*/
bool Patch_DragScale( patchMesh_t *p,idVec3 vAmt,idVec3 vMove ) {
	idVec3	vMin, vMax, vScale, vTemp, vMid;
	int		i;

	Patch_CalcBounds(p, vMin, vMax);

	VectorSubtract(vMax, vMin, vTemp);

	// if we are scaling in the same dimension the patch has no depth
	for ( i = 0; i < 3; i ++ ) {
		if ( vTemp[i] == 0 && vMove[i] != 0 ) {
			//Patch_Move(n, vMove, true);
			return false;
		}
	}

	for ( i = 0 ; i < 3 ; i++ )
		vMid[i] = (vMin[i] + ((vMax[i] - vMin[i]) / 2));

	for ( i = 0; i < 3; i++ ) {
		if ( vAmt[i] != 0 ) {
			vScale[i] = 1.0f + vAmt[i] / vTemp[i];
		} else {
			vScale[i] = 1.0f;
		}
	}

	Patch_Scale(p, vMid, vScale, false);

	VectorSubtract(vMax, vMin, vTemp);

	Patch_CalcBounds(p, vMin, vMax);

	VectorSubtract(vMax, vMin, vMid);

	VectorSubtract(vMid, vTemp, vTemp);

	VectorScale(vTemp, 0.5, vTemp);

	// abs of both should always be equal
	if ( !vMove.Compare(vAmt) ) {
		for ( i = 0; i < 3; i++ ) {
			if ( vMove[i] != vAmt[i] ) {
				vTemp[i] = -(vTemp[i]);
			}
		}
	}

	Patch_Move(p, vTemp);
	return true;
}


/*
==================
Patch_InsertColumn
==================
*/
void Patch_InsertColumn( patchMesh_t *p,bool bAdd ) {
	int		h, w, i, j;
	idVec3	vTemp;

	if ( p->width + 2 >= MAX_PATCH_WIDTH ) {
		return;
	}

	Patch_AdjustSize(p, 2, 0);

	// re-adjust til after routine
	//p->width -= 2;

	if ( bAdd ) {
		// add column?
		for ( h = 0; h < p->height; h++ ) {
			j = p->width - 3;

			VectorSubtract(p->ctrl(j, h).xyz, p->ctrl(j - 1, h).xyz, vTemp);

			for ( i = 0; i < 3; i++ ) {
				vTemp[i] /= 3;
			}

			memcpy(&p->ctrl(j + 2, h), &p->ctrl(j, h), sizeof(idDrawVert));
			memcpy(&p->ctrl(j, h), &p->ctrl(j - 1, h), sizeof(idDrawVert));

			VectorAdd(p->ctrl(j, h).xyz, vTemp, p->ctrl(j, h).xyz);
			memcpy(&p->ctrl(j + 1, h), &p->ctrl(j, h), sizeof(idDrawVert));
			VectorAdd(p->ctrl(j + 1, h).xyz, vTemp, p->ctrl(j + 1, h).xyz);
		}
	} else {
		for ( h = 0; h < p->height; h++ ) {
			w = p->width - 3;
			while ( w >= 0 ) {
				memcpy(&p->ctrl(w + 2, h), &p->ctrl(w, h), sizeof(idDrawVert));
				w--;
			}
			VectorSubtract(p->ctrl(1, h).xyz, p->ctrl(0, h).xyz, vTemp);
			for ( i = 0; i < 3; i++ ) {
				vTemp[i] /= 3;
			}
			VectorCopy(p->ctrl(0, h).xyz, p->ctrl(1, h).xyz);
			VectorAdd(p->ctrl(1, h).xyz, vTemp, p->ctrl(1, h).xyz);
			VectorCopy(p->ctrl(1, h).xyz, p->ctrl(2, h).xyz);
			VectorAdd(p->ctrl(2, h).xyz, vTemp, p->ctrl(2, h).xyz);
		}
	}
	//p->width += 2;
	UpdatePatchInspector();
}


/*
==================
Patch_InsertRow
==================
*/
void Patch_InsertRow( patchMesh_t *p,bool bAdd ) {
	int		h, w, i, j;
	idVec3	vTemp;

	if ( p->height + 2 >= MAX_PATCH_HEIGHT ) {
		return;
	}

	Patch_AdjustSize(p, 0, 2);

	if ( bAdd ) {
		// add column?
		for ( w = 0; w < p->width; w++ ) {
			j = p->height - 3;
			VectorSubtract(p->ctrl(w, j).xyz, p->ctrl(w, j - 1).xyz, vTemp);
			for ( i = 0; i < 3; i++ ) {
				vTemp[i] /= 3;
			}

			memcpy(&p->ctrl(w, j + 2), &p->ctrl(w, j), sizeof(idDrawVert));
			memcpy(&p->ctrl(w, j), &p->ctrl(w, j - 1), sizeof(idDrawVert));

			VectorAdd(p->ctrl(w, j).xyz, vTemp, p->ctrl(w, j).xyz);
			memcpy(&p->ctrl(w, j + 1), &p->ctrl(w, j), sizeof(idDrawVert));
			VectorAdd(p->ctrl(w, j + 1).xyz, vTemp, p->ctrl(w, j + 1).xyz);
		}
	} else {
		for ( w = 0; w < p->width; w++ ) {
			h = p->height - 3;
			while ( h >= 0 ) {
				memcpy(&p->ctrl(w, h + 2), &p->ctrl(w, h), sizeof(idDrawVert));
				h--;
			}
			VectorSubtract(p->ctrl(w, 1).xyz, p->ctrl(w, 0).xyz, vTemp);
			for ( i = 0; i < 3; i++ ) {
				vTemp[i] /= 3;
			}

			VectorCopy(p->ctrl(w, 0).xyz, p->ctrl(w, 1).xyz);
			VectorAdd(p->ctrl(w, 1).xyz, vTemp, p->ctrl(w, 1).xyz);
			VectorCopy(p->ctrl(w, 1).xyz, p->ctrl(w, 2).xyz);
			VectorAdd(p->ctrl(w, 2).xyz, vTemp, p->ctrl(w, 2).xyz);
		}
	}

	UpdatePatchInspector();
}


/*
==================
Patch_RemoveRow
==================
*/
void Patch_RemoveRow( patchMesh_t *p,bool bFirst ) {
	if ( p->height <= MIN_PATCH_HEIGHT ) {
		return;
	}

	if ( bFirst ) {
		for ( int w = 0; w < p->width; w++ ) {
			for ( int h = 0; h < p->height - 2; h++ ) {
				memcpy(&p->ctrl(w, h), &p->ctrl(w, h + 2), sizeof(idDrawVert));
			}
		}
	}

	Patch_AdjustSize(p, 0, -2);

	UpdatePatchInspector();
}


/*
==================
Patch_RemoveColumn
==================
*/
void Patch_RemoveColumn( patchMesh_t *p,bool bFirst ) {
	if ( p->width <= MIN_PATCH_WIDTH ) {
		return;
	}

	if ( bFirst ) {
		for ( int h = 0; h < p->height; h++ ) {
			for ( int w = 0; w < p->width - 2; w++ ) {
				memcpy(&p->ctrl(w, h), &p->ctrl(w + 2, h), sizeof(idDrawVert));
			}
		}
	}

	Patch_AdjustSize(p, -2, 0);

	UpdatePatchInspector();
}


void Patch_DisperseRows() {
	idVec3	vTemp, vTemp2;
	int		i, w, h;


	for ( brush_t*pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next ) {
		if ( pb->pPatch ) {
			patchMesh_t	*p	= pb->pPatch;
			Patch_Rebuild(p);
			for ( w = 0; w < p->width; w++ ) {
				// for each row, we need to evenly disperse p->height number
				// of points across the old bounds

				// calc total distance to interpolate
				VectorSubtract(p->ctrl(w, p->height - 1).xyz, p->ctrl(w, 0).xyz, vTemp);

				//vTemp[0] = vTemp[1] = vTemp[2] = 0;
				//for (h = 0; h < p->height - nRows; h ++)
				//{
				//  VectorAdd(vTemp, p->ctrl(w,h], vTemp);
				//}

				// amount per cycle
				for ( i = 0; i < 3; i ++ ) {
					vTemp2[i] = vTemp[i] / (p->height - 1);
				}

				// move along
				for ( h = 0; h < p->height - 1; h++ ) {
					VectorAdd(p->ctrl(w, h).xyz, vTemp2, p->ctrl(w, h + 1).xyz);
				}
				Patch_Naturalize(p);
			}
		}
	}
	UpdatePatchInspector();
}

/*
==================
Patch_AdjustColumns
==================
*/
void Patch_DisperseColumns() {
	idVec3	vTemp, vTemp2;
	int		i, w, h;

	for ( brush_t*pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next ) {
		if ( pb->pPatch ) {
			patchMesh_t	*p	= pb->pPatch;
			Patch_Rebuild(p);

			for ( h = 0; h < p->height; h++ ) {
				// for each column, we need to evenly disperse p->width number
				// of points across the old bounds

				// calc total distance to interpolate
				VectorSubtract(p->ctrl(p->width - 1, h).xyz, p->ctrl(0, h).xyz, vTemp);

				// amount per cycle
				for ( i = 0; i < 3; i ++ ) {
					vTemp2[i] = vTemp[i] / (p->width - 1);
				}

				// move along
				for ( w = 0; w < p->width - 1; w++ ) {
					VectorAdd(p->ctrl(w, h).xyz, vTemp2, p->ctrl(w + 1, h).xyz);
				}
			}
			Patch_Naturalize(p);
		}
	}
	UpdatePatchInspector();
}



/*
==================
Patch_AdjustSelected
==================
*/
void Patch_AdjustSelected( bool bInsert,bool bColumn,bool bFlag ) {
	bool	bUpdate	= false;
	for ( brush_t*pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next ) {
		if ( pb->pPatch ) {
			if ( bInsert ) {
				if ( bColumn ) {
					Patch_InsertColumn(pb->pPatch, bFlag);
				} else {
					Patch_InsertRow(pb->pPatch, bFlag);
				}
			} else {
				if ( bColumn ) {
					Patch_RemoveColumn(pb->pPatch, bFlag);
				} else {
					Patch_RemoveRow(pb->pPatch, bFlag);
				}
			}
			bUpdate = true;
			idVec3		vMin, vMax;
			patchMesh_t	*p	= pb->pPatch;
			Patch_CalcBounds(p, vMin, vMax);
			Brush_RebuildBrush(p->pSymbiot, vMin, vMax);
		}
	}
	if ( bUpdate ) {
		Sys_UpdateWindows(W_ALL);
	}
}

void Parse1DMatrix( int x,float *p ) {
	GetToken(true); // (
	for ( int i = 0; i < x; i++ ) {
		GetToken(false);
		p[i] = atof(token);
	}
	GetToken(true); // )
}

void Parse2DMatrix( int y,int x,float *p ) {
	GetToken(true); // (
	for ( int i = 0; i < y; i++ ) {
		Parse1DMatrix(x, p + i * x);
	}
	GetToken(true); // )
}

void Parse3DMatrix( int z,int y,int x,float *p ) {
	GetToken(true); // (
	for ( int i = 0; i < z; i++ ) {
		Parse2DMatrix(y, x, p + i * (x * MAX_PATCH_HEIGHT));
	}
	GetToken(true); // )
}

// parses a patch
brush_t * Patch_Parse( bool bOld ) {
	const idMaterial *tex = declManager->FindMaterial(NULL);
	GetToken(true);

	if ( strcmp(token, "{") ) {
		return NULL;
	}

	patchMesh_t	*pm	= NULL;

	if ( g_qeglobals.bSurfacePropertiesPlugin ) {
		assert(true);
		//GETPLUGINTEXDEF(pm)->ParsePatchTexdef();
	} else {
		// texture def
		GetToken(true);

		// band-aid
		if ( strcmp(token, "(") ) {
			if ( g_qeglobals.mapVersion < 2.0f ) {
				tex = Texture_ForName(va("textures/%s", token));
			} else {
				tex = Texture_ForName(token);
			}
			GetToken(true);
		} else {
			common->Printf("Warning: Patch read with no texture, using notexture... \n");
		}

		if ( strcmp(token, "(") ) {
			return NULL;
		}

		// width, height, flags (currently only negative)
		GetToken(false);
		int	width	= atoi(token);

		GetToken(false);
		int	height	= atoi(token);

		pm = MakeNewPatch(width, height);
		pm->d_texture = tex;

		if ( !bOld ) {
			GetToken(false);
			pm->horzSubdivisions = atoi(token);
			GetToken(false);
			pm->vertSubdivisions = atoi(token);
			pm->explicitSubdivisions = true;
		}

		GetToken(false);
		pm->contents = atoi(token);

		GetToken(false);
		pm->flags = atoi(token);

		GetToken(false);
		pm->value = atoi(token);

		//if (!bOld)
		//{
		//	GetToken(false);
		//	pm->type = atoi(token);
		//}

		GetToken(false);
		if ( strcmp(token, ")") )
			return NULL;
	}



	float	ctrl[MAX_PATCH_WIDTH][MAX_PATCH_HEIGHT][5];
	Parse3DMatrix(pm->width, pm->height, 5, reinterpret_cast< float*>(&ctrl));

	int	w, h;

	for ( w = 0; w < pm->width; w++ ) {
		for ( h = 0; h < pm->height; h++ ) {
			pm->ctrl(w, h).xyz[0] = ctrl[w][h][0];
			pm->ctrl(w, h).xyz[1] = ctrl[w][h][1];
			pm->ctrl(w, h).xyz[2] = ctrl[w][h][2];
			pm->ctrl(w, h).st[0] = ctrl[w][h][3];
			pm->ctrl(w, h).st[1] = ctrl[w][h][4];
		}
	}

	GetToken(true);

	if ( g_qeglobals.m_bBrushPrimitMode ) {
		// we are in brush primit mode, but maybe it's a classic patch that needs converting, test "}"
		if ( strcmp(token, "}") && strcmp(token, "(") ) {
			ParseEpair(pm->epairs);
			GetToken(true);
		}
	}

	if ( strcmp(token, "}") ) {
		return NULL;
	}

	brush_t	*b	= AddBrushForPatch(pm, false);

	return b;
}


/*
==================
Patch_Write
==================
*/
void Patch_Write( patchMesh_t *p,CMemFile *file ) {
	if ( g_qeglobals.bSurfacePropertiesPlugin ) {
		common->Printf("WARNING: Patch_Write to a CMemFile and Surface Properties plugin not done\n");
	}

	if ( p->explicitSubdivisions ) {
		MemFile_fprintf(file, " {\n  patchDef3\n  {\n");
		MemFile_fprintf(file, "   \"%s\"\n", p->d_texture->GetName());
		MemFile_fprintf(file, "   ( %i %i %i %i %i %i %i ) \n", p->width, p->height, p->horzSubdivisions, p->vertSubdivisions, p->contents, p->flags, p->value);
	} else {
		MemFile_fprintf(file, " {\n  patchDef2\n  {\n");
		MemFile_fprintf(file, "   \"%s\"\n", p->d_texture->GetName());
		MemFile_fprintf(file, "   ( %i %i %i %i %i ) \n", p->width, p->height, p->contents, p->flags, p->value);
	}


	float	ctrl[MAX_PATCH_WIDTH][MAX_PATCH_HEIGHT][5];

	int		w, h;
	for ( w = 0; w < p->width; w++ ) {
		for ( h = 0; h < p->height; h++ ) {
			ctrl[w][h][0] = p->ctrl(w, h).xyz[0];
			ctrl[w][h][1] = p->ctrl(w, h).xyz[1];
			ctrl[w][h][2] = p->ctrl(w, h).xyz[2];
			ctrl[w][h][3] = p->ctrl(w, h).st[0];
			ctrl[w][h][4] = p->ctrl(w, h).st[1];
		}
	}

	_Write3DMatrix(file, p->width, p->height, 5, reinterpret_cast< float*>(&ctrl));

	if ( g_qeglobals.m_bBrushPrimitMode ) {
		if ( p->epairs ) {
			int	count	= p->epairs->GetNumKeyVals();
			for ( int i = 0; i < count; i++ ) {
				MemFile_fprintf(file, "\"%s\" \"%s\"\n", p->epairs->GetKeyVal(i)->GetKey().c_str(), p->epairs->GetKeyVal(i)->GetValue().c_str());
			}
		}
	}

	MemFile_fprintf(file, "  }\n }\n");
}

void Patch_Write( patchMesh_t *p,FILE *file ) {
	if ( p->explicitSubdivisions ) {
		fprintf(file, " {\n  patchDef3\n  {\n");
		fprintf(file, "   \"%s\"\n", p->d_texture->GetName());
		fprintf(file, "   ( %i %i %i %i %i %i %i ) \n", p->width, p->height, p->horzSubdivisions, p->vertSubdivisions, p->contents, p->flags, p->value);
	} else {
		fprintf(file, " {\n  patchDef2\n  {\n");
		fprintf(file, "   \"%s\"\n", p->d_texture->GetName());
		fprintf(file, "   ( %i %i %i %i %i ) \n", p->width, p->height, p->contents, p->flags, p->value);
	}

	float	ctrl[MAX_PATCH_WIDTH][MAX_PATCH_HEIGHT][5];

	int		w, h;
	for ( w = 0; w < p->width; w++ ) {
		for ( h = 0; h < p->height; h++ ) {
			ctrl[w][h][0] = p->ctrl(w, h).xyz[0];
			ctrl[w][h][1] = p->ctrl(w, h).xyz[1];
			ctrl[w][h][2] = p->ctrl(w, h).xyz[2];
			ctrl[w][h][3] = p->ctrl(w, h).st[0];
			ctrl[w][h][4] = p->ctrl(w, h).st[1];
		}
	}

	_Write3DMatrix(file, p->width, p->height, 5, reinterpret_cast< float*>(&ctrl));

	if ( g_qeglobals.m_bBrushPrimitMode ) {
		if ( p->epairs ) {
			int	count	= p->epairs->GetNumKeyVals();
			for ( int i = 0; i < count; i++ ) {
				fprintf(file, "\"%s\" \"%s\"\n", p->epairs->GetKeyVal(i)->GetKey().c_str(), p->epairs->GetKeyVal(i)->GetValue().c_str());
			}
		}
	}

	fprintf(file, "  }\n }\n");
}


/*
==================
Patch_RotateTexture
==================
*/
void Patch_RotateTexture( patchMesh_t *p,float fAngle ) {
	idVec3	vMin, vMax;
	Patch_CalcBounds(p, vMin, vMax);
	Patch_MakeDirty(p);
	for ( int w = 0; w < p->width; w++ ) {
		for ( int h = 0; h < p->height; h++ ) {
			if ( g_qeglobals.d_select_mode == sel_curvepoint && PointInMoveList(&p->ctrl(w, h).xyz) == -1 ) {
				continue;
			}

			float	x	= p->ctrl(w, h).st[0];
			float	y	= p->ctrl(w, h).st[1];
			p->ctrl(w, h).st[0] = x * cos(DEG2RAD(fAngle)) - y * sin(DEG2RAD(fAngle));
			p->ctrl(w, h).st[1] = y * cos(DEG2RAD(fAngle)) + x * sin(DEG2RAD(fAngle));
		}
	}
}


/*
==================
Patch_ScaleTexture
==================
*/
void Patch_ScaleTexture( patchMesh_t *p,float fx,float fy,bool absolute ) {
	if ( fx == 0 ) {
		fx = 1.0f;
	}
	if ( fy == 0 ) {
		fy = 1.0f;
	}

	if ( absolute ) {
		Patch_ResetTexturing(1, 1);
	}

	for ( int w = 0; w < p->width; w++ ) {
		for ( int h = 0; h < p->height; h++ ) {
			if ( g_qeglobals.d_select_mode == sel_curvepoint && PointInMoveList(&p->ctrl(w, h).xyz) == -1 ) {
				continue;
			}
			p->ctrl(w, h).st[0] *= fx;
			p->ctrl(w, h).st[1] *= fy;
		}
	}
	Patch_MakeDirty(p);
}

/*
==================
Patch_ShiftTexture
==================
*/
void Patch_ShiftTexture( patchMesh_t *p,float fx,float fy,bool autoAdjust ) {
	//if (fx)
	//  fx = (fx > 0) ? 0.1 : -0.1;
	//if (fy)
	//  fy = (fy > 0) ? 0.1 : -0.1;

	if ( autoAdjust ) {
		fx /= p->d_texture->GetEditorImage()->uploadWidth;
		fy /= p->d_texture->GetEditorImage()->uploadHeight;
	}

	for ( int w = 0; w < p->width; w++ ) {
		for ( int h = 0; h < p->height; h++ ) {
			if ( g_qeglobals.d_select_mode == sel_curvepoint && PointInMoveList(&p->ctrl(w, h).xyz) == -1 )
				continue;

			p->ctrl(w, h).st[0] += fx;
			p->ctrl(w, h).st[1] += fy;
		}
	}
	Patch_MakeDirty(p);
}

void patchInvert( patchMesh_t *p ) {
	idDrawVert	vertTemp;
	Patch_MakeDirty(p);
	for ( int i = 0 ; i < p->width ; i++ ) {
		for ( int j = 0; j < p->height / 2; j++ ) {
			memcpy(&vertTemp, &p->ctrl(i, p->height - 1 - j), sizeof(idDrawVert));
			memcpy(&p->ctrl(i, p->height - 1 - j), &p->ctrl(i, j), sizeof(idDrawVert));
			memcpy(&p->ctrl(i, j), &vertTemp, sizeof(idDrawVert));
		}
	}
}

/*
==================
Patch_ToggleInverted
==================
*/
void Patch_ToggleInverted() {
	bool	bUpdate	= false;

	for ( brush_t*pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next ) {
		if ( pb->pPatch ) {
			bUpdate = true;
			patchInvert(pb->pPatch);
		}
	}

	if ( bUpdate ) {
		Sys_UpdateWindows(W_ALL);
	}
	UpdatePatchInspector();
}

void Patch_FlipTexture( patchMesh_t *p,bool y ) {
	idVec2	temp;
	Patch_MakeDirty(p);
	if ( y ) {
		for ( int i = 0 ; i < p->height ; i++ ) {
			for ( int j = 0; j < p->width / 2; j++ ) {
				temp = p->ctrl(p->width - 1 - j, i).st;
				p->ctrl(p->width - 1 - j, i).st = p->ctrl(j, i).st;
				p->ctrl(j, i).st = temp;
			}
		}
	} else {
		for ( int i = 0 ; i < p->width ; i++ ) {
			for ( int j = 0; j < p->height / 2; j++ ) {
				temp = p->ctrl(i, p->height - 1 - j).st;
				p->ctrl(i, p->height - 1 - j).st = p->ctrl(i, j).st;
				p->ctrl(i, j).st = temp;
			}
		}
	}
}


/*
==================
Patch_ToggleInverted
==================
*/
void Patch_InvertTexture( bool bY ) {
	bool	bUpdate	= false;
	for ( brush_t*pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next ) {
		if ( pb->pPatch ) {
			bUpdate = true;
			Patch_FlipTexture(pb->pPatch, bY);
		}
	}

	if ( bUpdate ) {
		Sys_UpdateWindows(W_ALL);
	}

	UpdatePatchInspector();
}




/*
==================
Patch_Save
==================
 Saves patch ctrl info (originally to deal with a
 cancel in the surface dialog
*/
void Patch_Save( patchMesh_t *p ) {
	if ( patchSave ) {
		Mem_Free(patchSave->verts);
		Mem_Free(patchSave);
	}

	patchSave = MakeNewPatch(p->width, p->height);
	memcpy(patchSave->verts, p->verts, sizeof(p->verts[0]) * p->width * p->height);
}


/*
==================
Patch_Restore
==================
*/
void Patch_Restore( patchMesh_t *p ) {
	if ( patchSave ) {
		p->width = patchSave->width;
		p->height = patchSave->height;
		memcpy(p->verts, patchSave->verts, sizeof(p->verts[0]) * p->width * p->height);
		Mem_Free(patchSave->verts);
		Mem_Free(patchSave);
		patchSave = NULL;
	}
}

void Patch_FitTexture( patchMesh_t *p,float fx,float fy ) {
	Patch_MakeDirty(p);
	for ( int i = 0 ; i < p->width ; i++ ) {
		for ( int j = 0 ; j < p->height ; j++ ) {
			p->ctrl(i, j).st[0] = fx * (float) i / (p->width - 1);
			p->ctrl(i, j).st[1] = fy * (float) j / (p->height - 1);
		}
	}
}

void Patch_ResetTexturing( float fx,float fy ) {
	for ( brush_t*pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next ) {
		if ( pb->pPatch ) {
			patchMesh_t	*p	= pb->pPatch;
			Patch_MakeDirty(p);
			for ( int i = 0 ; i < p->width ; i++ ) {
				for ( int j = 0 ; j < p->height ; j++ ) {
					p->ctrl(i, j).st[0] = fx * (float) i / (p->width - 1);
					p->ctrl(i, j).st[1] = fy * (float) j / (p->height - 1);
				}
			}
		}
	}
}


void Patch_FitTexturing() {
	for ( brush_t*pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next ) {
		if ( pb->pPatch ) {
			patchMesh_t	*p	= pb->pPatch;
			Patch_MakeDirty(p);
			for ( int i = 0 ; i < p->width ; i++ ) {
				for ( int j = 0 ; j < p->height ; j++ ) {
					p->ctrl(i, j).st[0] = 1 * (float) i / (p->width - 1);
					p->ctrl(i, j).st[1] = 1 * (float) j / (p->height - 1);
				}
			}
		}
	}
}

void Patch_SetTextureInfo( texdef_t *pt ) {
	for ( brush_t*pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next ) {
		if ( pb->pPatch ) {
			if ( pt->rotate )
				Patch_RotateTexture(pb->pPatch, pt->rotate);

			if ( pt->shift[0] || pt->shift[1] )
				Patch_ShiftTexture(pb->pPatch, pt->shift[0], pt->shift[1], false);

			if ( pt->scale[0] || pt->scale[1] )
				Patch_ScaleTexture(pb->pPatch, pt->scale[0], pt->scale[1], false);

			patchMesh_t	*p	= pb->pPatch;
			p->value = pt->value;
		}
	}
}

bool WINAPI OnlyPatchesSelected() {
	if ( g_ptrSelectedFaces.GetSize() > 0 || selected_brushes.next == &selected_brushes ) {
		return false;
	}
	for ( brush_t*pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next ) {
		if ( !pb->pPatch ) {
			return false;
		}
	}
	return true;
}

bool WINAPI AnyPatchesSelected() {
	if ( g_ptrSelectedFaces.GetSize() > 0 || selected_brushes.next == &selected_brushes ) {
		return false;
	}
	for ( brush_t*pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next ) {
		if ( pb->pPatch ) {
			return true;
		}
	}
	return false;
}

patchMesh_t * SinglePatchSelected() {
	if ( selected_brushes.next->pPatch ) {
		return selected_brushes.next->pPatch;
	}
	return NULL;
}

void Patch_BendToggle() {
	if ( g_bPatchBendMode ) {
		g_bPatchBendMode = false;
		HideInfoDialog();
		g_pParentWnd->UpdatePatchToolbarButtons() ;
		return;
	}

	brush_t	*b	= selected_brushes.next;

	if ( !QE_SingleBrush() || !b->pPatch ) {
		Sys_Status("Must bend a single patch");
		return;
	}

	Patch_Save(b->pPatch);
	g_bPatchBendMode = true;
	g_nPatchBendState = BEND_SELECT_ROTATION;
	g_bPatchAxisOnRow = true;
	g_nPatchAxisIndex = 1;
	ShowInfoDialog(g_pBendStateMsg[BEND_SELECT_ROTATION]);
}

void Patch_BendHandleTAB() {
	if ( !g_bPatchBendMode ) {
		return;
	}

	brush_t	*b	= selected_brushes.next;
	if ( !QE_SingleBrush() || !b->pPatch ) {
		Patch_BendToggle();
		Sys_Status("No patch to bend!");
		return;
	}

	patchMesh_t	*p		= b->pPatch;

	bool		bShift	= ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0);

	if ( g_nPatchBendState == BEND_SELECT_ROTATION ) {
		// only able to deal with odd numbered rows/cols
		g_nPatchAxisIndex += (bShift) ? -2 : 2;
		if ( g_bPatchAxisOnRow ) {
			if ( (bShift) ? g_nPatchAxisIndex <= 0 : g_nPatchAxisIndex >= p->height ) {
				g_bPatchAxisOnRow = false;
				g_nPatchAxisIndex = (bShift) ? p->width - 1 : 1;
			}
		} else {
			if ( (bShift) ? g_nPatchAxisIndex <= 0 : g_nPatchAxisIndex >= p->width ) {
				g_bPatchAxisOnRow = true;
				g_nPatchAxisIndex = (bShift) ? p->height - 1 : 1;
			}
		}
	} else if ( g_nPatchBendState == BEND_SELECT_ORIGIN ) {
		g_nBendOriginIndex += (bShift) ? -1 : 1;
		if ( g_bPatchAxisOnRow ) {
			if ( bShift ) {
				if ( g_nBendOriginIndex < 0 )
					g_nBendOriginIndex = p->width - 1;
			} else {
				if ( g_nBendOriginIndex > p->width - 1 )
					g_nBendOriginIndex = 0;
			}
			VectorCopy(p->ctrl(g_nBendOriginIndex, g_nPatchAxisIndex).xyz, g_vBendOrigin);
		} else {
			if ( bShift ) {
				if ( g_nBendOriginIndex < 0 )
					g_nBendOriginIndex = p->height - 1;
			} else {
				if ( g_nBendOriginIndex > p->height - 1 )
					g_nBendOriginIndex = 0;
			}
			VectorCopy(p->ctrl(g_nPatchAxisIndex, g_nBendOriginIndex).xyz, g_vBendOrigin);
		}
	} else if ( g_nPatchBendState == BEND_SELECT_EDGE ) {
		g_bPatchLowerEdge ^= 1;
	}
	Sys_UpdateWindows(W_ALL);
}

void Patch_BendHandleENTER() {
	if ( !g_bPatchBendMode ) {
		return;
	}

	if ( g_nPatchBendState < BEND_BENDIT ) {
		g_nPatchBendState++;
		ShowInfoDialog(g_pBendStateMsg[g_nPatchBendState]);
		if ( g_nPatchBendState == BEND_SELECT_ORIGIN ) {
			g_vBendOrigin[0] = g_vBendOrigin[1] = g_vBendOrigin[2] = 0;
			g_nBendOriginIndex = 0;
			Patch_BendHandleTAB();
		} else if ( g_nPatchBendState == BEND_SELECT_EDGE ) {
			g_bPatchLowerEdge = true;
		} else if ( g_nPatchBendState == BEND_BENDIT ) {
			// basically we go into rotation mode, set the axis to the center of the
		}
	} else {
		// done
		Patch_BendToggle();
	}
	Sys_UpdateWindows(W_ALL);
}


void Patch_BendHandleESC() {
	if ( !g_bPatchBendMode ) {
		return;
	}
	Patch_BendToggle();
	brush_t	*b	= selected_brushes.next;
	if ( QE_SingleBrush() && b->pPatch ) {
		Patch_Restore(b->pPatch);
	}
	Sys_UpdateWindows(W_ALL);
}

void Patch_SetBendRotateOrigin( patchMesh_t *p ) {
	int	nType	= g_pParentWnd->ActiveXY()->GetViewType();
	int	nDim3	= (nType == XY) ? 2 : (nType == YZ) ? 0 : 1;
	g_vBendOrigin[nDim3] = 0;
	VectorCopy(g_vBendOrigin, g_pParentWnd->ActiveXY()->RotateOrigin());
	return;
}

// also sets the rotational origin
void Patch_SelectBendAxis() {
	brush_t	*b	= selected_brushes.next;
	if ( !QE_SingleBrush() || !b->pPatch ) {
		// should not ever happen
		Patch_BendToggle();
		return;
	}

	patchMesh_t	*p	= b->pPatch;
	if ( g_bPatchAxisOnRow ) {
		SelectRow(p, g_nPatchAxisIndex, false);
	} else {
		SelectColumn(p, g_nPatchAxisIndex, false);
	}

	Patch_SetBendRotateOrigin(p);
}

void Patch_SelectBendNormal() {
	brush_t	*b	= selected_brushes.next;
	if ( !QE_SingleBrush() || !b->pPatch ) {
		// should not ever happen
		Patch_BendToggle();
		return;
	}

	patchMesh_t	*p	= b->pPatch;

	g_qeglobals.d_num_move_points = 0;
	if ( g_bPatchAxisOnRow ) {
		if ( g_bPatchLowerEdge ) {
			for ( int j = 0; j < g_nPatchAxisIndex; j++ )
				SelectRow(p, j, true);
		} else {
			for ( int j = p->height - 1; j > g_nPatchAxisIndex; j-- )
				SelectRow(p, j, true);
		}
	} else {
		if ( g_bPatchLowerEdge ) {
			for ( int j = 0; j < g_nPatchAxisIndex; j++ )
				SelectColumn(p, j, true);
		} else {
			for ( int j = p->width - 1; j > g_nPatchAxisIndex; j-- )
				SelectColumn(p, j, true);
		}
	}
	Patch_SetBendRotateOrigin(p);
}



void Patch_InsDelToggle() {
	if ( g_bPatchInsertMode ) {
		g_bPatchInsertMode = false;
		HideInfoDialog();
		g_pParentWnd->UpdatePatchToolbarButtons() ;
		return;
	}

	brush_t	*b	= selected_brushes.next;

	if ( !QE_SingleBrush() || !b->pPatch ) {
		Sys_Status("Must work with a single patch");
		return;
	}

	Patch_Save(b->pPatch);
	g_bPatchInsertMode = true;
	g_nPatchInsertState = INSERT_SELECT_EDGE;
	g_bPatchAxisOnRow = true;
	g_nPatchAxisIndex = 0;
	ShowInfoDialog(g_pInsertStateMsg[INSERT_SELECT_EDGE]);
}

void Patch_InsDelESC() {
	if ( !g_bPatchInsertMode ) {
		return;
	}
	Patch_InsDelToggle();
	Sys_UpdateWindows(W_ALL);
}


void Patch_InsDelHandleENTER() {
}

void Patch_InsDelHandleTAB() {
	if ( !g_bPatchInsertMode ) {
		Patch_InsDelToggle();
		return;
	}

	brush_t	*b	= selected_brushes.next;
	if ( !QE_SingleBrush() || !b->pPatch ) {
		Patch_BendToggle();
		common->Printf("No patch to bend!");
		return;
	}

	patchMesh_t	*p	= b->pPatch;

	// only able to deal with odd numbered rows/cols
	g_nPatchAxisIndex += 2;
	if ( g_bPatchAxisOnRow ) {
		if ( g_nPatchAxisIndex >= p->height - 1 ) {
			g_bPatchAxisOnRow = false;
			g_nPatchAxisIndex = 0;
		}
	} else {
		if ( g_nPatchAxisIndex >= p->width - 1 ) {
			g_bPatchAxisOnRow = true;
			g_nPatchAxisIndex = 0;
		}
	}
	Sys_UpdateWindows(W_ALL);
}


void _Write1DMatrix( FILE *f,int x,float *m ) {
	int	i;

	fprintf(f, "( ");
	for ( i = 0 ; i < x ; i++ ) {
		if ( m[i] == (int) m[i] ) {
			fprintf(f, "%i ", (int) m[i]);
		} else {
			fprintf(f, "%f ", m[i]);
		}
	}
	fprintf(f, ")");
}

void _Write2DMatrix( FILE *f,int y,int x,float *m ) {
	int	i;

	fprintf(f, "( ");
	for ( i = 0 ; i < y ; i++ ) {
		_Write1DMatrix(f, x, m + i * x);
		fprintf(f, " ");
	}
	fprintf(f, ")\n");
}


void _Write3DMatrix( FILE *f,int z,int y,int x,float *m ) {
	int	i;

	fprintf(f, "(\n");
	for ( i = 0 ; i < z ; i++ ) {
		_Write2DMatrix(f, y, x, m + i * (x * MAX_PATCH_HEIGHT));
	}
	fprintf(f, ")\n");
}

void _Write1DMatrix( CMemFile *f,int x,float *m ) {
	int	i;

	MemFile_fprintf(f, "( ");
	for ( i = 0 ; i < x ; i++ ) {
		if ( m[i] == (int) m[i] ) {
			MemFile_fprintf(f, "%i ", (int) m[i]);
		} else {
			MemFile_fprintf(f, "%f ", m[i]);
		}
	}
	MemFile_fprintf(f, ")");
}

void _Write2DMatrix( CMemFile *f,int y,int x,float *m ) {
	int	i;

	MemFile_fprintf(f, "( ");
	for ( i = 0 ; i < y ; i++ ) {
		_Write1DMatrix(f, x, m + i * x);
		MemFile_fprintf(f, " ");
	}
	MemFile_fprintf(f, ")\n");
}


void _Write3DMatrix( CMemFile *f,int z,int y,int x,float *m ) {
	int	i;

	MemFile_fprintf(f, "(\n");
	for ( i = 0 ; i < z ; i++ ) {
		_Write2DMatrix(f, y, x, m + i * (x * MAX_PATCH_HEIGHT));
	}
	MemFile_fprintf(f, ")\n");
}


void Patch_NaturalizeSelected( bool bCap,bool bCycleCap,bool alt ) {
	for ( brush_t*pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next ) {
		if ( pb->pPatch ) {
			if ( bCap ) {
				Patch_CapTexture(pb->pPatch, bCycleCap, alt);
			} else {
				Patch_Naturalize(pb->pPatch, true, true, alt);
			}
		}
	}
}

void Patch_SubdivideSelected( bool subdivide,int horz,int vert ) {
	for ( brush_t*pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next ) {
		if ( pb->pPatch ) {
			pb->pPatch->explicitSubdivisions = subdivide;
			if ( horz <= 0 ) {
				horz = 1;
			}
			if ( vert <= 0 ) {
				vert = 1;
			}
			pb->pPatch->horzSubdivisions = horz;
			pb->pPatch->vertSubdivisions = vert;
			Patch_MakeDirty(pb->pPatch);
		}
	}
}



bool within( idVec3 vTest,idVec3 vTL,idVec3 vBR ) {
	int	nDim1	= (g_pParentWnd->ActiveXY()->GetViewType() == YZ) ? 1 : 0;
	int	nDim2	= (g_pParentWnd->ActiveXY()->GetViewType() == XY) ? 1 : 2;
	if ( (vTest[nDim1] > vTL[nDim1] && vTest[nDim1] < vBR[nDim1]) || (vTest[nDim1] < vTL[nDim1] && vTest[nDim1] > vBR[nDim1]) ) {
		if ( (vTest[nDim2] > vTL[nDim2] && vTest[nDim2] < vBR[nDim2]) || (vTest[nDim2] < vTL[nDim2] && vTest[nDim2] > vBR[nDim2]) ) {
			return true;
		}
	}
	return false;
}


void Patch_SelectAreaPoints() {
	//jhefty - make patch selection additive ALWAYS
	//g_qeglobals.d_num_move_points = 0;
	g_nPatchClickedView = -1;

	for ( brush_t*pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next ) {
		if ( pb->pPatch ) {
			patchMesh_t	*p	= pb->pPatch;
			for ( int i = 0; i < p->width; i++ ) {
				for ( int j = 0; j < p->height; j++ ) {
					if ( within(p->ctrl(i, j).xyz, g_qeglobals.d_vAreaTL, g_qeglobals.d_vAreaBR) ) {
						g_qeglobals.d_move_points[g_qeglobals.d_num_move_points++] = &p->ctrl(i, j).xyz;
					}
				}
			}
		}
	}
}

const char * Patch_GetTextureName() {
	brush_t	*b	= selected_brushes.next;
	if ( b->pPatch ) {
		patchMesh_t	*p	= b->pPatch;
		if ( p->d_texture->GetName() )
			return p->d_texture->GetName();
	}
	return "";
}

patchMesh_t * Patch_Duplicate( patchMesh_t *pFrom ) {
	patchMesh_t	*p	= MakeNewPatch(pFrom->width, pFrom->height);
	p->contents = pFrom->contents;
	p->value = pFrom->value;
	p->horzSubdivisions = pFrom->horzSubdivisions;
	p->vertSubdivisions = pFrom->vertSubdivisions;
	p->explicitSubdivisions = pFrom->explicitSubdivisions;
	p->d_texture = pFrom->d_texture;
	p->bSelected = false;
	p->bOverlay = false;
	p->nListID = -1;

	memcpy(p->verts, pFrom->verts, p->width * p->height * sizeof(idDrawVert));

	AddBrushForPatch(p);
	return p;
}


void Patch_Thicken( int nAmount,bool bSeam ) {
	int			i, j, h, w;
	brush_t		*b;
	patchMesh_t	*pSeam;
	idVec3		vMin, vMax;
	CPtrArray	brushes;

	nAmount = -nAmount;


	if ( !QE_SingleBrush() ) {
		Sys_Status("Cannot thicken multiple patches. Please select a single patch.\n");
		return;
	}

	for ( brush_t*pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next ) {
		if ( !pb->pPatch ) {
			return;
		}

		patchMesh_t	*p	= pb->pPatch;
		Patch_MeshNormals(p);
		patchMesh_t	*pNew	= Patch_Duplicate(p);
		for ( i = 0; i < p->width; i++ ) {
			for ( j = 0; j < p->height; j++ ) {
				VectorMA(p->ctrl(i, j).xyz, nAmount, p->ctrl(i, j).normal, pNew->ctrl(i, j).xyz);
			}
		}

		Patch_Rebuild(pNew);
		pNew->type |= PATCH_THICK;
		brushes.Add(pNew->pSymbiot);

		if ( bSeam ) {
			// FIXME: this should detect if any edges of the patch are closed and act appropriately
			//
			if ( !(p->type & PATCH_CYLINDER) ) {
				b = Patch_GenericMesh(3, p->height, 2, false, true, p);
				pSeam = b->pPatch;
				pSeam->type |= PATCH_SEAM;
				for ( i = 0; i < p->height; i++ ) {
					VectorCopy(p->ctrl(0, i).xyz, pSeam->ctrl(0, i).xyz);
					VectorCopy(pNew->ctrl(0, i).xyz, pSeam->ctrl(2, i).xyz);
					VectorAdd(pSeam->ctrl(0, i).xyz, pSeam->ctrl(2, i).xyz, pSeam->ctrl(1, i).xyz);
					VectorScale(pSeam->ctrl(1, i).xyz, 0.5, pSeam->ctrl(1, i).xyz);
				}


				Patch_CalcBounds(pSeam, vMin, vMax);
				Brush_RebuildBrush(pSeam->pSymbiot, vMin, vMax);
				//--Patch_CapTexture(pSeam);
				Patch_Naturalize(pSeam);
				patchInvert(pSeam);
				brushes.Add(b);

				w = p->width - 1;
				b = Patch_GenericMesh(3, p->height, 2, false, true, p);
				pSeam = b->pPatch;
				pSeam->type |= PATCH_SEAM;
				for ( i = 0; i < p->height; i++ ) {
					VectorCopy(p->ctrl(w, i).xyz, pSeam->ctrl(0, i).xyz);
					VectorCopy(pNew->ctrl(w, i).xyz, pSeam->ctrl(2, i).xyz);
					VectorAdd(pSeam->ctrl(0, i).xyz, pSeam->ctrl(2, i).xyz, pSeam->ctrl(1, i).xyz);
					VectorScale(pSeam->ctrl(1, i).xyz, 0.5, pSeam->ctrl(1, i).xyz);
				}
				Patch_CalcBounds(pSeam, vMin, vMax);
				Brush_RebuildBrush(pSeam->pSymbiot, vMin, vMax);
				//--Patch_CapTexture(pSeam);
				Patch_Naturalize(pSeam);
				brushes.Add(b);
			}

			//--{
			// otherwise we will add one per end
			b = Patch_GenericMesh(p->width, 3, 2, false, true, p);
			pSeam = b->pPatch;
			pSeam->type |= PATCH_SEAM;
			for ( i = 0; i < p->width; i++ ) {
				VectorCopy(p->ctrl(i, 0).xyz, pSeam->ctrl(i, 0).xyz);
				VectorCopy(pNew->ctrl(i, 0).xyz, pSeam->ctrl(i, 2).xyz);
				VectorAdd(pSeam->ctrl(i, 0).xyz, pSeam->ctrl(i, 2).xyz, pSeam->ctrl(i, 1).xyz);
				VectorScale(pSeam->ctrl(i, 1).xyz, 0.5, pSeam->ctrl(i, 1).xyz);
			}


			Patch_CalcBounds(pSeam, vMin, vMax);
			Brush_RebuildBrush(pSeam->pSymbiot, vMin, vMax);
			//--Patch_CapTexture(pSeam);
			Patch_Naturalize(pSeam);
			patchInvert(pSeam);
			brushes.Add(b);

			h = p->height - 1;
			b = Patch_GenericMesh(p->width, 3, 2, false, true, p);
			pSeam = b->pPatch;
			pSeam->type |= PATCH_SEAM;
			for ( i = 0; i < p->width; i++ ) {
				VectorCopy(p->ctrl(i, h).xyz, pSeam->ctrl(i, 0).xyz);
				VectorCopy(pNew->ctrl(i, h).xyz, pSeam->ctrl(i, 2).xyz);
				VectorAdd(pSeam->ctrl(i, 0).xyz, pSeam->ctrl(i, 2).xyz, pSeam->ctrl(i, 1).xyz);
				VectorScale(pSeam->ctrl(i, 1).xyz, 0.5, pSeam->ctrl(i, 1).xyz);
			}
			Patch_CalcBounds(pSeam, vMin, vMax);
			Brush_RebuildBrush(pSeam->pSymbiot, vMin, vMax);
			//--Patch_CapTexture(pSeam);
			Patch_Naturalize(pSeam);
			brushes.Add(b);
			//--}
		}
		patchInvert(pNew);
	}

	for ( i = 0; i < brushes.GetSize(); i++ ) {
		Select_Brush(reinterpret_cast< brush_t*>(brushes.GetAt(i)));
	}

	if ( brushes.GetSize() > 0 ) {
		eclass_t*pecNew	= Eclass_ForName("func_static", false);
		if ( pecNew ) {
			entity_t*e	= Entity_Create(pecNew);
			SetKeyValue(e, "type", "patchThick");
		}
	}

	UpdatePatchInspector();
}


/*
lets get another list together as far as necessities..

*snapping stuff to the grid (i will only snap movements by the mouse to the grid.. snapping the rotational bend stuff will fubar everything)

capping bevels/endcaps

hot keys

texture fix for caps

clear clipboard

*region fix

*surface dialog

*/

void Patch_SetOverlays() {
	for ( brush_t*pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next ) {
		if ( pb->pPatch ) {
			pb->pPatch->bOverlay = true;
		}
	}
}



void Patch_ClearOverlays() {
	brush_t	*pb;
	for ( pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next ) {
		if ( pb->pPatch ) {
			pb->pPatch->bOverlay = false;
		}
	}

	for ( pb = active_brushes.next ; pb != &active_brushes ; pb = pb->next ) {
		if ( pb->pPatch ) {
			pb->pPatch->bOverlay = false;
		}
	}
}

// freezes selected vertices
void Patch_Freeze() {
	brush_t	*pb;
	for ( pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next ) {
		if ( pb->pPatch ) {
			pb->pPatch->bOverlay = false;
		}
	}

	for ( pb = active_brushes.next ; pb != &active_brushes ; pb = pb->next ) {
		if ( pb->pPatch ) {
			pb->pPatch->bOverlay = false;
		}
	}
}

void Patch_UnFreeze( bool bAll ) {
}


void Patch_Transpose() {
	int			i, j, w;
	idDrawVert	dv;
	for ( brush_t*pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next ) {
		if ( pb->pPatch ) {
			patchMesh_t	*p	= pb->pPatch;

			if ( p->width > p->height ) {
				for ( i = 0 ; i < p->height ; i++ ) {
					for ( j = i + 1 ; j < p->width ; j++ ) {
						if ( j < p->height ) {
							// swap the value
							memcpy(&dv, &p->ctrl(j, i), sizeof(idDrawVert));
							memcpy(&p->ctrl(j, i), &p->ctrl(i, j), sizeof(idDrawVert));
							memcpy(&p->ctrl(i, j), &dv, sizeof(idDrawVert));
						} else {
							// just copy
							memcpy(&p->ctrl(j, i), &p->ctrl(i, j), sizeof(idDrawVert));
						}
					}
				}
			} else {
				for ( i = 0 ; i < p->width ; i++ ) {
					for ( j = i + 1 ; j < p->height ; j++ ) {
						if ( j < p->width ) {
							// swap the value
							memcpy(&dv, &p->ctrl(i, j), sizeof(idDrawVert));
							memcpy(&p->ctrl(i, j), &p->ctrl(j, i), sizeof(idDrawVert));
							memcpy(&p->ctrl(j, i), &dv, sizeof(idDrawVert));
						} else {
							// just copy
							memcpy(&p->ctrl(i, j), &p->ctrl(j, i), sizeof(idDrawVert));
						}
					}
				}
			}

			w = p->width;
			p->width = p->height;
			p->height = w;
			patchInvert(p);
			Patch_Rebuild(p);
		}
	}
}



void Select_SnapToGrid() {
	int	i, j, k;
	for ( brush_t*pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next ) {
		if ( pb->pPatch ) {
			patchMesh_t	*p	= pb->pPatch;
			for ( i = 0; i < p->width; i++ ) {
				for ( j = 0; j < p->height; j++ ) {
					for ( k = 0; k < 3; k++ ) {
						p->ctrl(i, j).xyz[k] = floor(p->ctrl(i, j).xyz[k] / g_qeglobals.d_gridsize + 0.5) * g_qeglobals.d_gridsize;
					}
				}
			}
			idVec3	vMin, vMax;
			Patch_CalcBounds(p, vMin, vMax);
			Brush_RebuildBrush(p->pSymbiot, vMin, vMax);
		} else {
			Brush_SnapToGrid(pb);
		}
	}
}


void Patch_FindReplaceTexture( brush_t *pb,const char *pFind,const char *pReplace,bool bForce ) {
	if ( pb->pPatch ) {
		patchMesh_t	*p	= pb->pPatch;
		if ( bForce || idStr::Icmp(p->d_texture->GetName(), pFind) == 0 ) {
			p->d_texture = Texture_ForName(pReplace);
			//strcpy(p->d_texture->name, pReplace);
		}
	}
}

void Patch_ReplaceQTexture( brush_t *pb,idMaterial *pOld,idMaterial *pNew ) {
	if ( pb->pPatch ) {
		patchMesh_t	*p	= pb->pPatch;
		if ( p->d_texture == pOld ) {
			p->d_texture = pNew;
		}
	}
}

void Patch_Clone( patchMesh_t *p,brush_t *pNewOwner ) {
}

void Patch_FromTriangle( idVec5 vx,idVec5 vy,idVec5 vz ) {
	patchMesh_t	*p	= MakeNewPatch(3, 3);
	p->d_texture = Texture_ForName(g_qeglobals.d_texturewin.texdef.name);
	p->type = PATCH_TRIANGLE;

	// 0 0 goes to x
	// 0 1 goes to x
	// 0 2 goes to x

	// 1 0 goes to mid of x and z
	// 1 1 goes to mid of x y and z
	// 1 2 goes to mid of x and y

	// 2 0 goes to z
	// 2 1 goes to mid of y and z
	// 2 2 goes to y

	idVec5	vMidXZ;
	idVec5	vMidXY;
	idVec5	vMidYZ;


	vMidXZ.Lerp(vx, vz, 0.5);
	vMidXY.Lerp(vx, vy, 0.5);
	vMidYZ.Lerp(vy, vz, 0.5);

	p->ctrl(0, 0).xyz = vx.ToVec3();
	p->ctrl(0, 1).xyz = vx.ToVec3();
	p->ctrl(0, 2).xyz = vx.ToVec3();
	p->ctrl(0, 0).st[0] = vx[3];
	p->ctrl(0, 0).st[1] = vx[4];
	p->ctrl(0, 1).st[0] = vx[3];
	p->ctrl(0, 1).st[1] = vx[4];
	p->ctrl(0, 2).st[0] = vx[3];
	p->ctrl(0, 2).st[1] = vx[4];

	p->ctrl(1, 0).xyz = vMidXY.ToVec3();
	p->ctrl(1, 1).xyz = vx.ToVec3();
	p->ctrl(1, 2).xyz = vMidXZ.ToVec3();
	p->ctrl(1, 0).st[0] = vMidXY[3];
	p->ctrl(1, 0).st[1] = vMidXY[4];
	p->ctrl(1, 1).st[0] = vx[3];
	p->ctrl(1, 1).st[1] = vx[4];
	p->ctrl(1, 2).st[0] = vMidXZ[3];
	p->ctrl(1, 2).st[1] = vMidXZ[4];

	p->ctrl(2, 0).xyz = vy.ToVec3();
	p->ctrl(2, 1).xyz = vMidYZ.ToVec3();
	p->ctrl(2, 2).xyz = vz.ToVec3();
	p->ctrl(2, 0).st[0] = vy[3];
	p->ctrl(2, 0).st[1] = vy[4];
	p->ctrl(2, 1).st[0] = vMidYZ[3];
	p->ctrl(2, 1).st[1] = vMidYZ[4];
	p->ctrl(2, 2).st[0] = vz[3];
	p->ctrl(2, 2).st[1] = vz[4];


	//Patch_Naturalize(p);

	brush_t	*b	= AddBrushForPatch(p);
}


/*
==============
Patch_SetEpair
sets an epair for the given patch
==============
*/
void Patch_SetEpair( patchMesh_t *p,const char *pKey,const char *pValue ) {
	if ( g_qeglobals.m_bBrushPrimitMode ) {
		if ( p->epairs == NULL ) {
			p->epairs = new idDict;
		}
		p->epairs->Set(pKey, pValue);
	}
}

/*
=================
Patch_GetKeyValue
=================
*/
const char * Patch_GetKeyValue( patchMesh_t *p,const char *pKey ) {
	if ( g_qeglobals.m_bBrushPrimitMode ) {
		if ( p->epairs ) {
			return p->epairs->GetString(pKey);
		}
	}
	return "";
}


//Real nitpicky, but could you make CTRL-S save the current map with the current name? (ie: File/Save)
/*
Feature addition.
When reading in textures, please check for the presence of a file called "textures.link" or something, which contains one line such as;

g:\quake3\baseq3\textures\common

 So that, when I'm reading in, lets say, my \eerie directory, it goes through and adds my textures to the palette, along with everything in common.

  Don't forget to add "Finer texture alignment" to the list. I'd like to be able to move in 0.1 increments using the Shift-Arrow Keys.

  No. Sometimes textures are drawn the wrong way on patches. We'd like the ability to flip a texture. Like the way X/Y scale -1 used to worked.

  1) Easier way of deleting rows, columns
2) Fine tuning of textures on patches (X/Y shifts other than with the surface dialog)
2) Patch matrix transposition

  1) Actually, bump texture flipping on patches to the top of the list of things to do.
2) When you select a patch, and hit S, it should read in the selected patch texture. Should not work if you multiselect patches and hit S
3) Brandon has a wierd anomoly. He fine-tunes a patch with caps. It looks fine when the patch is selected, but as soon as he escapes out, it reverts to it's pre-tuned state. When he selects the patch again, it looks tuned


*1) Flipping textures on patches
*2) When you select a patch, and hit S, it should read in the selected patch texture. Should not work if you multiselect patches and hit S
3) Easier way of deleting rows columns
*4) Thick Curves
5) Patch matrix transposition
6) Inverted cylinder capping
*7) bugs
*8) curve speed

  Have a new feature request. "Compute Bounding Box" for mapobjects (md3 files). This would be used for misc_mapobject (essentially, drop in 3DS Max models into our maps)

  Ok, Feature Request. Load and draw MD3's in the Camera view with proper bounding boxes. This should be off misc_model

  Feature Addition: View/Hide Hint Brushes -- This should be a specific case.
*/
