Files
DoomRTX/neo/engine/tools/radiant/ModelViewDock.cpp
T
Justin Marshall 76f642286e Added modelviewer.
Added neural network code(turned off by default).
Lots of editor and rendering fixes.
2026-05-17 22:09:25 -07:00

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;
}