/* =========================================================================== IceTech GPL Source Code Copyright (C) 2026 Justin Marshall This file is part of the IceTech GPL Source Code (?IceTech Source Code?). IceTech 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. IceTech 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 IceTech Source Code. If not, see . In addition, the IceTech 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 IceTech 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 Justin Marshall, justinmarshall20@gmail.com =========================================================================== */ #include "precompiled.h" #pragma hdrstop #include "qe3.h" #include "Radiant.h" #include "XYWnd.h" #include "CamWnd.h" #include "OutlinerDlg.h" #include "splines.h" #include #include "../../renderer/tr_local.h" #include "../../models/model_local.h" // for idRenderModelMD5 #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif extern void DrawPathLines(); extern qertrace_t Test_Ray(const idVec3& origin, const idVec3& dir, int flags); extern brush_t *Brush_CreateFaceExtrusion(brush_t *sourceBrush, face_t *sourceFace, float distance); extern void Select_ShiftTexture(float x, float y); extern void Select_ScaleTexture(float x, float y); extern void Select_RotateTexture(float amt, bool absolute); /* ======================== Camera navigation extension All settings are read from radiant.ini, section [3d navigation]. The default style is 1 so existing camera behavior is unchanged until style=2 is enabled. ======================== */ enum { CAMERA_NAV_TIMER_ID = 713, CAMERA_NAV_INFO_NONE = 0, CAMERA_NAV_INFO_ENTITY = 1, CAMERA_NAV_INFO_MATERIAL = 2, CAMERA_NAV_MAX_INFO_LINES = 32 }; struct cameraNavConfig_t { bool loaded; int style; int forwardKey; int backKey; int upKey; int downKey; int leftKey; int rightKey; int modifier1Key; int modifier2Key; bool invertPitch; bool invertZTrans; float capsLockScale; int reticleMode; float reticleScale; idVec3 reticleColor; int teleportKey; int infoDisplayKey; int lockTargetKey; bool invertLockPitch; bool invertLockYaw; int texPositionKey; int texScaleKey; int texRotateKey; }; struct cameraNavState_t { CCamWnd* cam; bool active; bool lockActive; int infoMode; CPoint cursorAnchor; int lastTime; idVec3 lockTarget; float lockDistance; float lockPitch; float lockYaw; }; static cameraNavConfig_t s_cameraNavConfig; static cameraNavState_t s_cameraNavState; static void CameraNav_GetIniPath(char* path, int pathSize) { if (pathSize <= 0) { return; } path[0] = '\0'; DWORD len = GetModuleFileName(NULL, path, pathSize); if (len == 0 || len >= (DWORD)pathSize) { strncpy(path, "radiant.ini", pathSize - 1); path[pathSize - 1] = '\0'; return; } char* slash = strrchr(path, '\\'); char* slash2 = strrchr(path, '/'); if (slash2 && (!slash || slash2 > slash)) { slash = slash2; } if (slash) { strncpy(slash + 1, "radiant.ini", pathSize - (int)(slash + 1 - path) - 1); path[pathSize - 1] = '\0'; } else { strncpy(path, "radiant.ini", pathSize - 1); path[pathSize - 1] = '\0'; } } static void CameraNav_NormalizeKeyName(const char* in, char* out, int outSize) { if (outSize <= 0) { return; } out[0] = '\0'; if (!in) { return; } int outPos = 0; while (*in && outPos < outSize - 1) { unsigned char c = (unsigned char)*in++; if (c == ' ' || c == '\t' || c == '_' || c == '-') { continue; } out[outPos++] = (char)tolower(c); } out[outPos] = '\0'; } static int CameraNav_ParseKeyName(const char* text) { char keyName[64]; CameraNav_NormalizeKeyName(text, keyName, sizeof(keyName)); if (keyName[0] == '\0' || !stricmp(keyName, "none") || !stricmp(keyName, "0")) { return 0; } if (keyName[1] == '\0') { char c = keyName[0]; if (c >= 'a' && c <= 'z') { return c - 'a' + 'A'; } if (c >= '0' && c <= '9') { return c; } } if (!stricmp(keyName, "space")) return VK_SPACE; if (!stricmp(keyName, "backspace")) return VK_BACK; if (!stricmp(keyName, "escape") || !stricmp(keyName, "esc")) return VK_ESCAPE; if (!stricmp(keyName, "end")) return VK_END; if (!stricmp(keyName, "insert") || !stricmp(keyName, "ins")) return VK_INSERT; if (!stricmp(keyName, "delete") || !stricmp(keyName, "del")) return VK_DELETE; if (!stricmp(keyName, "pageup") || !stricmp(keyName, "pgup")) return VK_PRIOR; if (!stricmp(keyName, "pagedown") || !stricmp(keyName, "pgdn")) return VK_NEXT; if (!stricmp(keyName, "up")) return VK_UP; if (!stricmp(keyName, "down")) return VK_DOWN; if (!stricmp(keyName, "left")) return VK_LEFT; if (!stricmp(keyName, "right")) return VK_RIGHT; if (!stricmp(keyName, "tab")) return VK_TAB; if (!stricmp(keyName, "return") || !stricmp(keyName, "enter")) return VK_RETURN; if (!stricmp(keyName, "comma")) return VK_OEM_COMMA; if (!stricmp(keyName, "period")) return VK_OEM_PERIOD; if (!stricmp(keyName, "plus")) return VK_ADD; if (!stricmp(keyName, "multiply")) return VK_MULTIPLY; if (!stricmp(keyName, "subtract") || !stricmp(keyName, "minus")) return VK_SUBTRACT; if (!stricmp(keyName, "shift")) return VK_SHIFT; if (!stricmp(keyName, "ctrl") || !stricmp(keyName, "control")) return VK_CONTROL; if (!stricmp(keyName, "alt")) return VK_MENU; if (!stricmp(keyName, "capslock")) return VK_CAPITAL; if (keyName[0] == 'f') { int n = atoi(keyName + 1); if (n >= 1 && n <= 12) { return VK_F1 + n - 1; } } if (!strnicmp(keyName, "numpad", 6)) { int n = atoi(keyName + 6); if (n >= 0 && n <= 9) { return VK_NUMPAD0 + n; } } return 0; } static int CameraNav_ReadKey(const char* iniPath, const char* keyName, const char* defaultValue) { char value[64]; GetPrivateProfileString("3d navigation", keyName, defaultValue ? defaultValue : "", value, sizeof(value), iniPath); return CameraNav_ParseKeyName(value); } static float CameraNav_ReadFloat(const char* iniPath, const char* keyName, const char* defaultValue) { char value[64]; GetPrivateProfileString("3d navigation", keyName, defaultValue, value, sizeof(value), iniPath); return (float)atof(value); } static void CameraNav_ReadColor(const char* iniPath, const char* keyName, const char* defaultValue, idVec3& color) { char value[128]; float r = 50.0f; float g = 230.0f; float b = 50.0f; GetPrivateProfileString("3d navigation", keyName, defaultValue, value, sizeof(value), iniPath); if (sscanf(value, "%f%*[ ,]%f%*[ ,]%f", &r, &g, &b) != 3) { r = 50.0f; g = 230.0f; b = 50.0f; } color[0] = (r > 1.0f) ? r / 255.0f : r; color[1] = (g > 1.0f) ? g / 255.0f : g; color[2] = (b > 1.0f) ? b / 255.0f : b; } static cameraNavConfig_t& CameraNav_Config() { cameraNavConfig_t& cfg = s_cameraNavConfig; if (cfg.loaded) { return cfg; } char iniPath[MAX_PATH]; CameraNav_GetIniPath(iniPath, sizeof(iniPath)); memset(&cfg, 0, sizeof(cfg)); cfg.loaded = true; cfg.style = GetPrivateProfileInt("3d navigation", "style", 1, iniPath); cfg.forwardKey = CameraNav_ReadKey(iniPath, "forward", "w"); cfg.backKey = CameraNav_ReadKey(iniPath, "back", "s"); cfg.upKey = CameraNav_ReadKey(iniPath, "up", "space"); cfg.downKey = CameraNav_ReadKey(iniPath, "down", "shift"); cfg.leftKey = CameraNav_ReadKey(iniPath, "left", "a"); cfg.rightKey = CameraNav_ReadKey(iniPath, "right", "d"); cfg.modifier1Key = CameraNav_ReadKey(iniPath, "modifier1", "q"); cfg.modifier2Key = CameraNav_ReadKey(iniPath, "modifier2", "e"); cfg.invertPitch = GetPrivateProfileInt("3d navigation", "invertpitch", 0, iniPath) != 0; cfg.invertZTrans = GetPrivateProfileInt("3d navigation", "invertztrans", 0, iniPath) != 0; cfg.capsLockScale = CameraNav_ReadFloat(iniPath, "capslockscale", "1"); if (cfg.capsLockScale <= 0.0f) { cfg.capsLockScale = 1.0f; } cfg.reticleMode = GetPrivateProfileInt("3d navigation", "reticlemode", 0, iniPath); cfg.reticleScale = CameraNav_ReadFloat(iniPath, "reticlescale", "1"); if (cfg.reticleScale <= 0.0f) { cfg.reticleScale = 1.0f; } CameraNav_ReadColor(iniPath, "reticlecolor", "50 230 50", cfg.reticleColor); // Extra navigation features are opt-in. They only bind when a key is present // in radiant.ini, which avoids stealing existing editor shortcuts by default. cfg.teleportKey = CameraNav_ReadKey(iniPath, "teleport", ""); cfg.infoDisplayKey = CameraNav_ReadKey(iniPath, "infodisplay", ""); cfg.lockTargetKey = CameraNav_ReadKey(iniPath, "locktarget", ""); cfg.invertLockPitch = GetPrivateProfileInt("3d navigation", "invertlockpitch", 0, iniPath) != 0; cfg.invertLockYaw = GetPrivateProfileInt("3d navigation", "invertlockyaw", 0, iniPath) != 0; cfg.texPositionKey = CameraNav_ReadKey(iniPath, "texposition", ""); cfg.texScaleKey = CameraNav_ReadKey(iniPath, "texscale", ""); cfg.texRotateKey = CameraNav_ReadKey(iniPath, "texrotate", ""); return cfg; } static bool CameraNav_KeyIsDown(int key) { return key != 0 && (GetAsyncKeyState(key) & 0x8000) != 0; } static float CameraNav_CapsScale() { cameraNavConfig_t& cfg = CameraNav_Config(); return (GetKeyState(VK_CAPITAL) & 1) ? cfg.capsLockScale : 1.0f; } static float CameraNav_MoveSpeed() { float speed = (float)g_PrefsDlg.m_nMoveSpeed; if (speed <= 0.0f) { speed = 64.0f; } return speed * CameraNav_CapsScale(); } static void CameraNav_GetAxes(const camera_t& camera, idVec3& forward, idVec3& right, idVec3& up) { float yaw = camera.angles[YAW] * idMath::M_DEG2RAD; float pitch = -camera.angles[PITCH] * idMath::M_DEG2RAD; float sy = sin(yaw); float cy = cos(yaw); float sp = sin(pitch); float cp = cos(pitch); forward[0] = cp * cy; forward[1] = cp * sy; forward[2] = -sp; forward.Normalize(); right[0] = sy; right[1] = -cy; right[2] = 0.0f; right.Normalize(); up[0] = right[1] * forward[2] - right[2] * forward[1]; up[1] = right[2] * forward[0] - right[0] * forward[2]; up[2] = right[0] * forward[1] - right[1] * forward[0]; up.Normalize(); } static idVec3 CameraNav_ForwardFromAngles(float pitchDegrees, float yawDegrees) { camera_t temp; memset(&temp, 0, sizeof(temp)); temp.angles[PITCH] = pitchDegrees; temp.angles[YAW] = yawDegrees; idVec3 forward, right, up; CameraNav_GetAxes(temp, forward, right, up); return forward; } static void CameraNav_UpdateViews() { int nUpdate = (g_PrefsDlg.m_bCamXYUpdate) ? (W_CAMERA | W_XY) : W_CAMERA; Sys_UpdateWindows(nUpdate); if (g_pParentWnd) { g_pParentWnd->PostMessage(WM_TIMER, 0, 0); } } static bool CameraNav_IsActive(CCamWnd* cam) { return s_cameraNavState.active && s_cameraNavState.cam == cam; } static bool CameraNav_TraceCenter(CCamWnd* cam, qertrace_t& trace, idVec3* hitPoint = NULL, idVec3* outDir = NULL) { camera_t& camera = cam->Camera(); idVec3 forward, right, up; CameraNav_GetAxes(camera, forward, right, up); trace = Test_Ray(camera.origin, forward, 0); if (outDir) { *outDir = forward; } if (!trace.brush || trace.dist <= 0.0f || trace.dist >= HUGE_DISTANCE) { return false; } if (hitPoint) { *hitPoint = camera.origin + forward * trace.dist; } return true; } static void CameraNav_Stop(CCamWnd* cam) { if (!CameraNav_IsActive(cam)) { return; } cam->KillTimer(CAMERA_NAV_TIMER_ID); if (::GetCapture() == cam->GetSafeHwnd()) { ::ReleaseCapture(); } s_cameraNavState.active = false; s_cameraNavState.lockActive = false; Sys_UpdateWindows(W_CAMERA); } static bool CameraNav_Begin(CCamWnd* cam) { cameraNavConfig_t& cfg = CameraNav_Config(); if (cfg.style != 2) { return false; } if (s_cameraNavState.active && s_cameraNavState.cam != cam) { CameraNav_Stop(s_cameraNavState.cam); } memset(&s_cameraNavState, 0, sizeof(s_cameraNavState)); s_cameraNavState.cam = cam; s_cameraNavState.active = true; s_cameraNavState.lastTime = Sys_Milliseconds(); GetCursorPos(&s_cameraNavState.cursorAnchor); cam->SetFocus(); cam->SetCapture(); cam->SetTimer(CAMERA_NAV_TIMER_ID, 10, NULL); Sys_UpdateWindows(W_CAMERA); return true; } static void CameraNav_ClampPitch(float& pitch) { if (pitch > 89.0f) { pitch = 89.0f; } else if (pitch < -89.0f) { pitch = -89.0f; } } static bool CameraNav_StartLock(CCamWnd* cam) { qertrace_t trace; idVec3 hit; if (!CameraNav_TraceCenter(cam, trace, &hit)) { return false; } camera_t& camera = cam->Camera(); s_cameraNavState.lockActive = true; s_cameraNavState.lockTarget = hit; s_cameraNavState.lockDistance = trace.dist; if (s_cameraNavState.lockDistance < 16.0f) { s_cameraNavState.lockDistance = 16.0f; } s_cameraNavState.lockPitch = camera.angles[PITCH]; s_cameraNavState.lockYaw = camera.angles[YAW]; return true; } static void CameraNav_ApplyLock(CCamWnd* cam) { camera_t& camera = cam->Camera(); CameraNav_ClampPitch(s_cameraNavState.lockPitch); idVec3 forward = CameraNav_ForwardFromAngles(s_cameraNavState.lockPitch, s_cameraNavState.lockYaw); camera.origin = s_cameraNavState.lockTarget - forward * s_cameraNavState.lockDistance; camera.angles[PITCH] = s_cameraNavState.lockPitch; camera.angles[YAW] = s_cameraNavState.lockYaw; camera.angles[ROLL] = 0.0f; } static void CameraNav_Teleport(CCamWnd* cam) { qertrace_t trace; idVec3 hit; idVec3 dir; if (!CameraNav_TraceCenter(cam, trace, &hit, &dir)) { return; } camera_t& camera = cam->Camera(); float moveDist = trace.dist - 64.0f; if (moveDist < 0.0f) { moveDist = trace.dist * 0.5f; } camera.origin += dir * moveDist; CameraNav_UpdateViews(); } static bool CameraNav_IsMovementOrModifierKey(int key) { cameraNavConfig_t& cfg = CameraNav_Config(); return key == cfg.forwardKey || key == cfg.backKey || key == cfg.upKey || key == cfg.downKey || key == cfg.leftKey || key == cfg.rightKey || key == cfg.modifier1Key || key == cfg.modifier2Key || key == cfg.texPositionKey || key == cfg.texScaleKey || key == cfg.texRotateKey; } static bool CameraNav_HandleKeyDown(CCamWnd* cam, UINT key) { cameraNavConfig_t& cfg = CameraNav_Config(); if (cfg.style != 2 || !CameraNav_IsActive(cam)) { return false; } if (cfg.infoDisplayKey && key == (UINT)cfg.infoDisplayKey) { s_cameraNavState.infoMode = (s_cameraNavState.infoMode + 1) % 3; Sys_UpdateWindows(W_CAMERA); return true; } if (cfg.teleportKey && key == (UINT)cfg.teleportKey) { CameraNav_Teleport(cam); return true; } if (cfg.lockTargetKey && key == (UINT)cfg.lockTargetKey) { CameraNav_StartLock(cam); return true; } if (CameraNav_IsMovementOrModifierKey((int)key)) { return true; } return false; } static bool CameraNav_HandleKeyUp(CCamWnd* cam, UINT key) { cameraNavConfig_t& cfg = CameraNav_Config(); if (cfg.style != 2 || !CameraNav_IsActive(cam)) { return false; } if (cfg.lockTargetKey && key == (UINT)cfg.lockTargetKey) { s_cameraNavState.lockActive = false; return true; } if (CameraNav_IsMovementOrModifierKey((int)key)) { return true; } return false; } static bool CameraNav_Update(CCamWnd* cam) { if (!CameraNav_IsActive(cam)) { return false; } cameraNavConfig_t& cfg = CameraNav_Config(); int now = Sys_Milliseconds(); float dtime = (now - s_cameraNavState.lastTime) * 0.001f; if (dtime <= 0.0f) { dtime = 0.01f; } else if (dtime > 0.1f) { dtime = 0.1f; } s_cameraNavState.lastTime = now; if (cfg.lockTargetKey) { if (CameraNav_KeyIsDown(cfg.lockTargetKey)) { if (!s_cameraNavState.lockActive) { CameraNav_StartLock(cam); } } else if (s_cameraNavState.lockActive) { s_cameraNavState.lockActive = false; } } camera_t& camera = cam->Camera(); idVec3 forward, right, up; CameraNav_GetAxes(camera, forward, right, up); if (s_cameraNavState.lockActive) { float zoom = 0.0f; if (CameraNav_KeyIsDown(cfg.forwardKey)) { zoom -= CameraNav_MoveSpeed() * dtime; } if (CameraNav_KeyIsDown(cfg.backKey)) { zoom += CameraNav_MoveSpeed() * dtime; } if (zoom != 0.0f) { s_cameraNavState.lockDistance += zoom; if (s_cameraNavState.lockDistance < 16.0f) { s_cameraNavState.lockDistance = 16.0f; } CameraNav_ApplyLock(cam); CameraNav_UpdateViews(); } return true; } idVec3 move; move[0] = move[1] = move[2] = 0.0f; if (CameraNav_KeyIsDown(cfg.forwardKey)) { move += forward; } if (CameraNav_KeyIsDown(cfg.backKey)) { move -= forward; } if (CameraNav_KeyIsDown(cfg.leftKey)) { move -= right; } if (CameraNav_KeyIsDown(cfg.rightKey)) { move += right; } if (CameraNav_KeyIsDown(cfg.upKey)) { move[2] += 1.0f; } if (CameraNav_KeyIsDown(cfg.downKey)) { move[2] -= 1.0f; } if (move.LengthSqr() > 0.0f) { move.Normalize(); camera.origin += move * (CameraNav_MoveSpeed() * dtime); CameraNav_UpdateViews(); } return true; } static bool CameraNav_MouseMove(CCamWnd* cam) { if (!CameraNav_IsActive(cam)) { return false; } CPoint current; GetCursorPos(¤t); int dx = current.x - s_cameraNavState.cursorAnchor.x; int dy = current.y - s_cameraNavState.cursorAnchor.y; if (dx == 0 && dy == 0) { return true; } cameraNavConfig_t& cfg = CameraNav_Config(); camera_t& camera = cam->Camera(); bool textureAdjusted = false; if (cfg.texPositionKey && CameraNav_KeyIsDown(cfg.texPositionKey)) { Select_ShiftTexture((float)dx, (float)-dy, false); textureAdjusted = true; } else if (cfg.texScaleKey && CameraNav_KeyIsDown(cfg.texScaleKey)) { Select_ScaleTexture((float)dx, (float)-dy, false); textureAdjusted = true; } else if (cfg.texRotateKey && CameraNav_KeyIsDown(cfg.texRotateKey)) { Select_RotateTexture((float)dy, false); textureAdjusted = true; } if (textureAdjusted) { SetCursorPos(s_cameraNavState.cursorAnchor.x, s_cameraNavState.cursorAnchor.y); Sys_UpdateWindows(W_ALL); return true; } if (s_cameraNavState.lockActive) { float yawSign = cfg.invertLockYaw ? 1.0f : -1.0f; float pitchSign = cfg.invertLockPitch ? 1.0f : -1.0f; s_cameraNavState.lockYaw += dx * 0.25f * yawSign; s_cameraNavState.lockPitch += dy * 0.25f * pitchSign; CameraNav_ApplyLock(cam); } else if (CameraNav_KeyIsDown(cfg.modifier1Key)) { idVec3 forward, right, up; CameraNav_GetAxes(camera, forward, right, up); float scale = CameraNav_CapsScale(); float zMove = cfg.invertZTrans ? (float)dy : (float)-dy; camera.origin += right * ((float)dx * scale); camera.origin[2] += zMove * scale; } else if (CameraNav_KeyIsDown(cfg.modifier2Key)) { idVec3 forward, right, up; CameraNav_GetAxes(camera, forward, right, up); float scale = CameraNav_CapsScale(); camera.origin += forward * ((float)-dy * scale); camera.angles[YAW] -= (float)dx * 0.25f; } else { float pitchSign = cfg.invertPitch ? 1.0f : -1.0f; camera.angles[PITCH] += (float)dy * 0.25f * pitchSign; camera.angles[YAW] -= (float)dx * 0.25f; CameraNav_ClampPitch(camera.angles[PITCH]); } SetCursorPos(s_cameraNavState.cursorAnchor.x, s_cameraNavState.cursorAnchor.y); CameraNav_UpdateViews(); return true; } static bool CameraNav_HandleMouseWheel(HWND hWnd, WPARAM wParam) { CWnd* wnd = CWnd::FromHandlePermanent(hWnd); CCamWnd* cam = DYNAMIC_DOWNCAST(CCamWnd, wnd); if (!cam) { return false; } short wheelDelta = (short)HIWORD(wParam); if (wheelDelta == 0) { return false; } camera_t& camera = cam->Camera(); idVec3 forward, right, up; CameraNav_GetAxes(camera, forward, right, up); float notches = (float)wheelDelta / 120.0f; camera.origin += forward * (notches * CameraNav_MoveSpeed()); CameraNav_UpdateViews(); return true; } static const char* CameraNav_SurfaceTypeName(surfTypes_t type) { switch (type) { case SURFTYPE_METAL: return "metal"; case SURFTYPE_STONE: return "stone"; case SURFTYPE_FLESH: return "flesh"; case SURFTYPE_WOOD: return "wood"; case SURFTYPE_CARDBOARD: return "cardboard"; case SURFTYPE_LIQUID: return "liquid"; case SURFTYPE_GLASS: return "glass"; #ifndef PREY case SURFTYPE_PLASTIC: return "plastic"; case SURFTYPE_RICOCHET: return "ricochet"; #endif default: return "none"; } } static void CameraNav_AddLine(idStr* lines, int& numLines, const char* text) { if (numLines >= CAMERA_NAV_MAX_INFO_LINES) { return; } lines[numLines++] = text; } static void CameraNav_BuildEntityInfo(CCamWnd* cam, idStr* lines, int& numLines) { qertrace_t trace; if (!CameraNav_TraceCenter(cam, trace)) { CameraNav_AddLine(lines, numLines, "Entity: "); return; } entity_t* ent = trace.brush->owner; CameraNav_AddLine(lines, numLines, "Entity"); CameraNav_AddLine(lines, numLines, va("class: %s", ValueForKey(ent, "classname"))); CameraNav_AddLine(lines, numLines, va("name: %s", ValueForKey(ent, "name"))); for (int i = 0; i < ent->epairs.GetNumKeyVals() && numLines < CAMERA_NAV_MAX_INFO_LINES; i++) { const idKeyValue* kv = ent->epairs.GetKeyVal(i); if (!kv) { continue; } CameraNav_AddLine(lines, numLines, va("%s = %s", kv->GetKey().c_str(), kv->GetValue().c_str())); } } static void CameraNav_BuildMaterialInfo(CCamWnd* cam, idStr* lines, int& numLines) { qertrace_t trace; if (!CameraNav_TraceCenter(cam, trace) || !trace.face || !trace.face->d_texture) { CameraNav_AddLine(lines, numLines, "Material: "); return; } const idMaterial* material = trace.face->d_texture; CameraNav_AddLine(lines, numLines, "Material"); CameraNav_AddLine(lines, numLines, material->GetName()); CameraNav_AddLine(lines, numLines, va("size: %d x %d", material->GetImageWidth(), material->GetImageHeight())); CameraNav_AddLine(lines, numLines, va("surface: %s", CameraNav_SurfaceTypeName(material->GetSurfaceType()))); CameraNav_AddLine(lines, numLines, va("content flags: 0x%08x", material->GetContentFlags())); CameraNav_AddLine(lines, numLines, va("surface flags: 0x%08x", material->GetSurfaceFlags())); CameraNav_AddLine(lines, numLines, va("stages: %d", material->GetNumStages())); CameraNav_AddLine(lines, numLines, va("decl: %s:%d", material->GetFileName(), material->GetLineNum())); if (material->GetDescription() && material->GetDescription()[0]) { CameraNav_AddLine(lines, numLines, va("desc: %s", material->GetDescription())); } } static void CameraNav_DrawOverlay(CCamWnd* cam) { cameraNavConfig_t& cfg = CameraNav_Config(); camera_t& camera = cam->Camera(); bool drawReticle = (cfg.reticleMode == 2) || (cfg.reticleMode == 1 && CameraNav_IsActive(cam)); bool drawInfo = (s_cameraNavState.infoMode != CAMERA_NAV_INFO_NONE); if (!drawReticle && !drawInfo) { return; } glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(0, camera.width, 0, camera.height, -1, 1); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); glDisable(GL_BLEND); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); globalImages->BindNull(); if (drawReticle) { float cx = camera.width * 0.5f; float cy = camera.height * 0.5f; float size = 8.0f * cfg.reticleScale; float gap = 3.0f * cfg.reticleScale; glColor3fv(cfg.reticleColor.ToFloatPtr()); glBegin(GL_LINES); glVertex2f(cx - size, cy); glVertex2f(cx - gap, cy); glVertex2f(cx + gap, cy); glVertex2f(cx + size, cy); glVertex2f(cx, cy - size); glVertex2f(cx, cy - gap); glVertex2f(cx, cy + gap); glVertex2f(cx, cy + size); glEnd(); } if (drawInfo && g_qeglobals.d_font_list) { idStr lines[CAMERA_NAV_MAX_INFO_LINES]; int numLines = 0; if (s_cameraNavState.infoMode == CAMERA_NAV_INFO_ENTITY) { CameraNav_BuildEntityInfo(cam, lines, numLines); } else if (s_cameraNavState.infoMode == CAMERA_NAV_INFO_MATERIAL) { CameraNav_BuildMaterialInfo(cam, lines, numLines); } //glColor3f(1.0f, 1.0f, 1.0f); //glListBase(g_qeglobals.d_font_list); //int y = camera.height - 18; //for (int i = 0; i < numLines && y > 8; i++, y -= 14) { // const char* text = lines[i].c_str(); // glRasterPos2i(8, y); // glCallLists(strlen(text), GL_UNSIGNED_BYTE, text); //} } glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); } /* ======================== Outliner-aware entity target/path lines The original DrawPathLines() does not know about the outliner visibility list. When no entity is hidden, keep using the original function so the normal editor style is unchanged. When something is hidden, draw a filtered equivalent that skips links where either endpoint is hidden. ======================== */ static bool CamWnd_IsTargetLineKey(const char* key) { if (key == NULL || key[0] == '\0') { return false; } if (idStr::Icmp(key, "targetname") == 0) { return false; } if (idStr::Icmp(key, "next") == 0) { return true; } return idStr::Icmpn(key, "target", 6) == 0; } static bool CamWnd_EntityNameMatches(entity_t* ent, const char* name) { if (ent == NULL || name == NULL || name[0] == '\0') { return false; } const char* entName = ValueForKey(ent, "name"); if (entName != NULL && entName[0] != '\0' && idStr::Icmp(entName, name) == 0) { return true; } const char* targetName = ValueForKey(ent, "targetname"); if (targetName != NULL && targetName[0] != '\0' && idStr::Icmp(targetName, name) == 0) { return true; } return false; } static bool CamWnd_GetEntityLinePoint(entity_t* ent, idVec3& point) { if (ent == NULL) { return false; } if (GetVectorForKey(ent, "origin", point)) { return true; } idBounds bounds; bounds.Clear(); bool haveBounds = false; for (brush_t* brush = ent->brushes.onext; brush != NULL && brush != &ent->brushes; brush = brush->onext) { bounds.AddPoint(brush->mins); bounds.AddPoint(brush->maxs); haveBounds = true; } if (haveBounds) { point = bounds.GetCenter(); return true; } return false; } static void CamWnd_DrawVisibleTargetLine(entity_t* source, entity_t* target) { if (source == NULL || target == NULL || source == target) { return; } if (!Outliner_IsEntityVisible(source) || !Outliner_IsEntityVisible(target)) { return; } idVec3 start; idVec3 end; if (!CamWnd_GetEntityLinePoint(source, start) || !CamWnd_GetEntityLinePoint(target, end)) { return; } glVertex3fv(start.ToFloatPtr()); glVertex3fv(end.ToFloatPtr()); } static void CamWnd_DrawVisibleTargetLinesForName(entity_t* source, const char* targetName) { if (targetName == NULL || targetName[0] == '\0') { return; } if (world_entity != NULL && CamWnd_EntityNameMatches(world_entity, targetName)) { CamWnd_DrawVisibleTargetLine(source, world_entity); } if (entities.next == NULL) { return; } for (entity_t* target = entities.next; target != &entities; target = target->next) { if (CamWnd_EntityNameMatches(target, targetName)) { CamWnd_DrawVisibleTargetLine(source, target); } } } static void CamWnd_DrawVisibleTargetLinesForEntity(entity_t* source) { if (source == NULL || !Outliner_IsEntityVisible(source)) { return; } const int numKeys = source->epairs.GetNumKeyVals(); for (int keyIndex = 0; keyIndex < numKeys; keyIndex++) { const idKeyValue* kv = source->epairs.GetKeyVal(keyIndex); if (kv == NULL) { continue; } if (!CamWnd_IsTargetLineKey(kv->GetKey().c_str())) { continue; } CamWnd_DrawVisibleTargetLinesForName(source, kv->GetValue().c_str()); } } static void CamWnd_DrawVisiblePathLines() { if (!Outliner_HasHiddenEntities()) { DrawPathLines(); return; } glPushAttrib(GL_CURRENT_BIT); globalImages->BindNull(); glDisable(GL_BLEND); glLineWidth(1.0f); glColor3f(1.0f, 0.75f, 0.0f); glBegin(GL_LINES); if (world_entity != NULL) { CamWnd_DrawVisibleTargetLinesForEntity(world_entity); } if (entities.next != NULL) { for (entity_t* source = entities.next; source != &entities; source = source->next) { CamWnd_DrawVisibleTargetLinesForEntity(source); } } glEnd(); glPopAttrib(); } /* ======================== Camera window embedded menu bar The camera view is an OpenGL window, so the menu is a real child window at the very top of the camera client area. CCamWnd reserves that strip in OnSize() and all GL viewports/scissors use the reduced camera height, which keeps the camera view from drawing underneath the menu. ======================== */ bool IsBModel(brush_t* b); static const char* CAMWND_MENU_BAR_CLASS = "QER_CamWndMenuBar"; static const char* CAMWND_PROP_MENU_BAR = "QER_CamWnd_MenuBar"; static const char* CAMWND_PROP_RENDER_MODE = "QER_CamWnd_RenderMode"; static const char* CAMWND_PROP_REBUILD_MODE = "QER_CamWnd_RebuildMode"; static const char* CAMWND_PROP_ENTITY_MODE = "QER_CamWnd_EntityMode"; static const char* CAMWND_PROP_FILTER_MASK = "QER_CamWnd_FilterMask"; #define CAMWND_CAPTION_HEIGHT 30 #define CAMWND_MENU_HEIGHT 22 #define CAMWND_TOPBAR_HEIGHT (CAMWND_CAPTION_HEIGHT + CAMWND_MENU_HEIGHT) #define CAMWND_MENU_CHILD_ID 62000 #define CAMWND_MENU_CMD_REALTIME_LIGHTING 62100 #define CAMWND_MENU_CMD_SHOW_WORLD 62101 #define CAMWND_MENU_CMD_SHOW_PATCHES 62102 #define CAMWND_MENU_CMD_SHOW_MODELS 62103 #define CAMWND_MENU_CMD_SHOW_ENTITIES 62104 #define CAMWND_MENU_CMD_SHOW_LIGHTS 62105 #define CAMWND_MENU_CMD_RESET_FILTERS 62106 #define CAMWND_MENU_CMD_SHOW_TARGET_LINES 62107 #define CAMWND_MENU_CMD_SHOW_TRIGGER_BRUSHES 62108 #define CAMWND_FILTER_HIDE_WORLD 0x00000001 #define CAMWND_FILTER_HIDE_PATCHES 0x00000002 #define CAMWND_FILTER_HIDE_MODELS 0x00000004 #define CAMWND_FILTER_HIDE_ENTITIES 0x00000008 #define CAMWND_FILTER_HIDE_LIGHTS 0x00000010 #define CAMWND_FILTER_HIDE_TARGET_LINES 0x00000020 #define CAMWND_FILTER_HIDE_TRIGGER_BRUSHES 0x00000040 static const COLORREF CAMWND_DARK_PANEL = RGB(24, 27, 32); static const COLORREF CAMWND_DARK_MENU = RGB(28, 31, 36); static const COLORREF CAMWND_DARK_BORDER = RGB(58, 64, 72); static const COLORREF CAMWND_DARK_TEXT = RGB(226, 232, 240); static HBRUSH CamWnd_DarkMenuBrush() { static HBRUSH hBrush = ::CreateSolidBrush(CAMWND_DARK_MENU); return hBrush; } static LRESULT CALLBACK CamWnd_MenuBarProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); static int CamWnd_GetMenuHeight() { return CAMWND_TOPBAR_HEIGHT; } static int CamWnd_GetIntProp(HWND hWnd, const char* name, int defaultValue = 0) { if (!hWnd) { return defaultValue; } HANDLE value = GetProp(hWnd, name); if (!value) { return defaultValue; } return (int)(INT_PTR)value; } static void CamWnd_SetIntProp(HWND hWnd, const char* name, int value) { if (!hWnd) { return; } if (value == 0) { RemoveProp(hWnd, name); } else { SetProp(hWnd, name, (HANDLE)(INT_PTR)value); } } static int CamWnd_GetFilterMask(CCamWnd* cam) { return cam ? CamWnd_GetIntProp(cam->GetSafeHwnd(), CAMWND_PROP_FILTER_MASK, 0) : 0; } static void CamWnd_SetFilterMask(CCamWnd* cam, int mask) { if (!cam) { return; } CamWnd_SetIntProp(cam->GetSafeHwnd(), CAMWND_PROP_FILTER_MASK, mask); } static CCamWnd* CamWnd_FromMenuBar(HWND hWnd) { HWND parent = GetParent(hWnd); if (!parent) { return NULL; } CWnd* wnd = CWnd::FromHandlePermanent(parent); return DYNAMIC_DOWNCAST(CCamWnd, wnd); } static HWND CamWnd_GetMenuBar(CCamWnd* cam) { if (!cam || !cam->GetSafeHwnd()) { return NULL; } HWND hBar = (HWND)GetProp(cam->GetSafeHwnd(), CAMWND_PROP_MENU_BAR); return IsWindow(hBar) ? hBar : NULL; } static void CamWnd_RequestRedraw(CCamWnd* cam) { if (cam && cam->GetSafeHwnd()) { cam->InvalidateRect(NULL, FALSE); } Sys_UpdateWindows(W_CAMERA); if (g_pParentWnd) { g_pParentWnd->PostMessage(WM_TIMER, 0, 0); } } static void CamWnd_GetMenuItemRect(HWND hWnd, int item, RECT& rect) { GetClientRect(hWnd, &rect); rect.top = CAMWND_CAPTION_HEIGHT + 1; rect.bottom = CAMWND_TOPBAR_HEIGHT - 1; if (item == 0) { rect.left = 4; rect.right = 86; } else { rect.left = 86; rect.right = 156; } } static int CamWnd_MenuHitTest(HWND hWnd, int x, int y) { RECT rect; for (int i = 0; i < 2; i++) { CamWnd_GetMenuItemRect(hWnd, i, rect); if (x >= rect.left && x < rect.right && y >= rect.top && y < rect.bottom) { return i; } } return -1; } static bool CamWnd_RealtimeLightingEnabled(CCamWnd* cam) { if (!cam || !cam->GetSafeHwnd()) { return false; } const HWND hWnd = cam->GetSafeHwnd(); return CamWnd_GetIntProp(hWnd, CAMWND_PROP_RENDER_MODE, 0) != 0 && CamWnd_GetIntProp(hWnd, CAMWND_PROP_REBUILD_MODE, 0) != 0; } static void CamWnd_SetRealtimeLighting(CCamWnd* cam, bool enabled) { if (!cam || !cam->GetSafeHwnd()) { return; } const HWND hWnd = cam->GetSafeHwnd(); bool renderMode = CamWnd_GetIntProp(hWnd, CAMWND_PROP_RENDER_MODE, 0) != 0; bool rebuildMode = CamWnd_GetIntProp(hWnd, CAMWND_PROP_REBUILD_MODE, 0) != 0; if (enabled) { if (!renderMode) { cam->ToggleRenderMode(); } if (!rebuildMode) { cam->ToggleRebuildMode(); } } else { if (rebuildMode) { cam->ToggleRebuildMode(); } if (renderMode) { cam->ToggleRenderMode(); } } CamWnd_RequestRedraw(cam); } static void CamWnd_ToggleRealtimeLighting(CCamWnd* cam) { CamWnd_SetRealtimeLighting(cam, !CamWnd_RealtimeLightingEnabled(cam)); } static void CamWnd_ToggleFilterBit(CCamWnd* cam, int bit) { int mask = CamWnd_GetFilterMask(cam); mask ^= bit; CamWnd_SetFilterMask(cam, mask); CamWnd_RequestRedraw(cam); } static void CamWnd_ResetFilters(CCamWnd* cam) { CamWnd_SetFilterMask(cam, 0); CamWnd_RequestRedraw(cam); } static void CamWnd_HandleMenuCommand(CCamWnd* cam, int command) { switch (command) { case CAMWND_MENU_CMD_REALTIME_LIGHTING: CamWnd_ToggleRealtimeLighting(cam); break; case CAMWND_MENU_CMD_SHOW_WORLD: CamWnd_ToggleFilterBit(cam, CAMWND_FILTER_HIDE_WORLD); break; case CAMWND_MENU_CMD_SHOW_PATCHES: CamWnd_ToggleFilterBit(cam, CAMWND_FILTER_HIDE_PATCHES); break; case CAMWND_MENU_CMD_SHOW_MODELS: CamWnd_ToggleFilterBit(cam, CAMWND_FILTER_HIDE_MODELS); break; case CAMWND_MENU_CMD_SHOW_ENTITIES: CamWnd_ToggleFilterBit(cam, CAMWND_FILTER_HIDE_ENTITIES); break; case CAMWND_MENU_CMD_SHOW_LIGHTS: CamWnd_ToggleFilterBit(cam, CAMWND_FILTER_HIDE_LIGHTS); break; case CAMWND_MENU_CMD_SHOW_TARGET_LINES: CamWnd_ToggleFilterBit(cam, CAMWND_FILTER_HIDE_TARGET_LINES); break; case CAMWND_MENU_CMD_SHOW_TRIGGER_BRUSHES: CamWnd_ToggleFilterBit(cam, CAMWND_FILTER_HIDE_TRIGGER_BRUSHES); break; case CAMWND_MENU_CMD_RESET_FILTERS: CamWnd_ResetFilters(cam); break; } } static void CamWnd_AppendVisibleFilterItem(HMENU menu, int mask, int bit, UINT command, const char* text) { UINT flags = MF_STRING; if ((mask & bit) == 0) { flags |= MF_CHECKED; } AppendMenu(menu, flags, command, text); } static void CamWnd_ShowLightingMenu(HWND hWnd, CCamWnd* cam) { RECT itemRect; POINT pt; HMENU menu; int command; CamWnd_GetMenuItemRect(hWnd, 0, itemRect); pt.x = itemRect.left; pt.y = itemRect.bottom; ClientToScreen(hWnd, &pt); menu = CreatePopupMenu(); AppendMenu(menu, MF_STRING | (CamWnd_RealtimeLightingEnabled(cam) ? MF_CHECKED : 0), CAMWND_MENU_CMD_REALTIME_LIGHTING, "Real-time lighting"); command = TrackPopupMenu(menu, TPM_RETURNCMD | TPM_LEFTALIGN | TPM_TOPALIGN, pt.x, pt.y, 0, hWnd, NULL); DestroyMenu(menu); if (command) { CamWnd_HandleMenuCommand(cam, command); } } static void CamWnd_ShowFiltersMenu(HWND hWnd, CCamWnd* cam) { RECT itemRect; POINT pt; HMENU menu; int command; int mask = CamWnd_GetFilterMask(cam); CamWnd_GetMenuItemRect(hWnd, 1, itemRect); pt.x = itemRect.left; pt.y = itemRect.bottom; ClientToScreen(hWnd, &pt); menu = CreatePopupMenu(); CamWnd_AppendVisibleFilterItem(menu, mask, CAMWND_FILTER_HIDE_WORLD, CAMWND_MENU_CMD_SHOW_WORLD, "Show world geometry"); CamWnd_AppendVisibleFilterItem(menu, mask, CAMWND_FILTER_HIDE_PATCHES, CAMWND_MENU_CMD_SHOW_PATCHES, "Show patches"); CamWnd_AppendVisibleFilterItem(menu, mask, CAMWND_FILTER_HIDE_MODELS, CAMWND_MENU_CMD_SHOW_MODELS, "Show models"); CamWnd_AppendVisibleFilterItem(menu, mask, CAMWND_FILTER_HIDE_ENTITIES, CAMWND_MENU_CMD_SHOW_ENTITIES, "Show entities"); CamWnd_AppendVisibleFilterItem(menu, mask, CAMWND_FILTER_HIDE_LIGHTS, CAMWND_MENU_CMD_SHOW_LIGHTS, "Show lights"); AppendMenu(menu, MF_SEPARATOR, 0, NULL); CamWnd_AppendVisibleFilterItem(menu, mask, CAMWND_FILTER_HIDE_TARGET_LINES, CAMWND_MENU_CMD_SHOW_TARGET_LINES, "Show actor/link lines"); CamWnd_AppendVisibleFilterItem(menu, mask, CAMWND_FILTER_HIDE_TRIGGER_BRUSHES, CAMWND_MENU_CMD_SHOW_TRIGGER_BRUSHES, "Show trigger/utility brushes"); AppendMenu(menu, MF_SEPARATOR, 0, NULL); AppendMenu(menu, MF_STRING, CAMWND_MENU_CMD_RESET_FILTERS, "Reset filters"); command = TrackPopupMenu(menu, TPM_RETURNCMD | TPM_LEFTALIGN | TPM_TOPALIGN, pt.x, pt.y, 0, hWnd, NULL); DestroyMenu(menu); if (command) { CamWnd_HandleMenuCommand(cam, command); } } static void CamWnd_ShowTopMenu(HWND hWnd, int item) { CCamWnd* cam = CamWnd_FromMenuBar(hWnd); if (!cam) { return; } if (item == 0) { CamWnd_ShowLightingMenu(hWnd, cam); } else if (item == 1) { CamWnd_ShowFiltersMenu(hWnd, cam); } } static void CamWnd_RegisterMenuBarClass() { static bool registered = false; if (registered) { return; } WNDCLASS wc; memset(&wc, 0, sizeof(wc)); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = CamWnd_MenuBarProc; wc.hInstance = AfxGetInstanceHandle(); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = CamWnd_DarkMenuBrush(); wc.lpszClassName = CAMWND_MENU_BAR_CLASS; AfxRegisterClass(&wc); registered = true; } static HWND CamWnd_EnsureMenuBar(CCamWnd* cam) { if (!cam || !cam->GetSafeHwnd()) { return NULL; } HWND hBar = CamWnd_GetMenuBar(cam); if (hBar) { return hBar; } CamWnd_RegisterMenuBarClass(); CRect rect; cam->GetClientRect(rect); hBar = CreateWindowEx( 0, CAMWND_MENU_BAR_CLASS, "", WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0, 0, rect.Width(), CAMWND_TOPBAR_HEIGHT, cam->GetSafeHwnd(), (HMENU)CAMWND_MENU_CHILD_ID, AfxGetInstanceHandle(), NULL); if (hBar) { SetProp(cam->GetSafeHwnd(), CAMWND_PROP_MENU_BAR, hBar); SendMessage(hBar, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), FALSE); } return hBar; } static void CamWnd_LayoutMenuBar(CCamWnd* cam) { if (!cam || !cam->GetSafeHwnd()) { return; } HWND hBar = CamWnd_EnsureMenuBar(cam); if (!hBar) { return; } CRect rect; cam->GetClientRect(rect); MoveWindow(hBar, 0, 0, rect.Width(), CAMWND_TOPBAR_HEIGHT, TRUE); } static void CamWnd_DestroyMenuBar(CCamWnd* cam) { if (!cam || !cam->GetSafeHwnd()) { return; } HWND hBar = CamWnd_GetMenuBar(cam); if (hBar) { DestroyWindow(hBar); } RemoveProp(cam->GetSafeHwnd(), CAMWND_PROP_MENU_BAR); RemoveProp(cam->GetSafeHwnd(), CAMWND_PROP_RENDER_MODE); RemoveProp(cam->GetSafeHwnd(), CAMWND_PROP_REBUILD_MODE); RemoveProp(cam->GetSafeHwnd(), CAMWND_PROP_ENTITY_MODE); RemoveProp(cam->GetSafeHwnd(), CAMWND_PROP_FILTER_MASK); } static bool CamWnd_PointInMenuBar(CCamWnd* cam, const CPoint& point) { if (!cam || !cam->GetSafeHwnd()) { return false; } return point.y >= 0 && point.y < CAMWND_TOPBAR_HEIGHT; } static void CamWnd_UpdateRuntimeState(CCamWnd* cam, bool renderMode, bool rebuildMode, bool entityMode) { if (!cam || !cam->GetSafeHwnd()) { return; } const HWND hWnd = cam->GetSafeHwnd(); CamWnd_SetIntProp(hWnd, CAMWND_PROP_RENDER_MODE, renderMode ? 1 : 0); CamWnd_SetIntProp(hWnd, CAMWND_PROP_REBUILD_MODE, rebuildMode ? 1 : 0); CamWnd_SetIntProp(hWnd, CAMWND_PROP_ENTITY_MODE, entityMode ? 1 : 0); HWND hBar = CamWnd_GetMenuBar(cam); if (hBar) { InvalidateRect(hBar, NULL, FALSE); } } static bool CamWnd_StringStartsWithNoCase(const char* text, const char* prefix) { if (text == NULL || prefix == NULL || prefix[0] == '\0') { return false; } return idStr::Icmpn(text, prefix, (int)strlen(prefix)) == 0; } static bool CamWnd_StringContainsNoCase(const char* text, const char* needle) { if (text == NULL || needle == NULL || needle[0] == '\0') { return false; } const int needleLen = (int)strlen(needle); for (const char* scan = text; *scan; scan++) { if (idStr::Icmpn(scan, needle, needleLen) == 0) { return true; } } return false; } static bool CamWnd_MaterialPathHasSegment(const char* materialName, const char* segment) { if (materialName == NULL || segment == NULL || segment[0] == '\0') { return false; } const int segmentLen = (int)strlen(segment); for (const char* scan = materialName; *scan; scan++) { const bool segmentStart = (scan == materialName || scan[-1] == '/' || scan[-1] == '\\'); if (!segmentStart) { continue; } if (idStr::Icmpn(scan, segment, segmentLen) != 0) { continue; } const char end = scan[segmentLen]; if (end == '\0' || end == '/' || end == '\\') { return true; } } return false; } static bool CamWnd_IsUtilityMaterial(const idMaterial* material) { if (material == NULL || material->GetName() == NULL || material->GetName()[0] == '\0') { return false; } const char* materialName = material->GetName(); // Nodraw is the important case here and may appear outside textures/common // in some projects, so match it anywhere in the material name. if (CamWnd_StringContainsNoCase(materialName, "nodraw")) { return true; } // The broader names below are only treated as utility when they live in a // common/editor tool-material folder. This avoids hiding ordinary art // materials that happen to contain words like "clip" or "portal". if (!CamWnd_MaterialPathHasSegment(materialName, "common") && !CamWnd_MaterialPathHasSegment(materialName, "editor")) { return false; } static const char* const utilityTokens[] = { "caulk", "trigger", "clip", "playerclip", "monsterclip", "moveclip", "missileclip", "aas", "ladder", "origin", "portal", "visportal", "hint", "skip", "collision" }; for (int i = 0; i < (int)(sizeof(utilityTokens) / sizeof(utilityTokens[0])); i++) { if (CamWnd_StringContainsNoCase(materialName, utilityTokens[i])) { return true; } } return false; } static bool CamWnd_BrushUsesOnlyUtilityMaterials(brush_t* brush) { if (brush == NULL) { return false; } if (brush->pPatch) { return CamWnd_IsUtilityMaterial(brush->pPatch->d_texture); } bool foundMaterial = false; for (face_t* face = brush->brush_faces; face; face = face->next) { if (face->face_winding == NULL) { continue; } if (face->d_texture == NULL) { return false; } foundMaterial = true; if (!CamWnd_IsUtilityMaterial(face->d_texture)) { return false; } } return foundMaterial; } static bool CamWnd_IsTriggerLikeBrushEntity(brush_t* brush) { if (brush == NULL || brush->owner == NULL) { return false; } const char* className = ValueForKey(brush->owner, "classname"); if (className == NULL || className[0] == '\0') { return false; } // These brush entities are editor/gameplay volumes rather than visible map // geometry, so let the camera filter hide them independently from normal // func_* brush models such as doors, movers, and func_static. return CamWnd_StringStartsWithNoCase(className, "trigger") || CamWnd_StringStartsWithNoCase(className, "func_trigger") || CamWnd_StringStartsWithNoCase(className, "func_clip") || CamWnd_StringStartsWithNoCase(className, "func_playerclip") || CamWnd_StringStartsWithNoCase(className, "func_monsterclip") || CamWnd_StringStartsWithNoCase(className, "clip") || CamWnd_StringStartsWithNoCase(className, "playerclip") || CamWnd_StringStartsWithNoCase(className, "monsterclip"); } static bool CamWnd_TargetLinesVisible(CCamWnd* cam) { return (CamWnd_GetFilterMask(cam) & CAMWND_FILTER_HIDE_TARGET_LINES) == 0; } static bool CamWnd_MenuFilterBrush(CCamWnd* cam, brush_t* brush) { if (!brush) { return false; } if (brush->owner != NULL && !Outliner_IsEntityVisible(brush->owner)) { return true; } if (!cam) { return false; } const int mask = CamWnd_GetFilterMask(cam); if (mask == 0) { return false; } const bool hasOwner = (brush->owner != NULL && brush->owner->eclass != NULL); const bool isLight = hasOwner && ((brush->owner->eclass->nShowFlags & ECLASS_LIGHT) != 0); const bool isPatch = (brush->pPatch != NULL); const bool isModel = (brush->modelHandle > 0) || (hasOwner && brush->owner->eclass->entityModel) || brush->entityModel; const bool isBrushModel = hasOwner && IsBModel(brush); const bool isFixedEntity = hasOwner && brush->owner->eclass->fixedsize; const bool isTriggerLikeBrushEntity = CamWnd_IsTriggerLikeBrushEntity(brush); const bool isUtilityMaterialBrush = CamWnd_BrushUsesOnlyUtilityMaterials(brush); const bool isEntity = isLight || isModel || isBrushModel || isFixedEntity; if ((mask & CAMWND_FILTER_HIDE_TRIGGER_BRUSHES) && (isTriggerLikeBrushEntity || isUtilityMaterialBrush)) { return true; } if ((mask & CAMWND_FILTER_HIDE_LIGHTS) && isLight) { return true; } if ((mask & CAMWND_FILTER_HIDE_MODELS) && isModel) { return true; } if ((mask & CAMWND_FILTER_HIDE_ENTITIES) && isEntity) { return true; } if ((mask & CAMWND_FILTER_HIDE_PATCHES) && isPatch) { return true; } if ((mask & CAMWND_FILTER_HIDE_WORLD) && !isEntity && !isPatch && !isModel) { return true; } return false; } /* ======================== Ctrl+Shift camera face extrusion Ctrl+Shift+LMB on a brush face starts a camera-view extrusion preview. The preview is only drawn while dragging. On mouse release a real brush is created from the selected face and linked to the same entity as the source brush. ======================== */ #define CAMWND_FACE_EXTRUDE_EPSILON 0.01f struct camFaceExtrudeState_t { bool active; bool moved; CCamWnd* cam; brush_t* sourceBrush; face_t* sourceFace; idWinding* sourceWinding; idVec3 normal; CPoint startPoint; float distance; }; static camFaceExtrudeState_t s_faceExtrudeState; static bool CamWnd_FaceExtrudeIsActive(CCamWnd* cam) { return s_faceExtrudeState.active && s_faceExtrudeState.cam == cam; } static void CamWnd_FaceExtrudeReset() { if (s_faceExtrudeState.sourceWinding) { delete s_faceExtrudeState.sourceWinding; } memset(&s_faceExtrudeState, 0, sizeof(s_faceExtrudeState)); } static float CamWnd_FaceExtrudeGridSize() { float gridSize = (float)g_qeglobals.d_gridsize; if (gridSize <= 0.0f) { gridSize = 1.0f; } return gridSize; } static float CamWnd_FaceExtrudeSnapDistance(float distance) { const float gridSize = CamWnd_FaceExtrudeGridSize(); if (idMath::Fabs(distance) < gridSize * 0.5f) { return 0.0f; } return floor(distance / gridSize + (distance >= 0.0f ? 0.5f : -0.5f)) * gridSize; } static void CamWnd_FaceExtrudeBuildRay(CCamWnd* cam, const CPoint& point, idVec3& dir) { CRect rect; cam->GetClientRect(rect); camera_t& camera = cam->Camera(); const int x = point.x; const int y = rect.bottom - 1 - point.y; float u = 0.0f; float r = 0.0f; if (camera.width > 0) { u = (float)(y - camera.height / 2) / (camera.width / 2); r = (float)(x - camera.width / 2) / (camera.width / 2); } for (int i = 0; i < 3; i++) { dir[i] = camera.vpn[i] + camera.vright[i] * r + camera.vup[i] * u; } dir.Normalize(); } static bool CamWnd_FaceExtrudeBrushAllowed(CCamWnd* cam, brush_t* brush) { if (brush == NULL || brush->owner == NULL || brush->owner->eclass == NULL) { return false; } if (brush->pPatch || brush->modelHandle > 0 || brush->entityModel) { return false; } if (brush->owner->eclass->fixedsize) { return false; } if (FilterBrush(brush) || CamWnd_MenuFilterBrush(cam, brush) || Map_IsBrushFiltered(brush)) { return false; } return true; } static bool CamWnd_FaceExtrudeBegin(CCamWnd* cam, const CPoint& point) { if (cam == NULL || !cam->GetSafeHwnd()) { return false; } idVec3 dir; CamWnd_FaceExtrudeBuildRay(cam, point, dir); qertrace_t trace = Test_Ray(cam->Camera().origin, dir, 0); if (trace.brush == NULL || trace.face == NULL || trace.face->face_winding == NULL) { return false; } if (trace.face->face_winding->GetNumPoints() < 3) { return false; } if (!CamWnd_FaceExtrudeBrushAllowed(cam, trace.brush)) { return false; } idVec3 normal = trace.face->plane.Normal(); if (normal.LengthSqr() < 0.0001f) { return false; } normal.Normalize(); CamWnd_FaceExtrudeReset(); s_faceExtrudeState.active = true; s_faceExtrudeState.moved = false; s_faceExtrudeState.cam = cam; s_faceExtrudeState.sourceBrush = trace.brush; s_faceExtrudeState.sourceFace = trace.face; s_faceExtrudeState.sourceWinding = trace.face->face_winding->Copy(); s_faceExtrudeState.normal = normal; s_faceExtrudeState.startPoint = point; s_faceExtrudeState.distance = 0.0f; cam->SetFocus(); cam->SetCapture(); Sys_Status("Drag to extrude face; release mouse to create new brush.\n"); Sys_UpdateWindows(W_CAMERA); return true; } static float CamWnd_FaceExtrudeMouseDistance(CCamWnd* cam, const CPoint& point) { const int dx = point.x - s_faceExtrudeState.startPoint.x; const int dy = point.y - s_faceExtrudeState.startPoint.y; camera_t& camera = cam->Camera(); idVec3 screenMove = camera.vright * (float)dx - camera.vup * (float)dy; float distance = DotProduct(screenMove, s_faceExtrudeState.normal); // When the selected face is nearly screen-facing, projection onto the normal // is too small to be useful. In that case use vertical mouse motion, with // the sign chosen so dragging up extrudes toward the camera-facing normal. float verticalDistance = (float)-dy; if (DotProduct(camera.vpn, s_faceExtrudeState.normal) > 0.0f) { verticalDistance = -verticalDistance; } if (idMath::Fabs(distance) < idMath::Fabs(verticalDistance) * 0.25f) { distance = verticalDistance; } return CamWnd_FaceExtrudeSnapDistance(distance); } static bool CamWnd_FaceExtrudeMouseMove(CCamWnd* cam, const CPoint& point) { if (!CamWnd_FaceExtrudeIsActive(cam)) { return false; } s_faceExtrudeState.distance = CamWnd_FaceExtrudeMouseDistance(cam, point); s_faceExtrudeState.moved = true; Sys_UpdateWindows(W_CAMERA | W_XY | W_Z); return true; } static void CamWnd_FaceExtrudeEnd(CCamWnd* cam, bool commit) { if (!CamWnd_FaceExtrudeIsActive(cam)) { return; } brush_t* sourceBrush = s_faceExtrudeState.sourceBrush; face_t* sourceFace = s_faceExtrudeState.sourceFace; float distance = s_faceExtrudeState.distance; if (::GetCapture() == cam->GetSafeHwnd()) { ::ReleaseCapture(); } if (commit && sourceBrush && sourceFace && idMath::Fabs(distance) >= CAMWND_FACE_EXTRUDE_EPSILON) { brush_t* newBrush = Brush_CreateFaceExtrusion(sourceBrush, sourceFace, distance); if (newBrush != NULL) { Brush_AddToList(newBrush, &selected_brushes); Entity_LinkBrush(sourceBrush->owner ? sourceBrush->owner : world_entity, newBrush); Brush_Build(newBrush, true, true); Brush_RemoveEmptyFaces(newBrush); if (newBrush->brush_faces == NULL) { Brush_Free(newBrush); Sys_Status("Face extrusion produced an empty brush.\n"); } else { Sys_Status("Face extrusion created a new brush.\n"); } } } CamWnd_FaceExtrudeReset(); Sys_UpdateWindows(W_ALL); } static void CamWnd_FaceExtrudeDrawPreview(CCamWnd* cam) { if (!CamWnd_FaceExtrudeIsActive(cam) || s_faceExtrudeState.sourceWinding == NULL) { return; } if (idMath::Fabs(s_faceExtrudeState.distance) < CAMWND_FACE_EXTRUDE_EPSILON) { return; } idWinding* w = s_faceExtrudeState.sourceWinding; const int numPoints = w->GetNumPoints(); if (numPoints < 3) { return; } idVec3 offset = s_faceExtrudeState.normal * s_faceExtrudeState.distance; glPushAttrib(GL_CURRENT_BIT); globalImages->BindNull(); glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); glDisable(GL_BLEND); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glLineWidth(2.0f); glColor3f(0.1f, 0.9f, 1.0f); glBegin(GL_LINES); for (int i = 0; i < numPoints; i++) { const int j = (i + 1) % numPoints; idVec3 a = (*w)[i].ToVec3(); idVec3 b = (*w)[j].ToVec3(); idVec3 a2 = a + offset; idVec3 b2 = b + offset; glVertex3fv(a.ToFloatPtr()); glVertex3fv(b.ToFloatPtr()); glVertex3fv(a2.ToFloatPtr()); glVertex3fv(b2.ToFloatPtr()); glVertex3fv(a.ToFloatPtr()); glVertex3fv(a2.ToFloatPtr()); } glEnd(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glColor4f(0.1f, 0.9f, 1.0f, 0.18f); glBegin(GL_QUADS); for (int i = 0; i < numPoints; i++) { idVec3 p = (*w)[i].ToVec3() + offset; glVertex3fv(p.ToFloatPtr()); } glEnd(); glBegin(GL_QUADS); for (int i = 0; i < numPoints; i++) { const int j = (i + 1) % numPoints; idVec3 a = (*w)[i].ToVec3(); idVec3 b = (*w)[j].ToVec3(); idVec3 a2 = a + offset; idVec3 b2 = b + offset; glVertex3fv(a.ToFloatPtr()); glVertex3fv(b.ToFloatPtr()); glVertex3fv(b2.ToFloatPtr()); glVertex3fv(a2.ToFloatPtr()); } glEnd(); glPopAttrib(); } static COLORREF CamWnd_LerpColor(COLORREF a, COLORREF b, float t) { const int ar = GetRValue(a); const int ag = GetGValue(a); const int ab = GetBValue(a); const int br = GetRValue(b); const int bg = GetGValue(b); const int bb = GetBValue(b); const int r = ar + (int)((br - ar) * t); const int g = ag + (int)((bg - ag) * t); const int bl = ab + (int)((bb - ab) * t); return RGB(r, g, bl); } static void CamWnd_FillHorizontalGradient(HDC hDC, const RECT& rect, COLORREF leftColor, COLORREF rightColor) { const int width = rect.right - rect.left; if (width <= 0) { return; } for (int x = 0; x < width; x++) { const float t = (width > 1) ? ((float)x / (float)(width - 1)) : 0.0f; const COLORREF color = CamWnd_LerpColor(leftColor, rightColor, t); HPEN pen = CreatePen(PS_SOLID, 1, color); HPEN oldPen = (HPEN)SelectObject(hDC, pen); MoveToEx(hDC, rect.left + x, rect.top, NULL); LineTo(hDC, rect.left + x, rect.bottom); SelectObject(hDC, oldPen); DeleteObject(pen); } } static LRESULT CALLBACK CamWnd_MenuBarProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_ERASEBKGND: return 1; case WM_SETCURSOR: SetCursor(LoadCursor(NULL, IDC_ARROW)); return TRUE; case WM_LBUTTONDOWN: SetCapture(hWnd); return 0; case WM_LBUTTONUP: { if (GetCapture() == hWnd) { ReleaseCapture(); } const int x = (short)LOWORD(lParam); const int y = (short)HIWORD(lParam); const int item = CamWnd_MenuHitTest(hWnd, x, y); if (item >= 0) { CamWnd_ShowTopMenu(hWnd, item); } return 0; } case WM_PAINT: { PAINTSTRUCT ps; HDC hDC = BeginPaint(hWnd, &ps); RECT rect; GetClientRect(hWnd, &rect); RECT captionRect = rect; captionRect.top = 0; captionRect.bottom = CAMWND_CAPTION_HEIGHT; RECT menuRect = rect; menuRect.top = CAMWND_CAPTION_HEIGHT; menuRect.bottom = CAMWND_TOPBAR_HEIGHT; CamWnd_FillHorizontalGradient(hDC, captionRect, RGB(20, 36, 62), RGB(28, 31, 36)); FillRect(hDC, &menuRect, CamWnd_DarkMenuBrush()); RECT dividerRect = rect; dividerRect.top = CAMWND_CAPTION_HEIGHT - 1; dividerRect.bottom = CAMWND_CAPTION_HEIGHT; FillRect(hDC, ÷rRect, CamWnd_DarkMenuBrush()); RECT lineRect = rect; lineRect.top = CAMWND_TOPBAR_HEIGHT - 1; lineRect.bottom = CAMWND_TOPBAR_HEIGHT; FillRect(hDC, &lineRect, CamWnd_DarkMenuBrush()); HFONT font = (HFONT)SendMessage(hWnd, WM_GETFONT, 0, 0); HFONT oldFont = NULL; if (font) { oldFont = (HFONT)SelectObject(hDC, font); } SetBkMode(hDC, TRANSPARENT); RECT captionTextRect = captionRect; captionTextRect.left += 8; captionTextRect.right -= 8; SetTextColor(hDC, CAMWND_DARK_TEXT); DrawText(hDC, "Viewport", -1, &captionTextRect, DT_SINGLELINE | DT_LEFT | DT_VCENTER | DT_END_ELLIPSIS); SetTextColor(hDC, CAMWND_DARK_TEXT); RECT itemRect; HBRUSH itemBrush = ::CreateSolidBrush(CAMWND_DARK_PANEL); HPEN borderPen = ::CreatePen(PS_SOLID, 1, CAMWND_DARK_BORDER); HBRUSH oldBrush = (HBRUSH)::SelectObject(hDC, itemBrush); HPEN oldPen = (HPEN)::SelectObject(hDC, borderPen); CamWnd_GetMenuItemRect(hWnd, 0, itemRect); RoundRect(hDC, itemRect.left + 3, itemRect.top + 2, itemRect.right - 3, itemRect.bottom - 2, 6, 6); DrawText(hDC, "Lighting", -1, &itemRect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); CamWnd_GetMenuItemRect(hWnd, 1, itemRect); RoundRect(hDC, itemRect.left + 3, itemRect.top + 2, itemRect.right - 3, itemRect.bottom - 2, 6, 6); DrawText(hDC, "Filters", -1, &itemRect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); ::SelectObject(hDC, oldPen); ::SelectObject(hDC, oldBrush); ::DeleteObject(borderPen); ::DeleteObject(itemBrush); if (oldFont) { SelectObject(hDC, oldFont); } EndPaint(hWnd, &ps); return 0; } } return DefWindowProc(hWnd, uMsg, wParam, lParam); } 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 (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: { CWnd* wnd = CWnd::FromHandlePermanent(hWnd); CCamWnd* cam = DYNAMIC_DOWNCAST(CCamWnd, wnd); if (cam) { CamWnd_FaceExtrudeEnd(cam, false); CameraNav_Stop(cam); } SendMessage(hWnd, WM_NCACTIVATE, FALSE, 0); return 0; } case WM_SETFOCUS: SendMessage(hWnd, WM_NCACTIVATE, TRUE, 0); return 0; case WM_MOUSEWHEEL: if (CameraNav_HandleMouseWheel(hWnd, wParam)) { return 0; } break; 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; } // Keep the OpenGL camera surface out of child-window areas such as the // embedded menu bar. cs.style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS; 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) { if (CamWnd_FaceExtrudeIsActive(this) && nChar == VK_ESCAPE) { CamWnd_FaceExtrudeEnd(this, false); return; } if (CameraNav_HandleKeyDown(this, nChar)) { return; } 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(); CamWnd_EnsureMenuBar(this); CamWnd_LayoutMenuBar(this); idGraphicsDeviceContextHelper context(dc.m_hDC, hglrc, true); 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() { CamWnd_FaceExtrudeEnd(this, false); CamWnd_DestroyMenuBar(this); 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 (CamWnd_FaceExtrudeMouseMove(this, point)) { m_ptLastCursor = point; return; } if (CameraNav_MouseMove(this)) { m_ptLastCursor = point; return; } if (GetCapture() == this && (GetAsyncKeyState(VK_MENU) & 0x8000)) { // Alt-drag texture manipulation. The old condition excluded shift/control // before checking them, so alt+shift and alt+control never reached the // scale/rotate paths and could interrupt normal shift-select drags. 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, false); } else { Select_ShiftTexture((float)point.x - m_ptLastCursor.x, (float)m_ptLastCursor.y - point.y, false); } } else { Cam_MouseMoved(point.x, r.bottom - 1 - point.y, nFlags); } m_ptLastCursor = point; } /* ======================================================================================================================= ======================================================================================================================= */ void CCamWnd::OnLButtonDown(UINT nFlags, CPoint point) { if (CamWnd_PointInMenuBar(this, point)) { return; } m_ptLastCursor = point; if (CameraNav_IsActive(this)) { int x = m_Camera.width / 2; int y = m_Camera.height / 2; Cam_MouseDown(x, y, MK_LBUTTON); Cam_MouseUp(x, y, 0); Sys_UpdateWindows(W_ALL); return; } if ((nFlags & MK_CONTROL) && (nFlags & MK_SHIFT) && CamWnd_FaceExtrudeBegin(this, point)) { return; } OriginalMouseDown(nFlags, point); } /* ======================================================================================================================= ======================================================================================================================= */ void CCamWnd::OnLButtonUp(UINT nFlags, CPoint point) { if (CamWnd_FaceExtrudeIsActive(this)) { CamWnd_FaceExtrudeMouseMove(this, point); CamWnd_FaceExtrudeEnd(this, true); return; } OriginalMouseUp(nFlags, point); } /* ======================================================================================================================= ======================================================================================================================= */ void CCamWnd::OnMButtonDown(UINT nFlags, CPoint point) { if (CamWnd_PointInMenuBar(this, point)) { return; } OriginalMouseDown(nFlags, point); } /* ======================================================================================================================= ======================================================================================================================= */ void CCamWnd::OnMButtonUp(UINT nFlags, CPoint point) { OriginalMouseUp(nFlags, point); } /* ======================================================================================================================= ======================================================================================================================= */ void CCamWnd::OnRButtonDown(UINT nFlags, CPoint point) { if (CamWnd_PointInMenuBar(this, point)) { return; } m_ptLastCursor = point; if (!(nFlags & (MK_SHIFT | MK_CONTROL)) && CameraNav_Begin(this)) { return; } OriginalMouseDown(nFlags, point); } /* ======================================================================================================================= ======================================================================================================================= */ void CCamWnd::OnRButtonUp(UINT nFlags, CPoint point) { if (CameraNav_IsActive(this)) { CameraNav_Stop(this); return; } 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)); CamWnd_UpdateRuntimeState(this, renderMode, rebuildMode, entityMode); CamWnd_EnsureMenuBar(this); CamWnd_LayoutMenuBar(this); 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); glEnable(GL_SCISSOR_TEST); 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 (CamWnd_MenuFilterBrush(this, 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 (CamWnd_MenuFilterBrush(this, brush)) { continue; } 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 (CamWnd_MenuFilterBrush(this, brush)) { continue; } 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 (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 (g_ptrSelectedFaces.GetAt(i)); Face_Draw(selFace); } } glColor3f(1, 1, 1); for (brush = pList->next; brush != pList; brush = brush->next) { if (CamWnd_MenuFilterBrush(this, brush)) { continue; } if (brush->pPatch || brush->modelHandle > 0) { continue; } for (face = brush->brush_faces; face; face = face->next) { Face_Draw(face); } } CamWnd_FaceExtrudeDrawPreview(this); // 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(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); if (CamWnd_TargetLinesVisible(this)) { CamWnd_DrawVisiblePathLines(); } 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(); CameraNav_DrawOverlay(this); glDisable(GL_SCISSOR_TEST); 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); CamWnd_EnsureMenuBar(this); CamWnd_LayoutMenuBar(this); m_Camera.width = rect.Width(); m_Camera.height = rect.Height() - CamWnd_GetMenuHeight(); if (m_Camera.height < 1) { m_Camera.height = 1; } InvalidateRect(NULL, false); } /* ======================================================================================================================= ======================================================================================================================= */ void CCamWnd::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) { if (CameraNav_HandleKeyUp(this, nChar)) { return; } 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 s_editorWorldBrushStates; static idList s_editorEntityStates; static idList s_editorBModelStates; static unsigned int EditorHashBytes(unsigned int hash, const void* data, int numBytes) { const unsigned char* bytes = reinterpret_cast(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(*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(CCamWnd* cam, 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) { if (FilterBrush(brush) || CamWnd_MenuFilterBrush(cam, brush) || Map_IsBrushFiltered(brush)) { continue; } 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(CCamWnd* cam, 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) { if (FilterBrush(brush) || CamWnd_MenuFilterBrush(cam, brush) || Map_IsBrushFiltered(brush)) { continue; } 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(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 (CamWnd_MenuFilterBrush(cam, 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) || CamWnd_MenuFilterBrush(this, 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(this, 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(this, 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(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 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" : ""; CamWnd_UpdateRuntimeState(this, renderMode, rebuildMode, entityMode); 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 (CamWnd_MenuFilterBrush(this, 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 ); glViewport(0, 0, m_Camera.width, m_Camera.height); glEnable(GL_SCISSOR_TEST); 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 glDisable(GL_SCISSOR_TEST); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); Cam_BuildMatrix(); } void CCamWnd::OnTimer(UINT_PTR nIDEvent) { if (nIDEvent == CAMERA_NAV_TIMER_ID) { CameraNav_Update(this); return; } 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; } }