mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-12 08:11:10 +02:00
4771 lines
128 KiB
C++
4771 lines
128 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
|
|
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 <GL/glu.h>
|
|
|
|
#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: <none>");
|
|
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: <none>");
|
|
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 <face_t*> (g_ptrSelectedFaces.GetAt(0));
|
|
if (g_axialAnchor >= selFace->face_winding->GetNumPoints()) {
|
|
g_axialAnchor = 0;
|
|
}
|
|
if (g_axialDest >= selFace->face_winding->GetNumPoints()) {
|
|
g_axialDest = 0;
|
|
}
|
|
}
|
|
else {
|
|
g_axialDest = 0;
|
|
g_axialAnchor = 0;
|
|
}
|
|
}
|
|
|
|
// CCamWnd
|
|
IMPLEMENT_DYNCREATE(CCamWnd, CWnd);
|
|
|
|
/*
|
|
=======================================================================================================================
|
|
=======================================================================================================================
|
|
*/
|
|
CCamWnd::CCamWnd() {
|
|
m_pXYFriend = NULL;
|
|
memset(&m_Camera, 0, sizeof(camera_t));
|
|
m_pSide_select = NULL;
|
|
m_bClipMode = false;
|
|
worldDirty = true;
|
|
worldModel = NULL;
|
|
worldModelDef = -1;
|
|
renderMode = false;
|
|
rebuildMode = false;
|
|
entityMode = false;
|
|
animationMode = false;
|
|
selectMode = false;
|
|
soundMode = false;
|
|
saveValid = false;
|
|
Cam_Init();
|
|
}
|
|
|
|
/*
|
|
=======================================================================================================================
|
|
=======================================================================================================================
|
|
*/
|
|
CCamWnd::~CCamWnd() {
|
|
}
|
|
|
|
BEGIN_MESSAGE_MAP(CCamWnd, CWnd)
|
|
//{{AFX_MSG_MAP(CCamWnd)
|
|
ON_WM_KEYDOWN()
|
|
ON_WM_PAINT()
|
|
ON_WM_DESTROY()
|
|
ON_WM_CLOSE()
|
|
ON_WM_MOUSEMOVE()
|
|
ON_WM_LBUTTONDOWN()
|
|
ON_WM_LBUTTONUP()
|
|
ON_WM_MBUTTONDOWN()
|
|
ON_WM_MBUTTONUP()
|
|
ON_WM_RBUTTONDOWN()
|
|
ON_WM_RBUTTONUP()
|
|
ON_WM_CREATE()
|
|
ON_WM_SIZE()
|
|
ON_WM_KEYUP()
|
|
ON_WM_NCCALCSIZE()
|
|
ON_WM_KILLFOCUS()
|
|
ON_WM_SETFOCUS()
|
|
ON_WM_TIMER()
|
|
//}}AFX_MSG_MAP
|
|
END_MESSAGE_MAP()
|
|
/*
|
|
=======================================================================================================================
|
|
=======================================================================================================================
|
|
*/
|
|
INT_PTR WINAPI CamWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
|
RECT rect;
|
|
|
|
GetClientRect(hWnd, &rect);
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_KILLFOCUS:
|
|
{
|
|
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 <face_t*>(g_ptrSelectedFaces.GetAt(i));
|
|
Face_Draw(selFace);
|
|
DrawAxial(selFace);
|
|
}
|
|
}
|
|
|
|
// non-zbuffered outline
|
|
glDisable(GL_BLEND);
|
|
glDisable(GL_DEPTH_TEST);
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
|
|
|
|
if (renderMode) {
|
|
glColor3f(1, 0, 0);
|
|
for (int i = 0; i < nCount; i++) {
|
|
face_t* selFace = reinterpret_cast <face_t*>(g_ptrSelectedFaces.GetAt(i));
|
|
Face_Draw(selFace);
|
|
}
|
|
}
|
|
|
|
glColor3f(1, 1, 1);
|
|
for (brush = pList->next; brush != pList; brush = brush->next) {
|
|
if (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<bool>(g_qeglobals.d_select_mode == sel_addpoint || g_qeglobals.d_select_mode == sel_editpoint));
|
|
|
|
if (g_qeglobals.selectObject && (g_qeglobals.d_select_mode == sel_addpoint || g_qeglobals.d_select_mode == sel_editpoint)) {
|
|
g_qeglobals.selectObject->drawSelection();
|
|
}
|
|
|
|
// draw pointfile
|
|
glEnable(GL_DEPTH_TEST);
|
|
|
|
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<editorRenderWorldBrushState_t> s_editorWorldBrushStates;
|
|
static idList<editorRenderEntityState_t> s_editorEntityStates;
|
|
static idList<editorRenderBModelState_t> s_editorBModelStates;
|
|
|
|
static unsigned int EditorHashBytes(unsigned int hash, const void* data, int numBytes) {
|
|
const unsigned char* bytes = reinterpret_cast<const unsigned char*>(data);
|
|
for (int i = 0; i < numBytes; i++) {
|
|
hash ^= bytes[i];
|
|
hash *= EDITOR_RENDER_HASH_MUL;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
static unsigned int EditorHashInt(unsigned int hash, int value) {
|
|
return EditorHashBytes(hash, &value, sizeof(value));
|
|
}
|
|
|
|
static int EditorQuantizeFloatForHash(float value) {
|
|
const float scale = 10000.0f;
|
|
return (int)(value * scale + (value >= 0.0f ? 0.5f : -0.5f));
|
|
}
|
|
|
|
static unsigned int EditorHashFloat(unsigned int hash, float value) {
|
|
const int quantized = EditorQuantizeFloatForHash(value);
|
|
return EditorHashInt(hash, quantized);
|
|
}
|
|
|
|
static unsigned int EditorHashString(unsigned int hash, const char* value) {
|
|
if (value == NULL) {
|
|
value = "";
|
|
}
|
|
while (*value) {
|
|
hash ^= static_cast<unsigned char>(*value++);
|
|
hash *= EDITOR_RENDER_HASH_MUL;
|
|
}
|
|
hash ^= 0;
|
|
hash *= EDITOR_RENDER_HASH_MUL;
|
|
return hash;
|
|
}
|
|
|
|
static unsigned int EditorHashMaterial(unsigned int hash, const idMaterial* material) {
|
|
return EditorHashString(hash, material ? material->GetName() : "");
|
|
}
|
|
|
|
static unsigned int EditorHashVec3(unsigned int hash, const idVec3& value) {
|
|
hash = EditorHashFloat(hash, value.x);
|
|
hash = EditorHashFloat(hash, value.y);
|
|
hash = EditorHashFloat(hash, value.z);
|
|
return hash;
|
|
}
|
|
|
|
static bool EditorVec3Changed(const idVec3& a, const idVec3& b) {
|
|
const float epsilon = 0.001f;
|
|
return idMath::Fabs(a.x - b.x) > epsilon ||
|
|
idMath::Fabs(a.y - b.y) > epsilon ||
|
|
idMath::Fabs(a.z - b.z) > epsilon;
|
|
}
|
|
|
|
static void EditorBrushRenderOrigin(brush_t* brush, idVec3& origin) {
|
|
origin[0] = (brush->mins[0] + brush->maxs[0]) * 0.5f;
|
|
origin[1] = (brush->mins[1] + brush->maxs[1]) * 0.5f;
|
|
origin[2] = (brush->mins[2] + brush->maxs[2]) * 0.5f;
|
|
}
|
|
|
|
static unsigned int EditorHashBrushGeometry(brush_t* brush, const idVec3& origin) {
|
|
unsigned int hash = EDITOR_RENDER_HASH_INIT;
|
|
|
|
hash = EditorHashInt(hash, brush->pPatch ? 1 : 0);
|
|
hash = EditorHashInt(hash, brush->modelHandle > 0 ? 1 : 0);
|
|
hash = EditorHashInt(hash, brush->entityModel ? 1 : 0);
|
|
|
|
if (brush->pPatch) {
|
|
patchMesh_t* pm = brush->pPatch;
|
|
|
|
hash = EditorHashInt(hash, pm->width);
|
|
hash = EditorHashInt(hash, pm->height);
|
|
hash = EditorHashInt(hash, pm->explicitSubdivisions ? 1 : 0);
|
|
hash = EditorHashInt(hash, pm->horzSubdivisions);
|
|
hash = EditorHashInt(hash, pm->vertSubdivisions);
|
|
hash = EditorHashMaterial(hash, pm->d_texture);
|
|
|
|
for (int i = 0; i < pm->width; i++) {
|
|
for (int j = 0; j < pm->height; j++) {
|
|
hash = EditorHashVec3(hash, pm->ctrl(i, j).xyz - origin);
|
|
hash = EditorHashFloat(hash, pm->ctrl(i, j).st.x);
|
|
hash = EditorHashFloat(hash, pm->ctrl(i, j).st.y);
|
|
}
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
for (face_t* face = brush->brush_faces; face; face = face->next) {
|
|
idWinding* w = face->face_winding;
|
|
if (!w) {
|
|
continue;
|
|
}
|
|
|
|
hash = EditorHashMaterial(hash, face->d_texture);
|
|
hash = EditorHashVec3(hash, face->plane.Normal());
|
|
hash = EditorHashInt(hash, w->GetNumPoints());
|
|
|
|
for (int i = 0; i < w->GetNumPoints(); i++) {
|
|
hash = EditorHashFloat(hash, (*w)[i][0] - origin[0]);
|
|
hash = EditorHashFloat(hash, (*w)[i][1] - origin[1]);
|
|
hash = EditorHashFloat(hash, (*w)[i][2] - origin[2]);
|
|
hash = EditorHashFloat(hash, (*w)[i][3]);
|
|
hash = EditorHashFloat(hash, (*w)[i][4]);
|
|
}
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
|
|
static unsigned int EditorHashBModelGeometry(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<renderEntity_t*>(g_qeglobals.rw->GetRenderEntity(modelDef));
|
|
if (!refent) {
|
|
return;
|
|
}
|
|
|
|
if (refent->callbackData) {
|
|
Mem_Free(refent->callbackData);
|
|
refent->callbackData = NULL;
|
|
}
|
|
|
|
if (refent->joints) {
|
|
Mem_Free16(refent->joints);
|
|
refent->joints = NULL;
|
|
}
|
|
}
|
|
|
|
static void EditorFreeRenderEntityDef(int& modelDef) {
|
|
if (modelDef < 0) {
|
|
return;
|
|
}
|
|
EditorFreeRenderEntityAuxData(modelDef);
|
|
g_qeglobals.rw->FreeEntityDef(modelDef);
|
|
modelDef = -1;
|
|
}
|
|
|
|
static void EditorFreeRenderLightDef(int& lightDef) {
|
|
if (lightDef < 0) {
|
|
return;
|
|
}
|
|
g_qeglobals.rw->FreeLightDef(lightDef);
|
|
lightDef = -1;
|
|
}
|
|
|
|
static void EditorUpdateOrAddEntityDef(int& modelDef, const renderEntity_t* refent) {
|
|
if (modelDef >= 0) {
|
|
EditorFreeRenderEntityAuxData(modelDef);
|
|
g_qeglobals.rw->UpdateEntityDef(modelDef, refent);
|
|
}
|
|
else {
|
|
modelDef = g_qeglobals.rw->AddEntityDef(refent);
|
|
}
|
|
}
|
|
|
|
static int EditorFindWorldBrushState(brush_t* brush) {
|
|
for (int i = 0; i < s_editorWorldBrushStates.Num(); i++) {
|
|
if (s_editorWorldBrushStates[i].brush == brush) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void EditorFreeWorldBrushState(int index) {
|
|
editorRenderWorldBrushState_t& state = s_editorWorldBrushStates[index];
|
|
|
|
EditorFreeRenderEntityDef(state.modelDef);
|
|
|
|
if (state.model) {
|
|
renderModelManager->FreeModel(state.model);
|
|
state.model = NULL;
|
|
}
|
|
|
|
s_editorWorldBrushStates.RemoveIndex(index);
|
|
}
|
|
|
|
static bool EditorWorldBrushRenderable(CCamWnd* cam, brush_t* brush) {
|
|
if (brush->hiddenBrush) {
|
|
return false;
|
|
}
|
|
if (FilterBrush(brush)) {
|
|
return false;
|
|
}
|
|
if (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<idRenderModelMD5*>(refent.hModel);
|
|
if (md5) {
|
|
idStr str;
|
|
spawnArgs.GetString("anim", "idle", str);
|
|
refent.numJoints = md5->NumJoints();
|
|
refent.joints = (idJointMat*)Mem_Alloc16(refent.numJoints * sizeof(*refent.joints));
|
|
const idMD5Anim* anim = gameEdit->ANIM_GetAnimFromEntityDef(spawnArgs.GetString("classname"), str);
|
|
int frame = spawnArgs.GetInt("frame") + 1;
|
|
if (frame < 1) {
|
|
frame = 1;
|
|
}
|
|
const idVec3& offset = gameEdit->ANIM_GetModelOffsetFromEntityDef(spawnArgs.GetString("classname"));
|
|
gameEdit->ANIM_CreateAnimFrame(md5, anim, refent.numJoints, refent.joints, (frame * 1000) / 24, offset, false);
|
|
}
|
|
|
|
EditorUpdateOrAddEntityModelDef(entityState, ent, &refent);
|
|
}
|
|
}
|
|
else {
|
|
EditorFreeEntityModelDef(entityState, ent);
|
|
EditorFreeBModelStateForEntity(ent);
|
|
}
|
|
|
|
// check for lightdefs
|
|
if (!(ent->eclass->nShowFlags & ECLASS_LIGHT) || spawnArgs.GetBool("start_off")) {
|
|
EditorFreeEntityLightDef(entityState, ent);
|
|
return;
|
|
}
|
|
|
|
// use the game's epair parsing code so
|
|
// we can use the same renderLight generation
|
|
renderLight_t lightParms;
|
|
gameEdit->ParseSpawnArgsToRenderLight(&spawnArgs, &lightParms);
|
|
lightParms.referenceSound = ent->soundEmitter;
|
|
|
|
EditorUpdateOrAddEntityLightDef(entityState, ent, &lightParms);
|
|
}
|
|
|
|
|
|
void Tris_ToOBJ(const char* outFile, idTriList* tris, idMatList* mats) {
|
|
idFile* f = fileSystem->OpenExplicitFileWrite(outFile);
|
|
if (f) {
|
|
char out[1024];
|
|
strcpy(out, outFile);
|
|
StripExtension(out);
|
|
|
|
idList<idStr*> matNames;
|
|
int i, j, k;
|
|
int indexBase = 1;
|
|
idStr lastMaterial("");
|
|
int matCount = 0;
|
|
//idStr basePath = cvarSystem->GetCVarString( "fs_savepath" );
|
|
f->Printf("mtllib %s.mtl\n", out);
|
|
for (i = 0; i < tris->Num(); i++) {
|
|
srfTriangles_t* tri = (*tris)[i];
|
|
for (j = 0; j < tri->numVerts; j++) {
|
|
f->Printf("v %f %f %f\n", tri->verts[j].xyz.x, tri->verts[j].xyz.z, -tri->verts[j].xyz.y);
|
|
}
|
|
for (j = 0; j < tri->numVerts; j++) {
|
|
f->Printf("vt %f %f\n", tri->verts[j].st.x, 1.0f - tri->verts[j].st.y);
|
|
}
|
|
for (j = 0; j < tri->numVerts; j++) {
|
|
f->Printf("vn %f %f %f\n", tri->verts[j].normal.x, tri->verts[j].normal.y, tri->verts[j].normal.z);
|
|
}
|
|
|
|
if (stricmp((*mats)[i]->GetName(), lastMaterial)) {
|
|
lastMaterial = (*mats)[i]->GetName();
|
|
|
|
bool found = false;
|
|
for (k = 0; k < matNames.Num(); k++) {
|
|
if (idStr::Icmp(matNames[k]->c_str(), lastMaterial.c_str()) == 0) {
|
|
found = true;
|
|
// f->Printf( "usemtl m%i\n", k );
|
|
f->Printf("usemtl %s\n", lastMaterial.c_str());
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
// f->Printf( "usemtl m%i\n", matCount++ );
|
|
f->Printf("usemtl %s\n", lastMaterial.c_str());
|
|
matNames.Append(new idStr(lastMaterial));
|
|
}
|
|
}
|
|
|
|
for (int j = 0; j < tri->numIndexes; j += 3) {
|
|
int i1, i2, i3;
|
|
i1 = tri->indexes[j + 2] + indexBase;
|
|
i2 = tri->indexes[j + 1] + indexBase;
|
|
i3 = tri->indexes[j] + indexBase;
|
|
f->Printf("f %i/%i/%i %i/%i/%i %i/%i/%i\n", i1, i1, i1, i2, i2, i2, i3, i3, i3);
|
|
}
|
|
|
|
indexBase += tri->numVerts;
|
|
|
|
}
|
|
fileSystem->CloseFile(f);
|
|
|
|
strcat(out, ".mtl");
|
|
f = fileSystem->OpenExplicitFileWrite(out);
|
|
if (f) {
|
|
for (k = 0; k < matNames.Num(); k++) {
|
|
// This presumes the diffuse tga name matches the material name
|
|
f->Printf("newmtl %s\n\tNs 0\n\td 1\n\tillum 2\n\tKd 0 0 0 \n\tKs 0.22 0.22 0.22 \n\tKa 0 0 0 \n\tmap_Kd %s/base/%s.tga\n\n\n", matNames[k]->c_str(), "z:/d3xp", matNames[k]->c_str());
|
|
}
|
|
fileSystem->CloseFile(f);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
int Brush_TransformModel(brush_t* brush, idTriList* tris, idMatList* mats) {
|
|
int ret = 0;
|
|
if (brush->modelHandle > 0) {
|
|
idRenderModel* model = brush->modelHandle;
|
|
if (model) {
|
|
float a = FloatForKey(brush->owner, "angle");
|
|
float s, c;
|
|
//FIXME: support full rotation matrix
|
|
bool matrix = false;
|
|
if (a) {
|
|
s = sin(DEG2RAD(a));
|
|
c = cos(DEG2RAD(a));
|
|
}
|
|
idMat3 mat;
|
|
if (GetMatrixForKey(brush->owner, "rotation", mat)) {
|
|
matrix = true;
|
|
}
|
|
|
|
|
|
for (int i = 0; i < model->NumSurfaces(); i++) {
|
|
const modelSurface_t* surf = model->Surface(i);
|
|
srfTriangles_t* tri = surf->geometry;
|
|
srfTriangles_t* tri2 = R_CopyStaticTriSurf(tri);
|
|
for (int j = 0; j < tri2->numVerts; j++) {
|
|
idVec3 v;
|
|
if (matrix) {
|
|
v = tri2->verts[j].xyz * brush->owner->rotation + brush->owner->origin;
|
|
}
|
|
else {
|
|
v = tri2->verts[j].xyz;
|
|
VectorAdd(v, brush->owner->origin, v);
|
|
float x = v[0];
|
|
float y = v[1];
|
|
if (a) {
|
|
float x2 = (((x - brush->owner->origin[0]) * c) - ((y - brush->owner->origin[1]) * s)) + brush->owner->origin[0];
|
|
float y2 = (((x - brush->owner->origin[0]) * s) + ((y - brush->owner->origin[1]) * c)) + brush->owner->origin[1];
|
|
x = x2;
|
|
y = y2;
|
|
}
|
|
v[0] = x;
|
|
v[1] = y;
|
|
}
|
|
tri2->verts[j].xyz = v;
|
|
}
|
|
tris->Append(tri2);
|
|
mats->Append(surf->shader);
|
|
}
|
|
return model->NumSurfaces();
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
#define MAX_TRI_SURFACES 16384
|
|
int Brush_ToTris(brush_t* brush, idTriList* tris, idMatList* mats, bool models, bool bmodel) {
|
|
int i, j;
|
|
srfTriangles_t* tri;
|
|
//
|
|
// patches
|
|
//
|
|
if (brush->modelHandle > 0) {
|
|
if (!models) {
|
|
return 0;
|
|
}
|
|
else {
|
|
return Brush_TransformModel(brush, tris, mats);
|
|
}
|
|
}
|
|
|
|
int numSurfaces = 0;
|
|
|
|
if (brush->owner->eclass->fixedsize && !brush->entityModel) {
|
|
return NULL;
|
|
}
|
|
|
|
if (brush->pPatch) {
|
|
patchMesh_t* pm;
|
|
int width, height;
|
|
|
|
pm = brush->pPatch;
|
|
|
|
// build a patch mesh
|
|
idSurface_Patch* cp = new idSurface_Patch(pm->width * 6, pm->height * 6);
|
|
cp->SetSize(pm->width, pm->height);
|
|
for (i = 0; i < pm->width; i++) {
|
|
for (j = 0; j < pm->height; j++) {
|
|
(*cp)[j * cp->GetWidth() + i].xyz = pm->ctrl(i, j).xyz;
|
|
(*cp)[j * cp->GetWidth() + i].st = pm->ctrl(i, j).st;
|
|
}
|
|
}
|
|
|
|
// subdivide it
|
|
if (pm->explicitSubdivisions) {
|
|
cp->SubdivideExplicit(pm->horzSubdivisions, pm->vertSubdivisions, true);
|
|
}
|
|
else {
|
|
cp->Subdivide(DEFAULT_CURVE_MAX_ERROR, DEFAULT_CURVE_MAX_ERROR, DEFAULT_CURVE_MAX_LENGTH, true);
|
|
}
|
|
width = cp->GetWidth();
|
|
height = cp->GetHeight();
|
|
|
|
// convert to srfTriangles
|
|
tri = R_AllocStaticTriSurf();
|
|
tri->numVerts = width * height;
|
|
tri->numIndexes = 6 * (width - 1) * (height - 1);
|
|
R_AllocStaticTriSurfVerts(tri, tri->numVerts);
|
|
R_AllocStaticTriSurfIndexes(tri, tri->numIndexes);
|
|
for (i = 0; i < tri->numVerts; i++) {
|
|
tri->verts[i] = (*cp)[i];
|
|
if (bmodel) {
|
|
tri->verts[i].xyz -= brush->owner->origin;
|
|
}
|
|
}
|
|
|
|
tri->numIndexes = 0;
|
|
for (i = 1; i < width; i++) {
|
|
for (j = 1; j < height; j++) {
|
|
tri->indexes[tri->numIndexes++] = (j - 1) * width + i;
|
|
tri->indexes[tri->numIndexes++] = (j - 1) * width + i - 1;
|
|
tri->indexes[tri->numIndexes++] = j * width + i - 1;
|
|
|
|
tri->indexes[tri->numIndexes++] = j * width + i;
|
|
tri->indexes[tri->numIndexes++] = (j - 1) * width + i;
|
|
tri->indexes[tri->numIndexes++] = j * width + i - 1;
|
|
}
|
|
}
|
|
|
|
delete cp;
|
|
|
|
tris->Append(tri);
|
|
mats->Append(pm->d_texture);
|
|
//surfaces[numSurfaces] = tri;
|
|
//materials[numSurfaces] = pm->d_texture;
|
|
return 1;
|
|
}
|
|
|
|
//
|
|
// normal brush
|
|
//
|
|
for (face_t* face = brush->brush_faces; face; face = face->next) {
|
|
idWinding* w;
|
|
|
|
w = face->face_winding;
|
|
if (!w) {
|
|
continue; // freed or degenerate face
|
|
}
|
|
|
|
tri = R_AllocStaticTriSurf();
|
|
tri->numVerts = w->GetNumPoints();
|
|
tri->numIndexes = (w->GetNumPoints() - 2) * 3;
|
|
R_AllocStaticTriSurfVerts(tri, tri->numVerts);
|
|
R_AllocStaticTriSurfIndexes(tri, tri->numIndexes);
|
|
|
|
for (i = 0; i < tri->numVerts; i++) {
|
|
|
|
tri->verts[i].Clear();
|
|
|
|
tri->verts[i].xyz[0] = (*w)[i][0];
|
|
tri->verts[i].xyz[1] = (*w)[i][1];
|
|
tri->verts[i].xyz[2] = (*w)[i][2];
|
|
|
|
if (bmodel) {
|
|
tri->verts[i].xyz -= brush->owner->origin;
|
|
}
|
|
|
|
tri->verts[i].st[0] = (*w)[i][3];
|
|
tri->verts[i].st[1] = (*w)[i][4];
|
|
|
|
tri->verts[i].normal = face->plane.Normal();
|
|
}
|
|
|
|
tri->numIndexes = 0;
|
|
for (i = 2; i < w->GetNumPoints(); i++) {
|
|
tri->indexes[tri->numIndexes++] = 0;
|
|
tri->indexes[tri->numIndexes++] = i - 1;
|
|
tri->indexes[tri->numIndexes++] = i;
|
|
}
|
|
|
|
tris->Append(tri);
|
|
mats->Append(face->d_texture);
|
|
numSurfaces++;
|
|
}
|
|
|
|
return numSurfaces;
|
|
}
|
|
|
|
void Select_ToOBJ() {
|
|
int i;
|
|
CFileDialog dlgFile(FALSE, "obj", NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, "Wavefront object files (*.obj)|*.obj||", g_pParentWnd);
|
|
if (dlgFile.DoModal() == IDOK) {
|
|
idTriList tris(1024);
|
|
idMatList mats(1024);
|
|
|
|
for (brush_t* b = selected_brushes.next; b != &selected_brushes; b = b->next) {
|
|
|
|
if (b->hiddenBrush) {
|
|
continue;
|
|
}
|
|
|
|
if (FilterBrush(b)) {
|
|
continue;
|
|
}
|
|
|
|
Brush_ToTris(b, &tris, &mats, true, false);
|
|
}
|
|
|
|
Tris_ToOBJ(dlgFile.GetPathName().GetBuffer(0), &tris, &mats);
|
|
|
|
for (i = 0; i < tris.Num(); i++) {
|
|
R_FreeStaticTriSurf(tris[i]);
|
|
}
|
|
tris.Clear();
|
|
}
|
|
}
|
|
|
|
void Select_ToCM() {
|
|
CFileDialog dlgFile(FALSE, "lwo, ase", NULL, 0, "(*.lwo)|*.lwo|(*.ase)|*.ase|(*.ma)|*.ma||", g_pParentWnd);
|
|
|
|
if (dlgFile.DoModal() == IDOK) {
|
|
idMapEntity* mapEnt;
|
|
idMapPrimitive* p;
|
|
idStr name;
|
|
|
|
name = fileSystem->OSPathToRelativePath(dlgFile.GetPathName());
|
|
name.BackSlashesToSlashes();
|
|
|
|
mapEnt = new idMapEntity();
|
|
mapEnt->epairs.Set("name", name.c_str());
|
|
|
|
for (brush_t* b = selected_brushes.next; b != &selected_brushes; b = b->next) {
|
|
|
|
if (b->hiddenBrush) {
|
|
continue;
|
|
}
|
|
|
|
if (FilterBrush(b)) {
|
|
continue;
|
|
}
|
|
|
|
p = BrushToMapPrimitive(b, b->owner->origin);
|
|
if (p) {
|
|
mapEnt->AddPrimitive(p);
|
|
}
|
|
}
|
|
|
|
collisionModelManager->WriteCollisionModelForMapEntity(mapEnt, name.c_str());
|
|
|
|
delete mapEnt;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
BuildRendererState
|
|
|
|
Builds models, lightdefs, and modeldefs for the current editor data
|
|
so it can be rendered by the game renderSystem
|
|
=================
|
|
*/
|
|
void CCamWnd::BuildRendererState() {
|
|
entity_t* ent;
|
|
brush_t* brush;
|
|
|
|
if (!s_editorRenderWorldInitialized) {
|
|
// First build, or after FreeRendererState(). Do not do this on every
|
|
// dirty frame; it drops all defs from the renderWorld.
|
|
g_qeglobals.rw->InitFromMap(NULL);
|
|
s_editorRenderWorldInitialized = true;
|
|
}
|
|
|
|
// Compatibility cleanup for any old monolithic world model that may still
|
|
// exist from a previous build path.
|
|
if (worldModelDef >= 0) {
|
|
g_qeglobals.rw->FreeEntityDef(worldModelDef);
|
|
worldModelDef = -1;
|
|
}
|
|
|
|
if (worldModel) {
|
|
renderModelManager->FreeModel(worldModel);
|
|
worldModel = NULL;
|
|
}
|
|
|
|
EditorBeginWorldBrushReconcile();
|
|
|
|
for (brush_t* brushList = &active_brushes; brushList;
|
|
brushList = (brushList == &active_brushes) ? &selected_brushes : NULL) {
|
|
|
|
for (brush = brushList->next; brush != brushList; brush = brush->next) {
|
|
EditorReconcileWorldBrushState(this, brush);
|
|
}
|
|
}
|
|
|
|
EditorPurgeUntouchedWorldBrushStates();
|
|
|
|
// Create/update/remove the light and model entities exactly the way the
|
|
// game code would, but without destroying unrelated renderWorld defs.
|
|
EditorBeginEntityReconcile();
|
|
|
|
for (ent = entities.next; ent != &entities; ent = ent->next) {
|
|
BuildEntityRenderState(ent, true);
|
|
}
|
|
|
|
EditorPurgeUntouchedEntityStates();
|
|
EditorPurgeUntouchedBModelStates();
|
|
|
|
worldDirty = false;
|
|
}
|
|
|
|
/*
|
|
===============================
|
|
CCamWnd::UpdateRenderEntities
|
|
|
|
Creates a new entity state list
|
|
returns true if a repaint is needed
|
|
===============================
|
|
*/
|
|
bool CCamWnd::UpdateRenderEntities() {
|
|
|
|
if (rebuildMode) {
|
|
return false;
|
|
}
|
|
|
|
if (!s_editorRenderWorldInitialized) {
|
|
g_qeglobals.rw->InitFromMap(NULL);
|
|
s_editorRenderWorldInitialized = true;
|
|
}
|
|
|
|
bool ret = false;
|
|
EditorBeginEntityReconcile();
|
|
|
|
for (entity_t* ent = entities.next; ent != &entities; ent = ent->next) {
|
|
BuildEntityRenderState(ent, true);
|
|
if (!ret && (ent->modelDef >= 0 || ent->lightDef >= 0)) {
|
|
ret = true;
|
|
}
|
|
}
|
|
|
|
EditorPurgeUntouchedEntityStates();
|
|
EditorPurgeUntouchedBModelStates();
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
============================
|
|
CCamWnd::FreeRendererState
|
|
|
|
Frees the render state data
|
|
============================
|
|
*/
|
|
void CCamWnd::FreeRendererState() {
|
|
|
|
for (int i = s_editorEntityStates.Num() - 1; i >= 0; i--) {
|
|
EditorFreeRenderEntityDef(s_editorEntityStates[i].modelDef);
|
|
EditorFreeRenderLightDef(s_editorEntityStates[i].lightDef);
|
|
s_editorEntityStates.RemoveIndex(i);
|
|
}
|
|
|
|
if (entities.next != NULL)
|
|
{
|
|
for (entity_t* ent = entities.next; ent != &entities; ent = ent->next) {
|
|
ent->modelDef = -1;
|
|
ent->lightDef = -1;
|
|
}
|
|
}
|
|
|
|
|
|
for (int i = s_editorBModelStates.Num() - 1; i >= 0; i--) {
|
|
EditorFreeBModelState(i);
|
|
}
|
|
|
|
for (int i = s_editorWorldBrushStates.Num() - 1; i >= 0; i--) {
|
|
EditorFreeWorldBrushState(i);
|
|
}
|
|
|
|
if (worldModelDef >= 0) {
|
|
g_qeglobals.rw->FreeEntityDef(worldModelDef);
|
|
worldModelDef = -1;
|
|
}
|
|
|
|
if (worldModel) {
|
|
renderModelManager->FreeModel(worldModel);
|
|
worldModel = NULL;
|
|
}
|
|
|
|
s_editorRenderWorldInitialized = false;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
========================
|
|
CCamWnd::UpdateCaption
|
|
|
|
updates the caption based on rendermode and whether the render mode needs updated
|
|
========================
|
|
*/
|
|
void CCamWnd::UpdateCaption() {
|
|
|
|
idStr strCaption;
|
|
|
|
if (worldDirty) {
|
|
strCaption = "*";
|
|
}
|
|
// FIXME:
|
|
strCaption += (renderMode) ? "RENDER" : "CAM";
|
|
if (renderMode) {
|
|
strCaption += (rebuildMode) ? " (Realtime)" : "";
|
|
strCaption += (entityMode) ? " +lights" : "";
|
|
strCaption += (selectMode) ? " +selected" : "";
|
|
strCaption += (animationMode) ? " +anim" : "";
|
|
}
|
|
strCaption += (soundMode) ? " +snd" : "";
|
|
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;
|
|
}
|
|
}
|
|
|