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

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 "Radiant.h"
#include "XYWnd.h"
#include "CamWnd.h"
#include "splines.h"
#include <GL/glu.h>

#include "../../renderer/tr_local.h"
#include "../../renderer/model_local.h"	// for idRenderModelMD5


// TODO: DG: could merge SteelStorm2 "new 3D view navigation" improvements

#ifdef _DEBUG
	#define new DEBUG_NEW
	#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
extern void DrawPathLines();

int g_axialAnchor = -1;
int g_axialDest = -1;
bool g_bAxialMode = false;

void ValidateAxialPoints() {
	int faceCount = g_ptrSelectedFaces.GetSize();
	if (faceCount > 0) {
		face_t	*selFace = reinterpret_cast < face_t * > (g_ptrSelectedFaces.GetAt(0));
		if (g_axialAnchor >= selFace->face_winding->GetNumPoints()) {
			g_axialAnchor = 0;
		}
		if (g_axialDest >= selFace->face_winding->GetNumPoints()) {
			g_axialDest = 0;
		}
	} else {
		g_axialDest = 0;
		g_axialAnchor = 0;
	}
}

// CCamWnd
IMPLEMENT_DYNCREATE(CCamWnd, CWnd);

/*
 =======================================================================================================================
 =======================================================================================================================
 */
CCamWnd::CCamWnd() {
	m_pXYFriend = NULL;
	memset(&m_Camera, 0, sizeof(camera_t));
	m_pSide_select = NULL;
	m_bClipMode = false;
	worldDirty = true;
	worldModel = NULL;
	renderMode = false;
	rebuildMode = false;
	entityMode = false;
	animationMode = false;
	selectMode = false;
	soundMode = false;
	saveValid = false;
	Cam_Init();
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
CCamWnd::~CCamWnd() {
}

BEGIN_MESSAGE_MAP(CCamWnd, CWnd)
//{{AFX_MSG_MAP(CCamWnd)
	ON_WM_KEYDOWN()
	ON_WM_PAINT()
	ON_WM_DESTROY()
	ON_WM_CLOSE()
	ON_WM_MOUSEMOVE()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_MBUTTONDOWN()
	ON_WM_MBUTTONUP()
	ON_WM_RBUTTONDOWN()
	ON_WM_RBUTTONUP()
	ON_WM_CREATE()
	ON_WM_SIZE()
	ON_WM_KEYUP()
	ON_WM_NCCALCSIZE()
	ON_WM_KILLFOCUS()
	ON_WM_SETFOCUS()
	ON_WM_TIMER()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/*
 =======================================================================================================================
 =======================================================================================================================
 */
LONG_PTR WINAPI CamWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
	RECT	rect;

	GetClientRect(hWnd, &rect);

	switch (uMsg)
	{
		case WM_KILLFOCUS:
		case WM_SETFOCUS:
			SendMessage(hWnd, WM_NCACTIVATE, uMsg == WM_SETFOCUS, 0);
			return 0;

		case WM_NCCALCSIZE: // don't let windows copy pixels
			DefWindowProc(hWnd, uMsg, wParam, lParam);
			return WVR_REDRAW;
	}

	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

//
// =======================================================================================================================
//    CCamWnd message handlers
// =======================================================================================================================
//
BOOL CCamWnd::PreCreateWindow(CREATESTRUCT &cs) {
	WNDCLASS	wc;
	HINSTANCE	hInstance = AfxGetInstanceHandle();
	if (::GetClassInfo(hInstance, CAMERA_WINDOW_CLASS, &wc) == FALSE) {
		// Register a new class
		memset(&wc, 0, sizeof(wc));

		// wc.style = CS_NOCLOSE | CS_OWNDC;
		wc.style = CS_NOCLOSE;
		wc.lpszClassName = CAMERA_WINDOW_CLASS;
		wc.hCursor = LoadCursor(NULL, IDC_ARROW);
		wc.lpfnWndProc = CamWndProc;
		if (AfxRegisterClass(&wc) == FALSE) {
			Error("CCamWnd RegisterClass: failed");
		}
	}

	cs.lpszClass = CAMERA_WINDOW_CLASS;
	cs.lpszName = "CAM";
	if (cs.style != QE3_CHILDSTYLE) {
		cs.style = QE3_SPLITTER_STYLE;
	}

	BOOL	bResult = CWnd::PreCreateWindow(cs);

	//
	// See if the class already exists and if not then we need to register our new
	// window class.
	//
	return bResult;
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void CCamWnd::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) {
	g_pParentWnd->HandleKey(nChar, nRepCnt, nFlags);
}

brush_t *g_pSplitList = NULL;

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void CCamWnd::OnPaint() {
	CPaintDC	dc(this);	// device context for painting

	if (!qwglMakeCurrent(dc.m_hDC, win32.hGLRC)) {
		common->Printf("ERROR: wglMakeCurrent failed..\n ");
		common->Printf("Please restart " EDITOR_WINDOWTEXT " if the camera view is not working\n");
	}
	else {
		QE_CheckOpenGLForErrors();
		g_pSplitList = NULL;
		if (g_bClipMode) {
			if (g_Clip1.Set() && g_Clip2.Set()) {
				g_pSplitList = ((g_pParentWnd->ActiveXY()->GetViewType() == XZ) ? !g_bSwitch : g_bSwitch) ? &g_brBackSplits : &g_brFrontSplits;
			}
		}

		Cam_Draw();
		QE_CheckOpenGLForErrors();
		qwglSwapBuffers(dc.m_hDC);
	}
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void CCamWnd::SetXYFriend(CXYWnd *pWnd) {
	m_pXYFriend = pWnd;
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void CCamWnd::OnDestroy() {
	CWnd::OnDestroy();
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void CCamWnd::OnClose() {
	CWnd::OnClose();
}

extern void Select_RotateTexture(float amt, bool absolute);

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void CCamWnd::OnMouseMove(UINT nFlags, CPoint point) {
	CRect	r;
	GetClientRect(r);
	if	(GetCapture() == this && (GetAsyncKeyState(VK_MENU) & 0x8000) && !((GetAsyncKeyState(VK_SHIFT) & 0x8000) || (GetAsyncKeyState(VK_CONTROL) & 0x8000))) {
		if (GetAsyncKeyState(VK_CONTROL) & 0x8000) {
			Select_RotateTexture((float)point.y - m_ptLastCursor.y);
		}
		else if (GetAsyncKeyState(VK_SHIFT) & 0x8000) {
			Select_ScaleTexture((float)point.x - m_ptLastCursor.x, (float)m_ptLastCursor.y - point.y);
		}
		else {
			Select_ShiftTexture((float)point.x - m_ptLastCursor.x, (float)m_ptLastCursor.y - point.y);
		}
	}
	else {
		Cam_MouseMoved(point.x, r.bottom - 1 - point.y, nFlags);
	}

	m_ptLastCursor = point;
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void CCamWnd::OnLButtonDown(UINT nFlags, CPoint point) {
	m_ptLastCursor = point;
	OriginalMouseDown(nFlags, point);
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void CCamWnd::OnLButtonUp(UINT nFlags, CPoint point) {
	OriginalMouseUp(nFlags, point);
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void CCamWnd::OnMButtonDown(UINT nFlags, CPoint point) {
	OriginalMouseDown(nFlags, point);
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void CCamWnd::OnMButtonUp(UINT nFlags, CPoint point) {
	OriginalMouseUp(nFlags, point);
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void CCamWnd::OnRButtonDown(UINT nFlags, CPoint point) {
	OriginalMouseDown(nFlags, point);
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void CCamWnd::OnRButtonUp(UINT nFlags, CPoint point) {
	OriginalMouseUp(nFlags, point);
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
int CCamWnd::OnCreate(LPCREATESTRUCT lpCreateStruct) {
	if (CWnd::OnCreate(lpCreateStruct) == -1) {
		return -1;
	}

	CDC *pDC = GetDC();
	HDC hDC = pDC->GetSafeHdc();

	QEW_SetupPixelFormat(hDC, true);

	HFONT hfont = CreateFont(
				12,	// logical height of font
				0,	// logical average character width
				0,	// angle of escapement
				0,	// base-line orientation angle
				0,	// font weight
				0,	// italic attribute flag
				0,	// underline attribute flag
				0,	// strikeout attribute flag
				0,	// character set identifier
				0,	// output precision
				0,	// clipping precision
				0,	// output quality
				FIXED_PITCH | FF_MODERN,	// pitch and family
				"Lucida Console"	// pointer to typeface name string
				);

	if (!hfont) {
		Error("couldn't create font");
	}

	HFONT hOldFont = (HFONT)SelectObject(hDC, hfont);

	//qwglMakeCurrent (hDC, win32.hGLRC);
	if( qwglMakeCurrent ( hDC, win32.hGLRC ) == FALSE ) {
		common->Warning("wglMakeCurrent failed: %d", ::GetLastError());
		if ( r_multiSamples.GetInteger() > 0 ) {
			common->Warning("\n!!! Try setting r_multiSamples 0 when using the editor !!!\n");
		}
	}

	if ((g_qeglobals.d_font_list = qglGenLists(256)) == 0) {
		common->Warning( "couldn't create font dlists" );
	}

	// create the bitmap display lists we're making images of glyphs 0 thru 255
	if ( !qwglUseFontBitmaps(hDC, 0, 255, g_qeglobals.d_font_list) ) {
		common->Warning( "wglUseFontBitmaps failed (%d).  Trying again.", GetLastError() );

		// FIXME: This is really wacky, sometimes the first call fails, but calling it again makes it work
		//		This probably indicates there's something wrong somewhere else in the code, but I'm not sure what
		if ( !qwglUseFontBitmaps(hDC, 0, 255, g_qeglobals.d_font_list) ) {
			common->Warning( "wglUseFontBitmaps failed again (%d).  Trying outlines.", GetLastError() );

			if (!qwglUseFontOutlines(hDC, 0, 255, g_qeglobals.d_font_list, 0.0f, 0.1f, WGL_FONT_LINES, NULL)) {
				common->Warning( "wglUseFontOutlines also failed (%d), no coordinate text will be visible.", GetLastError() );
			}
		}
	}

	SelectObject(hDC, hOldFont);
	ReleaseDC(pDC);

	// indicate start of glyph display lists
	qglListBase(g_qeglobals.d_font_list);

	// report OpenGL information
	common->Printf("GL_VENDOR: %s\n", qglGetString(GL_VENDOR));
	common->Printf("GL_RENDERER: %s\n", qglGetString(GL_RENDERER));
	common->Printf("GL_VERSION: %s\n", qglGetString(GL_VERSION));
	common->Printf("GL_EXTENSIONS: %s\n", qglGetString(GL_EXTENSIONS));

	return 0;
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void CCamWnd::OriginalMouseUp(UINT nFlags, CPoint point) {
	CRect	r;
	GetClientRect(r);
	Cam_MouseUp(point.x, r.bottom - 1 - point.y, nFlags);
	if (!(nFlags & (MK_LBUTTON | MK_RBUTTON | MK_MBUTTON))) {
		ReleaseCapture();
	}
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void CCamWnd::OriginalMouseDown(UINT nFlags, CPoint point) {
	// if (GetTopWindow()->GetSafeHwnd() != GetSafeHwnd()) BringWindowToTop();
	CRect	r;
	GetClientRect(r);
	SetFocus();
	SetCapture();

	// if (!(GetAsyncKeyState(VK_MENU) & 0x8000))
	Cam_MouseDown(point.x, r.bottom - 1 - point.y, nFlags);
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void CCamWnd::Cam_Init() {
	// m_Camera.draw_mode = cd_texture;
	m_Camera.origin[0] = 0.0f;
	m_Camera.origin[1] = 20.0f;
	m_Camera.origin[2] = 72.0f;
	m_Camera.color[0] = 0.3f;
	m_Camera.color[1] = 0.3f;
	m_Camera.color[2] = 0.3f;
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void CCamWnd::Cam_BuildMatrix() {
	float	xa, ya;
	float	matrix[4][4];
	int		i;

	xa = ((renderMode) ? -m_Camera.angles[PITCH] : m_Camera.angles[PITCH]) * idMath::M_DEG2RAD;
	ya = m_Camera.angles[YAW] * idMath::M_DEG2RAD;

	// the movement matrix is kept 2d
	m_Camera.forward[0] = cos(ya);
	m_Camera.forward[1] = sin(ya);
	m_Camera.right[0] = m_Camera.forward[1];
	m_Camera.right[1] = -m_Camera.forward[0];

	qglGetFloatv(GL_PROJECTION_MATRIX, &matrix[0][0]);

	for (i = 0; i < 3; i++) {
		m_Camera.vright[i] = matrix[i][0];
		m_Camera.vup[i] = matrix[i][1];
		m_Camera.vpn[i] = matrix[i][2];
	}

	m_Camera.vright.Normalize();
	m_Camera.vup.Normalize();
	m_Camera.vpn.Normalize();
	InitCull();
}

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

void CCamWnd::Cam_ChangeFloor(bool up) {
	brush_t *b;
	float	d, bestd, current;
	idVec3	start, dir;

	start[0] = m_Camera.origin[0];
	start[1] = m_Camera.origin[1];
	start[2] = HUGE_DISTANCE;
	dir[0] = dir[1] = 0;
	dir[2] = -1;

	current = HUGE_DISTANCE - (m_Camera.origin[2] - 72);
	if (up) {
		bestd = 0;
	}
	else {
		bestd = HUGE_DISTANCE*2;
	}

	for (b = active_brushes.next; b != &active_brushes; b = b->next) {
		if (!Brush_Ray(start, dir, b, &d)) {
			continue;
		}

		if (up && d < current && d > bestd) {
			bestd = d;
		}

		if (!up && d > current && d < bestd) {
			bestd = d;
		}
	}

	if (bestd == 0 || bestd == HUGE_DISTANCE*2) {
		return;
	}

	m_Camera.origin[2] += current - bestd;
	Sys_UpdateWindows(W_CAMERA | W_Z_OVERLAY);
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void CCamWnd::Cam_PositionDrag() {
	int x, y;
	Sys_GetCursorPos(&x, &y);
	if (x != m_ptCursor.x || y != m_ptCursor.y) {
		x -= m_ptCursor.x;
		VectorMA(m_Camera.origin, x, m_Camera.vright, m_Camera.origin);
		y -= m_ptCursor.y;
		m_Camera.origin[2] -= y;
		SetCursorPos(m_ptCursor.x, m_ptCursor.y);
		Sys_UpdateWindows(W_CAMERA | W_XY_OVERLAY);
	}
}

void CCamWnd::Cam_MouseLook() {
	CPoint current;

	GetCursorPos(&current);
	if (current.x != m_ptCursor.x || current.y != m_ptCursor.y) {

		current.x -= m_ptCursor.x;
		current.y -= m_ptCursor.y;

		m_Camera.angles[PITCH] -= (float)((float)current.y * 0.25f);
		m_Camera.angles[YAW] -= (float)((float)current.x * 0.25f);

		SetCursorPos(m_ptCursor.x, m_ptCursor.y);

		Cam_BuildMatrix();
	}
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void CCamWnd::Cam_MouseControl(float dtime) {
	int		xl, xh;
	int		yl, yh;
	float	xf, yf;
	if (g_PrefsDlg.m_nMouseButtons == 2) {
		if (m_nCambuttonstate != (MK_RBUTTON | MK_SHIFT)) {
			return;
		}
	}
	else {
		if (m_nCambuttonstate != MK_RBUTTON) {
			return;
		}
	}

	xf = (float)(m_ptButton.x - m_Camera.width / 2) / (m_Camera.width / 2);
	yf = (float)(m_ptButton.y - m_Camera.height / 2) / (m_Camera.height / 2);

	xl = m_Camera.width / 3;
	xh = xl * 2;
	yl = m_Camera.height / 3;
	yh = yl * 2;

	// common->Printf("xf-%f yf-%f xl-%i xh-i% yl-i% yh-i%\n",xf,yf,xl,xh,yl,yh);
#if 0

	// strafe
	if (buttony < yl && (buttonx < xl || buttonx > xh)) {
		VectorMA(camera.origin, xf * dtime * g_nMoveSpeed, camera.right, camera.origin);
	}
	else
#endif
	{
		xf *= 1.0f - idMath::Fabs(yf);
		if ( xf < 0.0f ) {
			xf += 0.1f;
			if ( xf > 0.0f ) {
				xf = 0.0f;
			}
		}
		else {
			xf -= 0.1f;
			if ( xf < 0.0f ) {
				xf = 0.0f;
			}
		}

		VectorMA(m_Camera.origin, yf * dtime * g_PrefsDlg.m_nMoveSpeed, m_Camera.forward, m_Camera.origin);
		m_Camera.angles[YAW] += xf * -dtime * g_PrefsDlg.m_nAngleSpeed;
	}

	Cam_BuildMatrix();
	int nUpdate = (g_PrefsDlg.m_bCamXYUpdate) ? (W_CAMERA | W_XY) : (W_CAMERA);
	Sys_UpdateWindows(nUpdate);
	g_pParentWnd->PostMessage(WM_TIMER, 0, 0);
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void CCamWnd::Cam_MouseDown(int x, int y, int buttons) {
	idVec3	dir;
	float	f, r, u;
	int		i;

	// calc ray direction
	u = (float)(y - m_Camera.height / 2) / (m_Camera.width / 2);
	r = (float)(x - m_Camera.width / 2) / (m_Camera.width / 2);
	f = 1;

	for (i = 0; i < 3; i++) {
		dir[i] = m_Camera.vpn[i] * f + m_Camera.vright[i] * r + m_Camera.vup[i] * u;
	}

	dir.Normalize();

	GetCursorPos(&m_ptCursor);

	m_nCambuttonstate = buttons;
	m_ptButton.x = x;
	m_ptButton.y = y;

	//
	// LBUTTON = manipulate selection shift-LBUTTON = select middle button = grab
	// texture ctrl-middle button = set entire brush to texture ctrl-shift-middle
	// button = set single face to texture
	//
	int nMouseButton = g_PrefsDlg.m_nMouseButtons == 2 ? MK_RBUTTON : MK_MBUTTON;
	if
	(
		(buttons == MK_LBUTTON) ||
		(buttons == (MK_LBUTTON | MK_SHIFT)) ||
		(buttons == (MK_LBUTTON | MK_CONTROL)) ||
		(buttons == (MK_LBUTTON | MK_CONTROL | MK_SHIFT)) ||
		(buttons == nMouseButton) ||
		(buttons == (nMouseButton | MK_SHIFT)) ||
		(buttons == (nMouseButton | MK_CONTROL)) ||
		(buttons == (nMouseButton | MK_SHIFT | MK_CONTROL))
	) {
		if (g_PrefsDlg.m_nMouseButtons == 2 && (buttons == (MK_RBUTTON | MK_SHIFT))) {
			Cam_MouseControl( 0.1f );
		}
		else {
			// something global needs to track which window is responsible for stuff
			Patch_SetView(W_CAMERA);
			Drag_Begin(x, y, buttons, m_Camera.vright, m_Camera.vup, m_Camera.origin, dir);
		}

		return;
	}

	if ( buttons == MK_RBUTTON ) {
		Cam_MouseControl( 0.1f );
		return;
	}
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void CCamWnd::Cam_MouseUp(int x, int y, int buttons) {
	m_nCambuttonstate = 0;
	Drag_MouseUp(buttons);
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void CCamWnd::Cam_MouseMoved(int x, int y, int buttons) {
	m_nCambuttonstate = buttons;
	if (!buttons) {
		return;
	}

	m_ptButton.x = x;
	m_ptButton.y = y;

	if (buttons == (MK_RBUTTON | MK_CONTROL)) {
		Cam_PositionDrag();
		Sys_UpdateWindows(W_XY | W_CAMERA | W_Z);
		return;
	}
	else if ( buttons == (MK_RBUTTON | MK_CONTROL | MK_SHIFT) ) {
		Cam_MouseLook();
		Sys_UpdateWindows(W_XY | W_CAMERA | W_Z);
		return;
	}

	GetCursorPos(&m_ptCursor);

	if (buttons & (MK_LBUTTON | MK_MBUTTON)) {
		Drag_MouseMoved(x, y, buttons);
		Sys_UpdateWindows(W_XY | W_CAMERA | W_Z);
	}
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void CCamWnd::InitCull() {
	int i;

	VectorSubtract(m_Camera.vpn, m_Camera.vright, m_vCull1);
	VectorAdd(m_Camera.vpn, m_Camera.vright, m_vCull2);

	for (i = 0; i < 3; i++) {
		if (m_vCull1[i] > 0) {
			m_nCullv1[i] = 3 + i;
		}
		else {
			m_nCullv1[i] = i;
		}

		if (m_vCull2[i] > 0) {
			m_nCullv2[i] = 3 + i;
		}
		else {
			m_nCullv2[i] = i;
		}
	}
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
bool CCamWnd::CullBrush(brush_t *b, bool cubicOnly) {
	int		i;
	idVec3	point;
	float	d;

	if ( b->forceVisibile ) {
		return false;
	}

	if (g_PrefsDlg.m_bCubicClipping) {

		float distance = g_PrefsDlg.m_nCubicScale * 64;

		idVec3 mid;
		for (i = 0; i < 3; i++) {
			mid[i] = (b->mins[i] + ((b->maxs[i] - b->mins[i]) / 2));
		}

		point = mid - m_Camera.origin;
		if (point.Length() > distance) {
			return true;
		}

	}

	if (cubicOnly) {
		return false;
	}

	for (i = 0; i < 3; i++) {
		point[i] = b->mins[m_nCullv1[i]] - m_Camera.origin[i];
	}

	d = DotProduct(point, m_vCull1);
	if (d < -1) {
		return true;
	}

	for (i = 0; i < 3; i++) {
		point[i] = b->mins[m_nCullv2[i]] - m_Camera.origin[i];
	}

	d = DotProduct(point, m_vCull2);
	if (d < -1) {
		return true;
	}

	return false;
}

#if 0

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void CCamWnd::DrawLightRadius(brush_t *pBrush) {
	// if lighting
	int nRadius = Brush_LightRadius(pBrush);
	if (nRadius > 0) {
		Brush_SetLightColor(pBrush);
		qglEnable(GL_BLEND);
		qglPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
		qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		qglDisable(GL_BLEND);
		qglPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	}
}
#endif

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void setGLMode(int mode) {
	switch (mode)
	{
		case cd_wire:
			qglPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
			globalImages->BindNull();
			qglDisable(GL_BLEND);
			qglDisable(GL_DEPTH_TEST);
			qglColor3f( 1.0f, 1.0f, 1.0f );
			break;

		case cd_solid:
			qglCullFace(GL_FRONT);
			qglEnable(GL_CULL_FACE);
			qglShadeModel(GL_FLAT);
			qglPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
			globalImages->BindNull();
			qglDisable(GL_BLEND);
			qglEnable(GL_DEPTH_TEST);
			qglDepthFunc(GL_LEQUAL);
			break;

		case cd_texture:
			qglCullFace(GL_FRONT);
			qglEnable(GL_CULL_FACE);
			qglShadeModel(GL_FLAT);
			qglPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
			qglDisable(GL_BLEND);
			qglEnable(GL_DEPTH_TEST);
			qglDepthFunc(GL_LEQUAL);
			break;

		case cd_blend:
			qglCullFace(GL_FRONT);
			qglEnable(GL_CULL_FACE);
			qglShadeModel(GL_FLAT);
			qglPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
			qglDisable(GL_DEPTH_TEST);
			qglEnable(GL_BLEND);
			qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
			break;
	}
}


extern void glLabeledPoint(idVec4 &color, idVec3 &point, float size, const char *label);
void DrawAxial(face_t *selFace) {
	if (g_bAxialMode) {
		idVec3 points[4];

		for (int j = 0; j < selFace->face_winding->GetNumPoints(); j++) {
			glLabeledPoint(idVec4(1, 1, 1, 1), (*selFace->face_winding)[j].ToVec3(), 3, va("%i", j));
		}

		ValidateAxialPoints();
		points[0] = (*selFace->face_winding)[g_axialAnchor].ToVec3();
		VectorMA (points[0], 1, selFace->plane, points[0]);
		VectorMA (points[0], 4, selFace->plane, points[1]);
		points[3] = (*selFace->face_winding)[g_axialDest].ToVec3();
		VectorMA (points[3], 1, selFace->plane, points[3]);
		VectorMA (points[3], 4, selFace->plane, points[2]);
		glLabeledPoint(idVec4(1, 0, 0, 1), points[1], 3, "Anchor");
		glLabeledPoint(idVec4(1, 1, 0, 1), points[2], 3, "Dest");
		qglBegin (GL_LINE_STRIP);
		qglVertex3fv( points[0].ToFloatPtr() );
		qglVertex3fv( points[1].ToFloatPtr() );
		qglVertex3fv( points[2].ToFloatPtr() );
		qglVertex3fv( points[3].ToFloatPtr() );
		qglEnd();
	}
}


/*
 =======================================================================================================================
	Cam_Draw
 =======================================================================================================================
 */
void CCamWnd::SetProjectionMatrix() {
	float xfov = 90;
	float yfov = 2 * atan((float)m_Camera.height / m_Camera.width) * idMath::M_RAD2DEG;
#if 0
	float screenaspect = (float)m_Camera.width / m_Camera.height;
	qglLoadIdentity();
	gluPerspective(yfov, screenaspect, 2, 8192);
#else
	float	xmin, xmax, ymin, ymax;
	float	width, height;
	float	zNear;
	float	projectionMatrix[16];

	//
	// set up projection matrix
	//
	zNear	= r_znear.GetFloat();

	ymax = zNear * tan( yfov * idMath::PI / 360.0f );
	ymin = -ymax;

	xmax = zNear * tan( xfov * idMath::PI / 360.0f );
	xmin = -xmax;

	width = xmax - xmin;
	height = ymax - ymin;

	projectionMatrix[0] = 2 * zNear / width;
	projectionMatrix[4] = 0;
	projectionMatrix[8] = ( xmax + xmin ) / width;	// normally 0
	projectionMatrix[12] = 0;

	projectionMatrix[1] = 0;
	projectionMatrix[5] = 2 * zNear / height;
	projectionMatrix[9] = ( ymax + ymin ) / height;	// normally 0
	projectionMatrix[13] = 0;

	// this is the far-plane-at-infinity formulation
	projectionMatrix[2] = 0;
	projectionMatrix[6] = 0;
	projectionMatrix[10] = -1;
	projectionMatrix[14] = -2 * zNear;

	projectionMatrix[3] = 0;
	projectionMatrix[7] = 0;
	projectionMatrix[11] = -1;
	projectionMatrix[15] = 0;

	qglLoadMatrixf( projectionMatrix );
#endif
}

void CCamWnd::Cam_Draw() {
	brush_t *brush;
	face_t	*face;

	// float yfov;
	int		i;

	if (!active_brushes.next) {
		return;					// not valid yet
	}

	// set the sound origin for both simple draw and rendered mode
	// the editor uses opposite pitch convention
	idMat3	axis = idAngles( -m_Camera.angles.pitch, m_Camera.angles.yaw, m_Camera.angles.roll ).ToMat3();
	g_qeglobals.sw->PlaceListener( m_Camera.origin, axis, 0, Sys_Milliseconds(), "Undefined" );

	if (renderMode) {
		Cam_Render();
	}

	qglViewport(0, 0, m_Camera.width, m_Camera.height);
	qglScissor(0, 0, m_Camera.width, m_Camera.height);
	qglClearColor(g_qeglobals.d_savedinfo.colors[COLOR_CAMERABACK][0], g_qeglobals.d_savedinfo.colors[COLOR_CAMERABACK][1], g_qeglobals.d_savedinfo.colors[COLOR_CAMERABACK][2], 0);

	if (!renderMode) {
		qglClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	}

	qglDisable(GL_LIGHTING);
	qglMatrixMode(GL_PROJECTION);

	SetProjectionMatrix();

	qglRotatef(-90, 1, 0, 0);	// put Z going up
	qglRotatef(90, 0, 0, 1);	// put Z going up
	qglRotatef(m_Camera.angles[0], 0, 1, 0);
	qglRotatef(-m_Camera.angles[1], 0, 0, 1);
	qglTranslatef(-m_Camera.origin[0], -m_Camera.origin[1], -m_Camera.origin[2]);

	Cam_BuildMatrix();

	for (brush = active_brushes.next; brush != &active_brushes; brush = brush->next) {

		if ( CullBrush(brush, false) ) {
			continue;
		}

		if ( FilterBrush(brush) ) {
			continue;
		}

		if (renderMode) {
			if (!(entityMode && brush->owner->eclass->fixedsize)) {
				continue;
			}
		}

		setGLMode(m_Camera.draw_mode);
		Brush_Draw(brush);
	}


	//qglDepthMask ( 1 ); // Ok, write now
	qglMatrixMode(GL_PROJECTION);

	qglTranslatef(g_qeglobals.d_select_translate[0],g_qeglobals.d_select_translate[1],g_qeglobals.d_select_translate[2]);

	brush_t *pList = (g_bClipMode && g_pSplitList) ? g_pSplitList : &selected_brushes;

	if (!renderMode) {
		// draw normally
		for (brush = pList->next; brush != pList; brush = brush->next) {
			if (brush->pPatch) {
				continue;
			}
			setGLMode(m_Camera.draw_mode);
			Brush_Draw(brush, true);
		}
	}

	// blend on top

	setGLMode(m_Camera.draw_mode);
	qglDisable(GL_LIGHTING);
	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.25f );
	qglEnable(GL_BLEND);
	qglPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	globalImages->BindNull();
	for (brush = pList->next; brush != pList; brush = brush->next) {
		if (brush->pPatch || brush->modelHandle > 0) {
			Brush_Draw(brush, true);

			// DHM - Nerve:: patch display lists/models mess with the state
			qglEnable(GL_BLEND);
			qglPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
			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.25f );
			globalImages->BindNull();
			continue;
		}

		if ( brush->owner->eclass->entityModel ) {
			continue;
		}

		for (face = brush->brush_faces; face; face = face->next) {
			Face_Draw(face);
		}
	}

	int nCount = g_ptrSelectedFaces.GetSize();

	if (!renderMode) {
		for (i = 0; i < nCount; i++) {
			face_t	*selFace = reinterpret_cast < face_t * > (g_ptrSelectedFaces.GetAt(i));
			Face_Draw(selFace);
			DrawAxial(selFace);
		}
	}

	// non-zbuffered outline
	qglDisable(GL_BLEND);
	qglDisable(GL_DEPTH_TEST);
	qglPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

	if (renderMode) {
		qglColor3f(1, 0, 0);
		for (i = 0; i < nCount; i++) {
			face_t	*selFace = reinterpret_cast < face_t * > (g_ptrSelectedFaces.GetAt(i));
			Face_Draw(selFace);
		}
	}

	qglColor3f(1, 1, 1);
	for (brush = pList->next; brush != pList; brush = brush->next) {
		if (brush->pPatch || brush->modelHandle > 0) {
			continue;
		}

		for (face = brush->brush_faces; face; face = face->next) {
			Face_Draw(face);
		}
	}
	// edge / vertex flags
	if (g_qeglobals.d_select_mode == sel_vertex) {
		qglPointSize(4);
		qglColor3f(0, 1, 0);
		qglBegin(GL_POINTS);
		for (i = 0; i < g_qeglobals.d_numpoints; i++) {
			qglVertex3fv( g_qeglobals.d_points[i].ToFloatPtr() );
		}

		qglEnd();
		qglPointSize(1);
	}
	else if (g_qeglobals.d_select_mode == sel_edge) {
		float	*v1, *v2;

		qglPointSize(4);
		qglColor3f(0, 0, 1);
		qglBegin(GL_POINTS);
		for (i = 0; i < g_qeglobals.d_numedges; i++) {
			v1 = g_qeglobals.d_points[g_qeglobals.d_edges[i].p1].ToFloatPtr();
			v2 = g_qeglobals.d_points[g_qeglobals.d_edges[i].p2].ToFloatPtr();
			qglVertex3f( (v1[0] + v2[0]) * 0.5f, (v1[1] + v2[1]) * 0.5f, (v1[2] + v2[2]) * 0.5f );
		}

		qglEnd();
		qglPointSize(1);
	}

	g_splineList->draw (static_cast<bool>(g_qeglobals.d_select_mode == sel_addpoint || g_qeglobals.d_select_mode == sel_editpoint));

	if ( g_qeglobals.selectObject && (g_qeglobals.d_select_mode == sel_addpoint || g_qeglobals.d_select_mode == sel_editpoint) ) {
		g_qeglobals.selectObject->drawSelection();
	}

	// draw pointfile
	qglEnable(GL_DEPTH_TEST);

	DrawPathLines();

	if (g_qeglobals.d_pointfile_display_list) {
		Pointfile_Draw();
	}

	//
	// bind back to the default texture so that we don't have problems elsewhere
	// using/modifying texture maps between contexts
	//
	globalImages->BindNull();

	qglFinish();
	QE_CheckOpenGLForErrors();

	if (!renderMode) {
		// clean up any deffered tri's
		R_ToggleSmpFrame();
	}
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void CCamWnd::OnSize(UINT nType, int cx, int cy) {
	CWnd::OnSize(nType, cx, cy);

	CRect	rect;
	GetClientRect(rect);
	m_Camera.width = rect.right;
	m_Camera.height = rect.bottom;
	InvalidateRect(NULL, false);
}


/*
 =======================================================================================================================
 =======================================================================================================================
 */
void CCamWnd::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) {
	g_pParentWnd->HandleKey(nChar, nRepCnt, nFlags, false);
}

//
// =======================================================================================================================
//    Timo brush primitive texture shifting, using camera view to select translations::
// =======================================================================================================================
//
void CCamWnd::ShiftTexture_BrushPrimit(face_t *f, int x, int y) {
/*
	idVec3	texS, texT;
	idVec3	viewX, viewY;
	int		XS, XT, YS, YT;
	int		outS, outT;
#ifdef _DEBUG
	if (!g_qeglobals.m_bBrushPrimitMode) {
		common->Printf("Warning : unexpected call to CCamWnd::ShiftTexture_BrushPrimit with brush primitive mode disbaled\n");
		return;
	}
#endif
	// compute face axis base
	//ComputeAxisBase(f->plane.Normal(), texS, texT);

	// compute camera view vectors
	VectorCopy(m_Camera.vup, viewY);
	VectorCopy(m_Camera.vright, viewX);

	// compute best vectors
	//ComputeBest2DVector(viewX, texS, texT, XS, XT);
	//ComputeBest2DVector(viewY, texS, texT, YS, YT);

	// check this is not a degenerate case
	if ((XS == YS) && (XT == YT))
	{
#ifdef _DEBUG
		common->Printf("Warning : degenerate best vectors axis base in CCamWnd::ShiftTexture_BrushPrimit\n");
#endif
		// forget it
		Select_ShiftTexture_BrushPrimit(f, x, y, false);
		return;
	}

	// compute best fitted translation in face axis base
	outS = XS * x + YS * y;
	outT = XT * x + YT * y;

	// call actual texture shifting code
	Select_ShiftTexture_BrushPrimit(f, outS, outT, false);
*/
}


bool IsBModel(brush_t *b) {
	const char *v = ValueForKey( b->owner, "model" );
	if (v && *v) {
		const char *n = ValueForKey( b->owner, "name");
		return (stricmp( n, v ) == 0);
	}
	return false;
}

/*
================
BuildEntityRenderState

Creates or updates modelDef and lightDef for an entity
================
*/
int Brush_ToTris(brush_t *brush, idTriList *tris, idMatList *mats, bool models, bool bmodel);

void CCamWnd::BuildEntityRenderState( entity_t *ent, bool update) {
	const char	*v;
	idDict		spawnArgs;
	const char	*name = NULL;

	Entity_UpdateSoundEmitter( ent );

	// delete the existing def if we aren't creating a brand new world
	if ( !update ) {
		if ( ent->lightDef >= 0 ) {
			g_qeglobals.rw->FreeLightDef( ent->lightDef );
			ent->lightDef = -1;
		}

		if ( ent->modelDef >= 0 ) {
			g_qeglobals.rw->FreeEntityDef( ent->modelDef );
			ent->modelDef = -1;
		}
	}

	// if an entity doesn't have any brushes at all, don't do anything
	if ( ent->brushes.onext == &ent->brushes ) {
		return;
	}

	// if the brush isn't displayed (filtered or culled), don't do anything
	if (FilterBrush(ent->brushes.onext)) {
		return;
	}

	spawnArgs = ent->epairs;
	if (ent->eclass->defArgs.FindKey("model")) {
		spawnArgs.Set("model", ent->eclass->defArgs.GetString("model"));
	}

	// any entity can have a model
	name = ValueForKey( ent, "name" );
	v = spawnArgs.GetString("model");
	if ( v && *v ) {
		renderEntity_t	refent;

		refent.referenceSound = ent->soundEmitter;

		if ( !stricmp( name, v ) ) {
			// build the model from brushes
			idTriList tris(1024);
			idMatList mats(1024);

			for (brush_t *b = ent->brushes.onext; b != &ent->brushes; b = b->onext) {
				Brush_ToTris( b, &tris, &mats, false, true);
			}

			if ( ent->modelDef >= 0 ) {
				g_qeglobals.rw->FreeEntityDef( ent->modelDef );
				ent->modelDef = -1;
			}

			idRenderModel *bmodel = renderModelManager->FindModel( name );

			if ( bmodel ) {
				renderModelManager->RemoveModel( bmodel );
				renderModelManager->FreeModel( bmodel );
			}

			bmodel = renderModelManager->AllocModel();

			bmodel->InitEmpty( name );

			// add the surfaces to the renderModel
			modelSurface_t	surf;
			for ( int i = 0 ; i < tris.Num() ; i++ ) {
				surf.geometry = tris[i];
				surf.shader = mats[i];
				bmodel->AddSurface( surf );
			}

			bmodel->FinishSurfaces();

			renderModelManager->AddModel( bmodel );

			// FIXME: brush entities
			gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &refent );

			ent->modelDef = g_qeglobals.rw->AddEntityDef( &refent );

		} else {
			// use the game's epair parsing code so
			// we can use the same renderEntity generation
			gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &refent );
			idRenderModelMD5 *md5 = dynamic_cast<idRenderModelMD5 *>( refent.hModel );
			if (md5) {
				idStr str;
				spawnArgs.GetString("anim", "idle", str);
				refent.numJoints = md5->NumJoints();
				if ( update && refent.joints ) {
					Mem_Free16( refent.joints );
				}
				refent.joints = ( idJointMat * )Mem_Alloc16( refent.numJoints * sizeof( *refent.joints ) );
				const idMD5Anim *anim = gameEdit->ANIM_GetAnimFromEntityDef(spawnArgs.GetString("classname"), str);
				int frame = spawnArgs.GetInt("frame") + 1;
				if ( frame < 1 ) {
					frame = 1;
				}
				const idVec3 &offset = gameEdit->ANIM_GetModelOffsetFromEntityDef( spawnArgs.GetString("classname") );
				gameEdit->ANIM_CreateAnimFrame( md5, anim, refent.numJoints, refent.joints, ( frame * 1000 ) / 24, offset, false );
			}
			if (ent->modelDef >= 0) {
				g_qeglobals.rw->FreeEntityDef( ent->modelDef );
			}
			ent->modelDef = g_qeglobals.rw->AddEntityDef( &refent );
		}
	}

	// check for lightdefs
	if (!(ent->eclass->nShowFlags & ECLASS_LIGHT)) {
		return;
	}

	if ( spawnArgs.GetBool( "start_off" ) ) {
		return;
	}
	// use the game's epair parsing code so
	// we can use the same renderLight generation

	renderLight_t	lightParms;

	gameEdit->ParseSpawnArgsToRenderLight( &spawnArgs, &lightParms );
	lightParms.referenceSound = ent->soundEmitter;

	if (update && ent->lightDef >= 0) {
		g_qeglobals.rw->UpdateLightDef( ent->lightDef, &lightParms );
	} else {
		if (ent->lightDef >= 0) {
			g_qeglobals.rw->FreeLightDef(ent->lightDef);
		}
		ent->lightDef = g_qeglobals.rw->AddLightDef( &lightParms );
	}

}

void Tris_ToOBJ(const char *outFile, idTriList *tris, idMatList *mats) {
	idFile *f = fileSystem->OpenExplicitFileWrite( outFile );
	if ( f ) {
		char out[1024];
		strcpy(out, outFile);
		StripExtension(out);

		idList<idStr*> matNames;
		int i, j, k;
		int indexBase = 1;
		idStr lastMaterial("");
		//idStr basePath = cvarSystem->GetCVarString( "fs_savepath" );
		f->Printf( "mtllib %s.mtl\n", out );
		for (i = 0; i < tris->Num(); i++) {
			srfTriangles_t *tri = (*tris)[i];
			for (j = 0; j < tri->numVerts; j++) {
				f->Printf( "v %f %f %f\n", tri->verts[j].xyz.x, tri->verts[j].xyz.z, -tri->verts[j].xyz.y );
			}
			for (j = 0; j < tri->numVerts; j++) {
				f->Printf( "vt %f %f\n", tri->verts[j].st.x, 1.0f - tri->verts[j].st.y );
			}
			for (j = 0; j < tri->numVerts; j++) {
				f->Printf( "vn %f %f %f\n", tri->verts[j].normal.x, tri->verts[j].normal.y, tri->verts[j].normal.z );
			}

			if (stricmp( (*mats)[i]->GetName(), lastMaterial)) {
				lastMaterial = (*mats)[i]->GetName();

				bool found = false;
				for (k = 0; k < matNames.Num(); k++) {
					if ( idStr::Icmp(matNames[k]->c_str(), lastMaterial.c_str()) == 0 ) {
						found = true;
						// f->Printf( "usemtl m%i\n", k );
						f->Printf( "usemtl %s\n", lastMaterial.c_str() );
						break;
					}
				}

				if (!found) {
					// f->Printf( "usemtl m%i\n", matCount++ );
					f->Printf( "usemtl %s\n", lastMaterial.c_str() );
					matNames.Append(new idStr(lastMaterial));
				}
			}

			for (j = 0; j < tri->numIndexes; j += 3) {
				int i1, i2, i3;
				i1 = tri->indexes[j+2] + indexBase;
				i2 = tri->indexes[j+1] + indexBase;
				i3 = tri->indexes[j] + indexBase;
				f->Printf( "f %i/%i/%i %i/%i/%i %i/%i/%i\n", i1,i1,i1, i2,i2,i2, i3,i3,i3 );
			}

			indexBase += tri->numVerts;

		}
		fileSystem->CloseFile( f );

		strcat(out, ".mtl");
		f = fileSystem->OpenExplicitFileWrite( out );
		if (f) {
			for (k = 0; k < matNames.Num(); k++) {
				// This presumes the diffuse tga name matches the material name
				f->Printf( "newmtl %s\n\tNs 0\n\td 1\n\tillum 2\n\tKd 0 0 0 \n\tKs 0.22 0.22 0.22 \n\tKa 0 0 0 \n\tmap_Kd %s/base/%s.tga\n\n\n", matNames[k]->c_str(), "z:/d3xp", matNames[k]->c_str() );
			}
			fileSystem->CloseFile( f );
		}

	}
}

int Brush_TransformModel(brush_t *brush, idTriList *tris, idMatList *mats) {
	int ret = 0;
	if (brush->modelHandle > 0 ) {
		idRenderModel *model = brush->modelHandle;
		if (model) {
			float	a = FloatForKey(brush->owner, "angle");
			float	s = 0.0f, c = 0.0f;
			//FIXME: support full rotation matrix
			bool matrix = false;
			if (a) {
				s = sin( DEG2RAD(a) );
				c = cos( DEG2RAD(a) );
			}
			idMat3 mat;
			if (GetMatrixForKey(brush->owner, "rotation", mat)) {
				matrix = true;
			}


			for (int i = 0; i < model->NumSurfaces() ; i++) {
				const modelSurface_t	*surf = model->Surface( i );
				srfTriangles_t	*tri = surf->geometry;
				srfTriangles_t *tri2 = R_CopyStaticTriSurf(tri);
				for (int j = 0; j < tri2->numVerts; j++) {
					idVec3	v;
					if (matrix) {
						v = tri2->verts[j].xyz * brush->owner->rotation + brush->owner->origin;
					} else {
						v = tri2->verts[j].xyz;
						VectorAdd(v, brush->owner->origin, v);
						float x = v[0];
						float y = v[1];
						if (a) {
							float	x2 = (((x - brush->owner->origin[0]) * c) - ((y - brush->owner->origin[1]) * s)) + brush->owner->origin[0];
							float	y2 = (((x - brush->owner->origin[0]) * s) + ((y - brush->owner->origin[1]) * c)) + brush->owner->origin[1];
							x = x2;
							y = y2;
						}
						v[0] = x;
						v[1] = y;
					}
					tri2->verts[j].xyz = v;
				}
				tris->Append(tri2);
				mats->Append( surf->shader );
			}
			return model->NumSurfaces();
		}
	}
	return ret;
}


#define	MAX_TRI_SURFACES	16384
int Brush_ToTris(brush_t *brush, idTriList *tris, idMatList *mats, bool models, bool bmodel) {
	int i, j;
	srfTriangles_t	*tri;
	//
	// patches
	//
	if (brush->modelHandle > 0 ) {
		if (!models) {
			return 0;
		} else {
			return Brush_TransformModel(brush, tris, mats);
		}
	}

	int numSurfaces = 0;

	if ( brush->owner->eclass->fixedsize && !brush->entityModel) {
		return NULL;
	}

	if ( brush->pPatch ) {
		patchMesh_t *pm;
		int			width, height;

		pm = brush->pPatch;

		// build a patch mesh
		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;
			}
		}

		// subdivide it
		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 );
		}
		width = cp->GetWidth();
		height = cp->GetHeight();

		// convert to srfTriangles
		tri = R_AllocStaticTriSurf();
		tri->numVerts = width * height;
		tri->numIndexes = 6 * ( width - 1 ) * ( height - 1 );
		R_AllocStaticTriSurfVerts( tri, tri->numVerts );
		R_AllocStaticTriSurfIndexes( tri, tri->numIndexes );
		for ( i = 0 ; i < tri->numVerts ; i++ ) {
			tri->verts[i] = (*cp)[i];
			if (bmodel) {
				tri->verts[i].xyz -= brush->owner->origin;
			}
		}

		tri->numIndexes = 0;
		for ( i = 1 ; i < width ; i++ ) {
			for ( j = 1 ; j < height ; j++ ) {
				tri->indexes[tri->numIndexes++] = ( j - 1 ) * width + i;
				tri->indexes[tri->numIndexes++] = ( j - 1 ) * width + i - 1;
				tri->indexes[tri->numIndexes++] = j * width + i - 1;

				tri->indexes[tri->numIndexes++] = j * width + i;
				tri->indexes[tri->numIndexes++] = ( j - 1 ) * width + i;
				tri->indexes[tri->numIndexes++] = j * width + i - 1;
			}
		}

		delete cp;

		tris->Append(tri);
		mats->Append(pm->d_texture);
		//surfaces[numSurfaces] = tri;
		//materials[numSurfaces] = pm->d_texture;
		return 1;
	}

	//
	// normal brush
	//
	for ( face_t *face = brush->brush_faces ; face; face = face->next ) {
		idWinding *w;

		w = face->face_winding;
		if (!w) {
			continue;	// freed or degenerate face
		}

		tri = R_AllocStaticTriSurf();
		tri->numVerts = w->GetNumPoints();
		tri->numIndexes = ( w->GetNumPoints() - 2 ) * 3;
		R_AllocStaticTriSurfVerts( tri, tri->numVerts );
		R_AllocStaticTriSurfIndexes( tri, tri->numIndexes );

		for ( i = 0 ; i < tri->numVerts ; i++ ) {

			tri->verts[i].Clear();

			tri->verts[i].xyz[0] = (*w)[i][0];
			tri->verts[i].xyz[1] = (*w)[i][1];
			tri->verts[i].xyz[2] = (*w)[i][2];

			if ( bmodel ) {
				tri->verts[i].xyz -= brush->owner->origin;
			}

			tri->verts[i].st[0] = (*w)[i][3];
			tri->verts[i].st[1] = (*w)[i][4];

			tri->verts[i].normal = face->plane.Normal();
		}

		tri->numIndexes = 0;
		for ( i = 2 ; i < w->GetNumPoints() ; i++ ) {
			tri->indexes[tri->numIndexes++] = 0;
			tri->indexes[tri->numIndexes++] = i-1;
			tri->indexes[tri->numIndexes++] = i;
		}

		tris->Append(tri);
		mats->Append(face->d_texture);
		numSurfaces++;
	}

	return numSurfaces;
}

void Select_ToOBJ() {
	int i;
	CFileDialog dlgFile(FALSE, "obj", NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, "Wavefront object files (*.obj)|*.obj||", g_pParentWnd);
	if (dlgFile.DoModal() == IDOK) {
		idTriList tris(1024);
		idMatList mats(1024);

		for (brush_t *b = selected_brushes.next; b != &selected_brushes; b = b->next) {

			if ( b->hiddenBrush ) {
				continue;
			}

			if (FilterBrush(b)) {
				continue;
			}

			Brush_ToTris(b, &tris, &mats, true, false);
		}

		Tris_ToOBJ(dlgFile.GetPathName().GetBuffer(0), &tris, &mats);

		for( i = 0; i < tris.Num(); i++ ) {
			R_FreeStaticTriSurf( tris[i] );
		}
		tris.Clear();
	}
}

void Select_ToCM() {
#if USE_COLLADA
	CFileDialog dlgFile( FALSE, "lwo, ase, dae", NULL, 0, "(*.lwo)|*.lwo|(*.ase)|*.ase|(*.ma)|*.ma|(*.dae)|*.dae||", g_pParentWnd );
#else
	CFileDialog dlgFile(FALSE, "lwo, ase", NULL, 0, "(*.lwo)|*.lwo|(*.ase)|*.ase|(*.ma)|*.ma||", g_pParentWnd);
#endif

	if ( dlgFile.DoModal() == IDOK ) {
		idMapEntity *mapEnt;
		idMapPrimitive *p;
		idStr name;

		name = fileSystem->OSPathToRelativePath( dlgFile.GetPathName() );
		name.BackSlashesToSlashes();

		mapEnt = new idMapEntity();
		mapEnt->epairs.Set( "name", name.c_str() );

		for ( brush_t *b = selected_brushes.next; b != &selected_brushes; b = b->next ) {

			if ( b->hiddenBrush ) {
				continue;
			}

			if ( FilterBrush( b ) ) {
				continue;
			}

			p = BrushToMapPrimitive( b, b->owner->origin );
			if ( p ) {
				mapEnt->AddPrimitive( p );
			}
		}

		collisionModelManager->WriteCollisionModelForMapEntity( mapEnt, name.c_str() );

		delete mapEnt;
	}
}


/*
=================
BuildRendererState

Builds models, lightdefs, and modeldefs for the current editor data
so it can be rendered by the game renderSystem
=================
*/
void CCamWnd::BuildRendererState() {
	renderEntity_t	worldEntity;
	entity_t	*ent;
	brush_t		*brush;

	FreeRendererState();

	// the renderWorld holds all the references and defs
	g_qeglobals.rw->InitFromMap( NULL );

	// create the raw model for all the brushes
	int numBrushes = 0;
	int numSurfaces = 0;

	// the renderModel for the world holds all the geometry that isn't in an entity
	worldModel = renderModelManager->AllocModel();
	worldModel->InitEmpty( "EditorWorldModel" );

	for ( brush_t *brushList = &active_brushes ; brushList ;
		brushList = (brushList == &active_brushes) ? &selected_brushes : NULL ) {

		for (brush = brushList->next; brush != brushList; brush = brush->next) {

			if ( brush->hiddenBrush ) {
				continue;
			}

			if (FilterBrush(brush)) {
				continue;
			}

			if (CullBrush(brush, true)) {
				continue;
			}

			idTriList tris(1024);
			idMatList mats(1024);

			if (!IsBModel(brush)) {
				numSurfaces += Brush_ToTris( brush, &tris, &mats, false, false );
			}

			// add the surfaces to the renderModel
			modelSurface_t	surf;
			for ( int i = 0 ; i < tris.Num() ; i++ ) {
				surf.geometry = tris[i];
				surf.shader = mats[i];
				worldModel->AddSurface( surf );
			}

			numBrushes++;
		}
	}

	// bound and clean the triangles
	worldModel->FinishSurfaces();

	// the worldEntity just has the handle for the worldModel
	memset( &worldEntity, 0, sizeof( worldEntity ) );
	worldEntity.hModel = worldModel;
	worldEntity.axis = mat3_default;
	worldEntity.shaderParms[0] = 1;
	worldEntity.shaderParms[1] = 1;
	worldEntity.shaderParms[2] = 1;
	worldEntity.shaderParms[3] = 1;

	worldModelDef = g_qeglobals.rw->AddEntityDef( &worldEntity );

	// create the light and model entities exactly the way the game code would
	for ( ent = entities.next ; ent != &entities ; ent = ent->next ) {
		if ( ent->brushes.onext == &ent->brushes ) {
			continue;
		}

		if (CullBrush(ent->brushes.onext, true)) {
			continue;
		}

		if (Map_IsBrushFiltered(ent->brushes.onext)) {
			continue;
		}

		BuildEntityRenderState( ent, false );
	}

	//common->Printf("Render data used %d brushes\n", numBrushes);
	worldDirty = false;
	UpdateCaption();
}

/*
===============================
CCamWnd::UpdateRenderEntities

  Creates a new entity state list
  returns true if a repaint is needed
===============================
*/
bool CCamWnd::UpdateRenderEntities() {

	if (rebuildMode) {
		return false;
	}

	bool ret = false;
	for ( entity_t *ent = entities.next ; ent != &entities ; ent = ent->next ) {
		BuildEntityRenderState( ent, (ent->lightDef != -1 || ent->modelDef != -1 || ent->soundEmitter ) ? true : false );
		if (ret == false && ent->modelDef || ent->lightDef) {
			ret = true;
		}
	}
	return ret;
}

/*
============================
CCamWnd::FreeRendererState

  Frees the render state data
============================
*/
void CCamWnd::FreeRendererState() {

	for ( entity_t *ent = entities.next ; ent != &entities ; ent = ent->next ) {
		if (ent->lightDef >= 0) {
			g_qeglobals.rw->FreeLightDef( ent->lightDef );
			ent->lightDef = -1;
		}

		if (ent->modelDef >= 0) {
			renderEntity_t *refent = const_cast<renderEntity_t *>(g_qeglobals.rw->GetRenderEntity( ent->modelDef ));
			if ( refent ) {
				if ( refent->callbackData ) {
					Mem_Free( refent->callbackData );
					refent->callbackData = NULL;
				}
				if ( refent->joints ) {
					Mem_Free16(refent->joints);
					refent->joints = NULL;
				}
			}
			g_qeglobals.rw->FreeEntityDef( ent->modelDef );
			ent->modelDef = -1;
		}
	}

	if ( worldModel ) {
		renderModelManager->FreeModel( worldModel );
		worldModel = NULL;
	}

}


/*
========================
CCamWnd::UpdateCaption

  updates the caption based on rendermode and whether the render mode needs updated
========================
*/
void CCamWnd::UpdateCaption() {

	idStr strCaption;

	if (worldDirty) {
		strCaption = "*";
	}
	// FIXME:
	strCaption += (renderMode) ? "RENDER" : "CAM";
	if (renderMode) {
		strCaption += (rebuildMode) ? " (Realtime)" : "";
		strCaption += (entityMode) ? " +lights" : "";
		strCaption += (selectMode) ? " +selected" : "";
		strCaption += (animationMode) ? " +anim" : "";
	}
	strCaption += (soundMode) ? " +snd" : "";
	SetWindowText(strCaption);
}

/*
===========================
CCamWnd::ToggleRenderMode

	Toggles the render mode
===========================
*/
void CCamWnd::ToggleRenderMode() {
	renderMode ^= 1;
	UpdateCaption();
}

/*
===========================
CCamWnd::ToggleRebuildMode

	Toggles the rebuild mode
===========================
*/
void CCamWnd::ToggleRebuildMode() {
	rebuildMode ^= 1;
	UpdateCaption();
}

/*
===========================
CCamWnd::ToggleEntityMode

	Toggles the entity mode
===========================
*/
void CCamWnd::ToggleEntityMode() {
	entityMode ^= 1;
	UpdateCaption();
}


/*
===========================
CCamWnd::ToggleRenderMode

	Toggles the render mode
===========================
*/
void CCamWnd::ToggleAnimationMode() {
	animationMode ^= 1;
	if (animationMode)  {
		SetTimer(0, 10, NULL);
	} else {
		KillTimer(0);
	}
	UpdateCaption();
}

/*
===========================
CCamWnd::ToggleSoundMode

	Toggles the sound mode
===========================
*/
void CCamWnd::ToggleSoundMode() {
	soundMode ^= 1;

	UpdateCaption();

	for ( entity_t *ent = entities.next ; ent != &entities ; ent = ent->next ) {
		Entity_UpdateSoundEmitter( ent );
	}
}

/*
===========================
CCamWnd::ToggleRenderMode

	Toggles the render mode
===========================
*/
void CCamWnd::ToggleSelectMode() {
	selectMode ^= 1;
	UpdateCaption();
}

/*
=========================
CCamWnd::MarkWorldDirty

  marks the render world as dirty
=========================
*/
void CCamWnd::MarkWorldDirty() {
	worldDirty = true;
	UpdateCaption();
}


/*
=========================
CCamWnd::DrawEntityData

  Draws entity data ( experimental )
=========================
*/
extern void glBox(idVec4 &color, idVec3 &point, float size);

void CCamWnd::DrawEntityData() {

	qglMatrixMode( GL_MODELVIEW );
	qglLoadIdentity();
	qglMatrixMode( GL_PROJECTION );
	qglLoadIdentity();

	SetProjectionMatrix();

	qglRotatef(-90, 1, 0, 0);	// put Z going up
	qglRotatef(90, 0, 0, 1);	// put Z going up
	qglRotatef(m_Camera.angles[0], 0, 1, 0);
	qglRotatef(-m_Camera.angles[1], 0, 0, 1);
	qglTranslatef(-m_Camera.origin[0], -m_Camera.origin[1], -m_Camera.origin[2]);

	Cam_BuildMatrix();

	if (!(entityMode || selectMode)) {
		return;
	}

	qglDisable(GL_BLEND);
	qglDisable(GL_DEPTH_TEST);
	qglPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	globalImages->BindNull();
	idVec3 color(0, 1, 0);
	qglColor3fv( color.ToFloatPtr() );

	brush_t *brushList = &active_brushes;
	int pass = 0;
	while (brushList) {
		for (brush_t *brush = brushList->next; brush != brushList; brush = brush->next) {

			if (CullBrush(brush, true)) {
				continue;
			}

			if (FilterBrush(brush)) {
				continue;
			}

			if ((pass == 1 && selectMode) || (entityMode && pass == 0 && brush->owner->lightDef >= 0)) {
				Brush_DrawXY(brush, 0, true, true);
			}

		}
		brushList = (brushList == &active_brushes) ? &selected_brushes : NULL;
		color.x = 1;
		color.y = 0;
		pass++;
		qglColor3fv( color.ToFloatPtr() );
	}

}


/*
 =======================================================================================================================
	Cam_Render

	This used the renderSystem to draw a fully lit view of the world
 =======================================================================================================================
 */
void CCamWnd::Cam_Render() {

	renderView_t	refdef;
	CPaintDC	dc(this);	// device context for painting


	if (!active_brushes.next) {
		return;					// not valid yet
	}

	// DG: from SteelStorm2
	// Jmarshal23 recommended to disable this to fix lighting render in the Cam window
	/* if (!qwglMakeCurrent(dc.m_hDC, win32.hGLRC)) {
		common->Printf("ERROR: wglMakeCurrent failed..\n ");
		common->Printf("Please restart " EDITOR_WINDOWTEXT " if the camera view is not working\n");
		return;
	} */

	// save the editor state
	//qglPushAttrib( GL_ALL_ATTRIB_BITS );
	qglClearColor( 0.1f, 0.1f, 0.1f, 0.0f );
	qglScissor( 0, 0, m_Camera.width, m_Camera.height );
	qglClear( GL_COLOR_BUFFER_BIT );

	//	qwglSwapBuffers(dc.m_hDC);

	// create the model, using explicit normals
	if ( rebuildMode && worldDirty ) {
		BuildRendererState();
	}

	// render it
	renderSystem->BeginFrame( m_Camera.width, m_Camera.height );

	memset( &refdef, 0, sizeof( refdef ) );
	refdef.vieworg = m_Camera.origin;

	// the editor uses opposite pitch convention
	refdef.viewaxis = idAngles( -m_Camera.angles.pitch, m_Camera.angles.yaw, m_Camera.angles.roll ).ToMat3();

	refdef.width = SCREEN_WIDTH;
	refdef.height = SCREEN_HEIGHT;
	refdef.fov_x = 90;
	refdef.fov_y = 2 * atan((float)m_Camera.height / m_Camera.width) * idMath::M_RAD2DEG;

	// only set in animation mode to give a consistent look
	if (animationMode) {
		refdef.time = eventLoop->Milliseconds();
	}

	g_qeglobals.rw->RenderScene( &refdef );

	int	frontEnd, backEnd;

	renderSystem->EndFrame( &frontEnd, &backEnd );
//common->Printf( "front:%i back:%i\n", frontEnd, backEnd );

	//qglPopAttrib();
	//DrawEntityData();

	//qwglSwapBuffers(dc.m_hDC);
	// get back to the editor state
	qglMatrixMode( GL_MODELVIEW );
	qglLoadIdentity();
	Cam_BuildMatrix();
}


void CCamWnd::OnTimer(UINT_PTR nIDEvent)
{
	if (animationMode || nIDEvent == 1) {
		Sys_UpdateWindows(W_CAMERA);
	}
	if (nIDEvent == 1) {
		KillTimer(1);
	}

	if (!animationMode ) {
		KillTimer(0);
	}
}


void CCamWnd::UpdateCameraView() {
	if (QE_SingleBrush(true, true)) {
		brush_t *b = selected_brushes.next;
		if (b->owner->eclass->nShowFlags & ECLASS_CAMERAVIEW) {
			// find the entity that targets this
			const char *name = ValueForKey(b->owner, "name");
			entity_t *ent = FindEntity("target", name);
			if (ent) {
				if (!saveValid) {
					saveOrg = m_Camera.origin;
					saveAng = m_Camera.angles;
					saveValid = true;
				}
				idVec3 v = b->owner->origin - ent->origin;
				v.Normalize();
				idAngles ang = v.ToMat3().ToAngles();
				ang.pitch = -ang.pitch;
				ang.roll = 0.0f;
				SetView( ent->origin, ang );
				Cam_BuildMatrix();
				Sys_UpdateWindows( W_CAMERA );
				return;
			}
		}
	}
	if (saveValid) {
		SetView(saveOrg, saveAng);
		Cam_BuildMatrix();
		Sys_UpdateWindows(W_CAMERA);
		saveValid = false;
	}
}
