mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-15 15:51:07 +02:00
2679 lines
71 KiB
C++
2679 lines
71 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
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 "precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#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
|
|
|
|
#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;
|
|
worldModelDef = -1;
|
|
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()
|
|
/*
|
|
=======================================================================================================================
|
|
=======================================================================================================================
|
|
*/
|
|
INT_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
|
|
bool bPaint = true;
|
|
|
|
UpdateCaption();
|
|
|
|
idGraphicsDeviceContextHelper context(dc.m_hDC, hglrc);
|
|
|
|
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();
|
|
}
|
|
|
|
/*
|
|
=======================================================================================================================
|
|
=======================================================================================================================
|
|
*/
|
|
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);
|
|
|
|
if ((hglrc = (HGLRC)wglCreateContext(hDC)) == 0)
|
|
Error("wglCreateContext failed");
|
|
|
|
wglMakeCurrent(hDC, hglrc);
|
|
|
|
if ((g_qeglobals.d_font_list = glGenLists(256)) == 0) {
|
|
common->Warning("couldn't create font dlists");
|
|
}
|
|
|
|
// create the bitmap display lists we're making images of glyphs 0 thru 255
|
|
if (!wglUseFontBitmaps(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 (!wglUseFontBitmaps(hDC, 0, 255, g_qeglobals.d_font_list)) {
|
|
common->Warning("wglUseFontBitmaps failed again (%d). Trying outlines.", GetLastError());
|
|
|
|
// if (!wglUseFontOutlines(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
|
|
glListBase(g_qeglobals.d_font_list);
|
|
|
|
// report OpenGL information
|
|
common->Printf("GL_VENDOR: %s\n", glGetString(GL_VENDOR));
|
|
common->Printf("GL_RENDERER: %s\n", glGetString(GL_RENDERER));
|
|
common->Printf("GL_VERSION: %s\n", glGetString(GL_VERSION));
|
|
common->Printf("GL_EXTENSIONS: %s\n", glGetString(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];
|
|
|
|
glGetFloatv(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(¤t);
|
|
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 (int 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);
|
|
glEnable(GL_BLEND);
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glDisable(GL_BLEND);
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
=======================================================================================================================
|
|
=======================================================================================================================
|
|
*/
|
|
void setGLMode(int mode) {
|
|
switch (mode)
|
|
{
|
|
case cd_wire:
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
|
|
globalImages->BindNull();
|
|
glDisable(GL_BLEND);
|
|
glDisable(GL_DEPTH_TEST);
|
|
glColor3f(1.0f, 1.0f, 1.0f);
|
|
break;
|
|
|
|
case cd_solid:
|
|
glCullFace(GL_FRONT);
|
|
glEnable(GL_CULL_FACE);
|
|
glShadeModel(GL_FLAT);
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
|
globalImages->BindNull();
|
|
glDisable(GL_BLEND);
|
|
glEnable(GL_DEPTH_TEST);
|
|
glDepthFunc(GL_LEQUAL);
|
|
break;
|
|
|
|
case cd_texture:
|
|
glCullFace(GL_FRONT);
|
|
glEnable(GL_CULL_FACE);
|
|
glShadeModel(GL_FLAT);
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
|
glDisable(GL_BLEND);
|
|
glEnable(GL_DEPTH_TEST);
|
|
glDepthFunc(GL_LEQUAL);
|
|
break;
|
|
|
|
case cd_blend:
|
|
glCullFace(GL_FRONT);
|
|
glEnable(GL_CULL_FACE);
|
|
glShadeModel(GL_FLAT);
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
|
glDisable(GL_DEPTH_TEST);
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(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");
|
|
glBegin(GL_LINE_STRIP);
|
|
glVertex3fv(points[0].ToFloatPtr());
|
|
glVertex3fv(points[1].ToFloatPtr());
|
|
glVertex3fv(points[2].ToFloatPtr());
|
|
glVertex3fv(points[3].ToFloatPtr());
|
|
glEnd();
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=======================================================================================================================
|
|
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;
|
|
glLoadIdentity();
|
|
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;
|
|
|
|
glLoadMatrixf(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();
|
|
}
|
|
|
|
|
|
glViewport(0, 0, m_Camera.width, m_Camera.height);
|
|
glScissor(0, 0, m_Camera.width, m_Camera.height);
|
|
glClearColor(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) {
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
}
|
|
|
|
glDisable(GL_LIGHTING);
|
|
glMatrixMode(GL_PROJECTION);
|
|
|
|
SetProjectionMatrix();
|
|
|
|
glRotatef(-90, 1, 0, 0); // put Z going up
|
|
glRotatef(90, 0, 0, 1); // put Z going up
|
|
glRotatef(m_Camera.angles[0], 0, 1, 0);
|
|
glRotatef(-m_Camera.angles[1], 0, 0, 1);
|
|
glTranslatef(-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);
|
|
}
|
|
|
|
|
|
//glDepthMask ( 1 ); // Ok, write now
|
|
glMatrixMode(GL_PROJECTION);
|
|
|
|
glTranslatef(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);
|
|
glDisable(GL_LIGHTING);
|
|
glColor4f(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);
|
|
glEnable(GL_BLEND);
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
|
glBlendFunc(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
|
|
glEnable(GL_BLEND);
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
|
glColor4f(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 (int i = 0; i < nCount; i++) {
|
|
face_t* selFace = reinterpret_cast <face_t*>(g_ptrSelectedFaces.GetAt(i));
|
|
Face_Draw(selFace);
|
|
DrawAxial(selFace);
|
|
}
|
|
}
|
|
|
|
// non-zbuffered outline
|
|
glDisable(GL_BLEND);
|
|
glDisable(GL_DEPTH_TEST);
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
|
|
|
|
if (renderMode) {
|
|
glColor3f(1, 0, 0);
|
|
for (int i = 0; i < nCount; i++) {
|
|
face_t* selFace = reinterpret_cast <face_t*>(g_ptrSelectedFaces.GetAt(i));
|
|
Face_Draw(selFace);
|
|
}
|
|
}
|
|
|
|
glColor3f(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) {
|
|
glPointSize(4);
|
|
glColor3f(0, 1, 0);
|
|
glBegin(GL_POINTS);
|
|
for (i = 0; i < g_qeglobals.d_numpoints; i++) {
|
|
glVertex3fv(g_qeglobals.d_points[i].ToFloatPtr());
|
|
}
|
|
|
|
glEnd();
|
|
glPointSize(1);
|
|
}
|
|
else if (g_qeglobals.d_select_mode == sel_edge) {
|
|
float* v1, * v2;
|
|
|
|
glPointSize(4);
|
|
glColor3f(0, 0, 1);
|
|
glBegin(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();
|
|
glVertex3f((v1[0] + v2[0]) * 0.5f, (v1[1] + v2[1]) * 0.5f, (v1[2] + v2[2]) * 0.5f);
|
|
}
|
|
|
|
glEnd();
|
|
glPointSize(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
|
|
glEnable(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();
|
|
|
|
glFinish();
|
|
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);
|
|
|
|
|
|
/*
|
|
================
|
|
Incremental editor render state
|
|
|
|
The old editor render path rebuilt one giant EditorWorldModel and reinitialized
|
|
the renderWorld whenever worldDirty was set. That is especially expensive for
|
|
ray traced backends because every edit invalidates the entire acceleration
|
|
structure. The editor now keeps one render entity/model per world brush and
|
|
small caches for editor entities. BuildRendererState() is therefore a
|
|
reconcile pass:
|
|
- add render defs for new brushes/entities,
|
|
- update only transforms for moved brushes,
|
|
- rebuild only the model whose geometry/material hash changed,
|
|
- free only defs/models that disappeared or became filtered.
|
|
================
|
|
*/
|
|
|
|
#define EDITOR_RENDER_HASH_INIT 2166136261u
|
|
#define EDITOR_RENDER_HASH_MUL 16777619u
|
|
|
|
struct editorRenderWorldBrushState_t {
|
|
brush_t* brush;
|
|
idRenderModel* model;
|
|
int modelDef;
|
|
unsigned int geometryHash;
|
|
idVec3 origin;
|
|
bool touched;
|
|
};
|
|
|
|
struct editorRenderEntityState_t {
|
|
entity_t* ent;
|
|
int modelDef;
|
|
int lightDef;
|
|
bool touched;
|
|
};
|
|
|
|
struct editorRenderBModelState_t {
|
|
entity_t* ent;
|
|
idRenderModel* model;
|
|
idStr modelName;
|
|
unsigned int geometryHash;
|
|
bool touched;
|
|
};
|
|
|
|
static bool s_editorRenderWorldInitialized = false;
|
|
static idList<editorRenderWorldBrushState_t> s_editorWorldBrushStates;
|
|
static idList<editorRenderEntityState_t> s_editorEntityStates;
|
|
static idList<editorRenderBModelState_t> s_editorBModelStates;
|
|
|
|
static unsigned int EditorHashBytes(unsigned int hash, const void* data, int numBytes) {
|
|
const unsigned char* bytes = reinterpret_cast<const unsigned char*>(data);
|
|
for (int i = 0; i < numBytes; i++) {
|
|
hash ^= bytes[i];
|
|
hash *= EDITOR_RENDER_HASH_MUL;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
static unsigned int EditorHashInt(unsigned int hash, int value) {
|
|
return EditorHashBytes(hash, &value, sizeof(value));
|
|
}
|
|
|
|
static int EditorQuantizeFloatForHash(float value) {
|
|
const float scale = 10000.0f;
|
|
return (int)(value * scale + (value >= 0.0f ? 0.5f : -0.5f));
|
|
}
|
|
|
|
static unsigned int EditorHashFloat(unsigned int hash, float value) {
|
|
const int quantized = EditorQuantizeFloatForHash(value);
|
|
return EditorHashInt(hash, quantized);
|
|
}
|
|
|
|
static unsigned int EditorHashString(unsigned int hash, const char* value) {
|
|
if (value == NULL) {
|
|
value = "";
|
|
}
|
|
while (*value) {
|
|
hash ^= static_cast<unsigned char>(*value++);
|
|
hash *= EDITOR_RENDER_HASH_MUL;
|
|
}
|
|
hash ^= 0;
|
|
hash *= EDITOR_RENDER_HASH_MUL;
|
|
return hash;
|
|
}
|
|
|
|
static unsigned int EditorHashMaterial(unsigned int hash, const idMaterial* material) {
|
|
return EditorHashString(hash, material ? material->GetName() : "");
|
|
}
|
|
|
|
static unsigned int EditorHashVec3(unsigned int hash, const idVec3& value) {
|
|
hash = EditorHashFloat(hash, value.x);
|
|
hash = EditorHashFloat(hash, value.y);
|
|
hash = EditorHashFloat(hash, value.z);
|
|
return hash;
|
|
}
|
|
|
|
static bool EditorVec3Changed(const idVec3& a, const idVec3& b) {
|
|
const float epsilon = 0.001f;
|
|
return idMath::Fabs(a.x - b.x) > epsilon ||
|
|
idMath::Fabs(a.y - b.y) > epsilon ||
|
|
idMath::Fabs(a.z - b.z) > epsilon;
|
|
}
|
|
|
|
static void EditorBrushRenderOrigin(brush_t* brush, idVec3& origin) {
|
|
origin[0] = (brush->mins[0] + brush->maxs[0]) * 0.5f;
|
|
origin[1] = (brush->mins[1] + brush->maxs[1]) * 0.5f;
|
|
origin[2] = (brush->mins[2] + brush->maxs[2]) * 0.5f;
|
|
}
|
|
|
|
static unsigned int EditorHashBrushGeometry(brush_t* brush, const idVec3& origin) {
|
|
unsigned int hash = EDITOR_RENDER_HASH_INIT;
|
|
|
|
hash = EditorHashInt(hash, brush->pPatch ? 1 : 0);
|
|
hash = EditorHashInt(hash, brush->modelHandle > 0 ? 1 : 0);
|
|
hash = EditorHashInt(hash, brush->entityModel ? 1 : 0);
|
|
|
|
if (brush->pPatch) {
|
|
patchMesh_t* pm = brush->pPatch;
|
|
|
|
hash = EditorHashInt(hash, pm->width);
|
|
hash = EditorHashInt(hash, pm->height);
|
|
hash = EditorHashInt(hash, pm->explicitSubdivisions ? 1 : 0);
|
|
hash = EditorHashInt(hash, pm->horzSubdivisions);
|
|
hash = EditorHashInt(hash, pm->vertSubdivisions);
|
|
hash = EditorHashMaterial(hash, pm->d_texture);
|
|
|
|
for (int i = 0; i < pm->width; i++) {
|
|
for (int j = 0; j < pm->height; j++) {
|
|
hash = EditorHashVec3(hash, pm->ctrl(i, j).xyz - origin);
|
|
hash = EditorHashFloat(hash, pm->ctrl(i, j).st.x);
|
|
hash = EditorHashFloat(hash, pm->ctrl(i, j).st.y);
|
|
}
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
for (face_t* face = brush->brush_faces; face; face = face->next) {
|
|
idWinding* w = face->face_winding;
|
|
if (!w) {
|
|
continue;
|
|
}
|
|
|
|
hash = EditorHashMaterial(hash, face->d_texture);
|
|
hash = EditorHashVec3(hash, face->plane.Normal());
|
|
hash = EditorHashInt(hash, w->GetNumPoints());
|
|
|
|
for (int i = 0; i < w->GetNumPoints(); i++) {
|
|
hash = EditorHashFloat(hash, (*w)[i][0] - origin[0]);
|
|
hash = EditorHashFloat(hash, (*w)[i][1] - origin[1]);
|
|
hash = EditorHashFloat(hash, (*w)[i][2] - origin[2]);
|
|
hash = EditorHashFloat(hash, (*w)[i][3]);
|
|
hash = EditorHashFloat(hash, (*w)[i][4]);
|
|
}
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
|
|
static unsigned int EditorHashBModelGeometry(entity_t* ent) {
|
|
unsigned int hash = EDITOR_RENDER_HASH_INIT;
|
|
hash = EditorHashString(hash, ValueForKey(ent, "name"));
|
|
|
|
for (brush_t* brush = ent->brushes.onext; brush != &ent->brushes; brush = brush->onext) {
|
|
const unsigned int brushHash = EditorHashBrushGeometry(brush, ent->origin);
|
|
hash = EditorHashInt(hash, brushHash);
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
|
|
static void EditorInitRenderEntityForModel(renderEntity_t& refent, idRenderModel* model, const idVec3& origin) {
|
|
memset(&refent, 0, sizeof(refent));
|
|
refent.hModel = model;
|
|
refent.origin = origin;
|
|
refent.axis = mat3_default;
|
|
refent.shaderParms[0] = 1;
|
|
refent.shaderParms[1] = 1;
|
|
refent.shaderParms[2] = 1;
|
|
refent.shaderParms[3] = 1;
|
|
}
|
|
|
|
static void EditorLocalizeTriSurfaces(idTriList& tris, const idVec3& origin) {
|
|
for (int i = 0; i < tris.Num(); i++) {
|
|
srfTriangles_t* tri = tris[i];
|
|
for (int j = 0; j < tri->numVerts; j++) {
|
|
tri->verts[j].xyz -= origin;
|
|
}
|
|
}
|
|
}
|
|
|
|
static idRenderModel* EditorBuildSingleBrushModel(brush_t* brush, const idVec3& origin) {
|
|
idTriList tris(1024);
|
|
idMatList mats(1024);
|
|
|
|
if (Brush_ToTris(brush, &tris, &mats, false, false) <= 0 || tris.Num() <= 0) {
|
|
return NULL;
|
|
}
|
|
|
|
EditorLocalizeTriSurfaces(tris, origin);
|
|
|
|
idRenderModel* model = renderModelManager->AllocModel();
|
|
model->InitEmpty(va("EditorBrushModel_%p", brush));
|
|
|
|
modelSurface_t surf;
|
|
for (int i = 0; i < tris.Num(); i++) {
|
|
surf.geometry = tris[i];
|
|
surf.shader = mats[i];
|
|
model->AddSurface(surf);
|
|
}
|
|
|
|
model->FinishSurfaces();
|
|
return model;
|
|
}
|
|
|
|
static idRenderModel* EditorBuildBModel(entity_t* ent, const char* name) {
|
|
idTriList tris(1024);
|
|
idMatList mats(1024);
|
|
|
|
for (brush_t* brush = ent->brushes.onext; brush != &ent->brushes; brush = brush->onext) {
|
|
Brush_ToTris(brush, &tris, &mats, false, true);
|
|
}
|
|
|
|
if (tris.Num() <= 0) {
|
|
return NULL;
|
|
}
|
|
|
|
idRenderModel* model = renderModelManager->AllocModel();
|
|
model->InitEmpty(name);
|
|
|
|
modelSurface_t surf;
|
|
for (int i = 0; i < tris.Num(); i++) {
|
|
surf.geometry = tris[i];
|
|
surf.shader = mats[i];
|
|
model->AddSurface(surf);
|
|
}
|
|
|
|
model->FinishSurfaces();
|
|
return model;
|
|
}
|
|
|
|
static void EditorFreeRenderEntityAuxData(int modelDef) {
|
|
if (modelDef < 0) {
|
|
return;
|
|
}
|
|
|
|
renderEntity_t* refent = const_cast<renderEntity_t*>(g_qeglobals.rw->GetRenderEntity(modelDef));
|
|
if (!refent) {
|
|
return;
|
|
}
|
|
|
|
if (refent->callbackData) {
|
|
Mem_Free(refent->callbackData);
|
|
refent->callbackData = NULL;
|
|
}
|
|
|
|
if (refent->joints) {
|
|
Mem_Free16(refent->joints);
|
|
refent->joints = NULL;
|
|
}
|
|
}
|
|
|
|
static void EditorFreeRenderEntityDef(int& modelDef) {
|
|
if (modelDef < 0) {
|
|
return;
|
|
}
|
|
EditorFreeRenderEntityAuxData(modelDef);
|
|
g_qeglobals.rw->FreeEntityDef(modelDef);
|
|
modelDef = -1;
|
|
}
|
|
|
|
static void EditorFreeRenderLightDef(int& lightDef) {
|
|
if (lightDef < 0) {
|
|
return;
|
|
}
|
|
g_qeglobals.rw->FreeLightDef(lightDef);
|
|
lightDef = -1;
|
|
}
|
|
|
|
static void EditorUpdateOrAddEntityDef(int& modelDef, const renderEntity_t* refent) {
|
|
if (modelDef >= 0) {
|
|
EditorFreeRenderEntityAuxData(modelDef);
|
|
g_qeglobals.rw->UpdateEntityDef(modelDef, refent);
|
|
}
|
|
else {
|
|
modelDef = g_qeglobals.rw->AddEntityDef(refent);
|
|
}
|
|
}
|
|
|
|
static int EditorFindWorldBrushState(brush_t* brush) {
|
|
for (int i = 0; i < s_editorWorldBrushStates.Num(); i++) {
|
|
if (s_editorWorldBrushStates[i].brush == brush) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void EditorFreeWorldBrushState(int index) {
|
|
editorRenderWorldBrushState_t& state = s_editorWorldBrushStates[index];
|
|
|
|
EditorFreeRenderEntityDef(state.modelDef);
|
|
|
|
if (state.model) {
|
|
renderModelManager->FreeModel(state.model);
|
|
state.model = NULL;
|
|
}
|
|
|
|
s_editorWorldBrushStates.RemoveIndex(index);
|
|
}
|
|
|
|
static bool EditorWorldBrushRenderable(CCamWnd* cam, brush_t* brush) {
|
|
if (brush->hiddenBrush) {
|
|
return false;
|
|
}
|
|
if (FilterBrush(brush)) {
|
|
return false;
|
|
}
|
|
if (cam->CullBrush(brush, true)) {
|
|
return false;
|
|
}
|
|
if (IsBModel(brush)) {
|
|
return false;
|
|
}
|
|
if (brush->modelHandle > 0) {
|
|
return false;
|
|
}
|
|
if (brush->owner->eclass->fixedsize && !brush->entityModel) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void EditorReconcileWorldBrushState(CCamWnd* cam, brush_t* brush) {
|
|
if (!EditorWorldBrushRenderable(cam, brush)) {
|
|
return;
|
|
}
|
|
|
|
idVec3 origin;
|
|
EditorBrushRenderOrigin(brush, origin);
|
|
|
|
const unsigned int geometryHash = EditorHashBrushGeometry(brush, origin);
|
|
int index = EditorFindWorldBrushState(brush);
|
|
|
|
if (index < 0) {
|
|
editorRenderWorldBrushState_t newState;
|
|
newState.brush = brush;
|
|
newState.model = NULL;
|
|
newState.modelDef = -1;
|
|
newState.geometryHash = 0;
|
|
newState.origin = origin;
|
|
newState.touched = true;
|
|
s_editorWorldBrushStates.Append(newState);
|
|
index = s_editorWorldBrushStates.Num() - 1;
|
|
}
|
|
|
|
editorRenderWorldBrushState_t& state = s_editorWorldBrushStates[index];
|
|
state.touched = true;
|
|
|
|
const bool geometryChanged = (state.model == NULL || state.geometryHash != geometryHash);
|
|
|
|
if (geometryChanged) {
|
|
idRenderModel* newModel = EditorBuildSingleBrushModel(brush, origin);
|
|
if (!newModel) {
|
|
EditorFreeWorldBrushState(index);
|
|
return;
|
|
}
|
|
|
|
idRenderModel* oldModel = state.model;
|
|
state.model = newModel;
|
|
state.geometryHash = geometryHash;
|
|
|
|
renderEntity_t refent;
|
|
EditorInitRenderEntityForModel(refent, state.model, origin);
|
|
EditorUpdateOrAddEntityDef(state.modelDef, &refent);
|
|
|
|
if (oldModel) {
|
|
renderModelManager->FreeModel(oldModel);
|
|
}
|
|
|
|
state.origin = origin;
|
|
return;
|
|
}
|
|
|
|
if (EditorVec3Changed(state.origin, origin)) {
|
|
renderEntity_t refent;
|
|
EditorInitRenderEntityForModel(refent, state.model, origin);
|
|
EditorUpdateOrAddEntityDef(state.modelDef, &refent);
|
|
state.origin = origin;
|
|
}
|
|
}
|
|
|
|
static void EditorBeginWorldBrushReconcile() {
|
|
for (int i = 0; i < s_editorWorldBrushStates.Num(); i++) {
|
|
s_editorWorldBrushStates[i].touched = false;
|
|
}
|
|
}
|
|
|
|
static void EditorPurgeUntouchedWorldBrushStates() {
|
|
for (int i = s_editorWorldBrushStates.Num() - 1; i >= 0; i--) {
|
|
if (!s_editorWorldBrushStates[i].touched) {
|
|
EditorFreeWorldBrushState(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int EditorFindEntityState(entity_t* ent) {
|
|
for (int i = 0; i < s_editorEntityStates.Num(); i++) {
|
|
if (s_editorEntityStates[i].ent == ent) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static editorRenderEntityState_t* EditorTouchEntityState(entity_t* ent) {
|
|
int index = EditorFindEntityState(ent);
|
|
|
|
if (index < 0) {
|
|
editorRenderEntityState_t newState;
|
|
newState.ent = ent;
|
|
newState.modelDef = ent->modelDef;
|
|
newState.lightDef = ent->lightDef;
|
|
newState.touched = true;
|
|
s_editorEntityStates.Append(newState);
|
|
index = s_editorEntityStates.Num() - 1;
|
|
}
|
|
|
|
editorRenderEntityState_t& state = s_editorEntityStates[index];
|
|
state.touched = true;
|
|
|
|
if (state.modelDef < 0 && ent->modelDef >= 0) {
|
|
state.modelDef = ent->modelDef;
|
|
}
|
|
if (state.lightDef < 0 && ent->lightDef >= 0) {
|
|
state.lightDef = ent->lightDef;
|
|
}
|
|
|
|
ent->modelDef = state.modelDef;
|
|
ent->lightDef = state.lightDef;
|
|
|
|
return &state;
|
|
}
|
|
|
|
static void EditorFreeEntityModelDef(editorRenderEntityState_t* state, entity_t* ent) {
|
|
EditorFreeRenderEntityDef(state->modelDef);
|
|
if (ent) {
|
|
ent->modelDef = state->modelDef;
|
|
}
|
|
}
|
|
|
|
static void EditorFreeEntityLightDef(editorRenderEntityState_t* state, entity_t* ent) {
|
|
EditorFreeRenderLightDef(state->lightDef);
|
|
if (ent) {
|
|
ent->lightDef = state->lightDef;
|
|
}
|
|
}
|
|
|
|
static void EditorUpdateOrAddEntityModelDef(editorRenderEntityState_t* state, entity_t* ent, const renderEntity_t* refent) {
|
|
EditorUpdateOrAddEntityDef(state->modelDef, refent);
|
|
ent->modelDef = state->modelDef;
|
|
}
|
|
|
|
static void EditorUpdateOrAddEntityLightDef(editorRenderEntityState_t* state, entity_t* ent, const renderLight_t* lightParms) {
|
|
if (state->lightDef >= 0) {
|
|
g_qeglobals.rw->UpdateLightDef(state->lightDef, lightParms);
|
|
}
|
|
else {
|
|
state->lightDef = g_qeglobals.rw->AddLightDef(lightParms);
|
|
}
|
|
ent->lightDef = state->lightDef;
|
|
}
|
|
|
|
static void EditorBeginEntityReconcile() {
|
|
for (int i = 0; i < s_editorEntityStates.Num(); i++) {
|
|
s_editorEntityStates[i].touched = false;
|
|
}
|
|
for (int i = 0; i < s_editorBModelStates.Num(); i++) {
|
|
s_editorBModelStates[i].touched = false;
|
|
}
|
|
}
|
|
|
|
static void EditorPurgeUntouchedEntityStates() {
|
|
for (int i = s_editorEntityStates.Num() - 1; i >= 0; i--) {
|
|
if (s_editorEntityStates[i].touched) {
|
|
continue;
|
|
}
|
|
|
|
EditorFreeRenderEntityDef(s_editorEntityStates[i].modelDef);
|
|
EditorFreeRenderLightDef(s_editorEntityStates[i].lightDef);
|
|
s_editorEntityStates.RemoveIndex(i);
|
|
}
|
|
}
|
|
|
|
static int EditorFindBModelState(entity_t* ent) {
|
|
for (int i = 0; i < s_editorBModelStates.Num(); i++) {
|
|
if (s_editorBModelStates[i].ent == ent) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static editorRenderBModelState_t* EditorTouchBModelState(entity_t* ent) {
|
|
int index = EditorFindBModelState(ent);
|
|
|
|
if (index < 0) {
|
|
editorRenderBModelState_t newState;
|
|
newState.ent = ent;
|
|
newState.model = NULL;
|
|
newState.modelName = "";
|
|
newState.geometryHash = 0;
|
|
newState.touched = true;
|
|
s_editorBModelStates.Append(newState);
|
|
index = s_editorBModelStates.Num() - 1;
|
|
}
|
|
|
|
s_editorBModelStates[index].touched = true;
|
|
return &s_editorBModelStates[index];
|
|
}
|
|
|
|
static void EditorFreeBModelState(int index) {
|
|
editorRenderBModelState_t& state = s_editorBModelStates[index];
|
|
|
|
if (state.model) {
|
|
renderModelManager->RemoveModel(state.model);
|
|
renderModelManager->FreeModel(state.model);
|
|
state.model = NULL;
|
|
}
|
|
|
|
s_editorBModelStates.RemoveIndex(index);
|
|
}
|
|
|
|
static void EditorFreeBModelStateForEntity(entity_t* ent) {
|
|
const int index = EditorFindBModelState(ent);
|
|
if (index >= 0) {
|
|
EditorFreeBModelState(index);
|
|
}
|
|
}
|
|
|
|
static void EditorPurgeUntouchedBModelStates() {
|
|
for (int i = s_editorBModelStates.Num() - 1; i >= 0; i--) {
|
|
if (!s_editorBModelStates[i].touched) {
|
|
EditorFreeBModelState(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CCamWnd::BuildEntityRenderState(entity_t* ent, bool update) {
|
|
const char* v;
|
|
idDict spawnArgs;
|
|
const char* name = NULL;
|
|
editorRenderEntityState_t* entityState = EditorTouchEntityState(ent);
|
|
|
|
// The old code used update=false as "tear down and recreate". The
|
|
// incremental path keeps the handles and uses UpdateEntityDef /
|
|
// UpdateLightDef when possible.
|
|
(void)update;
|
|
|
|
Entity_UpdateSoundEmitter(ent);
|
|
|
|
// If the entity is no longer renderable, remove only this entity's defs.
|
|
if (ent->brushes.onext == &ent->brushes ||
|
|
FilterBrush(ent->brushes.onext) ||
|
|
CullBrush(ent->brushes.onext, true) ||
|
|
Map_IsBrushFiltered(ent->brushes.onext)) {
|
|
EditorFreeEntityModelDef(entityState, ent);
|
|
EditorFreeEntityLightDef(entityState, ent);
|
|
EditorFreeBModelStateForEntity(ent);
|
|
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;
|
|
memset(&refent, 0, sizeof(refent));
|
|
|
|
if (!stricmp(name, v)) {
|
|
// Brush model entity. Rebuild the renderModel only when the
|
|
// entity-local brush geometry/materials changed. Plain movement
|
|
// is handled by UpdateEntityDef below.
|
|
const unsigned int geometryHash = EditorHashBModelGeometry(ent);
|
|
editorRenderBModelState_t* bmodelState = EditorTouchBModelState(ent);
|
|
idRenderModel* oldModel = NULL;
|
|
|
|
const bool geometryChanged =
|
|
(bmodelState->model == NULL ||
|
|
bmodelState->geometryHash != geometryHash ||
|
|
idStr::Icmp(bmodelState->modelName.c_str(), name) != 0);
|
|
|
|
if (geometryChanged) {
|
|
idRenderModel* newModel = EditorBuildBModel(ent, name);
|
|
if (newModel) {
|
|
oldModel = bmodelState->model;
|
|
bmodelState->model = newModel;
|
|
bmodelState->modelName = name;
|
|
bmodelState->geometryHash = geometryHash;
|
|
}
|
|
else {
|
|
EditorFreeEntityModelDef(entityState, ent);
|
|
EditorFreeBModelStateForEntity(ent);
|
|
bmodelState = NULL;
|
|
}
|
|
}
|
|
|
|
if (bmodelState && bmodelState->model) {
|
|
gameEdit->ParseSpawnArgsToRenderEntity(&spawnArgs, &refent);
|
|
refent.hModel = bmodelState->model;
|
|
refent.referenceSound = ent->soundEmitter;
|
|
EditorUpdateOrAddEntityModelDef(entityState, ent, &refent);
|
|
|
|
if (geometryChanged) {
|
|
if (oldModel) {
|
|
renderModelManager->RemoveModel(oldModel);
|
|
renderModelManager->FreeModel(oldModel);
|
|
}
|
|
renderModelManager->AddModel(bmodelState->model);
|
|
}
|
|
}
|
|
|
|
}
|
|
else {
|
|
// use the game's epair parsing code so
|
|
// we can use the same renderEntity generation
|
|
EditorFreeBModelStateForEntity(ent);
|
|
|
|
gameEdit->ParseSpawnArgsToRenderEntity(&spawnArgs, &refent);
|
|
refent.referenceSound = ent->soundEmitter;
|
|
|
|
idRenderModelMD5* md5 = dynamic_cast<idRenderModelMD5*>(refent.hModel);
|
|
if (md5) {
|
|
idStr str;
|
|
spawnArgs.GetString("anim", "idle", str);
|
|
refent.numJoints = md5->NumJoints();
|
|
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);
|
|
}
|
|
|
|
EditorUpdateOrAddEntityModelDef(entityState, ent, &refent);
|
|
}
|
|
}
|
|
else {
|
|
EditorFreeEntityModelDef(entityState, ent);
|
|
EditorFreeBModelStateForEntity(ent);
|
|
}
|
|
|
|
// check for lightdefs
|
|
if (!(ent->eclass->nShowFlags & ECLASS_LIGHT) || spawnArgs.GetBool("start_off")) {
|
|
EditorFreeEntityLightDef(entityState, ent);
|
|
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;
|
|
|
|
EditorUpdateOrAddEntityLightDef(entityState, ent, &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("");
|
|
int matCount = 0;
|
|
//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 (int 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, c;
|
|
//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() {
|
|
CFileDialog dlgFile(FALSE, "lwo, ase", NULL, 0, "(*.lwo)|*.lwo|(*.ase)|*.ase|(*.ma)|*.ma||", g_pParentWnd);
|
|
|
|
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() {
|
|
entity_t* ent;
|
|
brush_t* brush;
|
|
|
|
if (!s_editorRenderWorldInitialized) {
|
|
// First build, or after FreeRendererState(). Do not do this on every
|
|
// dirty frame; it drops all defs from the renderWorld.
|
|
g_qeglobals.rw->InitFromMap(NULL);
|
|
s_editorRenderWorldInitialized = true;
|
|
}
|
|
|
|
// Compatibility cleanup for any old monolithic world model that may still
|
|
// exist from a previous build path.
|
|
if (worldModelDef >= 0) {
|
|
g_qeglobals.rw->FreeEntityDef(worldModelDef);
|
|
worldModelDef = -1;
|
|
}
|
|
|
|
if (worldModel) {
|
|
renderModelManager->FreeModel(worldModel);
|
|
worldModel = NULL;
|
|
}
|
|
|
|
EditorBeginWorldBrushReconcile();
|
|
|
|
for (brush_t* brushList = &active_brushes; brushList;
|
|
brushList = (brushList == &active_brushes) ? &selected_brushes : NULL) {
|
|
|
|
for (brush = brushList->next; brush != brushList; brush = brush->next) {
|
|
EditorReconcileWorldBrushState(this, brush);
|
|
}
|
|
}
|
|
|
|
EditorPurgeUntouchedWorldBrushStates();
|
|
|
|
// Create/update/remove the light and model entities exactly the way the
|
|
// game code would, but without destroying unrelated renderWorld defs.
|
|
EditorBeginEntityReconcile();
|
|
|
|
for (ent = entities.next; ent != &entities; ent = ent->next) {
|
|
BuildEntityRenderState(ent, true);
|
|
}
|
|
|
|
EditorPurgeUntouchedEntityStates();
|
|
EditorPurgeUntouchedBModelStates();
|
|
|
|
worldDirty = false;
|
|
}
|
|
|
|
/*
|
|
===============================
|
|
CCamWnd::UpdateRenderEntities
|
|
|
|
Creates a new entity state list
|
|
returns true if a repaint is needed
|
|
===============================
|
|
*/
|
|
bool CCamWnd::UpdateRenderEntities() {
|
|
|
|
if (rebuildMode) {
|
|
return false;
|
|
}
|
|
|
|
if (!s_editorRenderWorldInitialized) {
|
|
g_qeglobals.rw->InitFromMap(NULL);
|
|
s_editorRenderWorldInitialized = true;
|
|
}
|
|
|
|
bool ret = false;
|
|
EditorBeginEntityReconcile();
|
|
|
|
for (entity_t* ent = entities.next; ent != &entities; ent = ent->next) {
|
|
BuildEntityRenderState(ent, true);
|
|
if (!ret && (ent->modelDef >= 0 || ent->lightDef >= 0)) {
|
|
ret = true;
|
|
}
|
|
}
|
|
|
|
EditorPurgeUntouchedEntityStates();
|
|
EditorPurgeUntouchedBModelStates();
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
============================
|
|
CCamWnd::FreeRendererState
|
|
|
|
Frees the render state data
|
|
============================
|
|
*/
|
|
void CCamWnd::FreeRendererState() {
|
|
|
|
for (int i = s_editorEntityStates.Num() - 1; i >= 0; i--) {
|
|
EditorFreeRenderEntityDef(s_editorEntityStates[i].modelDef);
|
|
EditorFreeRenderLightDef(s_editorEntityStates[i].lightDef);
|
|
s_editorEntityStates.RemoveIndex(i);
|
|
}
|
|
|
|
if (entities.next != NULL)
|
|
{
|
|
for (entity_t* ent = entities.next; ent != &entities; ent = ent->next) {
|
|
ent->modelDef = -1;
|
|
ent->lightDef = -1;
|
|
}
|
|
}
|
|
|
|
|
|
for (int i = s_editorBModelStates.Num() - 1; i >= 0; i--) {
|
|
EditorFreeBModelState(i);
|
|
}
|
|
|
|
for (int i = s_editorWorldBrushStates.Num() - 1; i >= 0; i--) {
|
|
EditorFreeWorldBrushState(i);
|
|
}
|
|
|
|
if (worldModelDef >= 0) {
|
|
g_qeglobals.rw->FreeEntityDef(worldModelDef);
|
|
worldModelDef = -1;
|
|
}
|
|
|
|
if (worldModel) {
|
|
renderModelManager->FreeModel(worldModel);
|
|
worldModel = NULL;
|
|
}
|
|
|
|
s_editorRenderWorldInitialized = false;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
========================
|
|
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() {
|
|
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glLoadIdentity();
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadIdentity();
|
|
|
|
SetProjectionMatrix();
|
|
|
|
glRotatef(-90, 1, 0, 0); // put Z going up
|
|
glRotatef(90, 0, 0, 1); // put Z going up
|
|
glRotatef(m_Camera.angles[0], 0, 1, 0);
|
|
glRotatef(-m_Camera.angles[1], 0, 0, 1);
|
|
glTranslatef(-m_Camera.origin[0], -m_Camera.origin[1], -m_Camera.origin[2]);
|
|
|
|
Cam_BuildMatrix();
|
|
|
|
if (!(entityMode || selectMode)) {
|
|
return;
|
|
}
|
|
|
|
glDisable(GL_BLEND);
|
|
glDisable(GL_DEPTH_TEST);
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
|
|
globalImages->BindNull();
|
|
idVec3 color(0, 1, 0);
|
|
glColor3fv(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++;
|
|
glColor3fv(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
|
|
}
|
|
|
|
// save the editor state
|
|
//glPushAttrib( GL_ALL_ATTRIB_BITS );
|
|
glClearColor(0.1f, 0.1f, 0.1f, 0.0f);
|
|
glScissor(0, 0, m_Camera.width, m_Camera.height);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
// wglSwapBuffers(dc.m_hDC);
|
|
|
|
// create the model, using explicit normals
|
|
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, false);
|
|
//common->Printf( "front:%i back:%i\n", frontEnd, backEnd );
|
|
|
|
//glPopAttrib();
|
|
//DrawEntityData();
|
|
|
|
//wglSwapBuffers(dc.m_hDC);
|
|
// get back to the editor state
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glLoadIdentity();
|
|
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;
|
|
}
|
|
}
|
|
|