#include <Ogre.h>
#include <OIS/OIS.h>
#include <CEGUI/CEGUI.h>
#include <OgreCEGUIRenderer.h>
using namespace Ogre;
class ExitListener : public FrameListener
{
public:
ExitListener(OIS::Keyboard *keyboard)
: mKeyboard(keyboard)
{
}
bool frameStarted(const FrameEvent& evt)
{
mKeyboard->capture();
return !mKeyboard->isKeyDown(OIS::KC_ESCAPE);
}
private:
OIS::Keyboard *mKeyboard;
};
class Application
{
public:
void go()
{
/************************************************************************
1、建立Root對象。
2、定義Ogre将要使用的資源。
3、選擇并設定渲染系統(即DirectX, OpenGL等)。
4、建立渲染視窗(Ogre所處的視窗)。
5、初始化你要使用的資源。
6、用這些資源來建立一個場景。
7、設定第三方庫或插件。
8、建立一些幀監聽器。
9、啟動渲染循環
************************************************************************/
createRoot();
defineResources();
setupRenderSystem();
createRenderWindow();
initializeResourceGroups();
setupScene();
setupInputSystem();
setupCEGUI();
createFrameListener();
startRenderLoop();
}
~Application()
{
/************************************************************************
最後一件事是,當程式終止時,我們要對所建立的所有對象進行清理。為止,我們
将按照與建立時相反的順序來删除或銷毀這些對象。我們從OIS開始下手,它有一個
專門的方法來銷毀它的對象。添加如下代碼:
************************************************************************/
mInputManager->destroyInputObject(mKeyboard);
OIS::InputManager::destroyInputSystem(mInputManager);
/************************************************************************
現在我們來清理CEGUI,隻要删除對象即可:
************************************************************************/
delete mRenderer;
delete mSystem;
/************************************************************************
最後,我們要删除Root和FrameListener對象。當删除Root對象時,我們建立的其它
對象(SceneManager, the RenderWindow等)也會一并删除。
************************************************************************/
delete mListener;
delete mRoot;
/************************************************************************
好了! 你現在可以編譯并運作你的程式了,雖然你将隻能看見一個黑屏,因為我們
并沒有往場景添加任何東西。如果在編譯裡遇到連結問題,確定CEGUIBase_d.lib
和OgreGUIRenderer_d.lib添加到了連結器的輸入裡(這是debug模式的,如果是release
模式,去掉_d)。現在你應該對Ogre的啟動過程比較熟悉了,可以抛開示例架構而使
用其它的了。
************************************************************************/
}
private:
Root *mRoot;
OIS::Keyboard *mKeyboard;
OIS::InputManager *mInputManager;
CEGUI::OgreCEGUIRenderer *mRenderer;
CEGUI::System *mSystem;
ExitListener *mListener;
void createRoot()
{
/************************************************************************
1、建立Root對象
Root的構造函數需要三個參數。第一個是插件配置檔案的名稱和路徑。
第二個是Ogre配置檔案的路徑(它告訴Ogre關于顯示卡、顯示設定等資訊)。
最後一個是日志檔案的名稱和路徑。因為我們不需要修改任何一個屬性,是以用預設的。
************************************************************************/
mRoot = new Root();
}
void defineResources()
{
/************************************************************************
2、資源
注意: 你最好在SDK的bin/release目錄裡找到resources.cfg,看看它的内容是很有幫助的。
下面我們要定義程式将要使用的資源,包括紋理、模型、腳本等等。請記住,你必須預先定
義好你的資源,在Ogre能使用它們之前,你還必須對它進行初始化。在這一步裡,我們來定義
所有程式可能使用的資源。為止,我們把每一個資源所在的檔案夾添加到ResourceGroupManager。
************************************************************************/
String secName, typeName, archName;
ConfigFile cf;
cf.load("resources.cfg");
/************************************************************************
這裡使用了一個Ogre的ConfigFile類來解析"resources.cfg"裡的所有的資源,
但并不是把它們裝入到Ogre(你必須手工添加)。記住,你可以自由地使用你自己的
檔案格式和解析器,隻需要用你自己的解析器來替換ConfigFile的。裝入資源的方
式并不十分重要,隻要你能把資源添加到ResourceGroupManager。現在我們解析好
了cfg檔案,還需要把各部分添加到ResourceGroupManager中。以下代碼啟動一個循
環:
************************************************************************/
ConfigFile::SectionIterator seci = cf.getSectionIterator();
while (seci.hasMoreElements())
{
/************************************************************************
每次循環裡,我們再循環一次,提取它裡面所有的内容:
************************************************************************/
secName = seci.peekNextKey();
ConfigFile::SettingsMultiMap *settings = seci.getNext();
ConfigFile::SettingsMultiMap::iterator i;
/************************************************************************
添加部件名稱(那一組資源的),資源類型(zip,檔案夾等等),以及資源本身的檔案
名,給ResourceGroupManager:
************************************************************************/
for (i = settings->begin(); i != settings->end(); ++i)
{
typeName = i->first;
archName = i->second;
ResourceGroupManager::getSingleton().addResourceLocation(archName, typeName, secName);
}
}
}
void setupRenderSystem()
{
/************************************************************************
3、建立渲染系統
接下來,我們需要選擇一個渲染系統(在Windows機器上通常是DirectX或者OpenGL),
然後配置它。大多數Demo程式使用的是一個Ogre配置對話框,這是一個很好的一個東
東。Ogre提供了一種儲存使用者設定的方法,意味着除了第一次需要設定外,以後都不
需要了。并添加以下代碼:
************************************************************************/
if (!mRoot->restoreConfig() && !mRoot->showConfigDialog())
throw Exception(52, "User canceled the config dialog!", "Application::setupRenderSystem()");
/************************************************************************
在if語句裡的第一部分,嘗試恢複這個config檔案。如果函數傳回false,意味着文
件不存在,則應該顯示配置對話框,也就是if語句的第二部分。如果仍然傳回false
,意味着使用者取消了配置對話框(也就是他們想退出程式)。在這個例子裡,抛出了一
個異常,但實際上簡單地傳回false且關閉應用程式,這樣可能更好。由于 restoreConfig
和 showConfigDialog 也可能抛出異常,儲存這些真正的異常可能更好。然而,這将
增加了教程的不必要的複雜度,我在這裡隻使用了一個異常。
如果你在Ogre的啟動過程中捕獲了一個異常,最好在catch裡删除這個ogre.cfg檔案。
因為他們在配置對話框裡進行的設定可能導緻了問題的發生,是以他們需要更改它。
你也可以不使用它,關閉配置對話框可以節省開發時間,因為你不必每次程式運作時
确認這些顯示設定。
如果你不打算使用Ogre的配置對話框,你需要手動地設定渲染系統。以下是一個基本
的例子:
************************************************************************/
// Do not add this to the application
RenderSystem *rs = mRoot->getRenderSystemByName("Direct3D9 Rendering Subsystem");
// or use "OpenGL Rendering Subsystem"
mRoot->setRenderSystem(rs);
rs->setConfigOption("Full Screen", "No");
rs->setConfigOption("Video Mode", "1440 x 900 @ 32-bit colour");
}
void createRenderWindow()
{
/************************************************************************
4、建立渲染視窗
目前選擇了一個渲染系統,還需要一個渲染Ogre的視窗。實際上有許多種方式來實作
,但這裡隻介紹兩種。
************************************************************************/
mRoot->initialise(true, "Tutorial Render Window");
/************************************************************************
第一個參數表示是否讓Ogre為你建立一個渲染視窗。否則,你可以自己建立一個渲
染視窗,通過使用win32 API、wxWidgets或其它Windows/Linux的GUI系統。關于在
Windows下的一個簡單例子是這樣:
************************************************************************/
Do not add this to the application
//mRoot->initialise(false);
//HWND hWnd = 0; // Get the hWnd of the application!
//NameValuePairList misc;
//misc["externalWindowHandle"] = StringConverter::toString((int)hWnd);
//RenderWindow *win = mRoot->createRenderWindow("Main RenderWindow", 800, 600, false, &misc);
/************************************************************************
在這裡你仍然使用Root::initialise,第一個參數設定成了false。然後,你必須獲
取你希望Ogre渲染的視窗的句柄。你如何取得它,完全決定于你用來建立視窗的GUI
工具箱(在Linux下我估計這有一點差別)。你擁有了它之後,你通過NameValuePairList
handle把這個句柄賦予"externalWindowHandle"。Root::createRenderWindow方法被
用來從你建立的視窗來建立RenderWindow對象。想了解更多,參考這個方法的API文
檔。
************************************************************************/
}
void initializeResourceGroups()
{
/************************************************************************
5、初始化資源
現在我們建立了Root對象、渲染系統、以及渲染視窗,繼續。接下來是初始化将要使
用的資源。從mesh到腳本,所有的東西,在某一時刻,我們隻用到這些資源其中的一
小部分。為了減少記憶體消耗,我們可以隻加載正在使用的資源。為止,我們把資源分
解成各種部分,隻在運作時初始化它們。在本課裡,不将詳細介紹。其它的地方有一
個專門介紹資源的教程。初始化資源之前,我們應該設定紋理mipmap的預設值。添加
如下代碼:
************************************************************************/
TextureManager::getSingleton().setDefaultNumMipmaps(5);
ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
}
void setupScene()
{
/************************************************************************
6、建立場景
你應該了解在把各種東西添加到場景之前,你要做三件事:建立場景管理器
(SceneManager)、建立錄影機(Camera)、建立視口(Viewport)。在setupScene
方法裡添加如下代碼:
************************************************************************/
SceneManager *mgr = mRoot->createSceneManager(ST_GENERIC, "Default SceneManager");
Camera *cam = mgr->createCamera("Camera");
Viewport *vp = mRoot->getAutoCreatedWindow()->addViewport(cam);
/************************************************************************
如果需要的話,可以建立多個場景管理器、多個錄影機,但當真正打算使用錄影機
把事物渲染到螢幕上時,請確定已經為它添加了視口中。為止,要借助RenderWindow
類,它在“建立渲染視窗”一節裡被建立。由于我們沒有這個對象的指針,是以我們
通過Root::getAutoCreatedWindow方法來擷取它。
這三件事完了以後,你可以盡情地往你的場景裡添加物體了。
************************************************************************/
}
void setupInputSystem()
{
/************************************************************************
7、設定第三方庫
OIS
雖然在OGRE裡,OIS不是唯一的選擇,但它是最好的之一。我來簡單介紹一下OIS如何
在程式裡啟動。若真想要使用這個庫,請參考這個教程,以及OIS自身的文檔。
設定無緩沖輸入
OIS使用一個統一的InputManager,它比較難配置,但一旦正确地建立之後,非常好使
用。實際上,它隻是需要Ogre渲染視窗的句柄。幸好,由于我們使用的是自動建立的窗
口,Ogre使之簡化了。添加如下代碼:
************************************************************************/
size_t windowHnd = 0;
std::ostringstream windowHndStr;
OIS::ParamList pl;
RenderWindow *win = mRoot->getAutoCreatedWindow();
win->getCustomAttribute("WINDOW", &windowHnd);
windowHndStr << windowHnd;
pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str()));
mInputManager = OIS::InputManager::createInputSystem(pl);
/************************************************************************
這樣InputManager就建好了,但為了從鍵盤、滑鼠、或是搖桿中獲得輸入,你還必
須建立這些對象:
************************************************************************/
try
{
mKeyboard = static_cast<OIS::Keyboard*>(mInputManager->createInputObject(OIS::OISKeyboard, false));
//mMouse = static_cast<OIS::Mouse*>(mInputManager->createInputObject(OIS::OISMouse, false));
//mJoy = static_cast<OIS::JoyStick*>(mInputManager->createInputObject(OIS::OISJoyStick, false));
}
catch (const OIS::Exception &e)
{
throw new Exception(42, e.eText, "Application::setupInputSystem");
}
/************************************************************************
我把Mouse和Joystick對象注釋掉了,因為這裡我們不使用,但它們就是這樣建立的。
InputManager::createInputObject的第二個參數是指是否使用帶緩沖輸入(在以前的
教程裡介紹過)。把第二個參數設定成false,建立了一個無緩沖的輸入對象,我們這
裡就使用這個。
************************************************************************/
}
void setupCEGUI()
{
/************************************************************************
CEGUI
CEGUI是直接整合到Ogre裡的一個非常靈活的GUI庫。在這裡不使用CEGUI的任何功能
,但我還是來簡單介紹一下它的設定。CEGUI需要RenderWindow和SceneManager以供
渲染。
************************************************************************/
SceneManager *mgr = mRoot->getSceneManager("Default SceneManager");
RenderWindow *win = mRoot->getAutoCreatedWindow();
// CEGUI setup
mRenderer = new CEGUI::OgreCEGUIRenderer(win, Ogre::RENDER_QUEUE_OVERLAY, false, 3000, mgr);
mSystem = new CEGUI::System(mRenderer);
/************************************************************************
就這樣,你就能使用CEGUI了。如果你程式中途你改變了SceneManager,你必須通知
CEGUI應該渲染到一個新的SceneManager。
為止,使用OgreCEGUIRenderer::setTargetSceneManager就行了。
************************************************************************/
// Other CEGUI setup here.
}
void createFrameListener()
{
/************************************************************************
8、渲染循環以及最後的工作
幀監聽
在我們開始渲染循環,并讓程式運作之前,我們還需要添加幀監聽器。請注意我已經
建立一個非常簡單的幀監聽器,名為ExitListener,它等待ESC鍵被按下,以退出程
序。在你的程式裡,我可能需要更多的幀監聽器,來做更複雜的事情。看一下這個
ExitListener,確定了解它的流程。
************************************************************************/
mListener = new ExitListener(mKeyboard);
mRoot->addFrameListener(mListener);
}
void startRenderLoop()
{
/************************************************************************
9、渲染循環
最後我們要做的是啟動Ogre的渲染循環。非常簡單,添加如下代碼:
************************************************************************/
mRoot->startRendering();
/************************************************************************
這樣程式就開始渲染,直到FrameListener傳回false。你也可以提取單個幀,并在
每幀之間做一些事情。Root::renderOneFrame渲染一幀,如何任何一個FrameListener
傳回false,它也傳回false:
************************************************************************/
Do not add this to the application
//while (mRoot->renderOneFrame())
//{
// // Do some things here, like sleep for x milliseconds or perform other actions.
//}
/************************************************************************
然而,在我看來,你應該把所有while循環裡的代碼轉移到FrameListener。我能想
到的這種模式的唯一用處就是,中途睡眠某些毫秒,進而人為地降低幀率到某一個
值。一般在FrameListener裡不會這樣做,因為它會與FrameEvent::timeSinceLastFrame
變量搞混淆。
************************************************************************/
}
};
#if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT)
#else
int main(int argc, char **argv)
#endif
{
try
{
Application app;
app.go();
}
catch(Exception& e)
{
#if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32
MessageBoxA(NULL, e.getFullDescription().c_str(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
fprintf(stderr, "An exception has occurred: %s/n",
e.getFullDescription().c_str());
#endif
}
return 0;
}