这是一种对 3dsmax 的改造方法,可以作为制作Game Editor 的另一种参考方案:
选择 Views/Extended/Game 将进入自定义的渲染视图,右击菜单可以返回常规视图,或者通过Viewport Config也可以返回;
3dsmax允许创建自己的视图窗口,你可以从ViewWindow类继承,然后使用COREInterface 注册视图;要求实现几个纯虚函数
class GameViewWindow:public ViewWindow
{
//返回的名字,就是在Views/Extended中看到的名字
MCHAR *GetName(){return "Game";}
//在这里创建你自己的渲染窗口,一般使用WS_CHILD创建,如果你希望包含自己的菜单,可以使用对话框模版实现,这是惟一可以包含菜单又可以作为子窗口的方式
//然后响应父窗口的WM_SIZE和WM_MOVE来调整它;另外,如果使用OpenGL,请注意使用独立线程;防止3dsmax 使用opengl窗口时发生混乱;
HWND CreateViewWindow(HWND hParent, int x, int y, int w, int h);
//切换视图时会导致Destroy 发生,传入的hWnd就是你自己创建的那个窗口的句柄
void DestroyViewWindow(HWND hWnd);
...
}
在插件dll被初始化的时候注册视图:
GetCOREInterface()->RegisterViewWindow(&vw);
从上面看到,按照3dsmax 的协议,创建了自己的渲染窗口,基本上可以为所欲为了,因为在dll中和在宿主exe中的控制能力没有什么区别;
另外采取hook 手段,可以完全控制宿主3dsmax的行为,因为3dsmax 框架是基于纯虚函数实现的,hook纯虚函数是一件很容易的实际,然后
就可以在hook后的函数中做各种各样的事情:),这个方法已经试验成功,由于时间关系,这个话题将在后续的blog中写。
static GameViewWindow gvw;
Interface* f = 0;
LRESULT WINAPI __DefWindowProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;
RECT area;
GraphicsWindow* gw;
HMENU hMenu,HMenuView;
HINSTANCE hBase;
switch( Msg )
{
case WM_LBUTTONDOWN:
{
HWND hwnd1 = f->GetActiveViewport()->GetHWnd();
HWND hwnd2 = ::GetParent( hWnd );
if(hwnd1!=hWnd)
{
f->MakeExtendedViewportActive( hWnd );
}
}
break;
case WM_RBUTTONUP:
{
hBase = ::GetModuleHandle( 0 );
hMenu = ::LoadMenu( theApp.m_hInstance,MAKEINTRESOURCE(IDR_MENU1));
HMenuView = ::GetSubMenu( hMenu,0);
DWORD fwKeys = wParam;
POINT pt;
pt.x= LOWORD(lParam);
pt.y = HIWORD(lParam);
f->PutUpViewMenu( hWnd,pt);
::TrackPopupMenu( HMenuView,0,pt.x,pt.y,0,hWnd,&area);
}
break;
case WM_COMMAND:
{
DWORD type = HIWORD(wParam);
DWORD id = LOWORD(wParam);
if( type == 0 )
{
switch(id)
{
case ID_FILE_CLOSE:
break;
}
}
break;
}
case WM_PAINT:
break;
case WM_SIZE:
{
int cx = LOWORD( lParam );
int cy = HIWORD( lParam );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
::gluPerspective(90,1,1,100000.f);
glViewport(0,0,cx,cy);
glMatrixMode( GL_MODELVIEW);
Eng_Resize( cx, cy);
}
break;
}
return ::DefWindowProc( hWnd,Msg,wParam,lParam);
}
struct CreateStruct
{
DWORD dwExStyle;
DWORD style;
int x;
int y;
int w;
int h;
HWND hParent;
HINSTANCE hInst;
HWND hWnd;
volatile int lock;
};
class GameViewWindow : public ViewWindow
{
public:
int m_bClose;
HWND m_hWnd;
HGLRC m_hrc;
HANDLE m_hThread;
DWORD m_dwThreadID;
MCHAR *GetName(){return "Game";}
virtual int NumberCanCreate() { return 1; }
HWND CreateViewWindow(HWND hParent, int x, int y, int w, int h);
void DestroyViewWindow(HWND hWnd);
BOOL CanCreate();
};
void start_address( void * p )
{
CreateStruct* cs = (CreateStruct*)p;
gvw.m_dwThreadID = ::GetCurrentThreadId();
cs->lock = gvw.m_dwThreadID - gvw.m_dwThreadID;
HDC hdc = ::GetDC(gvw.m_hWnd);
PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR), // size of this pfd
1, // version number
PFD_DRAW_TO_WINDOW | // support window
PFD_SUPPORT_OPENGL | // support OpenGL
PFD_DOUBLEBUFFER, // double buffered
PFD_TYPE_RGBA, // RGBA type
24, // 24-bit color depth
0, 0, 0, 0, 0, 0, // color bits ignored
0, // no alpha buffer
0, // shift bit ignored
0, // no accumulation buffer
0, 0, 0, 0, // accum bits ignored
32, // 32-bit z-buffer
0, // no stencil buffer
0, // no auxiliary buffer
PFD_MAIN_PLANE, // main layer
0, // reserved
0, 0, 0 // layer masks ignored
};
int iPixelFormat;
// get the best available match of pixel format for the device context
iPixelFormat = ChoosePixelFormat(hdc, &pfd);
// make that the pixel format of the device context
SetPixelFormat(hdc, iPixelFormat, &pfd);
gvw.m_hrc = ::wglCreateContext( hdc );
wglMakeCurrent(hdc,gvw.m_hrc);
glClearColor( 0.2,0.2,0.2,1.0);
glEnable( GL_DEPTH_TEST);
MSG msg;
static int frame_count = 0;
while ( 1 )
{
if( gvw.m_bClose )
{
::wglMakeCurrent( 0,0);
::wglDeleteContext( gvw.m_hrc);
gvw.m_hrc =0;
break;
}
glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW);
glLoadIdentity();
static int a = -1;
static float b = -0.01;
glTranslatef(a*0.1,0,b);
glRotatef(a*10.f,0,1,0);
if( a == -1)
a = 1;
else
a = -1;
//Handle Input
if( GetKeyState( VK_LCONTROL)&0x8000)
{
}
glRectf(-0.2,-0.2,0.2,0.2);
SwapBuffers( hdc );
}
return ;
}
HWND GameViewWindow::CreateViewWindow(HWND hParent, int x, int y, int w, int h)
{
HGDIOBJ hbr = ::GetStockObject( DKGRAY_BRUSH );
HINSTANCE hInst = theApp.m_hInstance;
RECT area;
::GetClientRect(hParent,&area);
int ww = area.right - area.left;
int hh = area.bottom - area.top;
WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, __DefWindowProc, 0L, 0L,
hInst, NULL, NULL, NULL, NULL,
"OPENGLWINDOW", NULL };
::RegisterClassEx( &wc );
static CreateStruct cs;
cs.x = x;
cs.y = y;
cs.w = ww;
cs.h = hh;
cs.style = WS_CHILD|WS_VISIBLE;
cs.dwExStyle = 0;
cs.hParent = hParent;
cs.hInst = hInst;
HWND hWnd = CreateWindow(
"OPENGLWINDOW",
"",
cs.style,
cs.x,cs.y,cs.w,cs.h,
cs.hParent,
NULL,
cs.hInst,
NULL );
m_hWnd = hWnd;
cs.hWnd = hWnd;
cs.lock = 1;
ShowWindow( hWnd,SW_SHOW);
UpdateWindow( hWnd );
m_bClose = 0;
m_hThread = (HANDLE)::_beginthread(start_address,0,&cs);
//等待线程
while( cs.lock == 1 )
{
Sleep(0);
}
::AttachThreadInput( m_dwThreadID,::GetCurrentThreadId(),TRUE);
return cs.hWnd;
}
void GameViewWindow::DestroyViewWindow(HWND hWnd)
{
//等待线程退出
m_bClose = 1;
CONTEXT ctx;
while( ::GetThreadContext( m_hThread,&ctx) )
{
Sleep(0);
}
::DestroyWindow( m_hWnd );
m_hWnd = 0;
}
BOOL GameViewWindow::CanCreate()
{
return TRUE;
}
//注册Extend View 菜单项
DLL_Initinstance()
{
f = GetCOREInterface();
BOOL x = f->RegisterViewWindow(&gvw);
}