mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-12 16:21:04 +02:00
76f642286e
Added neural network code(turned off by default). Lots of editor and rendering fixes.
3004 lines
80 KiB
C++
3004 lines
80 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
IceTech GPL Source Code
|
|
Copyright (C) 2026 Justin Marshall
|
|
|
|
This file is part of the IceTech GPL Source Code.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "qe3.h"
|
|
#include "Radiant.h"
|
|
#include "ModelViewDock.h"
|
|
|
|
#include "../../renderer/tr_local.h"
|
|
|
|
#ifndef GL_CLAMP_TO_EDGE
|
|
#define GL_CLAMP_TO_EDGE 0x812F
|
|
#endif
|
|
|
|
#ifndef TVM_SETLINECOLOR
|
|
#define TVM_SETLINECOLOR (TV_FIRST + 40)
|
|
#endif
|
|
|
|
#ifndef BS_TYPEMASK
|
|
#define BS_TYPEMASK 0x0000000F
|
|
#endif
|
|
|
|
static const UINT MODELVIEW_TIMER_ID = 0x7A90;
|
|
static const UINT MODELVIEW_TIMER_MS = 16;
|
|
|
|
static const COLORREF MV_DARK_BG = RGB(17, 19, 23);
|
|
static const COLORREF MV_DARK_PANEL = RGB(23, 26, 31);
|
|
static const COLORREF MV_DARK_INPUT = RGB(14, 16, 20);
|
|
static const COLORREF MV_DARK_BUTTON = RGB(31, 35, 41);
|
|
static const COLORREF MV_DARK_BUTTON_D = RGB(22, 25, 30);
|
|
static const COLORREF MV_DARK_MENU = RGB(28, 31, 36);
|
|
static const COLORREF MV_DARK_MENU_HOT = RGB(45, 50, 58);
|
|
static const COLORREF MV_DARK_BORDER = RGB(58, 64, 72);
|
|
static const COLORREF MV_DARK_TEXT = RGB(226, 232, 240);
|
|
static const COLORREF MV_DARK_MUTED = RGB(148, 163, 184);
|
|
static const COLORREF MV_DARK_ACCENT = RGB(87, 166, 255);
|
|
|
|
static const char* MV_DARK_BUTTON_OLDPROC = "IceTech.ModelView.DarkButtonOldProc";
|
|
static const char* MV_DARK_STATIC_OLDPROC = "IceTech.ModelView.DarkStaticOldProc";
|
|
static const char* MV_DARK_COMBO_OLDPROC = "IceTech.ModelView.DarkComboOldProc";
|
|
|
|
struct modelViewAnimItem_t {
|
|
int declAnimIndex;
|
|
int md5AnimIndex;
|
|
const idAnimInterface* declAnim;
|
|
const idMD5AnimInterface* md5Anim;
|
|
int lengthMS;
|
|
int numFrames;
|
|
CString displayText;
|
|
};
|
|
|
|
static HBRUSH ModelViewBgBrush() {
|
|
static HBRUSH brush = ::CreateSolidBrush(MV_DARK_BG);
|
|
return brush;
|
|
}
|
|
|
|
static HBRUSH ModelViewPanelBrush() {
|
|
static HBRUSH brush = ::CreateSolidBrush(MV_DARK_PANEL);
|
|
return brush;
|
|
}
|
|
|
|
static HBRUSH ModelViewInputBrush() {
|
|
static HBRUSH brush = ::CreateSolidBrush(MV_DARK_INPUT);
|
|
return brush;
|
|
}
|
|
|
|
static HBRUSH ModelViewMenuBrush() {
|
|
static HBRUSH brush = ::CreateSolidBrush(MV_DARK_MENU);
|
|
return brush;
|
|
}
|
|
|
|
static void MV_FillRect(HDC hDC, const RECT& rc, COLORREF color) {
|
|
HBRUSH brush = ::CreateSolidBrush(color);
|
|
::FillRect(hDC, &rc, brush);
|
|
::DeleteObject(brush);
|
|
}
|
|
|
|
static void MV_FrameRect(HDC hDC, const RECT& rc, COLORREF color) {
|
|
HBRUSH brush = ::CreateSolidBrush(color);
|
|
::FrameRect(hDC, &rc, brush);
|
|
::DeleteObject(brush);
|
|
}
|
|
|
|
static void MV_FrameRect(CDC* dc, const CRect& rc, COLORREF color) {
|
|
if (!dc) {
|
|
return;
|
|
}
|
|
MV_FrameRect(dc->m_hDC, rc, color);
|
|
}
|
|
|
|
static int MV_MaxInt(int a, int b) {
|
|
return (a > b) ? a : b;
|
|
}
|
|
|
|
static int MV_MinInt(int a, int b) {
|
|
return (a < b) ? a : b;
|
|
}
|
|
|
|
static float MV_ClampFloat(float value, float lo, float hi) {
|
|
if (value < lo) {
|
|
return lo;
|
|
}
|
|
if (value > hi) {
|
|
return hi;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
|
|
static int MV_ClampInt(int value, int lo, int hi) {
|
|
if (value < lo) {
|
|
return lo;
|
|
}
|
|
if (value > hi) {
|
|
return hi;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
static CString MV_FormatAnimTime(int timeMS) {
|
|
if (timeMS < 0) {
|
|
timeMS = 0;
|
|
}
|
|
const int minutes = timeMS / 60000;
|
|
const int seconds = (timeMS / 1000) % 60;
|
|
const int millis = timeMS % 1000;
|
|
|
|
CString text;
|
|
text.Format("%d:%02d.%03d", minutes, seconds, millis);
|
|
return text;
|
|
}
|
|
|
|
static void MV_ApplyNativeTheme(HWND hWnd) {
|
|
if (!hWnd) {
|
|
return;
|
|
}
|
|
typedef HRESULT(WINAPI* SetWindowThemeProc)(HWND, LPCWSTR, LPCWSTR);
|
|
typedef BOOL(WINAPI* AllowDarkModeForWindowProc)(HWND, BOOL);
|
|
static HMODULE hUxTheme = ::LoadLibraryA("uxtheme.dll");
|
|
static SetWindowThemeProc pSetWindowTheme = hUxTheme ? (SetWindowThemeProc)::GetProcAddress(hUxTheme, "SetWindowTheme") : NULL;
|
|
static AllowDarkModeForWindowProc pAllowDarkModeForWindow = hUxTheme ? (AllowDarkModeForWindowProc)::GetProcAddress(hUxTheme, MAKEINTRESOURCEA(133)) : NULL;
|
|
|
|
if (pAllowDarkModeForWindow) {
|
|
pAllowDarkModeForWindow(hWnd, TRUE);
|
|
}
|
|
if (pSetWindowTheme) {
|
|
pSetWindowTheme(hWnd, L"DarkMode_Explorer", NULL);
|
|
}
|
|
::SendMessage(hWnd, WM_THEMECHANGED, 0, 0);
|
|
}
|
|
|
|
static void MV_DrawButton(HWND hWnd, HDC hDC) {
|
|
RECT rc;
|
|
::GetClientRect(hWnd, &rc);
|
|
|
|
const UINT style = (UINT)::GetWindowLong(hWnd, GWL_STYLE);
|
|
const UINT type = style & BS_TYPEMASK;
|
|
const bool isCheck = (type == BS_CHECKBOX || type == BS_AUTOCHECKBOX || type == BS_3STATE || type == BS_AUTO3STATE);
|
|
const bool enabled = ::IsWindowEnabled(hWnd) ? true : false;
|
|
const bool pushed = (::SendMessage(hWnd, BM_GETSTATE, 0, 0) & BST_PUSHED) != 0;
|
|
const bool checked = (::SendMessage(hWnd, BM_GETCHECK, 0, 0) == BST_CHECKED);
|
|
|
|
char text[256];
|
|
text[0] = '\0';
|
|
::GetWindowTextA(hWnd, text, sizeof(text));
|
|
|
|
HFONT font = (HFONT)::SendMessage(hWnd, WM_GETFONT, 0, 0);
|
|
HFONT oldFont = font ? (HFONT)::SelectObject(hDC, font) : NULL;
|
|
::SetBkMode(hDC, TRANSPARENT);
|
|
::SetTextColor(hDC, enabled ? MV_DARK_TEXT : RGB(101, 110, 126));
|
|
|
|
if (isCheck) {
|
|
MV_FillRect(hDC, rc, MV_DARK_PANEL);
|
|
RECT box = rc;
|
|
box.left += 3;
|
|
box.right = box.left + 14;
|
|
box.top += MV_MaxInt(0, (rc.bottom - rc.top - 14) / 2);
|
|
box.bottom = box.top + 14;
|
|
MV_FillRect(hDC, box, MV_DARK_INPUT);
|
|
MV_FrameRect(hDC, box, checked ? MV_DARK_ACCENT : MV_DARK_BORDER);
|
|
if (checked) {
|
|
HPEN pen = ::CreatePen(PS_SOLID, 2, MV_DARK_ACCENT);
|
|
HPEN oldPen = (HPEN)::SelectObject(hDC, pen);
|
|
::MoveToEx(hDC, box.left + 3, box.top + 7, NULL);
|
|
::LineTo(hDC, box.left + 6, box.top + 10);
|
|
::LineTo(hDC, box.right - 3, box.top + 3);
|
|
::SelectObject(hDC, oldPen);
|
|
::DeleteObject(pen);
|
|
}
|
|
RECT textRect = rc;
|
|
textRect.left = box.right + 7;
|
|
::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
|
|
}
|
|
else {
|
|
MV_FillRect(hDC, rc, pushed ? MV_DARK_BUTTON_D : MV_DARK_BUTTON);
|
|
MV_FrameRect(hDC, rc, enabled ? MV_DARK_BORDER : RGB(39, 44, 52));
|
|
RECT textRect = rc;
|
|
if (pushed) {
|
|
::OffsetRect(&textRect, 1, 1);
|
|
}
|
|
::DrawTextA(hDC, text, -1, &textRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
|
|
}
|
|
|
|
if (oldFont) {
|
|
::SelectObject(hDC, oldFont);
|
|
}
|
|
}
|
|
|
|
static LRESULT CALLBACK MVDarkButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
|
WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, MV_DARK_BUTTON_OLDPROC);
|
|
if (!oldProc) {
|
|
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
switch (uMsg) {
|
|
case WM_ERASEBKGND:
|
|
return 1;
|
|
case WM_PAINT:
|
|
{
|
|
PAINTSTRUCT ps;
|
|
HDC hDC = ::BeginPaint(hWnd, &ps);
|
|
MV_DrawButton(hWnd, hDC);
|
|
::EndPaint(hWnd, &ps);
|
|
return 0;
|
|
}
|
|
case WM_PRINTCLIENT:
|
|
MV_DrawButton(hWnd, (HDC)wParam);
|
|
return 0;
|
|
case WM_MOUSEMOVE:
|
|
case WM_LBUTTONDOWN:
|
|
case WM_LBUTTONUP:
|
|
case WM_ENABLE:
|
|
case WM_SETTEXT:
|
|
case BM_SETCHECK:
|
|
case BM_SETSTATE:
|
|
{
|
|
LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam);
|
|
::InvalidateRect(hWnd, NULL, FALSE);
|
|
return result;
|
|
}
|
|
case WM_NCDESTROY:
|
|
{
|
|
::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc);
|
|
::RemovePropA(hWnd, MV_DARK_BUTTON_OLDPROC);
|
|
return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam);
|
|
}
|
|
}
|
|
|
|
return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
static void MV_SubclassButton(CWnd& wnd) {
|
|
HWND hWnd = wnd.GetSafeHwnd();
|
|
if (!hWnd || ::GetPropA(hWnd, MV_DARK_BUTTON_OLDPROC)) {
|
|
return;
|
|
}
|
|
WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC);
|
|
::SetPropA(hWnd, MV_DARK_BUTTON_OLDPROC, (HANDLE)oldProc);
|
|
::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)MVDarkButtonProc);
|
|
::InvalidateRect(hWnd, NULL, TRUE);
|
|
}
|
|
|
|
static void MV_DrawStatic(HWND hWnd, HDC hDC) {
|
|
RECT rc;
|
|
::GetClientRect(hWnd, &rc);
|
|
MV_FillRect(hDC, rc, MV_DARK_PANEL);
|
|
char text[256];
|
|
text[0] = '\0';
|
|
::GetWindowTextA(hWnd, text, sizeof(text));
|
|
HFONT font = (HFONT)::SendMessage(hWnd, WM_GETFONT, 0, 0);
|
|
HFONT oldFont = font ? (HFONT)::SelectObject(hDC, font) : NULL;
|
|
::SetBkMode(hDC, TRANSPARENT);
|
|
::SetTextColor(hDC, MV_DARK_TEXT);
|
|
UINT format = DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS;
|
|
const UINT staticType = ((UINT)::GetWindowLong(hWnd, GWL_STYLE)) & 0x0000000F;
|
|
if (staticType == SS_CENTER) {
|
|
format |= DT_CENTER;
|
|
}
|
|
else if (staticType == SS_RIGHT) {
|
|
format |= DT_RIGHT;
|
|
}
|
|
else {
|
|
format |= DT_LEFT;
|
|
}
|
|
::DrawTextA(hDC, text, -1, &rc, format);
|
|
if (oldFont) {
|
|
::SelectObject(hDC, oldFont);
|
|
}
|
|
}
|
|
|
|
static LRESULT CALLBACK MVDarkStaticProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
|
WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, MV_DARK_STATIC_OLDPROC);
|
|
if (!oldProc) {
|
|
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
if (uMsg == WM_ERASEBKGND) {
|
|
return 1;
|
|
}
|
|
if (uMsg == WM_PAINT) {
|
|
PAINTSTRUCT ps;
|
|
HDC hDC = ::BeginPaint(hWnd, &ps);
|
|
MV_DrawStatic(hWnd, hDC);
|
|
::EndPaint(hWnd, &ps);
|
|
return 0;
|
|
}
|
|
if (uMsg == WM_NCDESTROY) {
|
|
::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc);
|
|
::RemovePropA(hWnd, MV_DARK_STATIC_OLDPROC);
|
|
return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam);
|
|
}
|
|
return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
static void MV_SubclassStatic(CWnd& wnd) {
|
|
HWND hWnd = wnd.GetSafeHwnd();
|
|
if (!hWnd || ::GetPropA(hWnd, MV_DARK_STATIC_OLDPROC)) {
|
|
return;
|
|
}
|
|
WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC);
|
|
::SetPropA(hWnd, MV_DARK_STATIC_OLDPROC, (HANDLE)oldProc);
|
|
::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)MVDarkStaticProc);
|
|
::InvalidateRect(hWnd, NULL, TRUE);
|
|
}
|
|
|
|
|
|
static void MV_DrawCombo(HWND hWnd, HDC hDC) {
|
|
RECT rc;
|
|
::GetClientRect(hWnd, &rc);
|
|
|
|
const bool enabled = ::IsWindowEnabled(hWnd) ? true : false;
|
|
const bool focused = (::GetFocus() == hWnd);
|
|
|
|
MV_FillRect(hDC, rc, MV_DARK_INPUT);
|
|
MV_FrameRect(hDC, rc, focused ? MV_DARK_ACCENT : MV_DARK_BORDER);
|
|
|
|
RECT arrowRect = rc;
|
|
arrowRect.left = MV_MaxInt(rc.left, rc.right - 22);
|
|
MV_FillRect(hDC, arrowRect, MV_DARK_BUTTON);
|
|
MV_FrameRect(hDC, arrowRect, focused ? MV_DARK_ACCENT : MV_DARK_BORDER);
|
|
|
|
POINT arrow[3];
|
|
const int midX = (arrowRect.left + arrowRect.right) / 2;
|
|
const int midY = (arrowRect.top + arrowRect.bottom) / 2;
|
|
arrow[0].x = midX - 4;
|
|
arrow[0].y = midY - 2;
|
|
arrow[1].x = midX + 4;
|
|
arrow[1].y = midY - 2;
|
|
arrow[2].x = midX;
|
|
arrow[2].y = midY + 4;
|
|
|
|
HBRUSH arrowBrush = ::CreateSolidBrush(enabled ? MV_DARK_TEXT : RGB(101, 110, 126));
|
|
HBRUSH oldBrush = (HBRUSH)::SelectObject(hDC, arrowBrush);
|
|
HPEN pen = ::CreatePen(PS_SOLID, 1, enabled ? MV_DARK_TEXT : RGB(101, 110, 126));
|
|
HPEN oldPen = (HPEN)::SelectObject(hDC, pen);
|
|
::Polygon(hDC, arrow, 3);
|
|
::SelectObject(hDC, oldPen);
|
|
::DeleteObject(pen);
|
|
::SelectObject(hDC, oldBrush);
|
|
::DeleteObject(arrowBrush);
|
|
|
|
char text[1024];
|
|
text[0] = '\0';
|
|
const int sel = (int)::SendMessage(hWnd, CB_GETCURSEL, 0, 0);
|
|
if (sel != CB_ERR) {
|
|
const int len = (int)::SendMessage(hWnd, CB_GETLBTEXTLEN, sel, 0);
|
|
if (len >= 0 && len < (int)sizeof(text)) {
|
|
::SendMessageA(hWnd, CB_GETLBTEXT, sel, (LPARAM)text);
|
|
text[sizeof(text) - 1] = '\0';
|
|
}
|
|
}
|
|
if (!text[0]) {
|
|
::GetWindowTextA(hWnd, text, sizeof(text));
|
|
text[sizeof(text) - 1] = '\0';
|
|
}
|
|
|
|
RECT textRect = rc;
|
|
textRect.left += 8;
|
|
textRect.right = MV_MaxInt(textRect.left, arrowRect.left - 6);
|
|
textRect.top += 1;
|
|
textRect.bottom -= 1;
|
|
|
|
HFONT font = (HFONT)::SendMessage(hWnd, WM_GETFONT, 0, 0);
|
|
HFONT oldFont = font ? (HFONT)::SelectObject(hDC, font) : NULL;
|
|
::SetBkMode(hDC, TRANSPARENT);
|
|
::SetTextColor(hDC, enabled ? MV_DARK_TEXT : RGB(101, 110, 126));
|
|
::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
|
|
if (oldFont) {
|
|
::SelectObject(hDC, oldFont);
|
|
}
|
|
}
|
|
|
|
static LRESULT CALLBACK MVDarkComboProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
|
WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, MV_DARK_COMBO_OLDPROC);
|
|
if (!oldProc) {
|
|
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
switch (uMsg) {
|
|
case WM_ERASEBKGND:
|
|
return 1;
|
|
case WM_PAINT:
|
|
{
|
|
PAINTSTRUCT ps;
|
|
HDC hDC = ::BeginPaint(hWnd, &ps);
|
|
MV_DrawCombo(hWnd, hDC);
|
|
::EndPaint(hWnd, &ps);
|
|
return 0;
|
|
}
|
|
case WM_PRINTCLIENT:
|
|
MV_DrawCombo(hWnd, (HDC)wParam);
|
|
return 0;
|
|
case WM_SETFOCUS:
|
|
case WM_KILLFOCUS:
|
|
case WM_ENABLE:
|
|
case WM_SETTEXT:
|
|
case CB_SETCURSEL:
|
|
case CB_ADDSTRING:
|
|
case CB_INSERTSTRING:
|
|
case CB_DELETESTRING:
|
|
case CB_RESETCONTENT:
|
|
{
|
|
LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam);
|
|
::InvalidateRect(hWnd, NULL, FALSE);
|
|
return result;
|
|
}
|
|
case WM_NCDESTROY:
|
|
{
|
|
::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc);
|
|
::RemovePropA(hWnd, MV_DARK_COMBO_OLDPROC);
|
|
return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam);
|
|
}
|
|
}
|
|
|
|
return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
static void MV_SubclassCombo(CWnd& wnd) {
|
|
HWND hWnd = wnd.GetSafeHwnd();
|
|
if (!hWnd || ::GetPropA(hWnd, MV_DARK_COMBO_OLDPROC)) {
|
|
return;
|
|
}
|
|
WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC);
|
|
::SetPropA(hWnd, MV_DARK_COMBO_OLDPROC, (HANDLE)oldProc);
|
|
::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)MVDarkComboProc);
|
|
::InvalidateRect(hWnd, NULL, TRUE);
|
|
}
|
|
|
|
static void MV_BindNullTexture() {
|
|
if (globalImages) {
|
|
globalImages->BindNull();
|
|
}
|
|
else {
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
}
|
|
glDisable(GL_TEXTURE_2D);
|
|
}
|
|
|
|
static idImage* MV_GetImageFromStage(const shaderStage_t* stage) {
|
|
if (!stage) {
|
|
return NULL;
|
|
}
|
|
if (stage->texture.image) {
|
|
return stage->texture.image;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static const shaderStage_t* MV_FindAlbedoStage(const idMaterial* shader) {
|
|
if (!shader) {
|
|
return NULL;
|
|
}
|
|
|
|
const shaderStage_t* firstUsableImageStage = NULL;
|
|
const int numStages = shader->GetNumStages();
|
|
for (int i = 0; i < numStages; i++) {
|
|
const shaderStage_t* stage = shader->GetStage(i);
|
|
if (!stage || !MV_GetImageFromStage(stage)) {
|
|
continue;
|
|
}
|
|
|
|
if (stage->lighting == SL_DIFFUSE) {
|
|
return stage;
|
|
}
|
|
|
|
if (!firstUsableImageStage && stage->lighting != SL_BUMP && stage->lighting != SL_SPECULAR) {
|
|
firstUsableImageStage = stage;
|
|
}
|
|
}
|
|
|
|
return firstUsableImageStage;
|
|
}
|
|
|
|
void GL_SelectTexture(int unit);
|
|
static bool MV_BindAlbedoForSurface(const modelSurface_t* surface) {
|
|
if (!surface || !surface->shader) {
|
|
MV_BindNullTexture();
|
|
return false;
|
|
}
|
|
|
|
const shaderStage_t* stage = MV_FindAlbedoStage(surface->shader);
|
|
idImage* image = MV_GetImageFromStage(stage);
|
|
if (!image) {
|
|
MV_BindNullTexture();
|
|
return false;
|
|
}
|
|
|
|
GL_SelectTexture(0);
|
|
glEnable(GL_TEXTURE_2D);
|
|
image->Bind();
|
|
return true;
|
|
}
|
|
|
|
//=============================================================================
|
|
// ModelView decl creation helpers
|
|
//=============================================================================
|
|
#define MV_TEXTPROMPT_CLASS "IceTechModelViewTextPrompt"
|
|
#define MV_TEXTPROMPT_EDIT 1001
|
|
|
|
struct modelViewTextPrompt_t {
|
|
CString title;
|
|
CString label;
|
|
CString text;
|
|
bool accepted;
|
|
HWND hEdit;
|
|
HWND hOwner;
|
|
};
|
|
|
|
static CString MV_NormalizeSlashes(const CString& path) {
|
|
CString out = path;
|
|
out.Replace('\\', '/');
|
|
return out;
|
|
}
|
|
|
|
static CString MV_Trimmed(CString text) {
|
|
text.TrimLeft();
|
|
text.TrimRight();
|
|
return text;
|
|
}
|
|
|
|
static CString MV_GetFileBaseName(const CString& path, bool stripExtension) {
|
|
CString normalized = MV_NormalizeSlashes(path);
|
|
int slash = normalized.ReverseFind('/');
|
|
CString name = (slash >= 0) ? normalized.Mid(slash + 1) : normalized;
|
|
if (stripExtension) {
|
|
int dot = name.ReverseFind('.');
|
|
if (dot > 0) {
|
|
name = name.Left(dot);
|
|
}
|
|
}
|
|
return name;
|
|
}
|
|
|
|
static CString MV_EnsureExtension(const CString& path, const char* extension) {
|
|
CString normalized = MV_NormalizeSlashes(path);
|
|
CString lower = normalized;
|
|
lower.MakeLower();
|
|
CString ext = extension ? extension : "";
|
|
ext.MakeLower();
|
|
if (!ext.IsEmpty() && lower.Right(ext.GetLength()) != ext) {
|
|
normalized += ext;
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
static CString MV_StripToGameRoot(const CString& path, const char** roots, int numRoots, const char* fallbackRoot) {
|
|
CString normalized = MV_NormalizeSlashes(path);
|
|
CString lower = normalized;
|
|
lower.MakeLower();
|
|
|
|
for (int i = 0; i < numRoots; i++) {
|
|
CString root = roots[i];
|
|
root.MakeLower();
|
|
if (lower.Left(root.GetLength()) == root) {
|
|
return normalized;
|
|
}
|
|
CString slashRoot = "/";
|
|
slashRoot += root;
|
|
int pos = lower.Find(slashRoot);
|
|
if (pos >= 0) {
|
|
return normalized.Mid(pos + 1);
|
|
}
|
|
}
|
|
|
|
CString baseToken = "/base/";
|
|
int basePos = lower.Find(baseToken);
|
|
if (basePos >= 0) {
|
|
return normalized.Mid(basePos + baseToken.GetLength());
|
|
}
|
|
|
|
CString fallback = fallbackRoot ? fallbackRoot : "";
|
|
if (!fallback.IsEmpty()) {
|
|
if (fallback[fallback.GetLength() - 1] != '/') {
|
|
fallback += "/";
|
|
}
|
|
return fallback + MV_GetFileBaseName(normalized, false);
|
|
}
|
|
|
|
return normalized;
|
|
}
|
|
|
|
static CString MV_NormalizeMayaAssetPath(const CString& path) {
|
|
static const char* roots[] = {
|
|
"models/",
|
|
"model_export/",
|
|
"animations/",
|
|
"anim/",
|
|
"art/"
|
|
};
|
|
return MV_StripToGameRoot(path, roots, sizeof(roots) / sizeof(roots[0]), NULL);
|
|
}
|
|
|
|
static CString MV_NormalizeDeclFilePath(const CString& path) {
|
|
static const char* roots[] = {
|
|
"def/",
|
|
"defs/",
|
|
"decls/"
|
|
};
|
|
CString declPath = MV_StripToGameRoot(path, roots, sizeof(roots) / sizeof(roots[0]), "def");
|
|
return MV_EnsureExtension(declPath, ".def");
|
|
}
|
|
|
|
static bool MV_IsTrimNameChar(char c) {
|
|
return c == '/' || c == '_' || c == '-';
|
|
}
|
|
|
|
static CString MV_TrimNameTokenChars(CString name) {
|
|
while (!name.IsEmpty() && MV_IsTrimNameChar(name[0])) {
|
|
name = name.Mid(1);
|
|
}
|
|
while (!name.IsEmpty() && MV_IsTrimNameChar(name[name.GetLength() - 1])) {
|
|
name = name.Left(name.GetLength() - 1);
|
|
}
|
|
return name;
|
|
}
|
|
|
|
static CString MV_BuildSafeNameFromAssetPath(const CString& assetPath) {
|
|
CString name = MV_NormalizeSlashes(assetPath);
|
|
CString lower = name;
|
|
lower.MakeLower();
|
|
if (lower.Left(7) == "models/") {
|
|
name = name.Mid(7);
|
|
}
|
|
else if (lower.Left(13) == "model_export/") {
|
|
name = name.Mid(13);
|
|
}
|
|
else if (lower.Left(11) == "animations/") {
|
|
name = name.Mid(11);
|
|
}
|
|
|
|
int dot = name.ReverseFind('.');
|
|
if (dot > 0) {
|
|
name = name.Left(dot);
|
|
}
|
|
|
|
for (int i = 0; i < name.GetLength(); i++) {
|
|
char c = name[i];
|
|
const bool valid = (c >= 'a' && c <= 'z') ||
|
|
(c >= 'A' && c <= 'Z') ||
|
|
(c >= '0' && c <= '9') ||
|
|
c == '_' || c == '-' || c == '/';
|
|
if (!valid) {
|
|
name.SetAt(i, '_');
|
|
}
|
|
}
|
|
|
|
name = MV_TrimNameTokenChars(name);
|
|
if (name.IsEmpty()) {
|
|
name = MV_GetFileBaseName(assetPath, true);
|
|
}
|
|
name.Replace('/', '_');
|
|
return name;
|
|
}
|
|
|
|
static CString MV_EscapeDeclPath(const CString& path) {
|
|
CString escaped = MV_NormalizeSlashes(path);
|
|
escaped.Replace("\\", "\\\\");
|
|
escaped.Replace("\"", "\\\"");
|
|
return escaped;
|
|
}
|
|
|
|
static CString MV_BuildModelDefText(const char* declName, const char* meshPath) {
|
|
const char* typeName = "modelDef";
|
|
if (declManager) {
|
|
typeName = declManager->GetDeclNameFromType(DECL_MODELDEF);
|
|
}
|
|
|
|
CString text;
|
|
text.Format("%s %s\r\n{\r\n\tmesh \"%s\"\r\n}\r\n",
|
|
typeName,
|
|
declName ? declName : "unnamed_model",
|
|
(LPCTSTR)MV_EscapeDeclPath(meshPath ? meshPath : ""));
|
|
return text;
|
|
}
|
|
|
|
static int MV_FindFinalDeclBrace(const CString& text) {
|
|
for (int i = text.GetLength() - 1; i >= 0; i--) {
|
|
char c = text[i];
|
|
if (c == '}') {
|
|
return i;
|
|
}
|
|
if (c != ' ' && c != '\t' && c != '\r' && c != '\n') {
|
|
break;
|
|
}
|
|
}
|
|
return text.ReverseFind('}');
|
|
}
|
|
|
|
static CString MV_InsertAnimIntoModelDefText(const CString& declText, const char* animName, const char* animPath) {
|
|
CString animLine;
|
|
animLine.Format("\tanim %s \"%s\"\r\n", animName ? animName : "new_anim", (LPCTSTR)MV_EscapeDeclPath(animPath ? animPath : ""));
|
|
|
|
int brace = MV_FindFinalDeclBrace(declText);
|
|
if (brace < 0) {
|
|
CString text = declText;
|
|
if (!text.IsEmpty() && text[text.GetLength() - 1] != '\n') {
|
|
text += "\r\n";
|
|
}
|
|
text += animLine;
|
|
text += "}\r\n";
|
|
return text;
|
|
}
|
|
|
|
CString prefix = declText.Left(brace);
|
|
CString suffix = declText.Mid(brace);
|
|
if (!prefix.IsEmpty() && prefix[prefix.GetLength() - 1] != '\n') {
|
|
prefix += "\r\n";
|
|
}
|
|
return prefix + animLine + suffix;
|
|
}
|
|
|
|
static CString MV_ReadDeclText(const idDecl* decl) {
|
|
CString text;
|
|
if (!decl) {
|
|
return text;
|
|
}
|
|
|
|
const int length = decl->GetTextLength();
|
|
if (length <= 0) {
|
|
return text;
|
|
}
|
|
|
|
char* buffer = new char[length + 1];
|
|
decl->GetText(buffer);
|
|
buffer[length] = '\0';
|
|
text = buffer;
|
|
delete[] buffer;
|
|
return text;
|
|
}
|
|
|
|
static void MV_SetDefaultGuiFont(HWND hWnd) {
|
|
HFONT font = (HFONT)::GetStockObject(DEFAULT_GUI_FONT);
|
|
if (font && hWnd) {
|
|
::SendMessage(hWnd, WM_SETFONT, (WPARAM)font, TRUE);
|
|
}
|
|
}
|
|
|
|
static LRESULT CALLBACK MVTextPromptWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
|
modelViewTextPrompt_t* prompt = (modelViewTextPrompt_t*)::GetWindowLongPtr(hWnd, GWLP_USERDATA);
|
|
|
|
switch (uMsg) {
|
|
case WM_CREATE:
|
|
{
|
|
CREATESTRUCT* cs = (CREATESTRUCT*)lParam;
|
|
prompt = (modelViewTextPrompt_t*)cs->lpCreateParams;
|
|
::SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)prompt);
|
|
|
|
HWND hLabel = ::CreateWindowExA(0, "STATIC", prompt->label, WS_CHILD | WS_VISIBLE | SS_LEFT,
|
|
12, 12, 356, 18, hWnd, NULL, AfxGetInstanceHandle(), NULL);
|
|
prompt->hEdit = ::CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", prompt->text,
|
|
WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL,
|
|
12, 36, 356, 22, hWnd, (HMENU)MV_TEXTPROMPT_EDIT, AfxGetInstanceHandle(), NULL);
|
|
HWND hOK = ::CreateWindowExA(0, "BUTTON", "OK", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,
|
|
206, 72, 78, 24, hWnd, (HMENU)IDOK, AfxGetInstanceHandle(), NULL);
|
|
HWND hCancel = ::CreateWindowExA(0, "BUTTON", "Cancel", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
|
|
290, 72, 78, 24, hWnd, (HMENU)IDCANCEL, AfxGetInstanceHandle(), NULL);
|
|
|
|
MV_SetDefaultGuiFont(hLabel);
|
|
MV_SetDefaultGuiFont(prompt->hEdit);
|
|
MV_SetDefaultGuiFont(hOK);
|
|
MV_SetDefaultGuiFont(hCancel);
|
|
::SendMessage(prompt->hEdit, EM_SETSEL, 0, -1);
|
|
::SetFocus(prompt->hEdit);
|
|
return 0;
|
|
}
|
|
case WM_COMMAND:
|
|
if (LOWORD(wParam) == IDOK) {
|
|
char buffer[MAX_STRING_CHARS];
|
|
buffer[0] = '\0';
|
|
if (prompt && prompt->hEdit) {
|
|
::GetWindowTextA(prompt->hEdit, buffer, sizeof(buffer));
|
|
}
|
|
if (prompt) {
|
|
prompt->text = buffer;
|
|
prompt->accepted = true;
|
|
}
|
|
::DestroyWindow(hWnd);
|
|
return 0;
|
|
}
|
|
if (LOWORD(wParam) == IDCANCEL) {
|
|
::DestroyWindow(hWnd);
|
|
return 0;
|
|
}
|
|
break;
|
|
case WM_CLOSE:
|
|
::DestroyWindow(hWnd);
|
|
return 0;
|
|
}
|
|
|
|
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
static ATOM MV_RegisterTextPromptClass() {
|
|
static ATOM atom = 0;
|
|
if (atom) {
|
|
return atom;
|
|
}
|
|
|
|
WNDCLASSA wc;
|
|
memset(&wc, 0, sizeof(wc));
|
|
wc.lpfnWndProc = MVTextPromptWndProc;
|
|
wc.hInstance = AfxGetInstanceHandle();
|
|
wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
|
|
wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
|
|
wc.lpszClassName = MV_TEXTPROMPT_CLASS;
|
|
atom = ::RegisterClassA(&wc);
|
|
return atom;
|
|
}
|
|
|
|
static bool MV_PromptForText(CWnd* parent, const char* title, const char* label, CString& value) {
|
|
if (!MV_RegisterTextPromptClass()) {
|
|
return false;
|
|
}
|
|
|
|
modelViewTextPrompt_t prompt;
|
|
prompt.title = title ? title : "Text";
|
|
prompt.label = label ? label : "Value:";
|
|
prompt.text = value;
|
|
prompt.accepted = false;
|
|
prompt.hEdit = NULL;
|
|
prompt.hOwner = parent ? parent->GetSafeHwnd() : NULL;
|
|
|
|
HWND hWnd = ::CreateWindowExA(
|
|
WS_EX_DLGMODALFRAME,
|
|
MV_TEXTPROMPT_CLASS,
|
|
prompt.title,
|
|
WS_POPUP | WS_CAPTION | WS_SYSMENU,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
390,
|
|
136,
|
|
prompt.hOwner,
|
|
NULL,
|
|
AfxGetInstanceHandle(),
|
|
&prompt
|
|
);
|
|
|
|
if (!hWnd) {
|
|
return false;
|
|
}
|
|
|
|
if (prompt.hOwner) {
|
|
RECT ownerRect;
|
|
RECT wndRect;
|
|
::GetWindowRect(prompt.hOwner, &ownerRect);
|
|
::GetWindowRect(hWnd, &wndRect);
|
|
const int w = wndRect.right - wndRect.left;
|
|
const int h = wndRect.bottom - wndRect.top;
|
|
const int x = ownerRect.left + ((ownerRect.right - ownerRect.left) - w) / 2;
|
|
const int y = ownerRect.top + ((ownerRect.bottom - ownerRect.top) - h) / 2;
|
|
::SetWindowPos(hWnd, NULL, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
|
|
::EnableWindow(prompt.hOwner, FALSE);
|
|
}
|
|
|
|
::ShowWindow(hWnd, SW_SHOW);
|
|
::UpdateWindow(hWnd);
|
|
|
|
MSG msg;
|
|
while (::IsWindow(hWnd) && ::GetMessage(&msg, NULL, 0, 0) > 0) {
|
|
if (!::IsDialogMessage(hWnd, &msg)) {
|
|
::TranslateMessage(&msg);
|
|
::DispatchMessage(&msg);
|
|
}
|
|
}
|
|
|
|
if (prompt.hOwner) {
|
|
::EnableWindow(prompt.hOwner, TRUE);
|
|
::SetActiveWindow(prompt.hOwner);
|
|
}
|
|
|
|
if (!prompt.accepted) {
|
|
return false;
|
|
}
|
|
|
|
value = MV_Trimmed(prompt.text);
|
|
return !value.IsEmpty();
|
|
}
|
|
|
|
static bool MV_ChooseMayaFile(CWnd* parent, const char* title, CString& assetPath) {
|
|
CFileDialog dlg(TRUE, "ma", NULL, OFN_HIDEREADONLY | OFN_FILEMUSTEXIST,
|
|
"Maya ASCII Files (*.ma)|*.ma|All Files (*.*)|*.*||", parent);
|
|
dlg.m_ofn.lpstrTitle = title ? title : "Select Maya ASCII File";
|
|
if (dlg.DoModal() != IDOK) {
|
|
return false;
|
|
}
|
|
|
|
assetPath = MV_NormalizeMayaAssetPath(dlg.GetPathName());
|
|
return !assetPath.IsEmpty();
|
|
}
|
|
|
|
static bool MV_ChooseDeclFile(CWnd* parent, CString& declFile, bool& createdNew) {
|
|
int choice = AfxMessageBox(
|
|
"Save this modelDef in an existing decl file?\n\nYes = choose an existing decl file\nNo = create a new decl file\nCancel = abort",
|
|
MB_ICONQUESTION | MB_YESNOCANCEL);
|
|
if (choice == IDCANCEL) {
|
|
return false;
|
|
}
|
|
|
|
const bool existing = (choice == IDYES);
|
|
createdNew = !existing;
|
|
|
|
CFileDialog dlg(existing ? TRUE : FALSE, "def", NULL,
|
|
existing ? (OFN_HIDEREADONLY | OFN_FILEMUSTEXIST) : (OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT),
|
|
"Decl Files (*.def)|*.def|All Files (*.*)|*.*||", parent);
|
|
dlg.m_ofn.lpstrTitle = existing ? "Choose Existing Decl File" : "Create New Decl File";
|
|
if (dlg.DoModal() != IDOK) {
|
|
return false;
|
|
}
|
|
|
|
declFile = MV_NormalizeDeclFilePath(dlg.GetPathName());
|
|
return !declFile.IsEmpty();
|
|
}
|
|
|
|
static CString MV_GetExportModelsNameFromDeclFile(const char* declFileName) {
|
|
CString exportName = MV_GetFileBaseName(declFileName ? declFileName : "", true);
|
|
exportName = MV_Trimmed(exportName);
|
|
exportName.Replace("\r", "");
|
|
exportName.Replace("\n", "");
|
|
return exportName;
|
|
}
|
|
|
|
static void MV_ExecuteExportModelsCommand(const char* declFileName, const char* reason) {
|
|
if (!cmdSystem) {
|
|
common->Warning("ModelView: cmdSystem is not available; cannot run exportModels.");
|
|
return;
|
|
}
|
|
|
|
CString exportName = MV_GetExportModelsNameFromDeclFile(declFileName);
|
|
if (exportName.IsEmpty()) {
|
|
common->Warning("ModelView: no containing decl file available; cannot run exportModels.");
|
|
return;
|
|
}
|
|
|
|
CString commandText;
|
|
commandText.Format("exportModels %s", (LPCTSTR)exportName);
|
|
|
|
if (reason && reason[0]) {
|
|
common->Printf("ModelView: running %s after %s.\n", (LPCTSTR)commandText, reason);
|
|
}
|
|
else {
|
|
common->Printf("ModelView: running %s.\n", (LPCTSTR)commandText);
|
|
}
|
|
|
|
commandText += "\n";
|
|
cmdSystem->BufferCommandText(CMD_EXEC_NOW, (LPCTSTR)commandText);
|
|
}
|
|
|
|
|
|
static int MV_MenuBarPreferredHeight() {
|
|
int h = ::GetSystemMetrics(SM_CYMENU) + 4;
|
|
return (h < 22) ? 22 : h;
|
|
}
|
|
|
|
#define MV_MENU_OWNERDRAW_MAGIC 0x4D564F44
|
|
|
|
struct modelViewMenuItemData_t {
|
|
DWORD magic;
|
|
UINT id;
|
|
char text[64];
|
|
};
|
|
|
|
struct modelViewMenuBarData_t {
|
|
HMENU hTopMenu;
|
|
HMENU hFileMenu;
|
|
RECT itemRects[8];
|
|
int numItemRects;
|
|
int hotItem;
|
|
modelViewMenuItemData_t newMD5DeclItem;
|
|
modelViewMenuItemData_t addAnimItem;
|
|
};
|
|
|
|
static modelViewMenuBarData_t* MV_MenuBarData(HWND hWnd) {
|
|
return (modelViewMenuBarData_t*)::GetWindowLongPtr(hWnd, GWLP_USERDATA);
|
|
}
|
|
|
|
static void MV_InitMenuItemData(modelViewMenuItemData_t& item, UINT id, const char* text) {
|
|
memset(&item, 0, sizeof(item));
|
|
item.magic = MV_MENU_OWNERDRAW_MAGIC;
|
|
item.id = id;
|
|
if (text) {
|
|
strncpy(item.text, text, sizeof(item.text) - 1);
|
|
item.text[sizeof(item.text) - 1] = '\0';
|
|
}
|
|
}
|
|
|
|
static void MV_MenuBarRebuildRects(HWND hWnd, HDC hDC, modelViewMenuBarData_t* data) {
|
|
if (!data || !data->hTopMenu) {
|
|
return;
|
|
}
|
|
|
|
data->numItemRects = 0;
|
|
int x = 4;
|
|
const int y = 1;
|
|
const int h = MV_MenuBarPreferredHeight() - 2;
|
|
const int count = ::GetMenuItemCount(data->hTopMenu);
|
|
|
|
HFONT font = (HFONT)::SendMessage(hWnd, WM_GETFONT, 0, 0);
|
|
HFONT oldFont = font ? (HFONT)::SelectObject(hDC, font) : NULL;
|
|
|
|
for (int i = 0; i < count && data->numItemRects < 8; i++) {
|
|
char text[128];
|
|
text[0] = '\0';
|
|
::GetMenuStringA(data->hTopMenu, i, text, sizeof(text), MF_BYPOSITION);
|
|
SIZE size = { 40, h };
|
|
if (text[0]) {
|
|
::GetTextExtentPoint32A(hDC, text, lstrlenA(text), &size);
|
|
}
|
|
|
|
RECT item;
|
|
item.left = x;
|
|
item.top = y;
|
|
item.right = x + size.cx + 20;
|
|
item.bottom = y + h;
|
|
data->itemRects[data->numItemRects++] = item;
|
|
x = item.right + 1;
|
|
}
|
|
|
|
if (oldFont) {
|
|
::SelectObject(hDC, oldFont);
|
|
}
|
|
}
|
|
|
|
static int MV_MenuBarHitTest(modelViewMenuBarData_t* data, const POINT& point) {
|
|
if (!data) {
|
|
return -1;
|
|
}
|
|
for (int i = 0; i < data->numItemRects; i++) {
|
|
if (::PtInRect(&data->itemRects[i], point)) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void MV_DrawOwnerMenuItem(const DRAWITEMSTRUCT* dis) {
|
|
if (!dis || dis->CtlType != ODT_MENU) {
|
|
return;
|
|
}
|
|
|
|
const modelViewMenuItemData_t* item = (const modelViewMenuItemData_t*)dis->itemData;
|
|
if (!item || item->magic != MV_MENU_OWNERDRAW_MAGIC) {
|
|
return;
|
|
}
|
|
|
|
const bool selected = (dis->itemState & ODS_SELECTED) != 0;
|
|
const bool disabled = (dis->itemState & ODS_DISABLED) != 0;
|
|
RECT rc = dis->rcItem;
|
|
MV_FillRect(dis->hDC, rc, selected ? MV_DARK_MENU_HOT : MV_DARK_MENU);
|
|
|
|
RECT accent = rc;
|
|
accent.right = accent.left + 3;
|
|
MV_FillRect(dis->hDC, accent, selected ? MV_DARK_ACCENT : MV_DARK_MENU);
|
|
|
|
RECT textRect = rc;
|
|
textRect.left += 22;
|
|
textRect.right -= 8;
|
|
|
|
HFONT font = (HFONT)::GetStockObject(DEFAULT_GUI_FONT);
|
|
HFONT oldFont = font ? (HFONT)::SelectObject(dis->hDC, font) : NULL;
|
|
::SetBkMode(dis->hDC, TRANSPARENT);
|
|
::SetTextColor(dis->hDC, disabled ? RGB(101, 110, 126) : MV_DARK_TEXT);
|
|
::DrawTextA(dis->hDC, item->text, -1, &textRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
|
|
if (oldFont) {
|
|
::SelectObject(dis->hDC, oldFont);
|
|
}
|
|
}
|
|
|
|
static void MV_MenuBarPaint(HWND hWnd) {
|
|
PAINTSTRUCT ps;
|
|
HDC hDC = ::BeginPaint(hWnd, &ps);
|
|
RECT client;
|
|
::GetClientRect(hWnd, &client);
|
|
MV_FillRect(hDC, client, MV_DARK_MENU);
|
|
|
|
modelViewMenuBarData_t* data = MV_MenuBarData(hWnd);
|
|
if (data) {
|
|
MV_MenuBarRebuildRects(hWnd, hDC, data);
|
|
HFONT font = (HFONT)::SendMessage(hWnd, WM_GETFONT, 0, 0);
|
|
HFONT oldFont = font ? (HFONT)::SelectObject(hDC, font) : NULL;
|
|
::SetBkMode(hDC, TRANSPARENT);
|
|
|
|
const int count = ::GetMenuItemCount(data->hTopMenu);
|
|
for (int i = 0; i < count && i < data->numItemRects; i++) {
|
|
RECT itemRect = data->itemRects[i];
|
|
if (i == data->hotItem) {
|
|
MV_FillRect(hDC, itemRect, MV_DARK_MENU_HOT);
|
|
MV_FrameRect(hDC, itemRect, MV_DARK_ACCENT);
|
|
::InflateRect(&itemRect, -1, -1);
|
|
}
|
|
|
|
char text[128];
|
|
text[0] = '\0';
|
|
::GetMenuStringA(data->hTopMenu, i, text, sizeof(text), MF_BYPOSITION);
|
|
::SetTextColor(hDC, MV_DARK_TEXT);
|
|
::DrawTextA(hDC, text, -1, &itemRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
|
|
}
|
|
|
|
if (oldFont) {
|
|
::SelectObject(hDC, oldFont);
|
|
}
|
|
}
|
|
|
|
RECT line = client;
|
|
line.top = client.bottom - 1;
|
|
MV_FillRect(hDC, line, MV_DARK_BORDER);
|
|
::EndPaint(hWnd, &ps);
|
|
}
|
|
|
|
static void MV_MenuBarTrackTopLevel(HWND hWnd, int index) {
|
|
modelViewMenuBarData_t* data = MV_MenuBarData(hWnd);
|
|
if (!data || !data->hTopMenu || index < 0 || index >= ::GetMenuItemCount(data->hTopMenu)) {
|
|
return;
|
|
}
|
|
|
|
HMENU popup = ::GetSubMenu(data->hTopMenu, index);
|
|
if (!popup) {
|
|
return;
|
|
}
|
|
|
|
RECT rect;
|
|
if (index < data->numItemRects) {
|
|
rect = data->itemRects[index];
|
|
}
|
|
else {
|
|
::GetClientRect(hWnd, &rect);
|
|
}
|
|
|
|
POINT pt;
|
|
pt.x = rect.left;
|
|
pt.y = rect.bottom;
|
|
::ClientToScreen(hWnd, &pt);
|
|
|
|
data->hotItem = index;
|
|
::InvalidateRect(hWnd, NULL, FALSE);
|
|
::UpdateWindow(hWnd);
|
|
|
|
HWND hOwner = ::GetAncestor(hWnd, GA_ROOT);
|
|
if (hOwner) {
|
|
::SetForegroundWindow(hOwner);
|
|
}
|
|
|
|
const UINT command = ::TrackPopupMenu(
|
|
popup,
|
|
TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RETURNCMD,
|
|
pt.x,
|
|
pt.y,
|
|
0,
|
|
hWnd,
|
|
NULL
|
|
);
|
|
|
|
if (command != 0) {
|
|
HWND hParent = ::GetParent(hWnd);
|
|
if (hParent) {
|
|
::SendMessage(hParent, WM_COMMAND, MAKEWPARAM(command, 0), 0);
|
|
}
|
|
}
|
|
|
|
data->hotItem = -1;
|
|
::InvalidateRect(hWnd, NULL, FALSE);
|
|
::PostMessage(hWnd, WM_NULL, 0, 0);
|
|
}
|
|
|
|
static LRESULT CALLBACK MVMenuBarWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
|
modelViewMenuBarData_t* data = MV_MenuBarData(hWnd);
|
|
|
|
switch (uMsg) {
|
|
case WM_NCCREATE:
|
|
{
|
|
CREATESTRUCT* cs = (CREATESTRUCT*)lParam;
|
|
::SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)cs->lpCreateParams);
|
|
return TRUE;
|
|
}
|
|
case WM_NCDESTROY:
|
|
if (data) {
|
|
if (data->hTopMenu) {
|
|
::DestroyMenu(data->hTopMenu);
|
|
data->hTopMenu = NULL;
|
|
}
|
|
data->hFileMenu = NULL;
|
|
delete data;
|
|
::SetWindowLongPtr(hWnd, GWLP_USERDATA, 0);
|
|
}
|
|
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
|
|
case WM_ERASEBKGND:
|
|
return 1;
|
|
case WM_PAINT:
|
|
MV_MenuBarPaint(hWnd);
|
|
return 0;
|
|
case WM_PRINTCLIENT:
|
|
{
|
|
HDC hDC = (HDC)wParam;
|
|
RECT client;
|
|
::GetClientRect(hWnd, &client);
|
|
MV_FillRect(hDC, client, MV_DARK_MENU);
|
|
if (data) {
|
|
MV_MenuBarRebuildRects(hWnd, hDC, data);
|
|
}
|
|
return 0;
|
|
}
|
|
case WM_MOUSEMOVE:
|
|
if (data) {
|
|
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
|
|
int hot = MV_MenuBarHitTest(data, pt);
|
|
if (hot != data->hotItem) {
|
|
data->hotItem = hot;
|
|
::InvalidateRect(hWnd, NULL, FALSE);
|
|
}
|
|
}
|
|
return 0;
|
|
case WM_LBUTTONDOWN:
|
|
if (data) {
|
|
HDC hDC = ::GetDC(hWnd);
|
|
MV_MenuBarRebuildRects(hWnd, hDC, data);
|
|
::ReleaseDC(hWnd, hDC);
|
|
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
|
|
MV_MenuBarTrackTopLevel(hWnd, MV_MenuBarHitTest(data, pt));
|
|
}
|
|
return 0;
|
|
case WM_MEASUREITEM:
|
|
{
|
|
MEASUREITEMSTRUCT* mis = (MEASUREITEMSTRUCT*)lParam;
|
|
if (mis && mis->CtlType == ODT_MENU) {
|
|
const modelViewMenuItemData_t* item = (const modelViewMenuItemData_t*)mis->itemData;
|
|
if (item && item->magic == MV_MENU_OWNERDRAW_MAGIC) {
|
|
mis->itemWidth = 128;
|
|
mis->itemHeight = 24;
|
|
return TRUE;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case WM_DRAWITEM:
|
|
{
|
|
DRAWITEMSTRUCT* dis = (DRAWITEMSTRUCT*)lParam;
|
|
if (dis && dis->CtlType == ODT_MENU) {
|
|
const modelViewMenuItemData_t* item = (const modelViewMenuItemData_t*)dis->itemData;
|
|
if (item && item->magic == MV_MENU_OWNERDRAW_MAGIC) {
|
|
MV_DrawOwnerMenuItem(dis);
|
|
return TRUE;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case WM_SYSKEYDOWN:
|
|
case WM_KEYDOWN:
|
|
if (wParam == VK_F10 || wParam == 'F') {
|
|
MV_MenuBarTrackTopLevel(hWnd, 0);
|
|
return 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
static ATOM MV_RegisterMenuBarClass() {
|
|
static ATOM atom = 0;
|
|
if (atom) {
|
|
return atom;
|
|
}
|
|
|
|
WNDCLASSA wc;
|
|
memset(&wc, 0, sizeof(wc));
|
|
wc.style = CS_HREDRAW | CS_VREDRAW;
|
|
wc.lpfnWndProc = MVMenuBarWndProc;
|
|
wc.hInstance = AfxGetInstanceHandle();
|
|
wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
|
|
wc.hbrBackground = ModelViewMenuBrush();
|
|
wc.lpszClassName = "IceTechModelViewMenuBar";
|
|
atom = ::RegisterClassA(&wc);
|
|
return atom;
|
|
}
|
|
|
|
static HWND MV_CreateModelViewMenuBar(HWND hParent) {
|
|
if (!hParent || !MV_RegisterMenuBarClass()) {
|
|
return NULL;
|
|
}
|
|
|
|
modelViewMenuBarData_t* data = new modelViewMenuBarData_t;
|
|
memset(data, 0, sizeof(*data));
|
|
data->hotItem = -1;
|
|
MV_InitMenuItemData(data->newMD5DeclItem, ID_MODELVIEW_FILE_NEWMD5DECL, "New MD5 Decl...");
|
|
MV_InitMenuItemData(data->addAnimItem, ID_MODELVIEW_FILE_ADDANIM, "Add Animation...");
|
|
|
|
data->hTopMenu = ::CreateMenu();
|
|
data->hFileMenu = ::CreatePopupMenu();
|
|
if (!data->hTopMenu || !data->hFileMenu) {
|
|
if (data->hTopMenu) {
|
|
::DestroyMenu(data->hTopMenu);
|
|
}
|
|
if (data->hFileMenu) {
|
|
::DestroyMenu(data->hFileMenu);
|
|
}
|
|
delete data;
|
|
return NULL;
|
|
}
|
|
|
|
::AppendMenuA(data->hFileMenu, MF_OWNERDRAW | MF_ENABLED, ID_MODELVIEW_FILE_NEWMD5DECL, (LPCSTR)&data->newMD5DeclItem);
|
|
::AppendMenuA(data->hFileMenu, MF_OWNERDRAW | MF_ENABLED, ID_MODELVIEW_FILE_ADDANIM, (LPCSTR)&data->addAnimItem);
|
|
::AppendMenuA(data->hTopMenu, MF_POPUP | MF_ENABLED, (UINT_PTR)data->hFileMenu, "&File");
|
|
|
|
HWND hWnd = ::CreateWindowExA(
|
|
0,
|
|
"IceTechModelViewMenuBar",
|
|
"ModelViewMenuBar",
|
|
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
hParent,
|
|
(HMENU)ID_MODELVIEW_MENUBAR,
|
|
AfxGetInstanceHandle(),
|
|
data
|
|
);
|
|
|
|
if (!hWnd) {
|
|
if (data->hTopMenu) {
|
|
::DestroyMenu(data->hTopMenu);
|
|
}
|
|
delete data;
|
|
return NULL;
|
|
}
|
|
|
|
HFONT font = (HFONT)::GetStockObject(DEFAULT_GUI_FONT);
|
|
if (font) {
|
|
::SendMessage(hWnd, WM_SETFONT, (WPARAM)font, FALSE);
|
|
}
|
|
return hWnd;
|
|
}
|
|
|
|
//=============================================================================
|
|
// CModelRenderWnd
|
|
//=============================================================================
|
|
IMPLEMENT_DYNAMIC(CModelRenderWnd, CWnd)
|
|
|
|
BEGIN_MESSAGE_MAP(CModelRenderWnd, CWnd)
|
|
ON_WM_CREATE()
|
|
ON_WM_DESTROY()
|
|
ON_WM_PAINT()
|
|
ON_WM_SIZE()
|
|
ON_WM_ERASEBKGND()
|
|
ON_WM_TIMER()
|
|
ON_WM_LBUTTONDOWN()
|
|
ON_WM_LBUTTONUP()
|
|
ON_WM_MOUSEMOVE()
|
|
ON_WM_MOUSEWHEEL()
|
|
END_MESSAGE_MAP()
|
|
|
|
CModelRenderWnd::CModelRenderWnd() {
|
|
m_hDC = NULL;
|
|
m_hGLRC = NULL;
|
|
m_modelDecl = NULL;
|
|
m_model = NULL;
|
|
m_cachedDynamicModel = NULL;
|
|
m_joints = NULL;
|
|
m_numJoints = 0;
|
|
m_haveAnimFrame = FALSE;
|
|
m_anim = NULL;
|
|
m_animLengthMS = 0;
|
|
m_animFrames = 0;
|
|
m_animTimeMS = 0;
|
|
m_lastTick = 0;
|
|
m_showSkeleton = FALSE;
|
|
m_active = FALSE;
|
|
m_playing = FALSE;
|
|
m_dragging = FALSE;
|
|
m_yaw = -35.0f;
|
|
m_pitch = 18.0f;
|
|
m_distance = 192.0f;
|
|
m_center[0] = m_center[1] = m_center[2] = 0.0f;
|
|
m_radius = 64.0f;
|
|
}
|
|
|
|
CModelRenderWnd::~CModelRenderWnd() {
|
|
ShutdownGL();
|
|
ResetCachedDynamicModel();
|
|
FreeJointBuffer();
|
|
}
|
|
|
|
BOOL CModelRenderWnd::Create(CWnd* pParent, UINT nID) {
|
|
CString className = AfxRegisterWndClass(
|
|
CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS | CS_OWNDC,
|
|
::LoadCursor(NULL, IDC_ARROW),
|
|
(HBRUSH)::GetStockObject(BLACK_BRUSH),
|
|
NULL
|
|
);
|
|
|
|
return CWnd::CreateEx(
|
|
WS_EX_CLIENTEDGE,
|
|
className,
|
|
"RadiantModelRenderWnd",
|
|
WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
|
|
CRect(0, 0, 0, 0),
|
|
pParent,
|
|
nID
|
|
);
|
|
}
|
|
|
|
int CModelRenderWnd::OnCreate(LPCREATESTRUCT lpCreateStruct) {
|
|
if (CWnd::OnCreate(lpCreateStruct) == -1) {
|
|
return -1;
|
|
}
|
|
InitGL();
|
|
m_lastTick = ::GetTickCount();
|
|
SetTimer(MODELVIEW_TIMER_ID, MODELVIEW_TIMER_MS, NULL);
|
|
return 0;
|
|
}
|
|
|
|
void CModelRenderWnd::OnDestroy() {
|
|
KillTimer(MODELVIEW_TIMER_ID);
|
|
ShutdownGL();
|
|
CWnd::OnDestroy();
|
|
}
|
|
|
|
void CModelRenderWnd::InitGL() {
|
|
if (m_hGLRC) {
|
|
return;
|
|
}
|
|
|
|
m_hDC = ::GetDC(GetSafeHwnd());
|
|
if (!m_hDC) {
|
|
return;
|
|
}
|
|
|
|
QEW_SetupPixelFormat(m_hDC, false);
|
|
m_hGLRC = (HGLRC)wglCreateContext(m_hDC);
|
|
}
|
|
|
|
void CModelRenderWnd::ShutdownGL() {
|
|
if (m_hGLRC) {
|
|
wglMakeCurrent(NULL, NULL);
|
|
wglDeleteContext(m_hGLRC);
|
|
m_hGLRC = NULL;
|
|
}
|
|
if (m_hDC && GetSafeHwnd()) {
|
|
::ReleaseDC(GetSafeHwnd(), m_hDC);
|
|
m_hDC = NULL;
|
|
}
|
|
}
|
|
|
|
void CModelRenderWnd::FreeJointBuffer() {
|
|
delete[] m_joints;
|
|
m_joints = NULL;
|
|
m_numJoints = 0;
|
|
m_haveAnimFrame = FALSE;
|
|
}
|
|
|
|
void CModelRenderWnd::AllocateJointBuffer() {
|
|
FreeJointBuffer();
|
|
if (!m_modelDecl) {
|
|
return;
|
|
}
|
|
|
|
m_numJoints = m_modelDecl->NumJoints();
|
|
if (m_numJoints > 0) {
|
|
m_joints = new idJointMat[m_numJoints];
|
|
memset(m_joints, 0, sizeof(idJointMat) * m_numJoints);
|
|
}
|
|
}
|
|
|
|
void CModelRenderWnd::ResetCachedDynamicModel() {
|
|
if (m_cachedDynamicModel && renderModelManager) {
|
|
renderModelManager->FreeModel(m_cachedDynamicModel);
|
|
}
|
|
m_cachedDynamicModel = NULL;
|
|
}
|
|
|
|
void CModelRenderWnd::ResolveModelFromGameEdit() {
|
|
m_model = NULL;
|
|
if (!m_modelDecl) {
|
|
return;
|
|
}
|
|
|
|
const char* modelName = m_modelDecl->GetModelName();
|
|
if (gameEdit && modelName && modelName[0]) {
|
|
m_model = gameEdit->ANIM_GetModelFromName(modelName);
|
|
}
|
|
|
|
// Keep this as a non-animator fallback for editor builds that have decls loaded
|
|
// before gameEdit is initialized.
|
|
if (!m_model) {
|
|
m_model = m_modelDecl->ModelHandle();
|
|
}
|
|
}
|
|
|
|
void CModelRenderWnd::UpdateAnimationFrame(BOOL forceResetCachedModel) {
|
|
m_haveAnimFrame = FALSE;
|
|
|
|
if (forceResetCachedModel) {
|
|
ResetCachedDynamicModel();
|
|
}
|
|
|
|
if (!m_model || !m_anim || !m_joints || m_numJoints <= 0 || !gameEdit) {
|
|
return;
|
|
}
|
|
|
|
int frameTime = m_animTimeMS;
|
|
if (m_animLengthMS > 0) {
|
|
frameTime = MV_ClampInt(frameTime, 0, m_animLengthMS - 1);
|
|
}
|
|
|
|
gameEdit->ANIM_CreateAnimFrame(
|
|
m_model,
|
|
m_anim,
|
|
m_numJoints,
|
|
m_joints,
|
|
frameTime,
|
|
m_modelDecl ? m_modelDecl->GetVisualOffset() : vec3_origin,
|
|
true
|
|
);
|
|
|
|
m_haveAnimFrame = TRUE;
|
|
}
|
|
|
|
void CModelRenderWnd::SetModelDecl(const idDeclModelDef* modelDecl) {
|
|
m_modelDecl = (idDeclModelDefInterface*)modelDecl;
|
|
m_model = NULL;
|
|
m_anim = NULL;
|
|
m_animDisplayName.Empty();
|
|
m_animLengthMS = 0;
|
|
m_animFrames = 0;
|
|
m_animTimeMS = 0;
|
|
m_playing = FALSE;
|
|
m_lastTick = ::GetTickCount();
|
|
|
|
ResetCachedDynamicModel();
|
|
ResolveModelFromGameEdit();
|
|
AllocateJointBuffer();
|
|
UpdateAnimationFrame(FALSE);
|
|
FitCameraToModel();
|
|
Invalidate(FALSE);
|
|
NotifyAnimationTimeChanged();
|
|
}
|
|
|
|
void CModelRenderWnd::SetAnimation(const idMD5Anim* anim, int animLengthMS, int numFrames, const char* displayName) {
|
|
m_anim = anim;
|
|
m_animLengthMS = MV_MaxInt(0, animLengthMS);
|
|
m_animFrames = MV_MaxInt(0, numFrames);
|
|
m_animDisplayName = displayName ? displayName : "";
|
|
m_animTimeMS = 0;
|
|
m_playing = FALSE;
|
|
m_lastTick = ::GetTickCount();
|
|
UpdateAnimationFrame(TRUE);
|
|
Invalidate(FALSE);
|
|
NotifyAnimationTimeChanged();
|
|
}
|
|
|
|
void CModelRenderWnd::SetAnimationTimeMS(int timeMS) {
|
|
if (!m_anim) {
|
|
m_animTimeMS = 0;
|
|
m_lastTick = ::GetTickCount();
|
|
NotifyAnimationTimeChanged();
|
|
return;
|
|
}
|
|
|
|
if (m_animLengthMS > 0) {
|
|
m_animTimeMS = MV_ClampInt(timeMS, 0, m_animLengthMS - 1);
|
|
}
|
|
else {
|
|
m_animTimeMS = 0;
|
|
}
|
|
m_lastTick = ::GetTickCount();
|
|
UpdateAnimationFrame(TRUE);
|
|
Invalidate(FALSE);
|
|
NotifyAnimationTimeChanged();
|
|
}
|
|
|
|
void CModelRenderWnd::PlayAnimation() {
|
|
if (!m_anim || m_animLengthMS <= 0) {
|
|
m_playing = FALSE;
|
|
NotifyAnimationTimeChanged();
|
|
return;
|
|
}
|
|
m_playing = TRUE;
|
|
m_lastTick = ::GetTickCount();
|
|
NotifyAnimationTimeChanged();
|
|
}
|
|
|
|
void CModelRenderWnd::PauseAnimation() {
|
|
m_playing = FALSE;
|
|
m_lastTick = ::GetTickCount();
|
|
NotifyAnimationTimeChanged();
|
|
}
|
|
|
|
void CModelRenderWnd::StopAnimation() {
|
|
m_playing = FALSE;
|
|
m_animTimeMS = 0;
|
|
m_lastTick = ::GetTickCount();
|
|
UpdateAnimationFrame(TRUE);
|
|
Invalidate(FALSE);
|
|
NotifyAnimationTimeChanged();
|
|
}
|
|
|
|
int CModelRenderWnd::GetAnimationTimeMS() const {
|
|
return m_animTimeMS;
|
|
}
|
|
|
|
int CModelRenderWnd::GetAnimationLengthMS() const {
|
|
return m_animLengthMS;
|
|
}
|
|
|
|
int CModelRenderWnd::GetAnimationFrames() const {
|
|
return m_animFrames;
|
|
}
|
|
|
|
BOOL CModelRenderWnd::HasAnimation() const {
|
|
return m_anim != NULL;
|
|
}
|
|
|
|
BOOL CModelRenderWnd::IsPlaying() const {
|
|
return m_playing;
|
|
}
|
|
|
|
void CModelRenderWnd::NotifyAnimationTimeChanged() {
|
|
CWnd* parent = GetParent();
|
|
if (parent && parent->GetSafeHwnd()) {
|
|
parent->SendMessage(WM_MODELVIEW_ANIMTIME_CHANGED, (WPARAM)m_animTimeMS, (LPARAM)m_animLengthMS);
|
|
}
|
|
}
|
|
|
|
void CModelRenderWnd::SetShowSkeleton(BOOL showSkeleton) {
|
|
m_showSkeleton = showSkeleton;
|
|
Invalidate(FALSE);
|
|
}
|
|
|
|
void CModelRenderWnd::SetActive(BOOL active) {
|
|
m_active = active;
|
|
if (m_active) {
|
|
m_lastTick = ::GetTickCount();
|
|
Invalidate(FALSE);
|
|
}
|
|
}
|
|
|
|
void CModelRenderWnd::FocusRenderWindow() {
|
|
if (GetSafeHwnd()) {
|
|
SetFocus();
|
|
}
|
|
}
|
|
|
|
void CModelRenderWnd::FitCameraToModel() {
|
|
m_center[0] = m_center[1] = m_center[2] = 0.0f;
|
|
m_radius = 64.0f;
|
|
|
|
idRenderModel* model = m_model;
|
|
if (!model && m_modelDecl) {
|
|
model = m_modelDecl->ModelHandle();
|
|
}
|
|
|
|
if (model) {
|
|
idBounds bounds = model->Bounds(NULL);
|
|
idVec3 center = (bounds[0] + bounds[1]) * 0.5f;
|
|
idVec3 extents = bounds[1] - bounds[0];
|
|
m_center[0] = center.x;
|
|
m_center[1] = center.y;
|
|
m_center[2] = center.z;
|
|
m_radius = extents.Length() * 0.5f;
|
|
if (m_radius < 32.0f) {
|
|
m_radius = 32.0f;
|
|
}
|
|
}
|
|
|
|
m_distance = m_radius * 3.0f;
|
|
if (m_distance < 96.0f) {
|
|
m_distance = 96.0f;
|
|
}
|
|
}
|
|
|
|
void CModelRenderWnd::StepAnimation() {
|
|
if (!m_active || !m_playing || !m_anim || m_animLengthMS <= 0) {
|
|
m_lastTick = ::GetTickCount();
|
|
return;
|
|
}
|
|
|
|
DWORD now = ::GetTickCount();
|
|
DWORD delta = now - m_lastTick;
|
|
m_lastTick = now;
|
|
|
|
if (delta > 250) {
|
|
delta = 250;
|
|
}
|
|
|
|
m_animTimeMS += static_cast<int>(delta);
|
|
if (m_animLengthMS > 0) {
|
|
m_animTimeMS %= m_animLengthMS;
|
|
}
|
|
|
|
UpdateAnimationFrame(FALSE);
|
|
Invalidate(FALSE);
|
|
NotifyAnimationTimeChanged();
|
|
}
|
|
|
|
void CModelRenderWnd::OnTimer(UINT_PTR nIDEvent) {
|
|
if (nIDEvent == MODELVIEW_TIMER_ID) {
|
|
StepAnimation();
|
|
return;
|
|
}
|
|
CWnd::OnTimer(nIDEvent);
|
|
}
|
|
|
|
void CModelRenderWnd::OnPaint() {
|
|
CPaintDC dc(this);
|
|
if (!m_hGLRC || !m_hDC) {
|
|
return;
|
|
}
|
|
idGraphicsDeviceContextHelper context(dc.m_hDC, m_hGLRC, false);
|
|
|
|
DrawScene();
|
|
}
|
|
|
|
void CModelRenderWnd::OnSize(UINT nType, int cx, int cy) {
|
|
CWnd::OnSize(nType, cx, cy);
|
|
Invalidate(FALSE);
|
|
}
|
|
|
|
BOOL CModelRenderWnd::OnEraseBkgnd(CDC* pDC) {
|
|
return TRUE;
|
|
}
|
|
|
|
void CModelRenderWnd::SetupCamera(const CRect& client) {
|
|
const int width = MV_MaxInt(1, client.Width());
|
|
const int height = MV_MaxInt(1, client.Height());
|
|
const float aspect = static_cast<float>(width) / static_cast<float>(height);
|
|
const float zNear = 1.0f;
|
|
const float zFar = MV_MaxInt(4096, static_cast<int>(m_distance + m_radius * 8.0f));
|
|
const float fovY = 60.0f;
|
|
const float ymax = zNear * idMath::Tan(fovY * idMath::M_DEG2RAD * 0.5f);
|
|
const float xmax = ymax * aspect;
|
|
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadIdentity();
|
|
glFrustum(-xmax, xmax, -ymax, ymax, zNear, zFar);
|
|
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glLoadIdentity();
|
|
glTranslatef(0.0f, 0.0f, -m_distance);
|
|
glRotatef(m_pitch, 1.0f, 0.0f, 0.0f);
|
|
glRotatef(m_yaw, 0.0f, 0.0f, 1.0f);
|
|
glTranslatef(-m_center[0], -m_center[1], -m_center[2]);
|
|
}
|
|
|
|
void CModelRenderWnd::DrawScene() {
|
|
CRect client;
|
|
GetClientRect(client);
|
|
const int width = MV_MaxInt(1, client.Width());
|
|
const int height = MV_MaxInt(1, client.Height());
|
|
|
|
glViewport(0, 0, width, height);
|
|
glScissor(0, 0, width, height);
|
|
glEnable(GL_SCISSOR_TEST);
|
|
glClearColor(0.045f, 0.052f, 0.065f, 1.0f);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
|
|
glEnable(GL_DEPTH_TEST);
|
|
glDepthFunc(GL_LEQUAL);
|
|
glDisable(GL_CULL_FACE);
|
|
glDisable(GL_TEXTURE_2D);
|
|
glDisable(GL_BLEND);
|
|
|
|
SetupCamera(client);
|
|
DrawGrid();
|
|
DrawModel();
|
|
if (m_showSkeleton) {
|
|
DrawSkeleton();
|
|
}
|
|
DrawOverlayText();
|
|
|
|
glFlush();
|
|
}
|
|
|
|
void CModelRenderWnd::DrawGrid() {
|
|
const float size = MV_MaxInt(128, static_cast<int>(m_radius * 2.0f));
|
|
const float step = 16.0f;
|
|
|
|
glDisable(GL_DEPTH_TEST);
|
|
glLineWidth(1.0f);
|
|
glColor3f(0.18f, 0.20f, 0.24f);
|
|
glBegin(GL_LINES);
|
|
for (float x = -size; x <= size; x += step) {
|
|
glVertex3f(x, -size, 0.0f);
|
|
glVertex3f(x, size, 0.0f);
|
|
}
|
|
for (float y = -size; y <= size; y += step) {
|
|
glVertex3f(-size, y, 0.0f);
|
|
glVertex3f(size, y, 0.0f);
|
|
}
|
|
glEnd();
|
|
|
|
glLineWidth(2.0f);
|
|
glBegin(GL_LINES);
|
|
glColor3f(0.80f, 0.20f, 0.20f);
|
|
glVertex3f(0.0f, 0.0f, 0.0f);
|
|
glVertex3f(size, 0.0f, 0.0f);
|
|
glColor3f(0.20f, 0.80f, 0.20f);
|
|
glVertex3f(0.0f, 0.0f, 0.0f);
|
|
glVertex3f(0.0f, size, 0.0f);
|
|
glColor3f(0.20f, 0.35f, 1.0f);
|
|
glVertex3f(0.0f, 0.0f, 0.0f);
|
|
glVertex3f(0.0f, 0.0f, size * 0.5f);
|
|
glEnd();
|
|
glLineWidth(1.0f);
|
|
glEnable(GL_DEPTH_TEST);
|
|
}
|
|
|
|
void CModelRenderWnd::DrawRenderModel(idRenderModel* model, bool wireOnly) {
|
|
if (!model) {
|
|
return;
|
|
}
|
|
|
|
glPolygonMode(GL_FRONT_AND_BACK, wireOnly ? GL_LINE : GL_FILL);
|
|
if (wireOnly) {
|
|
MV_BindNullTexture();
|
|
glColor3f(0.08f, 0.10f, 0.12f);
|
|
}
|
|
else {
|
|
glColor3f(1.0f, 1.0f, 1.0f);
|
|
}
|
|
|
|
const int surfaceCount = model->NumSurfaces();
|
|
for (int s = 0; s < surfaceCount; s++) {
|
|
const modelSurface_t* surface = model->Surface(s);
|
|
if (!surface || !surface->geometry || !surface->geometry->verts || !surface->geometry->indexes) {
|
|
continue;
|
|
}
|
|
|
|
const srfTriangles_t* tri = surface->geometry;
|
|
bool textured = false;
|
|
if (!wireOnly) {
|
|
textured = MV_BindAlbedoForSurface(surface);
|
|
if (textured) {
|
|
glColor3f(1.0f, 1.0f, 1.0f);
|
|
}
|
|
else {
|
|
glColor3f(0.62f, 0.68f, 0.78f);
|
|
}
|
|
}
|
|
|
|
glBegin(GL_TRIANGLES);
|
|
for (int i = 0; i < tri->numIndexes; i++) {
|
|
const int index = tri->indexes[i];
|
|
if (index < 0 || index >= tri->numVerts) {
|
|
continue;
|
|
}
|
|
if (textured) {
|
|
glTexCoord2fv(tri->verts[index].st.ToFloatPtr());
|
|
}
|
|
glVertex3fv(tri->verts[index].xyz.ToFloatPtr());
|
|
}
|
|
glEnd();
|
|
}
|
|
|
|
MV_BindNullTexture();
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
|
}
|
|
|
|
void CModelRenderWnd::DrawModel() {
|
|
if (!m_model) {
|
|
return;
|
|
}
|
|
|
|
idRenderModel* drawModel = m_model;
|
|
|
|
if (m_model->IsDynamicModel() != DM_STATIC && m_haveAnimFrame && m_joints && m_numJoints > 0) {
|
|
renderEntity_t renderEntity;
|
|
memset(&renderEntity, 0, sizeof(renderEntity));
|
|
renderEntity.hModel = m_model;
|
|
renderEntity.axis.Identity();
|
|
renderEntity.origin.Zero();
|
|
renderEntity.customSkin = m_modelDecl ? m_modelDecl->GetSkin() : NULL;
|
|
renderEntity.numJoints = m_numJoints;
|
|
renderEntity.joints = m_joints;
|
|
|
|
idRenderModel* dynamicModel = m_model->InstantiateDynamicModel(&renderEntity, NULL, m_cachedDynamicModel);
|
|
if (dynamicModel) {
|
|
m_cachedDynamicModel = dynamicModel;
|
|
drawModel = dynamicModel;
|
|
}
|
|
}
|
|
|
|
glEnable(GL_DEPTH_TEST);
|
|
DrawRenderModel(drawModel, false);
|
|
//DrawRenderModel(drawModel, true);
|
|
}
|
|
|
|
void CModelRenderWnd::DrawSkeleton() {
|
|
if (!m_modelDecl || !m_joints || !m_haveAnimFrame || m_numJoints <= 0) {
|
|
return;
|
|
}
|
|
|
|
const int* parents = m_modelDecl->JointParents();
|
|
|
|
glDisable(GL_DEPTH_TEST);
|
|
glLineWidth(2.0f);
|
|
glColor3f(0.10f, 0.80f, 1.00f);
|
|
glBegin(GL_LINES);
|
|
for (int i = 0; i < m_numJoints; i++) {
|
|
const int parent = parents ? parents[i] : -1;
|
|
if (parent < 0 || parent >= m_numJoints) {
|
|
continue;
|
|
}
|
|
const idVec3 a = m_joints[i].ToVec3();
|
|
const idVec3 b = m_joints[parent].ToVec3();
|
|
glVertex3fv(b.ToFloatPtr());
|
|
glVertex3fv(a.ToFloatPtr());
|
|
}
|
|
glEnd();
|
|
|
|
glPointSize(4.0f);
|
|
glBegin(GL_POINTS);
|
|
for (int i = 0; i < m_numJoints; i++) {
|
|
const idVec3 p = m_joints[i].ToVec3();
|
|
glVertex3fv(p.ToFloatPtr());
|
|
}
|
|
glEnd();
|
|
glPointSize(1.0f);
|
|
glLineWidth(1.0f);
|
|
glEnable(GL_DEPTH_TEST);
|
|
}
|
|
|
|
void CModelRenderWnd::DrawOverlayText() {
|
|
CRect client;
|
|
GetClientRect(client);
|
|
if (client.Width() <= 0 || client.Height() <= 0) {
|
|
return;
|
|
}
|
|
|
|
glDisable(GL_DEPTH_TEST);
|
|
glDisable(GL_TEXTURE_2D);
|
|
glMatrixMode(GL_PROJECTION);
|
|
glPushMatrix();
|
|
glLoadIdentity();
|
|
glOrtho(0, client.Width(), client.Height(), 0, -1, 1);
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glPushMatrix();
|
|
glLoadIdentity();
|
|
|
|
glColor3f(0.72f, 0.78f, 0.86f);
|
|
glRasterPos2f(8.0f, 18.0f);
|
|
CString text;
|
|
if (m_modelDecl) {
|
|
text.Format("%s", m_modelDecl->GetName());
|
|
}
|
|
else {
|
|
text = "No model decl selected";
|
|
}
|
|
const char* textPtr = text.GetBuffer(0);
|
|
glCallLists(text.GetLength(), GL_UNSIGNED_BYTE, textPtr);
|
|
text.ReleaseBuffer();
|
|
|
|
if (!m_animDisplayName.IsEmpty()) {
|
|
glRasterPos2f(8.0f, 36.0f);
|
|
const char* animTextPtr = m_animDisplayName.GetBuffer(0);
|
|
glCallLists(m_animDisplayName.GetLength(), GL_UNSIGNED_BYTE, animTextPtr);
|
|
m_animDisplayName.ReleaseBuffer();
|
|
}
|
|
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glPopMatrix();
|
|
glMatrixMode(GL_PROJECTION);
|
|
glPopMatrix();
|
|
glMatrixMode(GL_MODELVIEW);
|
|
}
|
|
|
|
void CModelRenderWnd::OnLButtonDown(UINT nFlags, CPoint point) {
|
|
m_dragging = TRUE;
|
|
m_lastMouse = point;
|
|
SetCapture();
|
|
SetFocus();
|
|
}
|
|
|
|
void CModelRenderWnd::OnLButtonUp(UINT nFlags, CPoint point) {
|
|
m_dragging = FALSE;
|
|
if (GetCapture() == this) {
|
|
ReleaseCapture();
|
|
}
|
|
}
|
|
|
|
void CModelRenderWnd::OnMouseMove(UINT nFlags, CPoint point) {
|
|
if (m_dragging) {
|
|
const int dx = point.x - m_lastMouse.x;
|
|
const int dy = point.y - m_lastMouse.y;
|
|
m_yaw += dx * 0.35f;
|
|
m_pitch += dy * 0.35f;
|
|
m_pitch = MV_ClampFloat(m_pitch, -89.0f, 89.0f);
|
|
m_lastMouse = point;
|
|
Invalidate(FALSE);
|
|
}
|
|
CWnd::OnMouseMove(nFlags, point);
|
|
}
|
|
|
|
BOOL CModelRenderWnd::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) {
|
|
const float scale = (zDelta > 0) ? 0.90f : 1.10f;
|
|
m_distance *= scale;
|
|
if (m_distance < m_radius * 0.50f) {
|
|
m_distance = m_radius * 0.50f;
|
|
}
|
|
if (m_distance > m_radius * 20.0f) {
|
|
m_distance = m_radius * 20.0f;
|
|
}
|
|
Invalidate(FALSE);
|
|
return TRUE;
|
|
}
|
|
|
|
//=============================================================================
|
|
// CModelViewDockWnd
|
|
//=============================================================================
|
|
IMPLEMENT_DYNAMIC(CModelViewDockWnd, CWnd)
|
|
|
|
BEGIN_MESSAGE_MAP(CModelViewDockWnd, CWnd)
|
|
ON_WM_CREATE()
|
|
ON_WM_SIZE()
|
|
ON_WM_ERASEBKGND()
|
|
ON_WM_CTLCOLOR()
|
|
ON_WM_DRAWITEM()
|
|
ON_WM_MEASUREITEM()
|
|
ON_WM_HSCROLL()
|
|
ON_MESSAGE(WM_MODELVIEW_ANIMTIME_CHANGED, OnAnimTimeChanged)
|
|
ON_CBN_SELCHANGE(ID_MODELVIEW_DECLCOMBO, OnDeclChanged)
|
|
ON_LBN_SELCHANGE(ID_MODELVIEW_ANIMLIST, OnAnimChanged)
|
|
ON_BN_CLICKED(ID_MODELVIEW_REFRESH, OnRefresh)
|
|
ON_BN_CLICKED(ID_MODELVIEW_REIMPORT, OnReimportModel)
|
|
ON_COMMAND(ID_MODELVIEW_FILE_NEWMD5DECL, OnNewMD5Decl)
|
|
ON_COMMAND(ID_MODELVIEW_FILE_ADDANIM, OnAddAnimationFile)
|
|
ON_BN_CLICKED(ID_MODELVIEW_SHOWSKELETON, OnShowSkeleton)
|
|
ON_BN_CLICKED(ID_MODELVIEW_PLAY, OnPlay)
|
|
ON_BN_CLICKED(ID_MODELVIEW_PAUSE, OnPause)
|
|
ON_BN_CLICKED(ID_MODELVIEW_STOP, OnStop)
|
|
END_MESSAGE_MAP()
|
|
|
|
CModelViewDockWnd::CModelViewDockWnd() {
|
|
m_hMenuBar = NULL;
|
|
m_modelDecl = NULL;
|
|
m_active = FALSE;
|
|
m_inLayout = false;
|
|
m_updatingTimeline = false;
|
|
m_timelineLengthMS = -1;
|
|
}
|
|
|
|
CModelViewDockWnd::~CModelViewDockWnd() {
|
|
ClearAnimationItems();
|
|
}
|
|
|
|
BOOL CModelViewDockWnd::Create(CWnd* pParent, UINT nID) {
|
|
CString className = AfxRegisterWndClass(
|
|
CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS,
|
|
::LoadCursor(NULL, IDC_ARROW),
|
|
ModelViewPanelBrush(),
|
|
NULL
|
|
);
|
|
|
|
return CWnd::CreateEx(
|
|
0,
|
|
className,
|
|
"RadiantModelViewDockWnd",
|
|
WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
|
|
CRect(0, 0, 0, 0),
|
|
pParent,
|
|
nID
|
|
);
|
|
}
|
|
|
|
int CModelViewDockWnd::OnCreate(LPCREATESTRUCT lpCreateStruct) {
|
|
if (CWnd::OnCreate(lpCreateStruct) == -1) {
|
|
return -1;
|
|
}
|
|
|
|
m_hMenuBar = MV_CreateModelViewMenuBar(GetSafeHwnd());
|
|
|
|
m_lblModel.Create("Model decl:", WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE, CRect(0, 0, 0, 0), this);
|
|
m_comboDecls.Create(WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWNLIST | CBS_SORT | CBS_OWNERDRAWFIXED | CBS_HASSTRINGS, CRect(0, 0, 0, 240), this, ID_MODELVIEW_DECLCOMBO);
|
|
m_comboDecls.SetItemHeight(-1, 22);
|
|
m_comboDecls.SetItemHeight(0, 22);
|
|
m_btnRefresh.Create("Refresh", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, CRect(0, 0, 0, 0), this, ID_MODELVIEW_REFRESH);
|
|
m_btnReimport.Create("Reimport", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, CRect(0, 0, 0, 0), this, ID_MODELVIEW_REIMPORT);
|
|
m_chkSkeleton.Create("Show skeleton", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX, CRect(0, 0, 0, 0), this, ID_MODELVIEW_SHOWSKELETON);
|
|
|
|
m_lblMesh.Create("Mesh hierarchy", WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE, CRect(0, 0, 0, 0), this);
|
|
m_treeMesh.Create(WS_CHILD | WS_VISIBLE | WS_BORDER | TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS | TVS_SHOWSELALWAYS, CRect(0, 0, 0, 0), this, ID_MODELVIEW_MESHTREE);
|
|
|
|
m_lblAnim.Create("Animations", WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE, CRect(0, 0, 0, 0), this);
|
|
m_listAnims.Create(WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL | LBS_NOTIFY, CRect(0, 0, 0, 0), this, ID_MODELVIEW_ANIMLIST);
|
|
|
|
if (!m_wndRender.Create(this, ID_MODELVIEW_RENDER)) {
|
|
return -1;
|
|
}
|
|
|
|
m_transportPanel.Create("", WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE, CRect(0, 0, 0, 0), this, ID_MODELVIEW_TRANSPORT);
|
|
m_btnPlay.Create("Play", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, CRect(0, 0, 0, 0), this, ID_MODELVIEW_PLAY);
|
|
m_btnPause.Create("Pause", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, CRect(0, 0, 0, 0), this, ID_MODELVIEW_PAUSE);
|
|
m_btnStop.Create("Stop", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, CRect(0, 0, 0, 0), this, ID_MODELVIEW_STOP);
|
|
m_sliderTimeline.Create(WS_CHILD | WS_VISIBLE | TBS_HORZ | TBS_NOTICKS, CRect(0, 0, 0, 0), this, ID_MODELVIEW_TIMELINE);
|
|
m_lblTime.Create("0:00.000 / 0:00.000", WS_CHILD | WS_VISIBLE | SS_RIGHT | SS_CENTERIMAGE, CRect(0, 0, 0, 0), this, ID_MODELVIEW_TIMETEXT);
|
|
|
|
SetTimelineRange(0);
|
|
UpdateTransportControls();
|
|
|
|
ApplyDarkTheme();
|
|
PopulateModelDecls();
|
|
return 0;
|
|
}
|
|
|
|
void CModelViewDockWnd::ApplyDarkTheme() {
|
|
MV_SubclassStatic(m_lblModel);
|
|
MV_SubclassStatic(m_lblMesh);
|
|
MV_SubclassStatic(m_lblAnim);
|
|
MV_SubclassStatic(m_transportPanel);
|
|
MV_SubclassStatic(m_lblTime);
|
|
MV_SubclassButton(m_btnRefresh);
|
|
MV_SubclassButton(m_btnReimport);
|
|
MV_SubclassButton(m_chkSkeleton);
|
|
MV_SubclassButton(m_btnPlay);
|
|
MV_SubclassButton(m_btnPause);
|
|
MV_SubclassButton(m_btnStop);
|
|
MV_SubclassCombo(m_comboDecls);
|
|
|
|
MV_ApplyNativeTheme(m_comboDecls.GetSafeHwnd());
|
|
MV_ApplyNativeTheme(m_treeMesh.GetSafeHwnd());
|
|
MV_ApplyNativeTheme(m_listAnims.GetSafeHwnd());
|
|
MV_ApplyNativeTheme(m_sliderTimeline.GetSafeHwnd());
|
|
MV_ApplyNativeTheme(m_btnRefresh.GetSafeHwnd());
|
|
MV_ApplyNativeTheme(m_btnReimport.GetSafeHwnd());
|
|
MV_ApplyNativeTheme(m_chkSkeleton.GetSafeHwnd());
|
|
MV_ApplyNativeTheme(m_btnPlay.GetSafeHwnd());
|
|
MV_ApplyNativeTheme(m_btnPause.GetSafeHwnd());
|
|
MV_ApplyNativeTheme(m_btnStop.GetSafeHwnd());
|
|
|
|
if (m_treeMesh.GetSafeHwnd()) {
|
|
m_treeMesh.SetBkColor(MV_DARK_INPUT);
|
|
m_treeMesh.SetTextColor(MV_DARK_TEXT);
|
|
::SendMessage(m_treeMesh.GetSafeHwnd(), TVM_SETLINECOLOR, 0, (LPARAM)MV_DARK_BORDER);
|
|
}
|
|
}
|
|
|
|
void CModelViewDockWnd::ClearAnimationItems() {
|
|
for (int i = 0; i < m_animItems.GetSize(); i++) {
|
|
modelViewAnimItem_t* item = reinterpret_cast<modelViewAnimItem_t*>(m_animItems[i]);
|
|
delete item;
|
|
}
|
|
m_animItems.RemoveAll();
|
|
}
|
|
|
|
void CModelViewDockWnd::SetActive(BOOL active) {
|
|
m_active = active;
|
|
m_wndRender.SetActive(active);
|
|
}
|
|
|
|
void CModelViewDockWnd::FocusModelView() {
|
|
if (m_wndRender.GetSafeHwnd()) {
|
|
m_wndRender.FocusRenderWindow();
|
|
}
|
|
}
|
|
|
|
void CModelViewDockWnd::PopulateModelDecls() {
|
|
m_comboDecls.ResetContent();
|
|
|
|
if (!declManager) {
|
|
return;
|
|
}
|
|
|
|
const int numDecls = declManager->GetNumDecls(DECL_MODELDEF);
|
|
for (int i = 0; i < numDecls; i++) {
|
|
const idDecl* decl = declManager->DeclByIndex(DECL_MODELDEF, i, false);
|
|
if (!decl) {
|
|
continue;
|
|
}
|
|
const int item = m_comboDecls.AddString(decl->GetName());
|
|
m_comboDecls.SetItemData(item, i);
|
|
}
|
|
|
|
if (m_comboDecls.GetCount() > 0) {
|
|
m_comboDecls.SetCurSel(0);
|
|
LoadSelectedModelDecl();
|
|
}
|
|
}
|
|
|
|
void CModelViewDockWnd::LoadSelectedModelDecl() {
|
|
int sel = m_comboDecls.GetCurSel();
|
|
if (sel == CB_ERR || !declManager) {
|
|
SetModelDecl(NULL);
|
|
return;
|
|
}
|
|
|
|
CString declName;
|
|
m_comboDecls.GetLBText(sel, declName);
|
|
|
|
const idDecl* decl = declManager->FindType(DECL_MODELDEF, (LPCTSTR)declName, false);
|
|
SetModelDecl((const idDeclModelDef*)(decl));
|
|
}
|
|
|
|
CString CModelViewDockWnd::GetSelectedModelDeclFileName() {
|
|
if (!declManager) {
|
|
return "";
|
|
}
|
|
|
|
CString declName;
|
|
const int sel = m_comboDecls.GetCurSel();
|
|
if (sel != CB_ERR) {
|
|
m_comboDecls.GetLBText(sel, declName);
|
|
}
|
|
else if (m_modelDecl && m_modelDecl->GetName()) {
|
|
declName.Format("%s", m_modelDecl->GetName());
|
|
}
|
|
|
|
declName = MV_Trimmed(declName);
|
|
if (declName.IsEmpty()) {
|
|
return "";
|
|
}
|
|
|
|
const idDecl* decl = declManager->FindType(DECL_MODELDEF, (LPCTSTR)declName, false);
|
|
if (!decl || decl->IsImplicit() || !decl->GetFileName() || !decl->GetFileName()[0]) {
|
|
return "";
|
|
}
|
|
|
|
CString declFileName;
|
|
declFileName.Format("%s", decl->GetFileName());
|
|
return MV_Trimmed(declFileName);
|
|
}
|
|
|
|
void CModelViewDockWnd::SetModelDecl(const idDeclModelDef* modelDecl) {
|
|
m_modelDecl = (idDeclModelDefInterface*)modelDecl;
|
|
m_wndRender.SetModelDecl(modelDecl);
|
|
PopulateMeshTree();
|
|
PopulateAnimationList();
|
|
|
|
if (m_listAnims.GetCount() > 0) {
|
|
m_listAnims.SetCurSel(0);
|
|
SelectAnimationItem(0);
|
|
}
|
|
else {
|
|
m_wndRender.SetAnimation(NULL, 0, 0, "");
|
|
UpdateTransportControls();
|
|
}
|
|
}
|
|
|
|
void CModelViewDockWnd::PopulateMeshTree() {
|
|
m_treeMesh.DeleteAllItems();
|
|
if (!m_modelDecl) {
|
|
return;
|
|
}
|
|
|
|
CString rootText;
|
|
rootText.Format("%s", m_modelDecl->GetName());
|
|
HTREEITEM root = m_treeMesh.InsertItem(rootText);
|
|
|
|
CString modelText;
|
|
modelText.Format("md5 mesh: %s", m_modelDecl->GetModelName());
|
|
m_treeMesh.InsertItem(modelText, root);
|
|
|
|
CString jointCountText;
|
|
jointCountText.Format("joints: %d", m_modelDecl->NumJoints());
|
|
HTREEITEM jointsRoot = m_treeMesh.InsertItem(jointCountText, root);
|
|
const int numJoints = m_modelDecl->NumJoints();
|
|
const int* parents = m_modelDecl->JointParents();
|
|
HTREEITEM* jointItems = NULL;
|
|
if (numJoints > 0) {
|
|
jointItems = new HTREEITEM[numJoints];
|
|
memset(jointItems, 0, sizeof(HTREEITEM) * numJoints);
|
|
}
|
|
|
|
for (int i = 0; i < numJoints; i++) {
|
|
CString jointText;
|
|
jointText.Format("%03d %s", i, m_modelDecl->GetJointName(i));
|
|
HTREEITEM parentItem = jointsRoot;
|
|
if (parents && parents[i] >= 0 && parents[i] < i && jointItems[parents[i]]) {
|
|
parentItem = jointItems[parents[i]];
|
|
}
|
|
jointItems[i] = m_treeMesh.InsertItem(jointText, parentItem);
|
|
}
|
|
|
|
delete[] jointItems;
|
|
|
|
HTREEITEM surfacesRoot = m_treeMesh.InsertItem("surfaces", root);
|
|
idRenderModel* model = NULL;
|
|
if (gameEdit && m_modelDecl->GetModelName() && m_modelDecl->GetModelName()[0]) {
|
|
model = gameEdit->ANIM_GetModelFromName(m_modelDecl->GetModelName());
|
|
}
|
|
if (!model) {
|
|
model = m_modelDecl->ModelHandle();
|
|
}
|
|
if (model) {
|
|
const int numSurfaces = model->NumSurfaces();
|
|
for (int i = 0; i < numSurfaces; i++) {
|
|
const modelSurface_t* surface = model->Surface(i);
|
|
CString surfaceText;
|
|
if (surface && surface->shader) {
|
|
surfaceText.Format("surface %03d %s", i, surface->shader->GetName());
|
|
}
|
|
else {
|
|
surfaceText.Format("surface %03d", i);
|
|
}
|
|
m_treeMesh.InsertItem(surfaceText, surfacesRoot);
|
|
}
|
|
}
|
|
|
|
m_treeMesh.Expand(root, TVE_EXPAND);
|
|
m_treeMesh.Expand(jointsRoot, TVE_EXPAND);
|
|
m_treeMesh.Expand(surfacesRoot, TVE_EXPAND);
|
|
}
|
|
|
|
void CModelViewDockWnd::PopulateAnimationList() {
|
|
m_listAnims.ResetContent();
|
|
ClearAnimationItems();
|
|
|
|
if (!m_modelDecl) {
|
|
return;
|
|
}
|
|
|
|
const int numAnims = m_modelDecl->NumAnims();
|
|
for (int i = 0; i < numAnims; i++) {
|
|
const idAnimInterface* declAnim = m_modelDecl->GetAnim(i);
|
|
if (!declAnim) {
|
|
continue;
|
|
}
|
|
|
|
const int syncedAnims = MV_MaxInt(1, declAnim->NumAnims());
|
|
for (int md5Index = 0; md5Index < syncedAnims; md5Index++) {
|
|
const idMD5AnimInterface* md5Anim = (const idMD5AnimInterface*)declAnim->MD5Anim(md5Index);
|
|
if (!md5Anim) {
|
|
continue;
|
|
}
|
|
|
|
if (gameEdit && md5Anim->Name() && md5Anim->Name()[0]) {
|
|
const idMD5AnimInterface* interfaceAnim = (const idMD5AnimInterface*)gameEdit->ANIM_GetAnim(md5Anim->Name());
|
|
if (interfaceAnim) {
|
|
md5Anim = interfaceAnim;
|
|
}
|
|
}
|
|
|
|
modelViewAnimItem_t* animItem = new modelViewAnimItem_t;
|
|
animItem->declAnimIndex = i;
|
|
animItem->md5AnimIndex = md5Index;
|
|
animItem->declAnim = declAnim;
|
|
animItem->md5Anim = md5Anim;
|
|
animItem->lengthMS = gameEdit ? gameEdit->ANIM_GetLength((const idMD5Anim*)md5Anim) : 0;
|
|
animItem->numFrames = gameEdit ? gameEdit->ANIM_GetNumFrames((const idMD5Anim*)md5Anim) : 0;
|
|
|
|
if (syncedAnims > 1) {
|
|
animItem->displayText.Format("%s [%d] (%d frames, %d ms)", declAnim->Name(), md5Index, animItem->numFrames, animItem->lengthMS);
|
|
}
|
|
else {
|
|
animItem->displayText.Format("%s (%d frames, %d ms)", declAnim->Name(), animItem->numFrames, animItem->lengthMS);
|
|
}
|
|
|
|
m_animItems.Add(animItem);
|
|
const int listItem = m_listAnims.AddString(animItem->displayText);
|
|
m_listAnims.SetItemData(listItem, reinterpret_cast<DWORD_PTR>(animItem));
|
|
}
|
|
}
|
|
}
|
|
|
|
void CModelViewDockWnd::SelectAnimationItem(int itemIndex) {
|
|
if (itemIndex < 0 || itemIndex >= m_listAnims.GetCount()) {
|
|
m_wndRender.SetAnimation(NULL, 0, 0, "");
|
|
UpdateTransportControls();
|
|
return;
|
|
}
|
|
|
|
modelViewAnimItem_t* animItem = reinterpret_cast<modelViewAnimItem_t*>(m_listAnims.GetItemData(itemIndex));
|
|
if (!animItem) {
|
|
m_wndRender.SetAnimation(NULL, 0, 0, "");
|
|
UpdateTransportControls();
|
|
return;
|
|
}
|
|
|
|
m_wndRender.SetAnimation((const idMD5Anim*)animItem->md5Anim, animItem->lengthMS, animItem->numFrames, animItem->displayText);
|
|
UpdateTransportControls();
|
|
}
|
|
|
|
void CModelViewDockWnd::SelectModelDeclByName(const char* declName) {
|
|
CString wanted = declName ? declName : "";
|
|
|
|
PopulateModelDecls();
|
|
|
|
if (wanted.IsEmpty()) {
|
|
return;
|
|
}
|
|
|
|
int sel = m_comboDecls.FindStringExact(-1, wanted);
|
|
if (sel != CB_ERR) {
|
|
m_comboDecls.SetCurSel(sel);
|
|
LoadSelectedModelDecl();
|
|
return;
|
|
}
|
|
|
|
if (declManager) {
|
|
const idDecl* decl = declManager->FindType(DECL_MODELDEF, (LPCTSTR)wanted, false);
|
|
if (decl) {
|
|
SetModelDecl((const idDeclModelDef*)decl);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CModelViewDockWnd::SelectAnimationByName(const char* animName) {
|
|
if (!animName || !animName[0]) {
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < m_listAnims.GetCount(); i++) {
|
|
modelViewAnimItem_t* animItem = reinterpret_cast<modelViewAnimItem_t*>(m_listAnims.GetItemData(i));
|
|
if (animItem && animItem->declAnim && animItem->declAnim->Name()) {
|
|
if (idStr::Icmp(animItem->declAnim->Name(), animName) == 0) {
|
|
m_listAnims.SetCurSel(i);
|
|
SelectAnimationItem(i);
|
|
return;
|
|
}
|
|
}
|
|
|
|
CString display;
|
|
m_listAnims.GetText(i, display);
|
|
if (display.Find(animName) >= 0) {
|
|
m_listAnims.SetCurSel(i);
|
|
SelectAnimationItem(i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void CModelViewDockWnd::SetTimelineRange(int animLengthMS) {
|
|
m_timelineLengthMS = animLengthMS;
|
|
|
|
const int maxPos = (animLengthMS > 0) ? MV_MaxInt(0, animLengthMS - 1) : 0;
|
|
HWND hSlider = m_sliderTimeline.GetSafeHwnd();
|
|
if (!hSlider) {
|
|
return;
|
|
}
|
|
|
|
::SendMessage(hSlider, TBM_SETRANGEMIN, FALSE, 0);
|
|
::SendMessage(hSlider, TBM_SETRANGEMAX, TRUE, maxPos);
|
|
::SendMessage(hSlider, TBM_SETPAGESIZE, 0, MV_MaxInt(1, animLengthMS / 20));
|
|
::SendMessage(hSlider, TBM_SETTICFREQ, MV_MaxInt(1, animLengthMS / 10), 0);
|
|
}
|
|
|
|
void CModelViewDockWnd::UpdateTimelinePosition(int animTimeMS, int animLengthMS) {
|
|
HWND hSlider = m_sliderTimeline.GetSafeHwnd();
|
|
if (!hSlider) {
|
|
return;
|
|
}
|
|
|
|
if (animLengthMS != m_timelineLengthMS) {
|
|
SetTimelineRange(animLengthMS);
|
|
}
|
|
|
|
const int maxPos = (animLengthMS > 0) ? MV_MaxInt(0, animLengthMS - 1) : 0;
|
|
const int pos = MV_ClampInt(animTimeMS, 0, maxPos);
|
|
m_updatingTimeline = true;
|
|
::SendMessage(hSlider, TBM_SETPOS, TRUE, pos);
|
|
m_updatingTimeline = false;
|
|
}
|
|
|
|
void CModelViewDockWnd::UpdateTimeLabel(int animTimeMS, int animLengthMS) {
|
|
if (!m_lblTime.GetSafeHwnd()) {
|
|
return;
|
|
}
|
|
|
|
CString current = MV_FormatAnimTime(animTimeMS);
|
|
CString total = MV_FormatAnimTime(animLengthMS);
|
|
CString text;
|
|
text.Format("%s / %s", (LPCTSTR)current, (LPCTSTR)total);
|
|
m_lblTime.SetWindowText(text);
|
|
}
|
|
|
|
void CModelViewDockWnd::UpdateTransportControls() {
|
|
const BOOL hasAnim = m_wndRender.HasAnimation();
|
|
const BOOL canScrub = (hasAnim && m_wndRender.GetAnimationLengthMS() > 0) ? TRUE : FALSE;
|
|
const BOOL playing = m_wndRender.IsPlaying();
|
|
const int animTimeMS = m_wndRender.GetAnimationTimeMS();
|
|
const int animLengthMS = m_wndRender.GetAnimationLengthMS();
|
|
|
|
if (m_btnPlay.GetSafeHwnd()) {
|
|
m_btnPlay.EnableWindow(canScrub && !playing);
|
|
}
|
|
if (m_btnPause.GetSafeHwnd()) {
|
|
m_btnPause.EnableWindow(canScrub && playing);
|
|
}
|
|
if (m_btnStop.GetSafeHwnd()) {
|
|
m_btnStop.EnableWindow(canScrub && (playing || animTimeMS > 0));
|
|
}
|
|
if (m_sliderTimeline.GetSafeHwnd()) {
|
|
m_sliderTimeline.EnableWindow(canScrub);
|
|
}
|
|
|
|
UpdateTimelinePosition(animTimeMS, animLengthMS);
|
|
UpdateTimeLabel(animTimeMS, animLengthMS);
|
|
}
|
|
|
|
|
|
void CModelViewDockWnd::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) {
|
|
if (nIDCtl == ID_MODELVIEW_DECLCOMBO && lpMeasureItemStruct) {
|
|
lpMeasureItemStruct->itemHeight = 22;
|
|
return;
|
|
}
|
|
CWnd::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
|
|
}
|
|
|
|
void CModelViewDockWnd::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) {
|
|
if (nIDCtl == ID_MODELVIEW_DECLCOMBO && lpDrawItemStruct) {
|
|
HDC hDC = lpDrawItemStruct->hDC;
|
|
RECT rc = lpDrawItemStruct->rcItem;
|
|
const bool selected = (lpDrawItemStruct->itemState & ODS_SELECTED) != 0;
|
|
const bool disabled = (lpDrawItemStruct->itemState & ODS_DISABLED) != 0;
|
|
const bool focus = (lpDrawItemStruct->itemState & ODS_FOCUS) != 0;
|
|
|
|
MV_FillRect(hDC, rc, selected ? MV_DARK_MENU_HOT : MV_DARK_INPUT);
|
|
if (focus) {
|
|
MV_FrameRect(hDC, rc, MV_DARK_ACCENT);
|
|
}
|
|
|
|
char text[1024];
|
|
text[0] = '\0';
|
|
if (lpDrawItemStruct->itemID != (UINT)-1) {
|
|
const int len = (int)m_comboDecls.SendMessage(CB_GETLBTEXTLEN, lpDrawItemStruct->itemID, 0);
|
|
if (len >= 0 && len < (int)sizeof(text)) {
|
|
m_comboDecls.SendMessage(CB_GETLBTEXT, lpDrawItemStruct->itemID, (LPARAM)text);
|
|
text[sizeof(text) - 1] = '\0';
|
|
}
|
|
}
|
|
else if (m_comboDecls.GetSafeHwnd()) {
|
|
m_comboDecls.GetWindowText(text, sizeof(text));
|
|
}
|
|
|
|
RECT textRect = rc;
|
|
textRect.left += 8;
|
|
textRect.right -= 6;
|
|
|
|
HFONT font = (HFONT)m_comboDecls.SendMessage(WM_GETFONT, 0, 0);
|
|
HFONT oldFont = font ? (HFONT)::SelectObject(hDC, font) : NULL;
|
|
::SetBkMode(hDC, TRANSPARENT);
|
|
::SetTextColor(hDC, disabled ? RGB(101, 110, 126) : MV_DARK_TEXT);
|
|
::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
|
|
if (oldFont) {
|
|
::SelectObject(hDC, oldFont);
|
|
}
|
|
return;
|
|
}
|
|
|
|
CWnd::OnDrawItem(nIDCtl, lpDrawItemStruct);
|
|
}
|
|
|
|
void CModelViewDockWnd::OnDeclChanged() {
|
|
LoadSelectedModelDecl();
|
|
}
|
|
|
|
void CModelViewDockWnd::OnAnimChanged() {
|
|
const int sel = m_listAnims.GetCurSel();
|
|
if (sel == LB_ERR) {
|
|
m_wndRender.SetAnimation(NULL, 0, 0, "");
|
|
UpdateTransportControls();
|
|
return;
|
|
}
|
|
SelectAnimationItem(sel);
|
|
}
|
|
|
|
void CModelViewDockWnd::OnRefresh() {
|
|
CString previous;
|
|
int oldSel = m_comboDecls.GetCurSel();
|
|
if (oldSel != CB_ERR) {
|
|
m_comboDecls.GetLBText(oldSel, previous);
|
|
}
|
|
|
|
PopulateModelDecls();
|
|
|
|
if (!previous.IsEmpty()) {
|
|
int newSel = m_comboDecls.FindStringExact(-1, previous);
|
|
if (newSel != CB_ERR) {
|
|
m_comboDecls.SetCurSel(newSel);
|
|
LoadSelectedModelDecl();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CModelViewDockWnd::OnReimportModel() {
|
|
CString declFileName = GetSelectedModelDeclFileName();
|
|
if (declFileName.IsEmpty()) {
|
|
AfxMessageBox("Select a modelDef with a real decl file before reimporting.", MB_ICONEXCLAMATION | MB_OK);
|
|
return;
|
|
}
|
|
|
|
MV_ExecuteExportModelsCommand((LPCTSTR)declFileName, "reimporting the selected modelDef");
|
|
}
|
|
|
|
void CModelViewDockWnd::OnNewMD5Decl() {
|
|
if (!declManager) {
|
|
AfxMessageBox("The decl manager is not available.", MB_ICONERROR | MB_OK);
|
|
return;
|
|
}
|
|
|
|
CString meshPath;
|
|
if (!MV_ChooseMayaFile(this, "Select Maya Mesh For New MD5 Decl", meshPath)) {
|
|
return;
|
|
}
|
|
|
|
CString declName = MV_BuildSafeNameFromAssetPath(meshPath);
|
|
bool replaceExisting = false;
|
|
CString existingFile;
|
|
|
|
while (true) {
|
|
if (!MV_PromptForText(this, "New MD5 Decl", "modelDef name:", declName)) {
|
|
return;
|
|
}
|
|
|
|
const idDecl* existing = declManager->FindType(DECL_MODELDEF, (LPCTSTR)declName, false);
|
|
if (!existing) {
|
|
break;
|
|
}
|
|
|
|
CString message;
|
|
message.Format("modelDef '%s' already exists in:\n%s\n\nReplace that decl with the selected Maya mesh?",
|
|
(LPCTSTR)declName, existing->GetFileName());
|
|
const int choice = AfxMessageBox(message, MB_ICONQUESTION | MB_YESNOCANCEL);
|
|
if (choice == IDYES) {
|
|
replaceExisting = true;
|
|
existingFile.Format("%s", existing->GetFileName());
|
|
break;
|
|
}
|
|
if (choice == IDCANCEL) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
CString declFile = existingFile;
|
|
bool createdNewFile = false;
|
|
if (!replaceExisting) {
|
|
if (!MV_ChooseDeclFile(this, declFile, createdNewFile)) {
|
|
return;
|
|
}
|
|
|
|
if (createdNewFile && !declManager->CreateDeclFile((LPCTSTR)declFile, DECL_MODELDEF)) {
|
|
CString message;
|
|
message.Format("Could not create decl file:\n%s", (LPCTSTR)declFile);
|
|
AfxMessageBox(message, MB_ICONERROR | MB_OK);
|
|
return;
|
|
}
|
|
|
|
idDecl* newDecl = declManager->CreateNewDecl(DECL_MODELDEF, (LPCTSTR)declName, (LPCTSTR)declFile, true);
|
|
if (!newDecl) {
|
|
CString message;
|
|
message.Format("Could not create modelDef '%s' in:\n%s", (LPCTSTR)declName, (LPCTSTR)declFile);
|
|
AfxMessageBox(message, MB_ICONERROR | MB_OK);
|
|
return;
|
|
}
|
|
}
|
|
|
|
CString declText = MV_BuildModelDefText((LPCTSTR)declName, (LPCTSTR)meshPath);
|
|
if (!declManager->SetDeclText(DECL_MODELDEF, (LPCTSTR)declName, (LPCTSTR)declText, true, true)) {
|
|
CString message;
|
|
message.Format("Could not save modelDef '%s'.", (LPCTSTR)declName);
|
|
AfxMessageBox(message, MB_ICONERROR | MB_OK);
|
|
return;
|
|
}
|
|
|
|
if (!declFile.IsEmpty()) {
|
|
declManager->ReloadFile((LPCTSTR)declFile, true);
|
|
}
|
|
|
|
declManager->FindType(DECL_MODELDEF, (LPCTSTR)declName, true);
|
|
MV_ExecuteExportModelsCommand((LPCTSTR)declFile, "creating a new MD5 modelDef");
|
|
SelectModelDeclByName((LPCTSTR)declName);
|
|
|
|
CString message;
|
|
message.Format("Created modelDef '%s' using mesh:\n%s", (LPCTSTR)declName, (LPCTSTR)meshPath);
|
|
AfxMessageBox(message, MB_ICONINFORMATION | MB_OK);
|
|
}
|
|
|
|
void CModelViewDockWnd::OnAddAnimationFile() {
|
|
if (!declManager) {
|
|
AfxMessageBox("The decl manager is not available.", MB_ICONERROR | MB_OK);
|
|
return;
|
|
}
|
|
if (!m_modelDecl) {
|
|
AfxMessageBox("Select or create a modelDef before adding an animation.", MB_ICONEXCLAMATION | MB_OK);
|
|
return;
|
|
}
|
|
|
|
CString animPath;
|
|
if (!MV_ChooseMayaFile(this, "Select Maya Animation", animPath)) {
|
|
return;
|
|
}
|
|
|
|
CString animName = MV_GetFileBaseName(animPath, true);
|
|
for (int i = 0; i < animName.GetLength(); i++) {
|
|
char c = animName[i];
|
|
const bool valid = (c >= 'a' && c <= 'z') ||
|
|
(c >= 'A' && c <= 'Z') ||
|
|
(c >= '0' && c <= '9') ||
|
|
c == '_' || c == '-';
|
|
if (!valid) {
|
|
animName.SetAt(i, '_');
|
|
}
|
|
}
|
|
animName = MV_TrimNameTokenChars(animName);
|
|
if (animName.IsEmpty()) {
|
|
animName = "new_anim";
|
|
}
|
|
|
|
if (!MV_PromptForText(this, "Add Animation", "animation name:", animName)) {
|
|
return;
|
|
}
|
|
|
|
CString modelName;
|
|
modelName.Format("%s", m_modelDecl->GetName());
|
|
const idDecl* decl = declManager->FindType(DECL_MODELDEF, (LPCTSTR)modelName, false);
|
|
CString declFile;
|
|
|
|
if (!decl || decl->IsImplicit()) {
|
|
bool createdNewFile = false;
|
|
if (!MV_ChooseDeclFile(this, declFile, createdNewFile)) {
|
|
return;
|
|
}
|
|
if (createdNewFile && !declManager->CreateDeclFile((LPCTSTR)declFile, DECL_MODELDEF)) {
|
|
CString message;
|
|
message.Format("Could not create decl file:\n%s", (LPCTSTR)declFile);
|
|
AfxMessageBox(message, MB_ICONERROR | MB_OK);
|
|
return;
|
|
}
|
|
idDecl* newDecl = declManager->CreateNewDecl(DECL_MODELDEF, (LPCTSTR)modelName, (LPCTSTR)declFile, true);
|
|
if (!newDecl) {
|
|
CString message;
|
|
message.Format("Could not create modelDef '%s'.", (LPCTSTR)modelName);
|
|
AfxMessageBox(message, MB_ICONERROR | MB_OK);
|
|
return;
|
|
}
|
|
decl = newDecl;
|
|
}
|
|
else {
|
|
declFile.Format("%s", decl->GetFileName());
|
|
}
|
|
|
|
CString declText = MV_ReadDeclText(decl);
|
|
if (declText.IsEmpty()) {
|
|
declText = MV_BuildModelDefText((LPCTSTR)modelName, m_modelDecl->GetModelName());
|
|
}
|
|
|
|
CString newText = MV_InsertAnimIntoModelDefText(declText, (LPCTSTR)animName, (LPCTSTR)animPath);
|
|
if (!declManager->SetDeclText(DECL_MODELDEF, (LPCTSTR)modelName, (LPCTSTR)newText, true, true)) {
|
|
CString message;
|
|
message.Format("Could not save animation '%s' into modelDef '%s'.", (LPCTSTR)animName, (LPCTSTR)modelName);
|
|
AfxMessageBox(message, MB_ICONERROR | MB_OK);
|
|
return;
|
|
}
|
|
|
|
if (!declFile.IsEmpty()) {
|
|
declManager->ReloadFile((LPCTSTR)declFile, true);
|
|
}
|
|
|
|
declManager->FindType(DECL_MODELDEF, (LPCTSTR)modelName, true);
|
|
MV_ExecuteExportModelsCommand((LPCTSTR)declFile, "adding a Maya animation to a modelDef");
|
|
SelectModelDeclByName((LPCTSTR)modelName);
|
|
SelectAnimationByName((LPCTSTR)animName);
|
|
|
|
CString message;
|
|
message.Format("Added animation '%s' to modelDef '%s':\n%s", (LPCTSTR)animName, (LPCTSTR)modelName, (LPCTSTR)animPath);
|
|
AfxMessageBox(message, MB_ICONINFORMATION | MB_OK);
|
|
}
|
|
|
|
|
|
void CModelViewDockWnd::OnShowSkeleton() {
|
|
m_wndRender.SetShowSkeleton(m_chkSkeleton.GetCheck() == BST_CHECKED);
|
|
}
|
|
|
|
void CModelViewDockWnd::OnPlay() {
|
|
m_wndRender.PlayAnimation();
|
|
UpdateTransportControls();
|
|
}
|
|
|
|
void CModelViewDockWnd::OnPause() {
|
|
m_wndRender.PauseAnimation();
|
|
UpdateTransportControls();
|
|
}
|
|
|
|
void CModelViewDockWnd::OnStop() {
|
|
m_wndRender.StopAnimation();
|
|
UpdateTransportControls();
|
|
}
|
|
|
|
LRESULT CModelViewDockWnd::OnAnimTimeChanged(WPARAM wParam, LPARAM lParam) {
|
|
UpdateTransportControls();
|
|
return 0;
|
|
}
|
|
|
|
void CModelViewDockWnd::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) {
|
|
if (pScrollBar && pScrollBar->GetSafeHwnd() == m_sliderTimeline.GetSafeHwnd()) {
|
|
if (!m_updatingTimeline && m_wndRender.HasAnimation()) {
|
|
const int pos = (int)::SendMessage(m_sliderTimeline.GetSafeHwnd(), TBM_GETPOS, 0, 0);
|
|
m_wndRender.SetAnimationTimeMS(pos);
|
|
UpdateTransportControls();
|
|
}
|
|
return;
|
|
}
|
|
|
|
CWnd::OnHScroll(nSBCode, nPos, pScrollBar);
|
|
}
|
|
|
|
void CModelViewDockWnd::LayoutChildren() {
|
|
if (!GetSafeHwnd() || m_inLayout) {
|
|
return;
|
|
}
|
|
m_inLayout = true;
|
|
|
|
CRect client;
|
|
GetClientRect(client);
|
|
if (client.Width() < 1 || client.Height() < 1) {
|
|
m_inLayout = false;
|
|
return;
|
|
}
|
|
|
|
const int margin = 6;
|
|
const int gap = 6;
|
|
const int menuH = MV_MenuBarPreferredHeight();
|
|
const int topH = 30;
|
|
const int leftW = MV_MaxInt(260, MV_MinInt(380, client.Width() / 4));
|
|
const int labelH = 20;
|
|
const int animH = MV_MaxInt(120, client.Height() / 4);
|
|
|
|
if (m_hMenuBar && ::IsWindow(m_hMenuBar)) {
|
|
::MoveWindow(m_hMenuBar, client.left, client.top, client.Width(), menuH, TRUE);
|
|
}
|
|
|
|
CRect topRect(client.left + margin, client.top + menuH + margin, client.right - margin, client.top + menuH + margin + topH);
|
|
int x = topRect.left;
|
|
if (m_lblModel.GetSafeHwnd()) {
|
|
m_lblModel.MoveWindow(x, topRect.top, 80, topH, TRUE);
|
|
x += 84;
|
|
}
|
|
if (m_comboDecls.GetSafeHwnd()) {
|
|
const int reimportW = 80;
|
|
const int refreshW = 72;
|
|
const int skeletonW = 140;
|
|
const int reserveW = reimportW + gap + refreshW + gap + skeletonW;
|
|
const int comboW = MV_MinInt(460, MV_MaxInt(120, topRect.right - x - reserveW));
|
|
m_comboDecls.MoveWindow(x, topRect.top + 2, comboW, 240, TRUE);
|
|
x += comboW + gap;
|
|
}
|
|
if (m_btnReimport.GetSafeHwnd()) {
|
|
m_btnReimport.MoveWindow(x, topRect.top + 2, 80, 24, TRUE);
|
|
x += 80 + gap;
|
|
}
|
|
if (m_btnRefresh.GetSafeHwnd()) {
|
|
m_btnRefresh.MoveWindow(x, topRect.top + 2, 72, 24, TRUE);
|
|
x += 72 + gap;
|
|
}
|
|
if (m_chkSkeleton.GetSafeHwnd()) {
|
|
m_chkSkeleton.MoveWindow(x, topRect.top + 2, 140, 24, TRUE);
|
|
}
|
|
|
|
CRect content(client.left + margin, topRect.bottom + margin, client.right - margin, client.bottom - margin);
|
|
CRect left(content.left, content.top, content.left + leftW, content.bottom);
|
|
CRect render(content.left + leftW + gap, content.top, content.right, content.bottom);
|
|
|
|
const int transportH = (render.Height() > 96) ? 58 : MV_MaxInt(36, render.Height() / 3);
|
|
CRect preview(render.left, render.top, render.right, MV_MaxInt(render.top, render.bottom - transportH - gap));
|
|
CRect transport(render.left, preview.bottom + gap, render.right, render.bottom);
|
|
|
|
if (m_lblMesh.GetSafeHwnd()) {
|
|
m_lblMesh.MoveWindow(left.left, left.top, left.Width(), labelH, TRUE);
|
|
}
|
|
if (m_treeMesh.GetSafeHwnd()) {
|
|
m_treeMesh.MoveWindow(left.left, left.top + labelH, left.Width(), MV_MaxInt(40, left.Height() - animH - labelH * 2 - gap), TRUE);
|
|
}
|
|
if (m_lblAnim.GetSafeHwnd()) {
|
|
m_lblAnim.MoveWindow(left.left, left.bottom - animH - labelH, left.Width(), labelH, TRUE);
|
|
}
|
|
if (m_listAnims.GetSafeHwnd()) {
|
|
m_listAnims.MoveWindow(left.left, left.bottom - animH, left.Width(), animH, TRUE);
|
|
}
|
|
if (m_wndRender.GetSafeHwnd()) {
|
|
m_wndRender.MoveWindow(preview, TRUE);
|
|
}
|
|
if (m_transportPanel.GetSafeHwnd()) {
|
|
m_transportPanel.MoveWindow(transport, TRUE);
|
|
}
|
|
|
|
CRect transportInner(transport);
|
|
transportInner.DeflateRect(8, 8);
|
|
const int btnW = 52;
|
|
const int btnH = 24;
|
|
const int btnY = transportInner.top + MV_MaxInt(0, (transportInner.Height() - btnH) / 2);
|
|
int tx = transportInner.left;
|
|
|
|
if (m_btnPlay.GetSafeHwnd()) {
|
|
m_btnPlay.MoveWindow(tx, btnY, btnW, btnH, TRUE);
|
|
tx += btnW + gap;
|
|
}
|
|
if (m_btnPause.GetSafeHwnd()) {
|
|
m_btnPause.MoveWindow(tx, btnY, btnW, btnH, TRUE);
|
|
tx += btnW + gap;
|
|
}
|
|
if (m_btnStop.GetSafeHwnd()) {
|
|
m_btnStop.MoveWindow(tx, btnY, btnW, btnH, TRUE);
|
|
tx += btnW + gap + 2;
|
|
}
|
|
|
|
const int timeW = (transportInner.Width() < 480) ? 108 : 128;
|
|
const int sliderLeft = tx;
|
|
const int sliderRight = MV_MaxInt(sliderLeft, transportInner.right - timeW - gap);
|
|
const int sliderW = MV_MaxInt(20, sliderRight - sliderLeft);
|
|
if (m_sliderTimeline.GetSafeHwnd()) {
|
|
m_sliderTimeline.MoveWindow(sliderLeft, btnY + 1, sliderW, btnH, TRUE);
|
|
}
|
|
if (m_lblTime.GetSafeHwnd()) {
|
|
m_lblTime.MoveWindow(sliderRight + gap, btnY, timeW, btnH, TRUE);
|
|
}
|
|
|
|
m_inLayout = false;
|
|
}
|
|
|
|
void CModelViewDockWnd::OnSize(UINT nType, int cx, int cy) {
|
|
CWnd::OnSize(nType, cx, cy);
|
|
LayoutChildren();
|
|
}
|
|
|
|
BOOL CModelViewDockWnd::OnEraseBkgnd(CDC* pDC) {
|
|
CRect client;
|
|
GetClientRect(client);
|
|
pDC->FillSolidRect(client, MV_DARK_BG);
|
|
|
|
CRect rc;
|
|
if (m_treeMesh.GetSafeHwnd()) {
|
|
m_treeMesh.GetWindowRect(rc);
|
|
ScreenToClient(rc);
|
|
rc.InflateRect(1, 1);
|
|
MV_FrameRect(pDC, rc, MV_DARK_BORDER);
|
|
}
|
|
if (m_listAnims.GetSafeHwnd()) {
|
|
m_listAnims.GetWindowRect(rc);
|
|
ScreenToClient(rc);
|
|
rc.InflateRect(1, 1);
|
|
MV_FrameRect(pDC, rc, MV_DARK_BORDER);
|
|
}
|
|
if (m_comboDecls.GetSafeHwnd()) {
|
|
m_comboDecls.GetWindowRect(rc);
|
|
ScreenToClient(rc);
|
|
rc.InflateRect(1, 1);
|
|
MV_FrameRect(pDC, rc, MV_DARK_BORDER);
|
|
}
|
|
if (m_transportPanel.GetSafeHwnd()) {
|
|
m_transportPanel.GetWindowRect(rc);
|
|
ScreenToClient(rc);
|
|
rc.InflateRect(1, 1);
|
|
MV_FrameRect(pDC, rc, MV_DARK_BORDER);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
HBRUSH CModelViewDockWnd::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) {
|
|
HBRUSH brush = CWnd::OnCtlColor(pDC, pWnd, nCtlColor);
|
|
|
|
if (!pDC) {
|
|
return brush;
|
|
}
|
|
|
|
switch (nCtlColor) {
|
|
case CTLCOLOR_STATIC:
|
|
pDC->SetBkColor(MV_DARK_PANEL);
|
|
pDC->SetTextColor(MV_DARK_TEXT);
|
|
return ModelViewPanelBrush();
|
|
case CTLCOLOR_LISTBOX:
|
|
case CTLCOLOR_EDIT:
|
|
pDC->SetBkColor(MV_DARK_INPUT);
|
|
pDC->SetTextColor(MV_DARK_TEXT);
|
|
return ModelViewInputBrush();
|
|
case CTLCOLOR_BTN:
|
|
pDC->SetBkColor(MV_DARK_PANEL);
|
|
pDC->SetTextColor(MV_DARK_TEXT);
|
|
return ModelViewPanelBrush();
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return brush;
|
|
}
|