OpenCASCADE View Manipulator
eryar@163.com
Abstract. When you finish modeling objects in the scene, you must want to use some operations to view the scene objects, such as Pan, Zoom and Rotate the scene. Pan and Zoom is easy to understand, rotate the 3D scene according to 2D point in the viewport is a little complicated. There are many methods to rotate the 3D scene, but the Arcball Controller is intuitive for the user and any viewport can be described. You can rotate your model at will just by using the mouse.
Key Words. OpenCASCADE Camera, View, ArcBall, Rotate
1. Introduction
當(dāng)用OpenGL建立了一個(gè)模型場(chǎng)景后,就需要有便捷的操作來觀察場(chǎng)景中的物體。場(chǎng)景的觀察即注重于一個(gè)從三維世界轉(zhuǎn)換到二維屏幕的過程。假設(shè)場(chǎng)景的觀察者使用一臺(tái)相機(jī)來記錄世界的變化,那么相機(jī)的移動(dòng)、角度偏轉(zhuǎn)、焦距變化都會(huì)改變底片上顯現(xiàn)的內(nèi)容,也就是觀察這個(gè)世界的方式,這涉及到三維人機(jī)的交互。
三維用戶交互是一種與三維環(huán)境本身特性相匹配的交互動(dòng)作,可使用戶在虛擬場(chǎng)景中獲得身臨其境的直觀感受。三維世界的交互技術(shù)相當(dāng)于一種“控制-顯示”的映射,用戶設(shè)備例如鼠標(biāo)、鍵盤等向系統(tǒng)輸入控制信息,然后系統(tǒng)向用戶輸出執(zhí)行結(jié)果。所以首先要對(duì)硬件設(shè)備的輸入信息進(jìn)行處理,然后就是根據(jù)這些信息來改變場(chǎng)景數(shù)據(jù)。
三維交互涉及的任務(wù)很多,包括三維場(chǎng)景對(duì)象的選擇和編輯、三維世界中的導(dǎo)航漫游,乃至?xí)r下流行的三維交互建模等。本文主要介紹如何通過改變像機(jī)參數(shù)來對(duì)場(chǎng)景進(jìn)行瀏覽,如對(duì)場(chǎng)景的平移、縮放和旋轉(zhuǎn)操作。
Figure 1.1 OpenCASCADE Viewer
2.Translate View
場(chǎng)景的移動(dòng)就是改變觀察相機(jī)的位置,相對(duì)容易理解,在OpenCASCADE的類V3d_View中也是這樣實(shí)現(xiàn)的,代碼如下所示:
// ==================================================================
// function : Translate
// purpose : Internal
// ==================================================================
void V3d_View::Translate (const Handle(Graphic3d_Camera)& theCamera,
const Standard_Real theDXv,
const Standard_Real theDYv) const
{
const gp_Pnt& aCenter = theCamera->Center();
const gp_Dir& aDir = theCamera->Direction();
const gp_Dir& anUp = theCamera->Up();
gp_Ax3 aCameraCS (aCenter, aDir.Reversed(), aDir ^ anUp);
gp_Vec aCameraPanXv = gp_Vec (aCameraCS.XDirection()) * theDXv;
gp_Vec aCameraPanYv = gp_Vec (aCameraCS.YDirection()) * theDYv;
gp_Vec aCameraPan = aCameraPanXv + aCameraPanYv;
gp_Trsf aPanTrsf;
aPanTrsf.SetTranslation (aCameraPan);
theCamera->Transform (aPanTrsf);
}
由上述代碼可知,根據(jù)兩次鼠標(biāo)位置計(jì)算出需要移動(dòng)的偏移量來對(duì)相機(jī)進(jìn)行移動(dòng)變換。根據(jù)鼠標(biāo)第一次按下及移動(dòng)過程中的坐標(biāo)點(diǎn)來計(jì)算偏移量。計(jì)算偏移量時(shí),需要注意坐標(biāo)系的統(tǒng)一,即要么都在視口坐標(biāo)系,要么都在世界坐標(biāo)系中。如下代碼是將鼠標(biāo)點(diǎn)變換到世界坐標(biāo)系中進(jìn)行移動(dòng):
void ArcballController::Translate(const gp_Pnt2d &thePoint)
{
gp_Pnt aCurrentPoint = Convert2World(thePoint);
gp_Trsf aTrsf;
aTrsf.SetTranslation(aCurrentPoint, mPreviousPoint);
mCamera->Transform(aTrsf);
}
對(duì)相機(jī)參數(shù)進(jìn)行修改后,需要更新場(chǎng)景數(shù)據(jù)。移動(dòng)場(chǎng)景只涉及到MODELVIEW變換,所以需要刷新模型視圖MODELVIEW變換矩陣數(shù)據(jù)并重繪場(chǎng)景,相關(guān)代碼如下所示:
// model/view transformation for pan and rotate.
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glLoadMatrixf(theArcballController.GetOrientationMatrix());
其中theArcballController的這個(gè)函數(shù)是調(diào)用了Graphic3d_Camera的函數(shù)來設(shè)置模型視圖變換矩陣。經(jīng)過測(cè)試,移動(dòng)效果還可以,如下圖所示為將一個(gè)Teapot從屏幕左上角移動(dòng)到了右下角:
Figure 2.1 Translate the Scene
3.Zoom View
對(duì)于透視投影而言,靠模型越近,看到模型就越大,因?yàn)橥敢曂队暗奶攸c(diǎn)就是近大遠(yuǎn)小。而對(duì)平行投影而言,這種規(guī)律就不適用了。其實(shí)二者都可以統(tǒng)一到通過調(diào)整視口大小來對(duì)場(chǎng)景模型進(jìn)行縮放。同樣的模型,當(dāng)投影到較大的視口中時(shí),模型的投影得到的二維圖形也會(huì)較大;當(dāng)投影到較小的視口中時(shí),模型的投影得到的二維圖形也會(huì)較小。這樣就達(dá)到對(duì)場(chǎng)景進(jìn)行縮放的目的了。其中OpenCASCADE中的實(shí)現(xiàn)是通過設(shè)置Graphic3d_Camera的Scale來實(shí)現(xiàn)的,代碼如下圖所示:
//===================================================================
//function : SetZoom
//purpose :
//===================================================================
void V3d_View::SetZoom(const Standard_Real Coef,const Standard_Boolean Start)
{
V3d_BadValue_Raise_if( Coef <= 0.,"V3d_View::SetZoom, bad coefficient");
if (Start)
{
myCamStartOpEye = myCamera->Eye();
myCamStartOpCenter = myCamera->Center();
}
Standard_Real aViewWidth = myCamera->ViewDimensions().X();
Standard_Real aViewHeight = myCamera->ViewDimensions().Y();
// ensure that zoom will not be too small or too big
Standard_Real coef = Coef;
if (aViewWidth < coef * Precision::Confusion())
{
coef = aViewWidth / Precision::Confusion();
}
else if (aViewWidth > coef * 1e12)
{
coef = aViewWidth / 1e12;
}
if (aViewHeight < coef * Precision::Confusion())
{
coef = aViewHeight / Precision::Confusion();
}
else if (aViewHeight > coef * 1e12)
{
coef = aViewHeight / 1e12;
}
myCamera->SetEye (myCamStartOpEye);
myCamera->SetCenter (myCamStartOpCenter);
myCamera->SetScale (myCamera->Scale() / Coef);
View()->AutoZFit();
ImmediateUpdate();
}
根據(jù)鼠標(biāo)點(diǎn)計(jì)算出縮改系數(shù),通過myCamera->SetScale()來達(dá)到對(duì)場(chǎng)景進(jìn)行縮放的目的。場(chǎng)景縮放操作涉及到需要更新OpenGL的投影矩陣數(shù)據(jù),代碼如下所示:
// projection transformation for zoom.
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glLoadMatrixf(theArcballController.GetProjectionMatrix());
Figure 3.1 Zoom the scene
4.Rotate View
通過鼠標(biāo)在二維屏幕上來旋轉(zhuǎn)三維的場(chǎng)景有幾種方法,如下圖所示:
Figure 4.1 3D Rotation(http://www.cabiatl.com/mricro/obsolete/graphics/3d.html)
方法一是通過Euler Angles來實(shí)現(xiàn),好處是用戶比較容易理解Euler角,如yaw, pitch和roll,如下圖所示:
Figure 4.2 Euler Angles: Yaw, Pitch and Roll
缺點(diǎn)就是因?yàn)樗梨i問題(gimbal lock)導(dǎo)致不能指定一些視圖,當(dāng)出現(xiàn)死鎖問題時(shí),操作就顯得不直觀了。
比較直觀的方法就是ArcBall方式了,使用這種方法可以以任意方向來查看場(chǎng)景中的模型。有個(gè)網(wǎng)頁(yè)版的實(shí)現(xiàn),可以去體驗(yàn)一下:
http://www.math.tamu.edu/~romwell/arcball_js/index.html
Figure 4.3 Arcball in Javascript
ArcBall的原理是將二維屏幕上鼠標(biāo)點(diǎn)轉(zhuǎn)換到球面上,拖動(dòng)鼠標(biāo)就是在轉(zhuǎn)動(dòng)這個(gè)球。根據(jù)映射到球面的兩個(gè)點(diǎn),通過矢量的點(diǎn)乘及叉乘得到旋轉(zhuǎn)角度及旋轉(zhuǎn)軸。通過這種方式可以將二維的鼠標(biāo)位置映射到三維的場(chǎng)景來實(shí)現(xiàn)對(duì)場(chǎng)景觀察的直觀操作。
OpenCASCADE中場(chǎng)景的旋轉(zhuǎn)方式是通過先遍歷場(chǎng)景中的模型計(jì)算出重心點(diǎn),再繞三個(gè)坐標(biāo)軸來旋轉(zhuǎn),代碼如下所示:
//=============================================================================
//function : Rotate
//purpose :
//=============================================================================
void V3d_View::Rotate(const Standard_Real ax, const Standard_Real ay, const Standard_Real az,
const Standard_Real X, const Standard_Real Y, const Standard_Real Z, const Standard_Boolean Start)
{
Standard_Real Ax = ax ;
Standard_Real Ay = ay ;
Standard_Real Az = az ;
if( Ax > 0. ) while ( Ax > DEUXPI ) Ax -= DEUXPI ;
else if( Ax < 0. ) while ( Ax < -DEUXPI ) Ax += DEUXPI ;
if( Ay > 0. ) while ( Ay > DEUXPI ) Ay -= DEUXPI ;
else if( Ay < 0. ) while ( Ay < -DEUXPI ) Ay += DEUXPI ;
if( Az > 0. ) while ( Az > DEUXPI ) Az -= DEUXPI ;
else if( Az < 0. ) while ( Az < -DEUXPI ) Az += DEUXPI ;
if (Start)
{
myGravityReferencePoint.SetCoord (X, Y, Z);
myCamStartOpUp = myCamera->Up();
myCamStartOpEye = myCamera->Eye();
myCamStartOpCenter = myCamera->Center();
}
const Graphic3d_Vertex& aVref = myGravityReferencePoint;
myCamera->SetUp (myCamStartOpUp);
myCamera->SetEye (myCamStartOpEye);
myCamera->SetCenter (myCamStartOpCenter);
// rotate camera around 3 initial axes
gp_Pnt aRCenter (aVref.X(), aVref.Y(), aVref.Z());
gp_Dir aZAxis (myCamera->Direction().Reversed());
gp_Dir aYAxis (myCamera->Up());
gp_Dir aXAxis (aYAxis.Crossed (aZAxis));
gp_Trsf aRot[3], aTrsf;
aRot[0].SetRotation (gp_Ax1 (aRCenter, aYAxis), -Ax);
aRot[1].SetRotation (gp_Ax1 (aRCenter, aXAxis), Ay);
aRot[2].SetRotation (gp_Ax1 (aRCenter, aZAxis), Az);
aTrsf.Multiply (aRot[0]);
aTrsf.Multiply (aRot[1]);
aTrsf.Multiply (aRot[2]);
myCamera->Transform (aTrsf);
View()->AutoZFit();
ImmediateUpdate();
}
5.Conclusion
當(dāng)實(shí)現(xiàn)三維場(chǎng)景的建模后,最激動(dòng)人心的應(yīng)該是對(duì)場(chǎng)景及場(chǎng)景中模型的控制。通過交互操作使用戶方便地觀察場(chǎng)景的模型,或直觀地編輯場(chǎng)景中的模型。所以交互也是三維軟件中的重要功能,且是給用戶最直接的感覺的操作。
因?yàn)榻换ゲ僮魃婕暗绞髽?biāo)鍵盤消息的處理,所以首先要設(shè)計(jì)好對(duì)這些消息的處理方式,在OpenSceneGraph中使用了適配器的方式來實(shí)現(xiàn)跨平臺(tái)的消息處理,使用戶通過繼承的方式來實(shí)現(xiàn)對(duì)消息的處理。這種方式使程序的可擴(kuò)展性及代碼的可讀性更好,OpenCASCADE中的消息的處理還是比較直接的,沒有什么封裝。
本文主要介紹了如何實(shí)現(xiàn)對(duì)場(chǎng)景的控制,如移動(dòng)、縮放及旋轉(zhuǎn)操作,這些功能的實(shí)現(xiàn)需要對(duì)OpenGL的渲染管線有一定的了解。在理解了對(duì)視圖/場(chǎng)景的控制后,為進(jìn)一步理解對(duì)場(chǎng)景中的模型的控制打下基礎(chǔ),如選擇Picking,拖拽Drag等操作。最后給出一個(gè)基于OpenCASCADE的類Graphic3d_Camera及GLUT實(shí)現(xiàn)的場(chǎng)景變換操作,功能不是很完善,僅供參考。若有好的意見,歡迎反饋。
6. References
1. Brad Smith. ArcBall. http://rainwarrior.ca/dragon/arcball.html
2. WikiBooks. Modern OpenGL Tutorial Arcball.
http://en.wikibooks.org/wiki/OpenGL_Programming/Modern_OpenGL_Tutorial_Arcball
3. sgCore demo code. http://m.shnenglu.com/eryar/archive/2013/06/30/201411.html
4. Virtual Trackballs Revisited. http://image.diku.dk/research/trackballs/index.html
5. http://oviliazhang.diandian.com/post/2012-05-19/40027878859
6. 王銳,錢學(xué)雷. OpenSceneGraph三維渲染引擎設(shè)計(jì)與實(shí)踐. 清華大學(xué)出版社. 2009