Coin3D三维可视化教程5

组节点是一个用于收集子节点对象的容器。组节点可以将属性、形体、和其它组节点收集到场景中。图 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

}

 

运行结果如下:

robot with leg

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)

 

posted @ 2022-08-21 10:13  Oliver2022  阅读(345)  评论(0编辑  收藏  举报