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

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 "../../idlib/precompiled.h"
#pragma hdrstop

#include "qe3.h"

#define ZERO_EPSILON	1.0E-6

class idVec3D {
public:
	double x, y, z;
	double &			operator[]( const int index ) {
		return (&x)[index];
	}
	void Zero() {
		x = y = z = 0.0;
	}
};

//
// =======================================================================================================================
//    compute a determinant using Sarrus rule ++timo "inline" this with a macro NOTE:: the three idVec3D are understood as
//    columns of the matrix
// =======================================================================================================================
//
double SarrusDet(idVec3D a, idVec3D b, idVec3D c) {
	return (double)a[0] * (double)b[1] * (double)c[2] + (double)b[0] * (double)c[1] * (double)a[2] + (double)c[0] * (double)a[1] * (double)b[2] - (double)c[0] * (double)b[1] * (double)a[2] - (double)a[1] * (double)b[0] * (double)c[2] -	(double)a[0] * (double)b[2] * (double)c[1];
}

//
// =======================================================================================================================
//    ++timo replace everywhere texX by texS etc. ( > and in q3map !) NOTE:: ComputeAxisBase here and in q3map code must
//    always BE THE SAME ! WARNING:: special case behaviour of atan2(y,x) <-> atan(y/x) might not be the same everywhere
//    when x == 0 rotation by (0,RotY,RotZ) assigns X to normal
// =======================================================================================================================
//
void ComputeAxisBase(idVec3 &normal, idVec3D &texS, idVec3D &texT) {
	double	RotY, RotZ;

	// do some cleaning
	if (idMath::Fabs(normal[0]) < 1e-6) {
		normal[0] = 0.0f;
	}

	if (idMath::Fabs(normal[1]) < 1e-6) {
		normal[1] = 0.0f;
	}

	if (idMath::Fabs(normal[2]) < 1e-6) {
		normal[2] = 0.0f;
	}

	RotY = -atan2(normal[2], idMath::Sqrt(normal[1] * normal[1] + normal[0] * normal[0]));
	RotZ = atan2(normal[1], normal[0]);

	// rotate (0,1,0) and (0,0,1) to compute texS and texT
	texS[0] = -sin(RotZ);
	texS[1] = cos(RotZ);
	texS[2] = 0;

	// the texT vector is along -Z ( T texture coorinates axis )
	texT[0] = -sin(RotY) * cos(RotZ);
	texT[1] = -sin(RotY) * sin(RotZ);
	texT[2] = -cos(RotY);
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void FaceToBrushPrimitFace(face_t *f) {
	idVec3D	texX, texY;
	idVec3D	proj;

	// ST of (0,0) (1,0) (0,1)
	idVec5	ST[3];	// [ point index ] [ xyz ST ]

	//
	// ++timo not used as long as brushprimit_texdef and texdef are static
	// f->brushprimit_texdef.contents=f->texdef.contents;
	// f->brushprimit_texdef.flags=f->texdef.flags;
	// f->brushprimit_texdef.value=f->texdef.value;
	// strcpy(f->brushprimit_texdef.name,f->texdef.name);
	//
#ifdef _DEBUG
	if (f->plane[0] == 0.0f && f->plane[1] == 0.0f && f->plane[2] == 0.0f) {
		common->Printf("Warning : f->plane.normal is (0,0,0) in FaceToBrushPrimitFace\n");
	}

	// check d_texture
	if (!f->d_texture) {
		common->Printf("Warning : f.d_texture is NULL in FaceToBrushPrimitFace\n");
		return;
	}
#endif
	// compute axis base
	ComputeAxisBase(f->plane.Normal(), texX, texY);

	// compute projection vector
	VectorCopy( f->plane, proj );
	VectorScale(proj, -f->plane[3], proj);

	//
	// (0,0) in plane axis base is (0,0,0) in world coordinates + projection on the
	// affine plane (1,0) in plane axis base is texX in world coordinates + projection
	// on the affine plane (0,1) in plane axis base is texY in world coordinates +
	// projection on the affine plane use old texture code to compute the ST coords of
	// these points
	//
	VectorCopy(proj, ST[0]);
	EmitTextureCoordinates(ST[0], f->d_texture, f);
	VectorCopy(texX, ST[1]);
	VectorAdd(ST[1], proj, ST[1]);
	EmitTextureCoordinates(ST[1], f->d_texture, f);
	VectorCopy(texY, ST[2]);
	VectorAdd(ST[2], proj, ST[2]);
	EmitTextureCoordinates(ST[2], f->d_texture, f);

	// compute texture matrix
	f->brushprimit_texdef.coords[0][2] = ST[0][3];
	f->brushprimit_texdef.coords[1][2] = ST[0][4];
	f->brushprimit_texdef.coords[0][0] = ST[1][3] - f->brushprimit_texdef.coords[0][2];
	f->brushprimit_texdef.coords[1][0] = ST[1][4] - f->brushprimit_texdef.coords[1][2];
	f->brushprimit_texdef.coords[0][1] = ST[2][3] - f->brushprimit_texdef.coords[0][2];
	f->brushprimit_texdef.coords[1][1] = ST[2][4] - f->brushprimit_texdef.coords[1][2];
}

//
// =======================================================================================================================
//    compute texture coordinates for the winding points
// =======================================================================================================================
//
void EmitBrushPrimitTextureCoordinates(face_t *f, idWinding *w, patchMesh_t *patch) {
	idVec3D	texX, texY;
	double	x, y;

	if (f== NULL || (w == NULL && patch == NULL)) {
		return;
	}

	// compute axis base
	ComputeAxisBase(f->plane.Normal(), texX, texY);

	//
	// in case the texcoords matrix is empty, build a default one same behaviour as if
	// scale[0]==0 && scale[1]==0 in old code
	//
	if (	f->brushprimit_texdef.coords[0][0] == 0 &&
			f->brushprimit_texdef.coords[1][0] == 0 &&
			f->brushprimit_texdef.coords[0][1] == 0 &&
			f->brushprimit_texdef.coords[1][1] == 0 ) {
		f->brushprimit_texdef.coords[0][0] = 1.0f;
		f->brushprimit_texdef.coords[1][1] = 1.0f;
		ConvertTexMatWithQTexture(&f->brushprimit_texdef, NULL, &f->brushprimit_texdef, f->d_texture);
	}

	int i;
	if (w) {
		for (i = 0; i < w->GetNumPoints(); i++) {
			x = DotProduct((*w)[i], texX);
			y = DotProduct((*w)[i], texY);
			(*w)[i][3] = f->brushprimit_texdef.coords[0][0] * x + f->brushprimit_texdef.coords[0][1] * y + f->brushprimit_texdef.coords[0][2];
			(*w)[i][4] = f->brushprimit_texdef.coords[1][0] * x + f->brushprimit_texdef.coords[1][1] * y + f->brushprimit_texdef.coords[1][2];
		}
	}

	if (patch) {
		int j;
		for ( i = 0; i < patch->width; i++ ) {
			for ( j = 0; j < patch->height; j++ ) {
				x = DotProduct(patch->ctrl(i, j).xyz, texX);
				y = DotProduct(patch->ctrl(i, j).xyz, texY);
				patch->ctrl(i, j).st.x = f->brushprimit_texdef.coords[0][0] * x + f->brushprimit_texdef.coords[0][1] * y + f->brushprimit_texdef.coords[0][2];
				patch->ctrl(i, j).st.y = f->brushprimit_texdef.coords[1][0] * x + f->brushprimit_texdef.coords[1][1] * y + f->brushprimit_texdef.coords[1][2];
			}
		}
	}
}

//
// =======================================================================================================================
//    parse a brush in brush primitive format
// =======================================================================================================================
//
void BrushPrimit_Parse(brush_t *b, bool newFormat, const idVec3 origin) {
	face_t	*f;
	int		i, j;
	GetToken(true);
	if (strcmp(token, "{")) {
		Warning("parsing brush primitive");
		return;
	}

	do {
		if (!GetToken(true)) {
			break;
		}

		if (!strcmp(token, "}")) {
			break;
		}

		// reading of b->epairs if any
		if (strcmp(token, "(")) {
			ParseEpair(&b->epairs);
		}
		else {	// it's a face
			f = Face_Alloc();
			f->next = NULL;
			if (!b->brush_faces) {
				b->brush_faces = f;
			}
			else {
				face_t	*scan;
				for (scan = b->brush_faces; scan->next; scan = scan->next)
					;
				scan->next = f;
			}

			if (newFormat) {
				// read the three point plane definition
				idPlane plane;
				for (j = 0; j < 4; j++) {
					GetToken(false);
					plane[j] = atof(token);
				}

				f->plane = plane;
				f->originalPlane = plane;
				f->dirty = false;

				//idWinding	*w = Brush_MakeFaceWinding(b, f, true);
				idWinding w;
				w.BaseForPlane( plane );

				for (j = 0; j < 3; j++) {
					f->planepts[j].x = w[j].x + origin.x;
					f->planepts[j].y = w[j].y + origin.y;
					f->planepts[j].z = w[j].z + origin.z;
				}

				GetToken(false);
			}
			else {
				for (i = 0; i < 3; i++) {
					if (i != 0) {
						GetToken(true);
					}

					if (strcmp(token, "(")) {
						Warning("parsing brush");
						return;
					}

					for (j = 0; j < 3; j++) {
						GetToken(false);
						f->planepts[i][j] = atof(token);
					}

					GetToken(false);
					if (strcmp(token, ")")) {
						Warning("parsing brush");
						return;
					}
				}
			}

			// texture coordinates
			GetToken(false);
			if (strcmp(token, "(")) {
				Warning("parsing brush primitive");
				return;
			}

			GetToken(false);
			if (strcmp(token, "(")) {
				Warning("parsing brush primitive");
				return;
			}

			for (j = 0; j < 3; j++) {
				GetToken(false);
				f->brushprimit_texdef.coords[0][j] = atof(token);
			}

			GetToken(false);
			if (strcmp(token, ")")) {
				Warning("parsing brush primitive");
				return;
			}

			GetToken(false);
			if (strcmp(token, "(")) {
				Warning("parsing brush primitive");
				return;
			}

			for (j = 0; j < 3; j++) {
				GetToken(false);
				f->brushprimit_texdef.coords[1][j] = atof(token);
			}

			GetToken(false);
			if (strcmp(token, ")")) {
				Warning("parsing brush primitive");
				return;
			}

			GetToken(false);
			if (strcmp(token, ")")) {
				Warning("parsing brush primitive");
				return;
			}

			// read the texturedef
			GetToken(false);

			// strcpy(f->texdef.name, token);
			if (g_qeglobals.mapVersion < 2.0) {
				f->texdef.SetName(va("textures/%s", token));
			}
			else {
				f->texdef.SetName(token);
			}

			if (TokenAvailable()) {
				GetToken(false);
				GetToken(false);
				GetToken(false);
				f->texdef.value = atoi(token);
			}
		}
	} while (1);
}

//
// =======================================================================================================================
//    compute a fake shift scale rot representation from the texture matrix these shift scale rot values are to be
//    understood in the local axis base
// =======================================================================================================================
//
void TexMatToFakeTexCoords(float texMat[2][3], float shift[2], float *rot, float scale[2])
{
#ifdef _DEBUG

	// check this matrix is orthogonal
	if (idMath::Fabs(texMat[0][0] * texMat[0][1] + texMat[1][0] * texMat[1][1]) > ZERO_EPSILON) {
		common->Printf("Warning : non orthogonal texture matrix in TexMatToFakeTexCoords\n");
	}
#endif
	scale[0] = idMath::Sqrt(texMat[0][0] * texMat[0][0] + texMat[1][0] * texMat[1][0]);
	scale[1] = idMath::Sqrt(texMat[0][1] * texMat[0][1] + texMat[1][1] * texMat[1][1]);
#ifdef _DEBUG
	if (scale[0] < ZERO_EPSILON || scale[1] < ZERO_EPSILON) {
		common->Printf("Warning : unexpected scale==0 in TexMatToFakeTexCoords\n");
	}
#endif
	// compute rotate value
	if (idMath::Fabs(texMat[0][0]) < ZERO_EPSILON)
	{
#ifdef _DEBUG
		// check brushprimit_texdef[1][0] is not zero
		if (idMath::Fabs(texMat[1][0]) < ZERO_EPSILON) {
			common->Printf("Warning : unexpected texdef[1][0]==0 in TexMatToFakeTexCoords\n");
		}
#endif
		// rotate is +-90
		if (texMat[1][0] > 0) {
			*rot = 90.0f;
		}
		else {
			*rot = -90.0f;
		}
	}
	else {
		*rot = RAD2DEG(atan2(texMat[1][0], texMat[0][0]));
	}

	shift[0] = -texMat[0][2];
	shift[1] = texMat[1][2];
}

//
// =======================================================================================================================
//    compute back the texture matrix from fake shift scale rot the matrix returned must be understood as a qtexture_t
//    with width=2 height=2 ( the default one )
// =======================================================================================================================
//
void FakeTexCoordsToTexMat(float shift[2], float rot, float scale[2], float texMat[2][3]) {
	texMat[0][0] = scale[0] * cos(DEG2RAD(rot));
	texMat[1][0] = scale[0] * sin(DEG2RAD(rot));
	texMat[0][1] = -1.0f * scale[1] * sin(DEG2RAD(rot));
	texMat[1][1] = scale[1] * cos(DEG2RAD(rot));
	texMat[0][2] = -shift[0];
	texMat[1][2] = shift[1];
}

//
// =======================================================================================================================
//    convert a texture matrix between two qtexture_t if NULL for qtexture_t, basic 2x2 texture is assumed ( straight
//    mapping between s/t coordinates and geometric coordinates )
// =======================================================================================================================
//
void ConvertTexMatWithQTexture(float texMat1[2][3], const idMaterial *qtex1, float texMat2[2][3], const idMaterial *qtex2, float sScale = 1.0, float tScale = 1.0) {
	float	s1, s2;
	s1 = (qtex1 ? static_cast<float>(qtex1->GetEditorImage()->uploadWidth) : 2.0f) / (qtex2 ? static_cast<float>(qtex2->GetEditorImage()->uploadWidth) : 2.0f);
	s2 = (qtex1 ? static_cast<float>(qtex1->GetEditorImage()->uploadHeight) : 2.0f) / (qtex2 ? static_cast<float>(qtex2->GetEditorImage()->uploadHeight) : 2.0f);
	s1 *= sScale;
	s2 *= tScale;
	texMat2[0][0] = s1 * texMat1[0][0];
	texMat2[0][1] = s1 * texMat1[0][1];
	texMat2[0][2] = s1 * texMat1[0][2];
	texMat2[1][0] = s2 * texMat1[1][0];
	texMat2[1][1] = s2 * texMat1[1][1];
	texMat2[1][2] = s2 * texMat1[1][2];
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void ConvertTexMatWithQTexture(brushprimit_texdef_t	*texMat1, const idMaterial *qtex1, brushprimit_texdef_t *texMat2, const idMaterial *qtex2, float sScale, float tScale) {
	ConvertTexMatWithQTexture(texMat1->coords, qtex1, texMat2->coords, qtex2, sScale, tScale);
}


//
// =======================================================================================================================
//    texture locking
// =======================================================================================================================
//
void Face_MoveTexture_BrushPrimit(face_t *f, idVec3 delta) {
	idVec3D	texS, texT;
	double	tx, ty;
	idVec3D	M[3];	// columns of the matrix .. easier that way
	double	det;
	idVec3D	D[2];

	// compute plane axis base ( doesn't change with translation )
	ComputeAxisBase(f->plane.Normal(), texS, texT);

	// compute translation vector in plane axis base
	tx = DotProduct(delta, texS);
	ty = DotProduct(delta, texT);

	// fill the data vectors
	M[0][0] = tx;
	M[0][1] = 1.0f + tx;
	M[0][2] = tx;
	M[1][0] = ty;
	M[1][1] = ty;
	M[1][2] = 1.0f + ty;
	M[2][0] = 1.0f;
	M[2][1] = 1.0f;
	M[2][2] = 1.0f;
	D[0][0] = f->brushprimit_texdef.coords[0][2];
	D[0][1] = f->brushprimit_texdef.coords[0][0] + f->brushprimit_texdef.coords[0][2];
	D[0][2] = f->brushprimit_texdef.coords[0][1] + f->brushprimit_texdef.coords[0][2];
	D[1][0] = f->brushprimit_texdef.coords[1][2];
	D[1][1] = f->brushprimit_texdef.coords[1][0] + f->brushprimit_texdef.coords[1][2];
	D[1][2] = f->brushprimit_texdef.coords[1][1] + f->brushprimit_texdef.coords[1][2];

	// solve
	det = SarrusDet(M[0], M[1], M[2]);
	f->brushprimit_texdef.coords[0][0] = SarrusDet(D[0], M[1], M[2]) / det;
	f->brushprimit_texdef.coords[0][1] = SarrusDet(M[0], D[0], M[2]) / det;
	f->brushprimit_texdef.coords[0][2] = SarrusDet(M[0], M[1], D[0]) / det;
	f->brushprimit_texdef.coords[1][0] = SarrusDet(D[1], M[1], M[2]) / det;
	f->brushprimit_texdef.coords[1][1] = SarrusDet(M[0], D[1], M[2]) / det;
	f->brushprimit_texdef.coords[1][2] = SarrusDet(M[0], M[1], D[1]) / det;
}

//
// =======================================================================================================================
//    call Face_MoveTexture_BrushPrimit after idVec3D computation
// =======================================================================================================================
//
void Select_ShiftTexture_BrushPrimit(face_t *f, float x, float y, bool autoAdjust) {
#if 0
	idVec3D	texS, texT;
	idVec3D	delta;
	ComputeAxisBase(f->plane.normal, texS, texT);
	VectorScale(texS, x, texS);
	VectorScale(texT, y, texT);
	VectorCopy(texS, delta);
	VectorAdd(delta, texT, delta);
	Face_MoveTexture_BrushPrimit(f, delta);
#else
	if (autoAdjust) {
		x /= f->d_texture->GetEditorImage()->uploadWidth;
		y /= f->d_texture->GetEditorImage()->uploadHeight;
	}
	f->brushprimit_texdef.coords[0][2] += x;
	f->brushprimit_texdef.coords[1][2] += y;
	EmitBrushPrimitTextureCoordinates(f, f->face_winding);
#endif
}

//
// =======================================================================================================================
//    best fitted 2D vector is x.X+y.Y
// =======================================================================================================================
//
void ComputeBest2DVector(idVec3 v, idVec3 X, idVec3 Y, int &x, int &y) {
	double	sx, sy;
	sx = DotProduct(v, X);
	sy = DotProduct(v, Y);
	if (idMath::Fabs(sy) > idMath::Fabs(sx)) {
		x = 0;
		if (sy > 0.0) {
			y = 1;
		}
		else {
			y = -1;
		}
	}
	else {
		y = 0;
		if (sx > 0.0) {
			x = 1;
		}
		else {
			x = -1;
		}
	}
}

//
// =======================================================================================================================
//    in many case we know three points A,B,C in two axis base B1 and B2 and we want the matrix M so that A(B1) = T *
//    A(B2) NOTE: 2D homogeneous space stuff NOTE: we don't do any check to see if there's a solution or we have a
//    particular case .. need to make sure before calling NOTE: the third coord of the A,B,C point is ignored NOTE: see
//    the commented out section to fill M and D ++timo TODO: update the other members to use this when possible
// =======================================================================================================================
//
void MatrixForPoints(idVec3D M[3], idVec3D D[2], brushprimit_texdef_t *T) {
	//
	// idVec3D M[3]; // columns of the matrix .. easier that way (the indexing is not
	// standard! it's column-line .. later computations are easier that way)
	//
	double	det;

	// idVec3D D[2];
	M[2][0] = 1.0f;
	M[2][1] = 1.0f;
	M[2][2] = 1.0f;
#if 0

	// fill the data vectors
	M[0][0] = A2[0];
	M[0][1] = B2[0];
	M[0][2] = C2[0];
	M[1][0] = A2[1];
	M[1][1] = B2[1];
	M[1][2] = C2[1];
	M[2][0] = 1.0f;
	M[2][1] = 1.0f;
	M[2][2] = 1.0f;
	D[0][0] = A1[0];
	D[0][1] = B1[0];
	D[0][2] = C1[0];
	D[1][0] = A1[1];
	D[1][1] = B1[1];
	D[1][2] = C1[1];
#endif
	// solve
	det = SarrusDet(M[0], M[1], M[2]);
	T->coords[0][0] = SarrusDet(D[0], M[1], M[2]) / det;
	T->coords[0][1] = SarrusDet(M[0], D[0], M[2]) / det;
	T->coords[0][2] = SarrusDet(M[0], M[1], D[0]) / det;
	T->coords[1][0] = SarrusDet(D[1], M[1], M[2]) / det;
	T->coords[1][1] = SarrusDet(M[0], D[1], M[2]) / det;
	T->coords[1][2] = SarrusDet(M[0], M[1], D[1]) / det;
}

//
// =======================================================================================================================
//    ++timo FIXME quick'n dirty hack, doesn't care about current texture settings (angle) can be improved .. bug #107311
//    mins and maxs are the face bounding box ++timo fixme: we use the face info, mins and maxs are irrelevant
// =======================================================================================================================
//
void Face_FitTexture_BrushPrimit(face_t *f, idVec3 mins, idVec3 maxs, float height, float width) {
	idVec3D					BBoxSTMin, BBoxSTMax;
	idWinding				*w;
	int						i, j;
	double					val;
	idVec3D					M[3], D[2];

	// idVec3D N[2],Mf[2];
	brushprimit_texdef_t	N;
	idVec3D					Mf[2];



	//memset(f->brushprimit_texdef.coords, 0, sizeof(f->brushprimit_texdef.coords));
	//f->brushprimit_texdef.coords[0][0] = 1.0f;
	//f->brushprimit_texdef.coords[1][1] = 1.0f;
	//ConvertTexMatWithQTexture(&f->brushprimit_texdef, NULL, &f->brushprimit_texdef, f->d_texture);
	//
	// we'll be working on a standardized texture size ConvertTexMatWithQTexture(
	// &f->brushprimit_texdef, f->d_texture, &f->brushprimit_texdef, NULL ); compute
	// the BBox in ST coords
	//
	EmitBrushPrimitTextureCoordinates(f, f->face_winding);
	BBoxSTMin[0] = BBoxSTMin[1] = BBoxSTMin[2] = 999999;
	BBoxSTMax[0] = BBoxSTMax[1] = BBoxSTMax[2] = -999999;

	w = f->face_winding;
	if (w) {
		for (i = 0; i < w->GetNumPoints(); i++) {
			// AddPointToBounds in 2D on (S,T) coordinates
			for (j = 0; j < 2; j++) {
				val = (*w)[i][j + 3];
				if (val < BBoxSTMin[j]) {
					BBoxSTMin[j] = val;
				}

				if (val > BBoxSTMax[j]) {
					BBoxSTMax[j] = val;
				}
			}
		}
	}

	//
	// we have the three points of the BBox (BBoxSTMin[0].BBoxSTMin[1])
	// (BBoxSTMax[0],BBoxSTMin[1]) (BBoxSTMin[0],BBoxSTMax[1]) in ST space the BP
	// matrix we are looking for gives (0,0) (nwidth,0) (0,nHeight) coordinates in
	// (Sfit,Tfit) space to these three points we have A(Sfit,Tfit) = (0,0) = Mf *
	// A(TexS,TexT) = N * M * A(TexS,TexT) = N * A(S,T) so we solve the system for N
	// and then Mf = N * M
	//
	M[0][0] = BBoxSTMin[0];
	M[0][1] = BBoxSTMax[0];
	M[0][2] = BBoxSTMin[0];
	M[1][0] = BBoxSTMin[1];
	M[1][1] = BBoxSTMin[1];
	M[1][2] = BBoxSTMax[1];
	D[0][0] = 0.0f;
	D[0][1] = width;
	D[0][2] = 0.0f;
	D[1][0] = 0.0f;
	D[1][1] = 0.0f;
	D[1][2] = height;
	MatrixForPoints(M, D, &N);

#if 0

	//
	// FIT operation gives coordinates of three points of the bounding box in (S',T'),
	// our target axis base A(S',T')=(0,0) B(S',T')=(nWidth,0) C(S',T')=(0,nHeight)
	// and we have them in (S,T) axis base: A(S,T)=(BBoxSTMin[0],BBoxSTMin[1])
	// B(S,T)=(BBoxSTMax[0],BBoxSTMin[1]) C(S,T)=(BBoxSTMin[0],BBoxSTMax[1]) we
	// compute the N transformation so that: A(S',T') = N * A(S,T)
	//
	N[0][0] = (BBoxSTMax[0] - BBoxSTMin[0]) / width;
	N[0][1] = 0.0f;
	N[0][2] = BBoxSTMin[0];
	N[1][0] = 0.0f;
	N[1][1] = (BBoxSTMax[1] - BBoxSTMin[1]) / height;
	N[1][2] = BBoxSTMin[1];
#endif
	// the final matrix is the product (Mf stands for Mfit)
	Mf[0][0] = N.coords[0][0] *
		f->brushprimit_texdef.coords[0][0] +
		N.coords[0][1] *
		f->brushprimit_texdef.coords[1][0];
	Mf[0][1] = N.coords[0][0] *
		f->brushprimit_texdef.coords[0][1] +
		N.coords[0][1] *
		f->brushprimit_texdef.coords[1][1];
	Mf[0][2] = N.coords[0][0] *
		f->brushprimit_texdef.coords[0][2] +
		N.coords[0][1] *
		f->brushprimit_texdef.coords[1][2] +
		N.coords[0][2];
	Mf[1][0] = N.coords[1][0] *
		f->brushprimit_texdef.coords[0][0] +
		N.coords[1][1] *
		f->brushprimit_texdef.coords[1][0];
	Mf[1][1] = N.coords[1][0] *
		f->brushprimit_texdef.coords[0][1] +
		N.coords[1][1] *
		f->brushprimit_texdef.coords[1][1];
	Mf[1][2] = N.coords[1][0] *
		f->brushprimit_texdef.coords[0][2] +
		N.coords[1][1] *
		f->brushprimit_texdef.coords[1][2] +
		N.coords[1][2];

	// copy back
	VectorCopy(Mf[0], f->brushprimit_texdef.coords[0]);
	VectorCopy(Mf[1], f->brushprimit_texdef.coords[1]);

	//
	// handle the texture size ConvertTexMatWithQTexture( &f->brushprimit_texdef,
	// NULL, &f->brushprimit_texdef, f->d_texture );
	//
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void Face_ScaleTexture_BrushPrimit(face_t *face, float sS, float sT) {
	if (!g_qeglobals.m_bBrushPrimitMode) {
		Sys_Status("BP mode required\n");
		return;
	}

	brushprimit_texdef_t	*pBP = &face->brushprimit_texdef;
	BPMatScale(pBP->coords, sS, sT);

	// now emit the coordinates on the winding
	EmitBrushPrimitTextureCoordinates(face, face->face_winding);
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void Face_RotateTexture_BrushPrimit(face_t *face, float amount, idVec3 origin) {
	brushprimit_texdef_t	*pBP = &face->brushprimit_texdef;
	if (amount) {
		float	x = pBP->coords[0][0];
		float	y = pBP->coords[0][1];
		float	x1 = pBP->coords[1][0];
		float	y1 = pBP->coords[1][1];
		float	s = sin( DEG2RAD( amount ) );
		float	c = cos( DEG2RAD( amount ) );
		pBP->coords[0][0] = (((x - origin[0]) * c) - ((y - origin[1]) * s)) + origin[0];
		pBP->coords[0][1] = (((x - origin[0]) * s) + ((y - origin[1]) * c)) + origin[1];
		pBP->coords[1][0] = (((x1 - origin[0]) * c) - ((y1 - origin[1]) * s)) + origin[0];
		pBP->coords[1][1] = (((x1 - origin[0]) * s) + ((y1 - origin[1]) * c)) + origin[1];
		EmitBrushPrimitTextureCoordinates(face, face->face_winding);
	}
}

//
// TEXTURE LOCKING (Relevant to the editor only?)
// internally used for texture locking on rotation and flipping the general
// algorithm is the same for both lockings, it's only the geometric transformation
// part that changes so I wanted to keep it in a single function if there are more
// linear transformations that need the locking, going to a C++ or code pointer
// solution would be best (but right now I want to keep brush_primit.cpp striclty
// C)
//
bool	txlock_bRotation;

// rotation locking params
int		txl_nAxis;
double	txl_fDeg;
idVec3D	txl_vOrigin;

// flip locking params
idVec3D	txl_matrix[3];
idVec3D	txl_origin;

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void TextureLockTransformation_BrushPrimit(face_t *f) {
	idVec3D	Orig, texS, texT;		// axis base of initial plane

	// used by transformation algo
	idVec3D	temp;
	int		j;
	//idVec3D	vRotate;				// rotation vector

	idVec3D	rOrig, rvecS, rvecT;	// geometric transformation of (0,0) (1,0) (0,1) { initial plane axis base }
	idVec3	rNormal;
	idVec3D	rtexS, rtexT;	// axis base for the transformed plane
	idVec3D	lOrig, lvecS, lvecT;	// [2] are not used ( but usefull for debugging )
	idVec3D	M[3];
	double	det;
	idVec3D	D[2];

	// silence compiler warnings
	rOrig.Zero();
	rvecS = rOrig;
	rvecT = rOrig;
	rNormal.x = rOrig.x;
	rNormal.y = rOrig.y;
	rNormal.z = rOrig.z;

	// compute plane axis base
	ComputeAxisBase(f->plane.Normal(), texS, texT);
	Orig.x = vec3_origin.x;
	Orig.y = vec3_origin.y;
	Orig.z = vec3_origin.z;

	//
	// compute coordinates of (0,0) (1,0) (0,1) ( expressed in initial plane axis base
	// ) after transformation (0,0) (1,0) (0,1) ( expressed in initial plane axis base
	// ) <-> (0,0,0) texS texT ( expressed world axis base ) input: Orig, texS, texT
	// (and the global locking params) ouput: rOrig, rvecS, rvecT, rNormal
	//
	if (txlock_bRotation) {
/*
		// rotation vector
		vRotate.x = vec3_origin.x;
		vRotate.y = vec3_origin.y;
		vRotate.z = vec3_origin.z;
		vRotate[txl_nAxis] = txl_fDeg;
		VectorRotate3Origin(Orig, vRotate, txl_vOrigin, rOrig);
		VectorRotate3Origin(texS, vRotate, txl_vOrigin, rvecS);
		VectorRotate3Origin(texT, vRotate, txl_vOrigin, rvecT);

		// compute normal of plane after rotation
		VectorRotate3(f->plane.Normal(), vRotate, rNormal);
*/
	}
	else {
		VectorSubtract(Orig, txl_origin, temp);
		for (j = 0; j < 3; j++) {
			rOrig[j] = DotProduct(temp, txl_matrix[j]) + txl_origin[j];
		}

		VectorSubtract(texS, txl_origin, temp);
		for (j = 0; j < 3; j++) {
			rvecS[j] = DotProduct(temp, txl_matrix[j]) + txl_origin[j];
		}

		VectorSubtract(texT, txl_origin, temp);
		for (j = 0; j < 3; j++) {
			rvecT[j] = DotProduct(temp, txl_matrix[j]) + txl_origin[j];
		}

		//
		// we also need the axis base of the target plane, apply the transformation matrix
		// to the normal too..
		//
		for (j = 0; j < 3; j++) {
			rNormal[j] = DotProduct(f->plane, txl_matrix[j]);
		}
	}

	// compute rotated plane axis base
	ComputeAxisBase(rNormal, rtexS, rtexT);

	// compute S/T coordinates of the three points in rotated axis base ( in M matrix )
	lOrig[0] = DotProduct(rOrig, rtexS);
	lOrig[1] = DotProduct(rOrig, rtexT);
	lvecS[0] = DotProduct(rvecS, rtexS);
	lvecS[1] = DotProduct(rvecS, rtexT);
	lvecT[0] = DotProduct(rvecT, rtexS);
	lvecT[1] = DotProduct(rvecT, rtexT);
	M[0][0] = lOrig[0];
	M[1][0] = lOrig[1];
	M[2][0] = 1.0f;
	M[0][1] = lvecS[0];
	M[1][1] = lvecS[1];
	M[2][1] = 1.0f;
	M[0][2] = lvecT[0];
	M[1][2] = lvecT[1];
	M[2][2] = 1.0f;

	// fill data vector
	D[0][0] = f->brushprimit_texdef.coords[0][2];
	D[0][1] = f->brushprimit_texdef.coords[0][0] + f->brushprimit_texdef.coords[0][2];
	D[0][2] = f->brushprimit_texdef.coords[0][1] + f->brushprimit_texdef.coords[0][2];
	D[1][0] = f->brushprimit_texdef.coords[1][2];
	D[1][1] = f->brushprimit_texdef.coords[1][0] + f->brushprimit_texdef.coords[1][2];
	D[1][2] = f->brushprimit_texdef.coords[1][1] + f->brushprimit_texdef.coords[1][2];

	// solve
	det = SarrusDet(M[0], M[1], M[2]);
	f->brushprimit_texdef.coords[0][0] = SarrusDet(D[0], M[1], M[2]) / det;
	f->brushprimit_texdef.coords[0][1] = SarrusDet(M[0], D[0], M[2]) / det;
	f->brushprimit_texdef.coords[0][2] = SarrusDet(M[0], M[1], D[0]) / det;
	f->brushprimit_texdef.coords[1][0] = SarrusDet(D[1], M[1], M[2]) / det;
	f->brushprimit_texdef.coords[1][1] = SarrusDet(M[0], D[1], M[2]) / det;
	f->brushprimit_texdef.coords[1][2] = SarrusDet(M[0], M[1], D[1]) / det;
}

//
// =======================================================================================================================
//    texture locking called before the points on the face are actually rotated
// =======================================================================================================================
//
void RotateFaceTexture_BrushPrimit(face_t *f, int nAxis, float fDeg, idVec3 vOrigin) {
	// this is a placeholder to call the general texture locking algorithm
	txlock_bRotation = true;
	txl_nAxis = nAxis;
	txl_fDeg = fDeg;
	VectorCopy(vOrigin, txl_vOrigin);
	TextureLockTransformation_BrushPrimit(f);
}

//
// =======================================================================================================================
//    compute the new brush primit texture matrix for a transformation matrix and a flip order flag (change plane o
//    rientation) this matches the select_matrix algo used in select.cpp this needs to be called on the face BEFORE any
//    geometric transformation it will compute the texture matrix that will represent the same texture on the face after
//    the geometric transformation is done
// =======================================================================================================================
//
void ApplyMatrix_BrushPrimit(face_t *f, idMat3 matrix, idVec3 origin) {
	// this is a placeholder to call the general texture locking algorithm
	txlock_bRotation = false;
	VectorCopy(matrix[0], txl_matrix[0]);
	VectorCopy(matrix[1], txl_matrix[1]);
	VectorCopy(matrix[2], txl_matrix[2]);
	VectorCopy(origin, txl_origin);
	TextureLockTransformation_BrushPrimit(f);
}

//
// =======================================================================================================================
//    don't do C==A!
// =======================================================================================================================
//
void BPMatMul(float A[2][3], float B[2][3], float C[2][3]) {
	C[0][0] = A[0][0] * B[0][0] + A[0][1] * B[1][0];
	C[1][0] = A[1][0] * B[0][0] + A[1][1] * B[1][0];
	C[0][1] = A[0][0] * B[0][1] + A[0][1] * B[1][1];
	C[1][1] = A[1][0] * B[0][1] + A[1][1] * B[1][1];
	C[0][2] = A[0][0] * B[0][2] + A[0][1] * B[1][2] + A[0][2];
	C[1][2] = A[1][0] * B[0][2] + A[1][1] * B[1][2] + A[1][2];
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void BPMatDump(float A[2][3]) {
	common->Printf("%g %g %g\n%g %g %g\n0 0 1\n", A[0][0], A[0][1], A[0][2], A[1][0], A[1][1], A[1][2]);
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void BPMatRotate(float A[2][3], float theta) {
	float	m[2][3];
	float	aux[2][3];
	memset(&m, 0, sizeof (float) *6);
	m[0][0] = cos( DEG2RAD( theta ) );
	m[0][1] = -sin( DEG2RAD( theta ) );
	m[1][0] = -m[0][1];
	m[1][1] = m[0][0];
	BPMatMul(A, m, aux);
	BPMatCopy(aux, A);
}

void Face_GetScale_BrushPrimit(face_t *face, float *s, float *t, float *rot) {
	idVec3D	texS, texT;
	ComputeAxisBase(face->plane.Normal(), texS, texT);

	if (face == NULL || face->face_winding == NULL) {
		return;
	}
	// find ST coordinates for the center of the face
	double	Os = 0, Ot = 0;
	for (int i = 0; i < face->face_winding->GetNumPoints(); i++) {
		Os += DotProduct((*face->face_winding)[i], texS);
		Ot += DotProduct((*face->face_winding)[i], texT);
	}

	Os /= face->face_winding->GetNumPoints();
	Ot /= face->face_winding->GetNumPoints();

	brushprimit_texdef_t	*pBP = &face->brushprimit_texdef;

	// here we have a special case, M is a translation and it's inverse is easy
	float					BPO[2][3];
	float					aux[2][3];
	float					m[2][3];
	memset(&m, 0, sizeof (float) *6);
	m[0][0] = 1;
	m[1][1] = 1;
	m[0][2] = -Os;
	m[1][2] = -Ot;
	BPMatMul(m, pBP->coords, aux);
	m[0][2] = Os;
	m[1][2] = Ot;			// now M^-1
	BPMatMul(aux, m, BPO);

	// apply a given scale (on S and T)
	ConvertTexMatWithQTexture(BPO, face->d_texture, aux, NULL);

	*s = idMath::Sqrt(aux[0][0] * aux[0][0] + aux[1][0] * aux[1][0]);
	*t = idMath::Sqrt(aux[0][1] * aux[0][1] + aux[1][1] * aux[1][1]);

	// compute rotate value
	if (idMath::Fabs(face->brushprimit_texdef.coords[0][0]) < ZERO_EPSILON)
	{
		// rotate is +-90
		if (face->brushprimit_texdef.coords[1][0] > 0) {
			*rot = 90.0f;
		}
		else {
			*rot = -90.0f;
		}
	}
	else {
		*rot = RAD2DEG(atan2(face->brushprimit_texdef.coords[1][0] / (*s) ? (*s) : 1.0f, face->brushprimit_texdef.coords[0][0] / (*t) ? (*t) : 1.0f));
	}


}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void Face_SetExplicitScale_BrushPrimit(face_t *face, float s, float t) {
	idVec3D	texS, texT;
	ComputeAxisBase(face->plane.Normal(), texS, texT);

	// find ST coordinates for the center of the face
	double	Os = 0, Ot = 0;

	for (int i = 0; i < face->face_winding->GetNumPoints(); i++) {
		Os += DotProduct((*face->face_winding)[i], texS);
		Ot += DotProduct((*face->face_winding)[i], texT);
	}

	Os /= face->face_winding->GetNumPoints();
	Ot /= face->face_winding->GetNumPoints();

	brushprimit_texdef_t	*pBP = &face->brushprimit_texdef;

	// here we have a special case, M is a translation and it's inverse is easy
	float					BPO[2][3];
	float					aux[2][3];
	float					m[2][3];
	memset(&m, 0, sizeof (float) *6);
	m[0][0] = 1;
	m[1][1] = 1;
	m[0][2] = -Os;
	m[1][2] = -Ot;
	BPMatMul(m, pBP->coords, aux);
	m[0][2] = Os;
	m[1][2] = Ot;			// now M^-1
	BPMatMul(aux, m, BPO);

	// apply a given scale (on S and T)
	ConvertTexMatWithQTexture(BPO, face->d_texture, aux, NULL);

	// reset the scale (normalize the matrix)
	double	v1, v2;
	v1 = idMath::Sqrt(aux[0][0] * aux[0][0] + aux[1][0] * aux[1][0]);
	v2 = idMath::Sqrt(aux[0][1] * aux[0][1] + aux[1][1] * aux[1][1]);

	if (s == 0.0) {
		s = v1;
	}
	if (t == 0.0) {
		t = v2;
	}

	double	sS, sT;

	// put the values for scale on S and T here:
	sS = s / v1;
	sT = t / v2;
	aux[0][0] *= sS;
	aux[1][0] *= sS;
	aux[0][1] *= sT;
	aux[1][1] *= sT;
	ConvertTexMatWithQTexture(aux, NULL, BPO, face->d_texture);
	BPMatMul(m, BPO, aux);	// m is M^-1
	m[0][2] = -Os;
	m[1][2] = -Ot;
	BPMatMul(aux, m, pBP->coords);

	// now emit the coordinates on the winding
	EmitBrushPrimitTextureCoordinates(face, face->face_winding);
}


void Face_FlipTexture_BrushPrimit(face_t *f, bool y) {

	float s, t, rot;
	Face_GetScale_BrushPrimit(f, &s, &t, &rot);
	if (y) {
		Face_SetExplicitScale_BrushPrimit(f, 0.0, -t);
	} else {
		Face_SetExplicitScale_BrushPrimit(f, -s, 0.0);
	}
#if 0

	idVec3D	texS, texT;
	ComputeAxisBase(f->plane.normal, texS, texT);
	double	Os = 0, Ot = 0;
	for (int i = 0; i < f->face_winding->numpoints; i++) {
		Os += DotProduct(f->face_winding->p[i], texS);
		Ot += DotProduct(f->face_winding->p[i], texT);
	}

	Ot = abs(Ot);
	Ot *= t;
	Ot /= f->d_texture->GetEditorImage()->uploadHeight;

	Os = abs(Os);
	Os *= s;
	Os /= f->d_texture->GetEditorImage()->uploadWidth;


	if (y) {
		Face_FitTexture_BrushPrimit(f, texS, texT, -Ot, 1.0);
	} else {
		Face_FitTexture_BrushPrimit(f, texS, texT, 1.0, -Os);
	}
	EmitBrushPrimitTextureCoordinates(f, f->face_winding);
#endif
}

void Brush_FlipTexture_BrushPrimit(brush_t *b, bool y) {
	for (face_t *f = b->brush_faces; f; f = f->next) {
		Face_FlipTexture_BrushPrimit(f, y);
	}
}

void Face_SetAxialScale_BrushPrimit(face_t *face, bool y) {

	if (!face) {
		return;
	}

	if (!face->face_winding) {
		return;
	}

	//float oldS, oldT, oldR;
	//Face_GetScale_BrushPrimit(face, &oldS, &oldT, &oldR);

	idVec3D min, max;
	min.x = min.y = min.z = 999999.0;
	max.x = max.y = max.z = -999999.0;
	for (int i = 0; i < face->face_winding->GetNumPoints(); i++) {
		for (int j = 0; j < 3; j++) {
			if ((*face->face_winding)[i][j] < min[j]) {
				min[j] = (*face->face_winding)[i][j];
			}
			if ((*face->face_winding)[i][j] > max[j]) {
				max[j] = (*face->face_winding)[i][j];
			}
		}
	}

	idVec3 len;

	if (g_bAxialMode) {
		if (g_axialAnchor >= 0 && g_axialAnchor < face->face_winding->GetNumPoints() &&
			g_axialDest >= 0 && g_axialDest < face->face_winding->GetNumPoints() &&
			g_axialAnchor != g_axialDest) {
				len = (*face->face_winding)[g_axialDest].ToVec3() - (*face->face_winding)[g_axialAnchor].ToVec3();
		} else {
			return;
		}
	} else {
		if (y) {
			len = (*face->face_winding)[2].ToVec3() - (*face->face_winding)[1].ToVec3();
		} else {
			len = (*face->face_winding)[1].ToVec3() - (*face->face_winding)[0].ToVec3();
		}
	}

	double dist = len.Length();
	double width = idMath::Fabs(max.x - min.x);
	double height = idMath::Fabs(max.z - min.z);

	//len = maxs[2] - mins[2];
	//double yDist = len.Length();


	if (dist != 0.0) {
		if (dist > face->d_texture->GetEditorImage()->uploadHeight) {
			height = 1.0 / (dist / face->d_texture->GetEditorImage()->uploadHeight);
		} else {
			height /= dist;
		}
		if (dist > face->d_texture->GetEditorImage()->uploadWidth) {
			width = 1.0 / (dist / face->d_texture->GetEditorImage()->uploadWidth);
		} else {
			width /= dist;
		}
	}

	if (y) {
		Face_SetExplicitScale_BrushPrimit(face, 0.0, height);
		//oldT = oldT / height * 10;
		//Select_ShiftTexture_BrushPrimit(face, 0, -oldT, true);
	} else {
		Face_SetExplicitScale_BrushPrimit(face, width, 0.0);
	}
/*
	common->Printf("Face x: %f  y: %f  xr: %f  yr: %f\n", x, y, xRatio, yRatio);
	common->Printf("Texture x: %i  y: %i  \n",face->d_texture->GetEditorImage()->uploadWidth, face->d_texture->GetEditorImage()->uploadHeight);

	idVec3D texS, texT;
	ComputeAxisBase(face->plane.normal, texS, texT);
	float	Os = 0, Ot = 0;
	for (int i = 0; i < face->face_winding->numpoints; i++) {
		Os += DotProduct(face->face_winding->p[i], texS);
		Ot += DotProduct(face->face_winding->p[i], texT);
	}

	common->Printf("Face2 x: %f  y: %f  \n", Os, Ot);
	Os /= face->face_winding->numpoints;
	Ot /= face->face_winding->numpoints;


	//Os /= face->face_winding->numpoints;
	//Ot /= face->face_winding->numpoints;

*/
}