組
組節點是一個用於收集子節點對象的容器。組節點可以將屬性、形體、和其它組節點收集到場景中。圖 3-4 展示了部分組節點類的類樹圖。Inventor 中有多種不同類型的組節點,每種組節點都有其特定的“分組”(grouping)特性。
當創建完一個組節點後,初始情況下它是沒有子節點在其中的。SoGroup是所有組節點的基類。所有從它派生出來的節點類都有一個addChild()方法。
創建組節點
假設,如果想將前面創建的幾何變換節點、材質節點和球體節點合併到機器人的“head”組節點中,首先要創建一個SoGroup節點,然後按照下列的步驟調用addChild() 方法,將其他子節點包含進組節點中。
SoGroup* head = new SoGroup;
head->addChild(myTransform);
head->addChild(bronze);
head->addChild(headSphere);
子節點的順序
如前面的代碼所示,addChild()方法會將某個特定節點增加到組節點內子節點列表的末端位置上。每個加到組中的子節點都分配有一個關聯的索引值。組中第一個子節點索引值爲0,第二個子節點的索引值爲 1,以此類似。
insertChild()方法
void insertChild( SoNode * child , int newChildIndex );
按照參數 newChildIndex 所指定的位置,將子節點插入到組中。例如:
SoDrawStyle *wireStyle;
wireStyle = new SoDrawStyle;
wireStyle->style = SoDrawStyle::LINES;
// Insert as child 1 (the node right after the first child,
// which is child 0.
body->insertChild(wireStyle, 1);
將一個框線(wireframe)繪製風格的節點作爲第二個子節點插入到 body 組中。
組節點還有其他可調用的方法,例如查詢在組中有多少個子節點,查找某個特定子節點的索引值,根據給定的索引值使用相應的子節點,以及移除子節點等方法。
子節點的順序爲什麼是重要的 ?
每種節點類對於給定的數據庫動作都有自己的響應方式。在本節的討論中,我們假設只處理 GL 渲染動作 (簡稱渲染)。
- 如果當前渲染的節點是組節點,那麼組節點將按照順序對其每個子節點調用渲染動作,調用順序通常都是按照子節點在場景中從左到右的順序。
- 每個子節點依次執行它們自己的渲染方法,這些方法在某些方面會影響遍歷狀態(見第 9 章“應用動作”)。如果子節點是一個屬性節點,那麼它可能會修改諸如像散射光顏色、物體縮放比例、線寬度等當前遍歷狀態的元素數據。絕大多數的屬性節點只是簡單地(使用自己的數值)替換掉遍歷狀態中對應元素的數值(例如,青銅材質的節點將使用自己的數值替換掉遍歷狀態中當前材質的數值)。幾何變換是一種例外情況,它們是彼此互相結合,累積產生合成變換。
- 如果當前渲染的節點是形體節點的話,那麼形體節點將使用當前的遍歷狀態來繪製自己。
渲染時,Inventor 將以場景的根節點作爲開始,按照從左到右,從上到下的順序遍歷整個場景。注意,在場景中的右邊節點(下邊節點)將繼承由左邊節點(上邊節點)設置的遍.歷狀態。
圖 3-6 展示了節點的繼承狀態。當渲染 waterMolecule 節點時,waterMolecule 節點將首先訪問它的第一個子節點 oxygen。 然後 oxygen 組節點將按照下面的順序分別訪問它自己的子節點:
1. 材質節點(redPlastic)將當前遍歷狀態中的材質元素修改成有紅色光澤的材質。
2. 球體節點(sphere1)將使用當前的遍歷狀態渲染一個球體。一個有紅色光澤的球體將被繪製在座標原點的位置上。
場景繼續遍歷右邊的下一個組節點 hydrogen1,這個組節點同樣按照從左到右的順序依次訪問它的每個子節點:
1. 幾何變換節點(hydrogenXform1)修改了變換矩陣(也就是說,它在 x,y,z 軸上等比縮小了 75%)。同時它還爲變換矩陣增加上了一個 0.0, -1.2, 0.0(分別在 x,y,z 軸方向)的平移變換。
2. 材質節點(whitePlastic)將當前遍歷狀態中的材質元素修改成有白色光澤的材質。
3. 球體節點(sphere2) 將使用修改過的遍歷狀態渲染另一個球體。這個球體是白色的。
另外,因爲在它的組節點中有SoTransform節點(hydrogenXform1),所以sphere2 顯示在一個新的位置上,並且它的大小也是按比例縮小的。
接下來,hydrogen2 組節點按照從左到右的順序訪問它的子節點:
1. 幾何變換節點(hydrogenXform2)修改變換矩陣,在+x 軸和+y 軸方向進行了平移。
2. 球體節點(sphere3) 將使用修改過的遍歷狀態渲染第三個球體。這個球仍然是白色的,並且也被縮小了 0.75,這是因爲它繼承了在 hydrogen1 組節點中的屬性。
例子 3-1 演示了創建這個分子節點的代碼:
#include "Coin3Dtest1.h"
#include <QtWidgets/QApplication>
#include <Inventor/Qt/SoQt.h>
#include <Inventor/Qt/viewers/SoQtExaminerViewer.h>
#include <Inventor/Qt/SoQtRenderArea.h>
#include <Inventor/nodes/SoGroup.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoTransform.h>
#include <Inventor/nodes/SoDrawStyle.h>
#include <Inventor/nodes/SoPerspectiveCamera.h>
#include <Inventor/nodes/SoDirectionalLight.h>
#include <Inventor/nodes/SoCube.h>
#include <Inventor/nodes/SoCone.h>
#include <Inventor/nodes/SoSphere.h>
int main(int argc, char* argv[])
{
//Initialize Inventor. This returns a main window to use
QWidget* myWindow = SoQt::init(argc, argv, argv[0]);
if (myWindow == NULL)exit(1);
SoGroup* waterMolecule = new SoGroup;
SoGroup* oxygen = new SoGroup;
SoMaterial* redPlastic = new SoMaterial;
SoSphere* sphere1 = new SoSphere;
SoGroup* hydrogen1 = new SoGroup;
SoGroup* hydrogen2 = new SoGroup;
SoTransform* hydrogenXform1 = new SoTransform;
SoTransform* hydrogenXform2 = new SoTransform;
SoMaterial* whitePlastic = new SoMaterial;
SoSphere* sphere2 = new SoSphere;
SoSphere* sphere3 = new SoSphere;
//set all field values for the oxygen atom
redPlastic->ambientColor.setValue(1.0, 0.0, 0.0);
redPlastic->diffuseColor.setValue(1.0, 0.0, 0.0);
redPlastic->specularColor.setValue(0.5, 0.5, 0.5);
redPlastic->shininess = 0.5;
//set all field values for the hydrogen atoms
hydrogenXform1->scaleFactor.setValue(0.75, 0.75, 0.75);
hydrogenXform1->translation.setValue(0.0, - 1.2, 0.0);
hydrogenXform2->translation.setValue(1.1852, 1.3877, 0.0);
whitePlastic->ambientColor.setValue(1.0, 1.0, 1.0);
whitePlastic->diffuseColor.setValue(1.0, 1.0, 1.0);
whitePlastic->specularColor.setValue(0.5, 0.5, 0.5);
whitePlastic->shininess = 0.5;
// Create a hierarchy
waterMolecule->addChild(oxygen);
waterMolecule->addChild(hydrogen1);
waterMolecule->addChild(hydrogen2);
oxygen->addChild(redPlastic);
oxygen->addChild(sphere1);
hydrogen1->addChild(hydrogenXform1);
hydrogen1->addChild(whitePlastic);
hydrogen1->addChild(sphere2);
hydrogen2->addChild(hydrogenXform2);
hydrogen2->addChild(sphere3);
//set up viewer
SoQtExaminerViewer* myViewer = new SoQtExaminerViewer(myWindow);
myViewer->setSceneGraph(waterMolecule);
myViewer->setTitle("Examiner Viewer");
myViewer->show();
SoQt::show(myWindow); //Display main window
SoQt::mainLoop(); //Main Inventor event loop
}
運行效果如下
這裏例子只用了SoGroup,下面講述SoGroup的子類SoSeparator
隔離節點(Separators )
使用從SoGroup派生出來的子類SoSeparator節點,可以隔離其子節點所產生的影響。SoSeparator節點在遍歷其子節點之前,首先會保存當前的遍歷狀態,當遍歷完所有的子節點後,SoSeparator會恢復以前的遍歷狀態。因此,位於SoSeparator中的節點將不會對位於其之上或之右的任何節點產生影響。
例如,圖 3-7 顯示了一個機器人頭部與身體的場景。body 組節點是一個隔離節點,它包含的 SoTransform 和 SoMaterial 兩個節點影響了圓柱節點所要使用的遍歷狀態。當遍歷完位於 body 組中的所有子節點後,隔離節點將恢復原來的遍歷狀態。這樣,head 組就不會受到 body 組中子節點的影響。因爲 head 組也是一個隔離節點,所以遍歷狀態在遍歷開始的時候再次被保存起來,在遍歷結束後被自動恢復。隔離節點的使用代價非常低,對構建場景有很大的幫助。我們將會經常使用到它。
提示:在連續渲染的時候,如果希望重新使用遍歷狀態的話,那麼場景的根節點應該是一個隔離節點
下面給出了創建機器人身體的代碼
#include "Coin3Dtest1.h"
#include <QtWidgets/QApplication>
#include <Inventor/Qt/SoQt.h>
#include <Inventor/Qt/viewers/SoQtExaminerViewer.h>
#include <Inventor/Qt/SoQtRenderArea.h>
#include <Inventor/nodes/SoGroup.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoTransform.h>
#include <Inventor/nodes/SoDrawStyle.h>
#include <Inventor/nodes/SoPerspectiveCamera.h>
#include <Inventor/nodes/SoDirectionalLight.h>
#include <Inventor/nodes/SoCube.h>
#include <Inventor/nodes/SoCone.h>
#include <Inventor/nodes/SoSphere.h>
#include <Inventor/nodes/SoCylinder.h>
int main(int argc, char* argv[])
{
//Initialize Inventor. This returns a main window to use
QWidget* myWindow = SoQt::init(argc, argv, argv[0]);
if (myWindow == NULL)exit(1);
////robot with legs
SoSeparator* robot = new SoSeparator;
//construct parts for legs (thigh, calf and foot)
SoCube* thight = new SoCube;
thight->width = 1.2;
thight->height = 2.2;
thight->depth = 1.1;
SoTransform* calfTransform = new SoTransform;
calfTransform->translation.setValue(0, -2.25, 0);
SoCube* calf = new SoCube;
calf->width = 1;
calf->height = 2.2;
calf->depth = 1;
SoTransform* footTransform = new SoTransform;
footTransform->translation.setValue(0, -2, 0.5);
SoCube* foot = new SoCube;
foot->width = 0.8;
foot->height = 0.8;
foot->depth = 2;
//put leg parts together
SoGroup* leg = new SoGroup;
leg->addChild(thight);
leg->addChild(calfTransform);
leg->addChild(calf);
leg->addChild(footTransform);
leg->addChild(foot);
SoTransform* leftTransform = new SoTransform;
leftTransform->translation = SbVec3f(1, -4.25, 0);
//left leg
SoSeparator* leftLeg = new SoSeparator;
leftLeg->addChild(leftTransform);
leftLeg->addChild(leg);
SoTransform* rightTransform = new SoTransform;
rightTransform->translation.setValue(-1, -4.25, 0);
//right leg
SoSeparator* rightLeg = new SoSeparator;
rightLeg->addChild(rightTransform);
rightLeg->addChild(leg);
//create body parts
SoTransform* xf1 = new SoTransform;
xf1->translation.setValue(0.0, 3.0, 0.0);
SoMaterial* bronze = new SoMaterial;
bronze->ambientColor.setValue(.33, .22, .27);
bronze->diffuseColor.setValue(.78, .57, .11);
bronze->specularColor.setValue(.99, .94, .81);
bronze->shininess = .28;
SoCylinder* myCylinder = new SoCylinder;
myCylinder->radius = 2.5;
myCylinder->height = 6;
//construct body out of parts
SoSeparator* body = new SoSeparator;
body->addChild(xf1);
body->addChild(bronze);
body->addChild(myCylinder);
body->addChild(leftLeg);
body->addChild(rightLeg);
//create head parts
SoTransform* xf2 = new SoTransform;
xf2->translation.setValue(0.0, 7.5, 0);
xf2->scaleFactor.setValue(1.5, 1.5, 1.5);
SoMaterial* silver = new SoMaterial;
silver->ambientColor.setValue(.2, .2, .2);
silver->diffuseColor.setValue(.6, .6, .6);
silver->specularColor.setValue(.5, .5, .5);
silver->shininess = .5;
SoSphere* mySphere = new SoSphere;
//construct head out of parts
SoSeparator* head = new SoSeparator;
head->addChild(xf2);
head->addChild(silver);
head->addChild(mySphere);
robot->addChild(body);
robot->addChild(head);
//set up viewer
SoQtExaminerViewer* myViewer = new SoQtExaminerViewer(myWindow);
myViewer->setSceneGraph(robot);
myViewer->setTitle("Robot with leg");
myViewer->show();
SoQt::show(myWindow); //Display main window
SoQt::mainLoop(); //Main Inventor event loop
}
運行結果如下:
SoGroup 的其它子類
除了 SoSeparator 之外,SoGroup 還包括下列的子類:
- ? SoSwitch
- ? SoLevelOfDetail
- ? SoSelection (見第 10 章,“處理事件和選擇器”)
在機器人的例子中,SoSeparator 節點將節點的影響隔離在一個特定的組中;因爲我們不希望讓“頭部”節點去繼承“身體”節點的幾何變換和材質等屬性。相反,在分子的例子中使用了 SoGroup 節點,它是累積了一組屬性數據後,將累積後的狀態應用到後面的節點上。
切換節點(SoSwitch )
切換節點很像 SoGroup 節點,除了遍歷子節時,它只訪問其中的某個子節點之外。它包含一個叫做 whichChild 域,這個域用來指定要遍歷的子節點的索引值。例如,下面的代碼指定了要遍歷訪問切換節點的 c 子節點。
SoSwitch *s = new SoSwitch;
s->addChild(a); // this child has an index of 0
s->addChild(b); // this child has an index of 1
s->addChild(c); // this child has an index of 2
s->addChild(d); // this child has an index of 3
s->whichChild = 2;
whichChild 缺省值是 SO_SWITCH_NONE,表示不遍歷組中任何子節點。
可以通過使用 SoSwitch 節點切換若干不同的照相機節點,以此達到以不同的方式來觀察場景的目的。也可以使用 SoSwitch 節點來創建一種簡單原始的動畫效果(rudimentary animation)。例如,我們可以通過循環操作一連串的組節點,使鴨子上下扇動翅膀。或者可以讓機器人在屏幕上行走。SoBlinker 節點是從 SoSwitch 派生來的,它可以循環操作其子節點,並還提供了一些對動畫顯示非常有用的附加功能。(見 13 章“引擎”)
SoLevelOfDetail
SoLevelOfDetail 節點可以對相同的物體指定不同的細節變化程度。它的子節點是按照從高到低的細節程度進行安排的。投影到視口中的物體尺寸決定着那些子節點可以被真正渲染。這個節點對於那些需要快速渲染的應用程序是很有幫助的。( 當物體投影后的尺寸變得非常小時,可以認爲人的肉眼很難分辨了,所以可以不必渲染它,進而可以提高渲染速度。譯者注當物體投影后的尺寸變得非常小時,可以認爲人的肉眼很難分辨了,所以可以不必渲染它,進而可以提高渲染速度。譯者注 )。
它含有一個域:
screenArea 屏幕上的面積,用於和level-of-detail 組節點的包圍盒進行比較。缺省(SoMFFloat) 值爲 0.0。表示將只渲染組中的第一個子節點。
爲了決定遍歷渲染那個子節點,Inventor 首先要計算 SoLevelOfDetail 組中所有子節點的包圍盒數據,然後將包圍盒投影到視口上,接着再計算包圍這個包圍盒且和屏幕方向對齊的2D 矩形的面積。最後將這個面積與保存在 screenArea 域中的面積進行比較。
共享節點實例
我們可以將任意一個節點增加到多個組節點中。例如,一個自行車模型可以使用同一個車輪組節點來代表前後兩個車輪,只需要稍加修改這兩個車輪的大小和位置即可。術語共享實例(shared instancing)就是那種一個單一節點有多個父節點的情況。
如圖 3-10 所示,機器人的左右腿模型共享使用了 leg 組節點。leg 組包含有一個圓柱(大腿),一個經過平移的圓柱(小腿),和另外一個經過平移的立方體(腳)。左右腿組節點(即rightLeg 和 leftLeg)中都包含有一個額外的 SoTransform 節點。這個節點將整個腿定位到機器人身體的正確位置上。
對於 leg 組內的任何修改都將影響到它的所有實例。例如,如果將 foot 節點的立方體高度放大兩倍的話,那麼左右腳的高度將都被放大兩倍。共享實例對於數據庫和程序而言是非常經濟實惠的,因爲對象數據是重用的而非是複製了一份。如有可能的話,應該儘可能地重用節點(和組)以節約程序所需的時間和內存資源。然而,注意不能在場景中創建一個循環節點。一個節點可以連接到多個父節點中,但不能作爲自己或任何其後代節點的子節點。(Do not, however, create cycles within a given scene graph. A node can connect to multiple parents but should not be a child of itself or any of its descendants)