1. 程式人生 > >OSG拖拽器簡介與示例

OSG拖拽器簡介與示例

摘要Abstract:本文主要對工廠和海工設計軟體AVEVA的互動方式進行詳細介紹,對OpenSceneGraph中的人機互動工具拖拽器進行說明,並在其中實現了模型直接互動操作。對互動建模感興趣的讀者可結合其原始碼,對其實現細節進行分析。 

關鍵字Key Words:AVEVA, Model Editor, OpenSceneGraph, Dragger 

一、引言 Introduction

在當代的三維輔助設計軟體中,互動建模設計已經成為主流。友好、高效的對三維模型直接進行編輯或修改,不僅可以提高使用者的工作效率,還會給使用者留下美好印象,即軟體良好的使用者體驗。互動建模的常見方法有:拖曳、約束、柵格捕捉、橡皮筋方法、引力場等,拖曳就是直接對選擇的模型在三維空間中拖動來改變位置和方向;約束方法就是在拖曳的時候新增約束條件,如只能沿某個方向進行拖曳,軟體中的應用有AutoCAD中的極軸捕捉功能;柵格捕捉也是一種帶約束的拖曳,即拖曳的過程中只能沿正交網格中直線的交點拖動;橡皮筋方法主要用在繪製二維圖形;引力場方法就像AutoCAD中的磁吸功能。如何設計高效、友好、方便的使用者介面是當前各開發系統的廠家和專家所共同關心的問題,它的設計好壞可能直接影響使用者是否接受其產品。 

本文主要對工廠和海工設計軟體AVEVA的互動方式進行詳細介紹,對OpenSceneGraph中的人機互動工具拖拽器進行說明,並在其中實現了模型直接互動操作。通過程式實踐,感覺使用OpenSceneGraph來進行編碼還是很舒服的,因為其程式碼規範,設計很好。對互動建模感興趣的讀者可結合其原始碼,對其實現細節進行分析。 

二、模型編輯器Model Editor of AVEVA

AVEVA(原CADCENTRE)是國際著名的工廠工程資訊科技企業,成立於1967年,總部設在英國劍橋;AVEVA所提供的工廠工程一體化解決方案涵蓋了陸地和海洋石油天然氣、電力、石化、化工、核電、造船、環保、造紙、製藥、冶金、礦山等多個行業,同時提供專業工廠工程技術諮詢、技術服務和本地化可持續發展的應用開發。 AVEVA是目前全球發展最快的工廠工程資訊科技企業之一,1996年在英國倫敦上市,2007財年年產值超過25億美元。AVEVA在全球擁有超過1600名使用者,每天有超過26,000名工程人員在使用AVEVA的解決方案。AVEVA在世界30多個國家和地區設有超過50個常駐辦事機構,在英國劍橋總部及其他研發中心擁有超過300名研究和開發人員,為世界上最大的工廠工程資訊科技研究和開發團隊。AVEVA的快速發展與其方便易用,良好的互動建模方式分不開,本文主要對其互動建模部分進行介紹。 

AVEVA互動建模主要是使用模型編輯器Model Editor,使用Model Editor可以只用滑鼠就可以進行建模設計了。編輯選擇的模型如下圖所示: 

wps_clip_image-19277

Figure 2.1 Model Editor on a Equipment 

使用的Handle可以對模型多種方式的編輯,如軸向移動、平面移動、旋轉等,如下圖所示: 

wps_clip_image-19280

Figure 2.2 Locator Handle of Model Editor 

2.1 移動 Movement

對選擇的模型進行軸向移動或平面移動時,可以使用Model Editor的Linear and Planar handles。沿軸向或鎖定在某個平面上拖動模型,即可以移動模型。移動是按一定的長度遞增的,可由移動增量(the Movement Increment)來設定,這樣來確保拖動模型相對初始位置的精度。選中handle上某個軸,就可以沿這個軸的方向來移動模型,如下圖所示: 

wps_clip_image-3474

Figure 2.3 Linear Movement 

選中並拖動Model Editor的平面移動handle,就可以在鎖定的平面上移動模型,如下圖所示: 

wps_clip_image-107

Figure 2.4 Planar Movement 

2.2 旋轉 Rotation

旋轉是通過Model Editor的Rotation handle來完成的。旋轉是按一定的角度來遞增的,可由旋轉增量(the Rotation Increment)來設定。這樣就確保了旋轉相對於初始位置的精度。選中並拖動Rotation handle,就可以對模型進行旋轉了,如下圖所示: 

wps_clip_image-693

Figure 2.5 Rotation 

wps_clip_image-5192 wps_clip_image-23407

Figure 2.6 Use Model Editor to Modify a Valve 

互動建模時使用Model Editor如上圖2.6所示,由圖可知,在AVEVA中對模型的移動和旋轉非常方便。 

2.3 對齊 Alignment

通過對齊功能,可以方便地將模型對齊到點、邊或面,如下圖所示: 

wps_clip_image-710

Figure 2.7 Alignment features 

通過上文對AVEVA中的Model Editor的介紹可知,在AVEVA中三維互動建模很方便,且精度高。縱觀國內目前類似產品,有些還停留在二維建模方式,有些藉助於其他平臺的互動方法,但是在易用性上感覺都稍有不足,或沒有自主的智慧財產權。 

三、拖拽器Dragger of OpenSceneGraph

1997年,一個名叫Don Burns的軟體工程師受僱於當時的Silicon Graphics(SGI)公司,負責針對滑翔機飛行的虛擬模擬工作進行研究。他使用當時的OpenGL Performer系統,設計了一套廣受好評的滑翔模擬軟體,並開始嘗試在Linux中使用Mesa3D和3dfx Voodoo顯示卡裝置繼續完善自己的模擬軟體。 

1998年,Don在一個滑翔愛好者的郵件組中遇到了Robert Osfield,也就是目前OpenSceneGraph專案的主要負責人。當時Robert在蘇格蘭的油氣公司工作,但對計算機圖形學和視覺化技術有著濃厚的興趣。志趣相投的兩個人走到了一起,開始合作對Don的模擬軟體進行改善。Robert建議將SG作為獨立的開源場景圖形專案繼續開發,並由自己擔任專案主導,專案的名稱改為OpenSceneGraph,簡稱OSG。 

如今,相當一部分高效能的軟體已經使用了OSG來完成複雜場景的渲染工作。大部分基於OSG的軟體開發更適用於視覺化設計和工業模擬,包括地理資訊系統(GIS)、計算機輔助設計(CAD)、建模和數字媒體創作(DCC)及資料庫開發、虛擬現實、動畫、遊戲和娛樂業等。 

OpenSceneGraph引擎由一系列圖形學相關的功能模組組成,主要為圖形影象應用程式的開發提供場景管理和圖形渲染優化的功能。它使用可移植的ANSI C++編寫,並使用已成為工業標準的OpenGL底層渲染API。OSG具備跨平臺的特性,可以執行在大多數型別的作業系統上,並使用抽象層的概念,使OSG的函式介面可以獨立於使用者的本地作業系統使用。OSG遵循開源協議釋出,其使用者許可方式是一種修改過的GNU寬通用公共許可證(GNU Lesser General Public License, LGPL),稱為OSGPL。OSG主要具備以下優勢:快速開發,高品質,高效能,高質量程式碼,可擴充套件性,可移植性,低費用,沒有智慧財產權問題,但是OSG目前也存在諸多不足,如參考文件較少、程式碼風格不統一、部分功能的實現過於臃腫,無法應用於實踐等,這些都有待更多的開發者和貢獻者去發現和完善。 

三維使用者互動是一種與三維環境本身特性相匹配的互動動作,可使使用者在虛擬場景中獲得身臨其境的直觀感受。三維世界的互動技術相當於一種“控制-顯示”的對映,使用者裝置(例如滑鼠、鍵盤、操縱桿等)向系統輸入控制資訊,然後系統向用戶輸出執行結果。三維互動涉及的任務繁多,包括三維場景物件的選擇和操控、三維世界中的導航漫遊、改變三維場景的狀態,乃至時下流行的三維互動建模等。作為一款全面的實時渲染引擎,OSG實現了三維場景的漫遊及場景中三維物件的操控這兩種主要的三維場景互動方式,更多的互動動作則需要我們自行研究和實現。 

作為重要的三維空間的人機互動手段之一,場景漫遊的特點是通過不斷改變觀察者(相機)的位置、姿態,使其相對世界的觀察方位和角度有所變化,但是世界本身卻不會發生任何改變。無論草木、建築,還是街道上的車水馬龍,構成它們的每一個頂點都沒有發生任何偏移,如果觀察者有朝一日回到原地的話,他眼中的一切都不會發生改變。 

而對於三維物體的操控則是另一種概念,它沒有改變觀察者的視角和視點,而是根據使用者傳遞的互動事件,對選中的物件進行平移、縮放和旋轉操作,就像是玩弄橡皮泥一樣。被修整過的物件將改變原有的形態,換句話說,只要不恢復到操控前的狀態,那麼無論從什麼地方進行觀察,這個物件都將維持它最終的模樣。當然,即使操控物體的定義如此,直接修改物體的頂點座標未免還是有些費力不討好,最好的方式是為要操控的物體設定一個矩陣變換的父節點(MatrixTransform),通過改變這個父節點的變換矩陣的值,進行改變作為操控物件的子節點的表現形式—這就是osgManipulator庫中拖拽器(Dragger)的實現方式。OSG內建了幾種拖拽器,其操作方式和效果說明如下: 

l TabPlaneDragger平面拖拽器:其邊、頂點上都有拖拽點,可以進行某個2D平面上的縮放; 

wps_clip_image-1294

Figure 3.1 TabPlaneDragger in OpenSceneGraph 

l TabPlaneTrackballDragger平面軌跡球拖拽器:顧名思義除了平面拖拽器的功能外,還多了個軌跡球拖拽功能; 

wps_clip_image-2369

Figure 3.2 TabPlaneTrackballDragger in OpenSceneGraph 

l TrackballDragger軌跡球拖拽器:即旋轉操縱器,沒有縮放功能; 

wps_clip_image-16308

Figure 3.3 TrackballDragger in OpenSceneGraph 

l Translate1DDragger一維平移拖拽器:沿一個直線進行拖拽; 

wps_clip_image-26274

Figure 3.4 Translate1DDragger in OpenSceneGraph 

l Translate2DDragger二維平移拖拽器:在某個平面上對模型進行拖拽; 

wps_clip_image-6327

Figure 3.5 Translate2DDragger in OpenSceneGraph 

l TranslateAxisDragger三維平移拖拽器:可在三個方向上對模型進行拖拽; 

wps_clip_image-15562

Figure 3.6 TranslateAxisDragger in OpenSceneGraph 

l TabBoxDragger盒式拖拽器:由六個平面拖拽器構成,可在各個面上進行縮放、平移; 

wps_clip_image-21705

Figure 3.7 TabBoxDragger in OpenSceneGraph 

還有其他的拖拽器可以參考其原始碼: 

n ScaleAxisDragger:三維縮放拖拽器; 

n Scale2DDragger:二維縮放拖拽器; 

n Scale1DDragger一維縮放拖拽器; 

n RotateSphereDragger:旋轉球拖拽器; 

n RotateCylinderDragger旋轉圓柱拖拽器; 

由於使用了組合模式(CompositeDragger),可將上面的拖拽器組合成更復雜的拖拽器。如三維平稱拖拽器(TranslateAxisDragger)就是包含了三個一維拖拽器(Translate1DDragger)的組合拖拽器。因為程式開源,所以可根據實際需要,將拖拽器進行自定義。即可以定義出和AVEVA的Model Editor完全一樣的操縱器。 

四、示例程式 Example Code

要對場景中的模型進行拖拽,首先需要將其選中,然後在選中的模型上開啟拖拽器,對模型的位置和方向進行編輯。物件的拾取主要依靠場景圖形的交運算來實現。拖拽器中用到的一個類PointerInfo表示一個資訊的集合。例如,使用滑鼠選中待操作物件上的某個點,以及當前相機的觀察矩陣和投影矩陣等,都需要及時反映到這個輸入引數中,以便拖拽器根據實際情況進行判斷並生成命令。下面給出一個使用拖拽器Dragger來操作場景中模型的具體例項,程式程式碼如下所示: 

1.首先定義了一個帶拖拽器的節點ModelShape: 

#pragma once #include <osg/Group> #include <osgManipulator/Selection> #include <osgManipulator/CommandManager> #include <osgManipulator/TrackballDragger> #include <osgManipulator/TranslateAxisDragger>


class ModelShape : public osg::Group { public: ModelShape(osg::Node* shape); ~ModelShape(void); void EnableDragger(void); void DisableDragger(void); private: osg::ref_ptr<osg::Node> mShape; osg::ref_ptr<osgManipulator::Dragger> mDragger; osg::ref_ptr<osgManipulator::Selection> mSelection; };

類實現程式碼如下所示: 

#include "ModelShape.h" ModelShape::ModelShape(osg::Node* shape) : mShape(shape) , mDragger(new osgManipulator::TranslateAxisDragger()) , mSelection(new osgManipulator::Selection()) { float scale = shape->getBound().radius() * 1.6; mDragger->setMatrix(osg::Matrix::scale(scale, scale, scale) * osg::Matrix::translate(shape->getBound().center())); mDragger->setupDefaultGeometry(); mSelection->addChild(shape); addChild(mSelection); } ModelShape::~ModelShape(void) { } void ModelShape::EnableDragger() { addChild(mDragger); mDragger->addTransformUpdating(mSelection); mDragger->setHandleEvents(true); } void ModelShape::DisableDragger() { removeChild(mDragger); mDragger->removeTransformUpdating(mSelection); mDragger->setHandleEvents(false); }

2.在處理選擇事件時,開啟拖拽器,實現類是PickHandler: 

#pragma once #include <osgGA/GUIEventHandler>


class PickHandler : public osgGA::GUIEventHandler { public: PickHandler(void); ~PickHandler(void); virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa); protected: void pick(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa); private: float mX; float mY; bool mEnableDragger; };

類實現程式碼如下所示: 

#include "PickHandler.h" #include "ModelShape.h" #include <osgViewer/Viewer> PickHandler::PickHandler(void) : mX(0.0f) , mY(0.0f) , mEnableDragger(true) { } PickHandler::~PickHandler(void) { } bool PickHandler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa) { osgViewer::View* view = dynamic_cast<osgViewer::View*> (&aa); if (NULL == view) { return false; } switch (ea.getEventType()) { case osgGA::GUIEventAdapter::PUSH: { mX = ea.getX(); mY = ea.getY(); } break; case osgGA::GUIEventAdapter::RELEASE: { if (ea.getX() == mX && ea.getY() == mY) { pick(ea, aa); } } break; case osgGA::GUIEventAdapter::KEYDOWN: { if (ea.getKey() == 'd') { mEnableDragger = !mEnableDragger; } } break; default: break; } return false; } void PickHandler::pick(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa) { osgViewer::View* view = dynamic_cast<osgViewer::View*> (&aa); osgUtil::LineSegmentIntersector::Intersections hits; if (view->computeIntersections(ea.getX(), ea.getY(), hits)) { osgUtil::LineSegmentIntersector::Intersection intersection = *hits.begin(); osg::NodePath& nodePath = intersection.nodePath; int nNodeSize = static_cast<int> (nodePath.size()); if (nNodeSize > 0) { osg::Node* node = nodePath[nNodeSize - 1]; osg::Node* grandParent = node->getParent(0)->getParent(0); // This method maybe not right?
            ModelShape* shape = dynamic_cast<ModelShape*> (grandParent); if (shape) { mEnableDragger ? shape->EnableDragger() : shape->DisableDragger(); } } } }

3.在主函式中建立場景: 

/* * Copyright (c) 2013 eryar All Rights Reserved. * * File : Main.cpp * Author : [email protected] * Date : 2013-12-28 17:00 * Version : 1.0v * * Description : Use dragger to manipulate shape objects. * press key 'd' for enable or disable the dragger. * * Key Words : OpenSceneGraph, Dragger * */ #include <osgDB/ReadFile> #include <osgGA/StateSetManipulator> #include <osgViewer/Viewer> #include <osgViewer/ViewerEventHandlers> #include "ModelShape.h" #include "PickHandler.h"


#pragma comment(lib, "osgd.lib")
#pragma comment(lib, "osgDBd.lib")
#pragma comment(lib, "osgGAd.lib")
#pragma comment(lib, "osgViewerd.lib")
#pragma comment(lib, "osgManipulatord.lib")


int main(void) { osgViewer::Viewer viewer; osg::ref_ptr<osg::Group> root = new osg::Group(); // build the scene with boxes and gliders.
    for (int i = 1; i < 10; ++i) { for (int j = 1; j < 10; ++j) { osg::ref_ptr<osg::MatrixTransform> box = new osg::MatrixTransform(); osg::ref_ptr<osg::MatrixTransform> glider = new osg::MatrixTransform(); box->setMatrix(osg::Matrix::translate(i * 6.0, j * 6.0, 0.0)); glider->setMatrix(osg::Matrix::translate(i * 2.5, j * 2.5, 6.0)); box->addChild(new ModelShape(osgDB::readNodeFile("box.stl"))); glider->addChild(new ModelShape(osgDB::readNodeFile("glider.osg"))); root->addChild(box); root->addChild(glider); } } viewer.setSceneData(root.get()); viewer.addEventHandler(new osgViewer::StatsHandler()); viewer.addEventHandler(new osgViewer::WindowSizeHandler()); viewer.addEventHandler(new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet())); // add pick event handler to add dragger on the shape.
    viewer.addEventHandler(new PickHandler()); return viewer.run(); }

程式使用方法為選擇要拖拽的模型,選中後為模型開啟拖拽器,使用拖拽器對模型進行拖拽就可以修改模型的位置了。在鍵盤上按下‘d’可以開啟/關閉拖拽器,預設為開啟。當設定為關閉時,再選中帶有拖拽器的模型後,將會關閉拖拽器。程式執行效果如下圖所示: 

wps_clip_image-9879

Figure 3.8  Use TranslateAxisDragger to dragger box 

wps_clip_image-12412

Figure 3.9 Use TranslateAxisDragger to dragger glider 

wps_clip_image-28372

Figure 3.10 Use TranslateAxisDragger in OpenSceneGraph 

五、結論 Conclusion

通過程式實踐表明,OpenSceneGraph中的人機互動的方式還是很方便的,並提供了幾種拖拽器來操縱模型。並且拖拽器採用了組合模式,便於擴充套件,即根據使用者實際需要組合出更復雜或更具個性的拖拽器。 

由此可見,使用OpenSceneGraph來對模型進行顯示與操作很方便,且是開源程式,方便程式除錯,還不存在智慧財產權的問題。因為OpenSceneGraph主要是用於虛擬模擬,還提供了很多模擬效果,如煙霧、火焰、粒子效果(雨、雪、爆炸)、動畫等,如果在建模設計的過程中適量新增部分效果,是不是很cool? 

六、參考資料 References

1. AVEVA,Graphical Model Manipulation Guide 

2. Donald Hearn,M. Pauline Baker,Computer Graphics with OpenGL,電子工業出版社 

3. 何援軍,計算機圖形學,機械工業出版社 

4. 王銳,錢學雷,OpenSceneGraph三維渲染引擎設計與實踐,清華大學出版社 

5. 肖鵬,劉更代,徐明亮,OpenSceneGraph三維渲染引擎程式設計指南,清華大學出版社