(三)着重於介紹如何讓場景動起來,以及如何獲取和處理消息。
1. Irrlicht 的運動機制
所謂運動,實際上計算機在不停得繪製場景,每繪製一次稱之爲一幀。 當各幀中物體的位置或外觀有所變化,那麼它就動起來了。 在irrlicht中,繪製一幀是在run循環中完成的:
- while (device->run())
- {
- if (device->isWindowActive())
- {
- driver->beginScene(true, true, video::SColor(0, 0, 0, 0));
- scene_mgr->drawAll(); // 繪製一幀
- driver->endScene();
- }
- }
- device->drop();
while (device->run())
{
if (device->isWindowActive())
{
driver->beginScene(true, true, video::SColor(0, 0, 0, 0));
scene_mgr->drawAll(); // 繪製一幀
driver->endScene();
}
}
device->drop();
我們所要做的,就是在 drawAll() 函數中,更新物體的位置,那麼場景就動起來了。
所有的一切,都與 scene::ISecenNodeAnimator 這個接口相關。凡是實現這個接口的類實例,都可以通過 addAnimator() 函數加入到 ISceneNode 的 animators 列表中。
SceneManager 的drawAll() 函數在繪製(render)場景前,會調用 OnAnimate() 函數。這個函數是遞歸的,以保證加入場景中的每個 SceneNode 都被調用。 在OnAnimate() 函數中, SceneNode 的每一個 ISecenNodeAnimator 的 animateNode() 函數都會被調用,以更新 SceneNode 的位置、大小或紋理。
整理其調用順序如下:
1. SceneManager --> drawAll() 繪製一幀
2. ISceneNode --> OnAnimate() SceneNode運動
3. ISceneNodeAnimator --> animateNode(ISceneNode) 實現運動的具體函數
4. SceneManager --> render() 繪製
可見,只要實現 ISecenNodeAnimator 接口,並加入到 SceneNode 中,就能夠讓其動起來。
2. Irrlicht 的消息傳遞
Irrlicht消息的傳遞是從device->run()開始的,在windows中首先調用 WndProc() 收集消息,打包成其內部的SEvent結構,再由 postEventFromUser() 將消息依次傳遞給 UserReceiver, GUI 和 3D Scene。
Irrlicht 中所有處理消息的類都必須實現 IEventReciever 接口。UserReceiver 就是在CreateDevice函數中指定的一個 IEventReceiver。 也就是說,消息處理的優先級爲 UserReceiver > GUI > 3D Scene。
在我們的程序中,並沒有指定UserReceiver,暫時也沒有GUI,所以消息直接交給了 SceneManager。
SceneManager 的消息,也由 ISceneManager 的 postEventFromUser() 傳遞。 這個函數的實現如下:
- bool CSceneManager::postEventFromUser(const SEvent& event)
- {
- bool ret = false;
- ICameraSceneNode* cam = getActiveCamera();
- if (cam)
- ret = cam->OnEvent(event);
- _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
- return ret;
- }
bool CSceneManager::postEventFromUser(const SEvent& event)
{
bool ret = false;
ICameraSceneNode* cam = getActiveCamera();
if (cam)
ret = cam->OnEvent(event);
_IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
return ret;
}
也就是說,只有當前Active(有效)的 ICameraSceneNode 纔會接收到消息。ICameraSceneNode 會檢測ISceneNodeAnimator 的isEventReceiverEnabled(), 如果爲真則調用其 OnEvent 函數。
整理消息傳遞的機制如下:
1. IrrlichtDevice --> run() 蒐集消息並打包
2. IrrlichtDevice --> postEventFromUser() 傳遞消息到userReceiver GUI 和 Scene
3. ISceneManager --> postEventFromUser() 傳遞消息到CameraNode
4. ICameraSceneNode --> onEvent() 調用 Animator 的onEvent
5. ISceneNodeAnimator --> onEvent() // if enabled 響應消息。
可見,實現 ISceneNodeAnimator 雖然可以 SceneNode 動起來,但只有在 CameraSceneNode 上,才能夠接收和處理消息。
3. 實現CameraAnimator
如果我們實現一個 CameraAnimator,在其中處理消息,更新 CameraNode 和 CubeNode 的位置,就可以實現交互和動畫。該類關鍵的幾個函數如下:
- class CameraAnimator : public ISceneNodeAnimator
- {
- public:
- //! Constructor
- CameraAnimator(...
- //! Destructor
- virtual ~CameraAnimator(){};
- //! 更新 SceneNode 位置
- virtual void animateNode(ISceneNode* node, u32 timeMs);
- //! 處理消息
- virtual bool OnEvent(const SEvent& event);
- //! This animator will receive events when attached to the active camera
- virtual bool isEventReceiverEnabled() const
- {
- return true; // 必須爲true才能接收消息
- }
- .... // other member functions.
- private:
- ... // fields
- };
class CameraAnimator : public ISceneNodeAnimator
{
public:
//! Constructor
CameraAnimator(...
//! Destructor
virtual ~CameraAnimator(){};
//! 更新 SceneNode 位置
virtual void animateNode(ISceneNode* node, u32 timeMs);
//! 處理消息
virtual bool OnEvent(const SEvent& event);
//! This animator will receive events when attached to the active camera
virtual bool isEventReceiverEnabled() const
{
return true; // 必須爲true才能接收消息
}
.... // other member functions.
private:
... // fields
};
需要實現的關鍵有三點:
(1) 拖拽時, 場景跟着移動
(2) 單擊某個圖片時,場景移動到該位置,圖片突出顯示
(3) 滾輪實現縮放
在編程時有以下幾個關鍵點:
(1) 三維場景中Node的選取,還好 irrlicht 替我們實現了這點,只需如下代碼,就可以將鼠標點擊處的node選取出來:
- core::position2d<s32> mouse_position(event.MouseInput.X, event.MouseInput.Y);
- ISceneNode* node = scene_manager_->getSceneCollisionManager()
- ->getSceneNodeFromScreenCoordinatesBB(mouse_position, -1);
core::position2d<s32> mouse_position(event.MouseInput.X, event.MouseInput.Y);
ISceneNode* node = scene_manager_->getSceneCollisionManager()
->getSceneNodeFromScreenCoordinatesBB(mouse_position, -1);
(2) 運動的實現
包括三個運動: CameraNode 的位置, CameraNode 的拍攝點,選取的圖片的位置
CameraNode的位置決定了場景的移動;
CameraNode 的拍攝點決定了拍攝的角度,可以實現場景的傾斜;
被選取的圖片的位置可以使其比其它圖片更接近Camera,實現突出的效果。
(二)中描述的圖片位置是排布在 z = 0 這個平面上的,Camera 的位置在 z = -700 平面上,初始拍攝點爲原點。
camera拍攝點(target)在 X 軸移動的處理如下:
- if (is_drag_ && current_node_ == NULL) // 拖拽
- {
- core::position2di mouse_position = device_->getCursorControl()->getPosition();
- s32 xdiff = drag_start_point_.X - mouse_position.X;
- drag_start_point_ = mouse_position;
- x_target += 0.01*xdiff / core::max_(dt, 0.0000001f);
- }
- if (current_node_!=NULL) // 選取了某幅圖片
- {
- x_target = current_node_->getPosition().X;
- }
- sign = (x_target < x_current) ? -1 : 1;
- x_speed = sign*sqrt(abs(x_target - x_current)) * 50; // 運動速度
- target_position.X += x_speed * dt; // 移動位置
if (is_drag_ && current_node_ == NULL) // 拖拽
{
core::position2di mouse_position = device_->getCursorControl()->getPosition();
s32 xdiff = drag_start_point_.X - mouse_position.X;
drag_start_point_ = mouse_position;
x_target += 0.01*xdiff / core::max_(dt, 0.0000001f);
}
if (current_node_!=NULL) // 選取了某幅圖片
{
x_target = current_node_->getPosition().X;
}
sign = (x_target < x_current) ? -1 : 1;
x_speed = sign*sqrt(abs(x_target - x_current)) * 50; // 運動速度
target_position.X += x_speed * dt; // 移動位置
在 Y 軸和 Z 軸方向的移動,包括圖片的移動, 均用類似的方式處理。參見(animateNode)函數。
(3) 鼠標滾輪實現縮放
只需要改變 Camera 在Z軸的位置,作簡單的限幅即可:
- z_target += 100*event.MouseInput.Wheel;
- if (z_target > -200)
- {
- z_target = -200;
- }
- if (z_target < -3000)
- {
- z_target = -3000;
- }
z_target += 100*event.MouseInput.Wheel;
if (z_target > -200)
{
z_target = -200;
}
if (z_target < -3000)
{
z_target = -3000;
}
4.最後
最後我們還需要將實現的 animator 附加到 Camera 上:
- scene::ICameraSceneNode* camera = scene_mgr->addCameraSceneNode(0,
- core::vector3df(0,0,-700),
- core::vector3df(0,0,0), 0);
- scene::CameraAnimator* animator = new scene::CameraAnimator(wall_mgr->GetWallItemList(),
- device, scene_mgr,
- device->getTimer()->getTime());
- camera->addAnimator(animator);
- animator->drop();
scene::ICameraSceneNode* camera = scene_mgr->addCameraSceneNode(0,
core::vector3df(0,0,-700),
core::vector3df(0,0,0), 0);
scene::CameraAnimator* animator = new scene::CameraAnimator(wall_mgr->GetWallItemList(),
device, scene_mgr,
device->getTimer()->getTime());
camera->addAnimator(animator);
animator->drop();
編譯運行後,您應該能夠用鼠標實現類似 cooliris 的交互。更進一步的完善,還需要支持鍵盤等……
p.s.
3d 界面就介紹到這裏了,也算告一段落。
還有兩個部分:
(1) GUI: 介紹GUI編寫,並加入Truetype中文字體支持;
(2) AnimatedGUI:介紹目錄瀏覽控件的編寫,實現一個皮膚系統。
由於臨近畢業,導師催着要論文,過段時間再繼續吧。
鑑於本人的學習經歷,勸各位千萬不要盲目讀研,不要盲目崇拜名校,別被糟蹋了。
我是學機械方向的,盲目接受了當年所謂的推研資格。sigh~~學術氣氛糟糕啊……
From: http://arec.iteye.com/blog/338135