OSG开源教程(转)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OSG开源教程(转)相关的知识,希望对你有一定的参考价值。
整理:荣明、王伟
北 京
2008年4月
序
第一次接触OSG是在2001年,当时开源社区刚刚兴起,还没有现在这么火。下载了OSG源码,但是在看了几个Demo之后,感觉没有什么特别之处。时隔七年之后,我再次将目光投向OSG,发现OSG确实有其独到之处,很多3D效果已经不弱于甚至超过商业软件,有感于开源力量的巨大。但是,与当前主流3D商业软件如Vega、VegaPrime、VTree、Performer等相比,开源软件的缺点也很明显,其中文档缺乏可谓其致命弱点之一。开发者只能从浩瀚的源码中,进行编程的学习,效率不高。
OSG组织也意识到这一点,不断推出一些官方教程,网络上有很多OSG爱好者发布的心得和教程。我收集、整理了许多学习资料,其中美国海军研究生院发布的OSG教程非常好,可作为OSG的官方教程的一个很好补充。它共有十一个专题,结合例子程序,一步步教你如何进行OSG的开发。我将其编辑成一个较为完善的教材,供大家学习。在教材整理过程中,王伟调试了源程序,对该书编辑和修订做了大量的工作;实验室的学生杨宇蒙、冯锐、章仕锋、李永川和李益强阅读了本书,并提出了宝贵的修改意见,在此一并表示感谢。
希望这本小册子能对大家学习OSG编程起到一定的帮助作用。
谢谢大家阅读本书!
荣 明
二OO八年四月于崔春园
Email:[email protected]
(本书的资料全部来自互联网,仅供个人学习交流使用。感谢竹林小舍、array翻译了Navy的教程。)
目录
7.搜索并控制开关和DOF(自由度)节点(Finding and Manipulating a Switch and DOF Node)... 20
10.使用自定义矩阵来放置相机(Positioning a Camera with a User-Defined Matrix)... 36
本节涵盖了生成基本几何形状的一些方法。生成几何物体的方法有这么几种:在最底层对OpenGL基本几何进行松散的包装,中级是使用Open Scene Graph的基本形状,以及更高级一些的从文件读取。这篇教程涵盖的是最低层的。这种方法弹性最大但最费力。通常在Scene Graph级别,几何形状是从文件加载的。文件加载器完成了跟踪顶点的大部分工作。
对一下几个类的简单解释:
Geode类:
geode类继承自node类。在一个Scene Graph中,node(当然包含geode)可以作为叶子节点。Geode实例可以有多个相关的drawable。
Drawable类层次:
基类drawable是一个有六个具体子类的抽象类。
geometry类可以直接有vertex和vertex数据,或者任意个primitiveSet实例。
vertex和vertex属性数据(颜色、法线、纹理坐标)存放在数组中。既然多个顶点可以共享相同的颜色、法线或纹理坐标,那么数组索引就可以用来将顶点数组映射到颜色、法线、或纹理坐标数组。
PrimitiveSet类:
这个类松散的包装了OpenGL的基本图形-POINTS,LINES,LINE_STRIP,LINE_LOOP,...,POLYGON.
以下这节代码安装了一个viewer来观察我们创建的场景,一个‘group’实例作为scene graph的根节点,一个几何节点(geode)来收集drawable,和一个geometry实例来关联顶点和顶点数据。(这个例子中渲染的形状是一个四面体)
...
int main()
{
...
osgProducer::Viewer viewer;
osg::Group* root = new osg::Group();
osg::Geode* pyramidGeode = new osg::Geode();
osg::Geometry* pyramidGeometry = new osg::Geometry();
下一步,需要将锥体geometry和锥体geode关联起来,并将pyramid geode加到scene graph的根节点上。
pyramidGeode->addDrawable(pyramidGeometry);
root->addChild(pyramidGeode);
声明一个顶点数组。每个顶点由一个三元组表示——vec3类的实例。这些三元组用osg::Vec3Array类的实例存贮。既然osg::Vec3Array继承自STL的vector类,那么我们就可以使用push_back方法来添加数组成员。push_back将元素加到向量的尾端,因此第一个元素的索引是0,第二个是1,依此类推。
使用‘z’轴向上的右手坐标系系统,下面的0...4数组元素代表着产生一个简单锥体所需的5个点。
osg::Vec3Array* pyramidVertices = new osg::Vec3Array;
pyramidVertices->push_back( osg::Vec3( 0, 0, 0) ); // front left
pyramidVertices->push_back( osg::Vec3(10, 0, 0) ); // front right
pyramidVertices->push_back( osg::Vec3(10,10, 0) ); // back right
pyramidVertices->push_back( osg::Vec3( 0,10, 0) ); // back left
pyramidVertices->push_back( osg::Vec3( 5, 5,10) ); // peak
将这个顶点集合和与我们加到场景中的geode相关的geometry关联起来。
pyramidGeometry->setVertexArray( pyramidVertices );
下一步,产生一个基本集合并将其加入到pyramid geometry中。使用pyramid的前四个点通过DrawElementsUint类的实例来定义基座。这个类也继承自STL的vector,所以push_back方法会顺序添加元素。为了保证合适的背面剔除,顶点的顺序应当是逆时针方向的。构造器的参数是基本的枚举类型(和opengl的基本枚举类型一致),和起始的顶点数组索引。
osg::DrawElementsUInt* pyramidBase =
new osg::DrawElementsUInt(osg::PrimitiveSet::QUADS, 0);
pyramidBase->push_back(3);
pyramidBase->push_back(2);
pyramidBase->push_back(1);
pyramidBase->push_back(0);
pyramidGeometry->addPrimitiveSet(pyramidBase);
对每个面重复相同的动作。顶点仍要按逆时针方向指定。
osg::DrawElementsUInt* pyramidFaceOne =
new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, 0);
pyramidFaceOne->push_back(0);
pyramidFaceOne->push_back(1);
pyramidFaceOne->push_back(4);
pyramidGeometry->addPrimitiveSet(pyramidFaceOne);
osg::DrawElementsUInt* pyramidFaceTwo =
new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, 0);
pyramidFaceTwo->push_back(1);
pyramidFaceTwo->push_back(2);
pyramidFaceTwo->push_back(4);
pyramidGeometry->addPrimitiveSet(pyramidFaceTwo);
osg::DrawElementsUInt* pyramidFaceThree =
new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, 0);
pyramidFaceThree->push_back(2);
pyramidFaceThree->push_back(3);
pyramidFaceThree->push_back(4);
pyramidGeometry->addPrimitiveSet(pyramidFaceThree);
osg::DrawElementsUInt* pyramidFaceFour =
new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, 0);
pyramidFaceFour->push_back(3);
pyramidFaceFour->push_back(0);
pyramidFaceFour->push_back(4);
pyramidGeometry->addPrimitiveSet(pyramidFaceFour)
声明并加载一个vec4为元素的数组来存储颜色。
osg::Vec4Array* colors = new osg::Vec4Array;
colors->push_back(osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f) ); //index 0 red
colors->push_back(osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f) ); //index 1 green
colors->push_back(osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f) ); //index 2 blue
colors->push_back(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f) ); //index 3 white
声明的这个变量可以将顶点数组元素和颜色数组元素匹配起来。这个容器的元素数应当和顶点数一致。这个容器是顶点数组和颜色数组的连接。这个索引数组中的条目就对应着顶点数组中的元素。他们的值就是颜色数组中的索引。顶点数组元素与normal和纹理坐标数组的匹配也是遵循这种模式。
注意,这种情况下,我们将5个顶点指定4种颜色。顶点数组的0和4元素都被指定为颜色数组的0元素。
osg::TemplateIndexArray
<unsigned int, osg::Array::UIntArrayType,4,4> *colorIndexArray;
colorIndexArray =
new osg::TemplateIndexArray<unsigned int, osg::Array::UIntArrayType,4,4>;
colorIndexArray->push_back(0); // vertex 0 assigned color array element 0
colorIndexArray->push_back(1); // vertex 1 assigned color array element 1
colorIndexArray->push_back(2); // vertex 2 assigned color array element 2
colorIndexArray->push_back(3); // vertex 3 assigned color array element 3
colorIndexArray->push_back(0); // vertex 4 assigned color array element 0
下一步,将颜色数组和geometry关联起来,将上面产生的颜色索引指定给geometry,设定绑定模式为_PER_VERTEX。
pyramidGeometry->setColorArray(colors);
pyramidGeometry->setColorIndices(colorIndexArray);
pyramidGeometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
osg::Vec2Array* texcoords = new osg::Vec2Array(5);
(*texcoords)[0].set(0.00f,0.0f);
(*texcoords)[1].set(0.25f,0.0f);
(*texcoords)[2].set(0.50f,0.0f);
(*texcoords)[3].set(0.75f,0.0f);
(*texcoords)[4].set(0.50f,1.0f);
pyramidGeometry->setTexCoordArray(0,texcoords);
注:一下部分可能错误。
// Declare and initialize a transform node.
osg::PositionAttitudeTransform* pyramidTwoXForm =
new osg::PositionAttitudeTransform();
// Use the ‘addChild‘ method of the osg::Group class to
// add the transform as a child of the root node and the
// pyramid node as a child of the transform.
root->addChild(pyramidTwoXForm);
pyramidTwoXForm->addChild(pyramidGeode);
// Declare and initialize a Vec3 instance to change the
// position of the tank model in the scene
osg::Vec3 pyramidTwoPosition(15,0,0);
pyramidTwoXForm->setPosition( pyramidTwoPosition );
既然我们生成了一个geometry节点并将它加到了场景中,我们就可以重用这个geometry。例如,如果我们想让另一个pyramid在第一个的右侧15个单位处,我们就可以在我们的scene graph中将这个geode加到transform节点的子节点上。
最后一步,建立并进入一个仿真循环。
viewer.setUpViewer(osgProducer::Viewer::STANDARD_SETTINGS);
viewer.setSceneData( root );
viewer.realize();
while( !viewer.done() )
{
viewer.sync();
viewer.update();
viewer.frame();
}
为教程1中介绍的由OpenGL基本绘制单位定义的几何体添加纹理。
前一节教程介绍了包含由OpenGL基本单位产生的基本形状的视景。本节讲解如何为这些形状添加纹理。为了使代码更方便使用,我们将pyramid的代码放到一个函数中,产生geode并返回它的指针。下面的代码来自教程1。
osg::Geode* createPyramid()
{
osg::Geode* pyramidGeode = new osg::Geode();
osg::Geometry* pyramidGeometry = new osg::Geometry();
pyramidGeode->addDrawable(pyramidGeometry);
// Specify the vertices:
osg::Vec3Array* pyramidVertices = new osg::Vec3Array;
pyramidVertices->push_back( osg::Vec3(0, 0, 0) ); // front left
pyramidVertices->push_back( osg::Vec3(2, 0, 0) ); // front right
pyramidVertices->push_back( osg::Vec3(2, 2, 0) ); // back right
pyramidVertices->push_back( osg::Vec3( 0,2, 0) ); // back left
pyramidVertices->push_back( osg::Vec3( 1, 1,2) ); // peak
// Associate this set of vertices with the geometry associated with the
// geode we added to the scene.
pyramidGeometry->setVertexArray( pyramidVertices );
// Create a QUAD primitive for the base by specifying the
// vertices from our vertex list that make up this QUAD:
osg::DrawElementsUInt* pyramidBase =
new osg::DrawElementsUInt(osg::PrimitiveSet::QUADS, 0);
pyramidBase->push_back(3);
pyramidBase->push_back(2);
pyramidBase->push_back(1);
pyramidBase->push_back(0);
//Add this primitive to the geometry:
pyramidGeometry->addPrimitiveSet(pyramidBase);
// code to create other faces goes here!
// (removed to save space, see tutorial two)
osg::Vec4Array* colors = new osg::Vec4Array;
colors->push_back(osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f) ); //index 0 red
colors->push_back(osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f) ); //index 1 green
colors->push_back(osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f) ); //index 2 blue
colors->push_back(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f) ); //index 3 white
osg::TemplateIndexArray
<unsigned int, osg::Array::UIntArrayType,4,4> *colorIndexArray;
colorIndexArray =
new osg::TemplateIndexArray<unsigned int, osg::Array::UIntArrayType,4,4>;
colorIndexArray->push_back(0); // vertex 0 assigned color array element 0
colorIndexArray->push_back(1); // vertex 1 assigned color array element 1
colorIndexArray->push_back(2); // vertex 2 assigned color array element 2
colorIndexArray->push_back(3); // vertex 3 assigned color array element 3
colorIndexArray->push_back(0); // vertex 4 assigned color array element 0
pyramidGeometry->setColorArray(colors);
pyramidGeometry->setColorIndices(colorIndexArray);
pyramidGeometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
// Since the mapping from vertices to texture coordinates is 1:1,
// we don‘t need to use an index array to map vertices to texture
// coordinates. We can do it directly with the ‘setTexCoordArray‘
// method of the Geometry class.
// This method takes a variable that is an array of two dimensional
// vectors (osg::Vec2). This variable needs to have the same
// number of elements as our Geometry has vertices. Each array element
// defines the texture coordinate for the cooresponding vertex in the
// vertex array.
osg::Vec2Array* texcoords = new osg::Vec2Array(5);
(*texcoords)[0].set(0.00f,0.0f); // tex coord for vertex 0
(*texcoords)[1].set(0.25f,0.0f); // tex coord for vertex 1
(*texcoords)[2].set(0.50f,0.0f); // ""
(*texcoords)[3].set(0.75f,0.0f); // ""
(*texcoords)[4].set(0.50f,1.0f); // ""
pyramidGeometry->setTexCoordArray(0,texcoords);
return pyramidGeode;
}
渲染基本单位的方法是使用StateSet。这节代码演示了怎样从文件中加载纹理,产生此纹理起作用的一个StateSet,并将这个StateSet附加到场景中的一个节点上。前面开始的代码和上一节教程中的一样,初始化一个viewer并建立有一个pyramid的场景。
int main()
{
osgProducer::Viewer viewer;
// Declare a group to act as root node of a scene:
osg::Group* root = new osg::Group();
osg::Geode* pyramidGeode = createPyramid();
root->addChild(pyramidGeode);
现在,准备加纹理。这里我们会声明一个纹理实例并将它的数据不一致性设为‘DYNAMIC‘。(如果不把纹理声明为dynamic,osg的一些优化程序会删除它。)这个texture类包装了OpenGL纹理模式(wrap,filter,等等)和一个osg::Image。下面的代码说明了如何从文件里读取osg::Image实例并把这个图像和纹理关联起来。
osg::Texture2D* KLN89FaceTexture = new osg::Texture2D;
// protect from being optimized away as static state:
KLN89FaceTexture->setDataVariance(osg::Object::DYNAMIC);
// load an image by reading a file:
osg::Image* klnFace = osgDB::readImageFile("KLN89FaceB.tga");
if (!klnFace)
{
std::cout << " couldn‘t find texture, quiting." << std::endl;
return -1;
}
// Assign the texture to the image we read from file:
KLN89FaceTexture->setImage(klnFace);
纹理可以和渲染StateSet关联起来。下一步就产生一个StateSet,关联并启动我们的纹理,并将这个StateSet附加到我们的geometry上。
// Create a new StateSet with default settings:
osg::StateSet* stateOne = new osg::StateSet();
// Assign texture unit 0 of our new StateSet to the texture
// we just created and enable the texture.
stateOne->setTextureAttributeAndModes(0,KLN89FaceTexture,
osg::StateAttribute::ON);
// Associate this state set with the Geode that contains
// the pyramid:
pyramidGeode->setStateSet(stateOne);
最后一步是仿真循环:
//The final step is to set up and enter a simulation loop.
viewer.setUpViewer(osgProducer::Viewer::STANDARD_SETTINGS);
viewer.setSceneData( root );
viewer.realize();
while( !viewer.done() )
{
viewer.sync();
viewer.update();
viewer.frame();
}
return 0;
}
用osg::Shape实例构建场景。使用osg::StateSet控制shape的渲染。
Shape类是所有形状类别的基类。Shape既可用于剪裁和碰撞检测也可用于定义程序性地产生几何体的那些基本形状。下面的类继承自Shape类:
TriangleMesh
Sphere
InfinitePlane
HeightField
Cylinder
Cone
CompositeShape
Box
为了使这些形状可以被渲染,我们需要把他们和Drawable类的实例关联起来。ShapeDrawable类提供了这样的功能。这个类继承自Drawable并允许我们把Shape实例附加到可以被渲染的东西上。既然ShapeDrawable类继承自Drawable,ShapDrawable实例就可以被加到Geode类实例上。下面的步骤演示了将一个单位立方体加到空场景中时是如何做到这些的。
// Declare a group to act as root node of a scene:
osg::Group* root = new osg::Group();
// Declare a box class (derived from shape class) instance
// This constructor takes an osg::Vec3 to define the center
// and a float to define the height, width and depth.
// (an overloaded constructor allows you to specify unique
// height, width and height values.)
osg::Box* unitCube = new osg::Box( osg::Vec3(0,0,0), 1.0f);
// Declare an instance of the shape drawable class and initialize
// it with the unitCube shape we created above.
// This class is derived from ‘drawable‘ so instances of this
// class can be added to Geode instances.
osg::ShapeDrawable* unitCubeDrawable = new osg::ShapeDrawable(unitCube);
// Declare a instance of the geode class:
osg::Geode* basicShapesGeode = new osg::Geode();
// Add the unit cube drawable to the geode:
basicShapesGeode->addDrawable(unitCubeDrawable);
// Add the goede to the scene:
root->addChild(basicShapesGeode);
产生一个球体和上面的代码基本相似。没有太多的注释的代码看起来是这个样子:
// Create a sphere centered at the origin, unit radius:
osg::Sphere* unitSphere = new osg::Sphere( osg::Vec3(0,0,0), 1.0);
osg::ShapeDrawable* unitSphereDrawable=new osg::ShapeDrawable(unitSphere);
现在,我们可以使用transform节点将这个球体加到场景中,以便让它离开原点。unitSphereDrawable不能直接添加到场景中(因为它不是继承自node类),所以我们需要一个新的geode以便添加它。
osg::PositionAttitudeTransform* sphereXForm =
new osg::PositionAttitudeTransform();
sphereXForm->setPosition(osg::Vec3(2.5,0,0));
osg::Geode* unitSphereGeode = new osg::Geode();
root->addChild(sphereXForm);
sphereXForm->addChild(unitSphereGeode);
unitSphereGeode->addDrawable(unitSphereDrawable);
前面的教程讲解了如何生成纹理,将其指定为从文件加载的图像,生成一个带纹理的StateSet。下面的代码建立了两个状态集合——一个是BLEND纹理模式,另一个是DECAL纹理模式。BLEND模式:
// Declare a state set for ‘BLEND‘ texture mode
osg::StateSet* blendStateSet = new osg::StateSet();
// Declare a TexEnv instance, set the mode to ‘BLEND‘
osg::TexEnv* blendTexEnv = new osg::TexEnv;
blendTexEnv->setMode(osg::TexEnv::BLEND);
// Turn the attribute of texture 0 - the texture we loaded above - ‘ON‘
blendStateSet->setTextureAttributeAndModes(0,KLN89FaceTexture,
osg::StateAttribute::ON);
// Set the texture texture environment for texture 0 to the
// texture envirnoment we declared above:
blendStateSet->setTextureAttribute(0,blendTexEnv);
重复这些步骤,产生DECAL纹理模式的状态集合。
osg::StateSet* decalStateSet = new osg::StateSet();
osg::TexEnv* decalTexEnv = new osg::TexEnv();
decalTexEnv->setMode(osg::TexEnv::DECAL);
decalStateSet->setTextureAttributeAndModes(0,KLN89FaceTexture,
osg::StateAttribute::ON);
decalStateSet->setTextureAttribute(0,decalTexEnv);
产生了状态集合后我们就可以把它们应用在场景中的节点上。在scene graph的绘制遍历(root->leaf)中状态是积累的。除非这个节点有一个它自己的状态,否则它会继承其父节点的状态。(如果一个节点有一个以上的父节点,它会使用一个以上的状态渲染。)
root->setStateSet(blendStateSet);
unitSphereGeode->setStateSet(decalStateSet);
最后一步是进入仿真循环。
viewer.setUpViewer(osgProducer::Viewer::STANDARD_SETTINGS);
viewer.setSceneData( root );
viewer.realize();
while( !viewer.done() )
{
viewer.sync();
viewer.update();
viewer.frame();
}
return 0;
场景图管理器遍历scene graph,决定哪些几何体需要送到图形管道渲染。在遍历的过程中,场景图管理器也搜集几何体如何被渲染的信息。这些信息存在osg::StateSet实例中。StateSet包含OpenGL的属性/数值对列表。这些StateSet可以和scenegraph中的节点关联起来。在预渲染的遍历中,StateSet从根节点到叶子节点是积累的。一个节点的没有变化的状态属性也是简单的遗传自父节点。
几个附加的特性允许更多的控制和弹性。一个状态的属性值可以被设为OVERRIDE。这意味着这个节点的所有孩子节点——不管其属性值是什么——会遗传其父节点的属性值。OVERRIDE意味着可以被覆盖。如果一个孩子节点把那个属性设为PROTECTED,那么它就可以设置自己的属性值,而不用理会父节点的设置。
下面的场景示范了state如何影响scene graph。根节点有一个BLEND模式纹理的基本状态。这个基本状态会被所有子节点继承,除非这个状态的参数改变。根节点的右子树就是这样的。右孩子没有被指定任何状态,所以它使用和根节点一样的状态来渲染。对于节点6,纹理的混合模式没有改变但是使用了一个新纹理。
根节点的左子树的纹理模式被设为DECAL。其他的参数是一样的,所以它们从根节点继承。在节点3中,FOG被打开了并设置为OVERRIDE。这个节点——节点3——的左孩子将FOG设为了OFF。既然它没有设置PROTECTED并且它父节点设置了OVERRIDE,所以就像父节点一样FOG仍然是ON。右孩子4设置FOG属性值为PROTECTED,因此可以覆盖其父节点的设置。
a.jpg (86.96 KB)
2007-11-23 01:05 PM
下面是操作状态配置并用节点将这些状态关联起来的代码。
// Set an osg::TexEnv instance‘s mode to BLEND,
// make this TexEnv current for texture unit 0 and assign
// a valid texture to texture unit 0
blendTexEnv->setMode(osg::TexEnv::BLEND);
stateRootBlend->setTextureAttribute(0,blendTexEnv,osg::StateAttribute::ON);
stateRootBlend->setTextureAttributeAndModes(0,ocotilloTexture,
osg::StateAttribute::ON);
// For state five, change the texture associated with texture unit 0
//all other attributes will remain unchanged as inherited from above.
// (texture mode will still be BLEND)
stateFiveDustTexture->setTextureAttributeAndModes(0,dustTexture,
osg::StateAttribute::ON);
// Set the mode of an osg::TexEnv instance to DECAL
//Use this mode for stateOneDecal.
decalTexEnv->setMode(osg::TexEnv::DECAL);
stateOneDecal->setTextureAttribute(0,decalTexEnv,osg::StateAttribute::ON);
// For stateTwo, turn FOG OFF and set to OVERRIDE.
//Descendants in this sub-tree will not be able to change FOG unless
//they set the FOG attribute value to PROTECTED
stateTwoFogON_OVRD->setAttribute(fog, osg::StateAttribute::ON);
stateTwoFogON_OVRD->setMode(GL_FOG,
osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
// For stateThree, try to turn FOG OFF.
Since the attribute is not PROTECTED, and
// the parents set this attribute value to OVERRIDE, the parent‘s value will be used.
// (i.e. FOG will remain ON.)
stateThreeFogOFF->setMode(GL_FOG, osg::StateAttribute::OFF);
// For stateFour, set the mode to PROTECTED, thus overriding the parent setting
stateFourFogOFF_PROT->setMode(GL_FOG,
osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED);
// apply the StateSets above to appropriates nodes in the scene graph.
root->setStateSet(stateRootBlend);
mtOne->setStateSet(stateOneDecal);
mtTwo->setStateSet(stateTwoFogON_OVRD);
mtThree->setStateSet(stateThreeFogOFF);
mtSix->setStateSet(stateFiveDustTexture);
mtFour->setStateSet(stateFourFogOFF_PROT);
加载几何模型并加入到场景中,调整其中一个模型在场景中的位置并通过安装仿真循环观察场景。
如果你下载了当前版本的Open Scene Graph,那么你就可以将在有相应插件的任何文件格式。包括以下的几何文件格式:3dc,3ds,flt,geo,iv,ive,lwo,md2,obj,osg和以下这些图像文件格式:bmp,gif,jpeg,rgb,tga,tif。
Open Scene Graph安装包里包含了很多open scene graph格式(.osg)的几何文件。我们会加载其中一个,还有一个MPI Open Flight(.flt)文件。为了便于找到模型,建立一个models文件夹,并用OSG_DATA_PATH系统变量指向它。(通常为C:\Projects\OpenSceneGraph\OpenSceneGraph-Data\)。解压此文件到那个文件夹下。
几何模型使用scene graph的节点表示。因此,为了加载并操作一个几何模型文件,我们需要声明一个句柄(或指针)指向osg::Node类型实例。(在一些要求的#include后)。
#include <osg/Node>
#include <osgDB/ReadFile>
...
osg::Node* cessnaNode = NULL;
osg::Node* tankNode = NULL;
...
cessnaNode = osgDB::readNodeFile("cessna.osg");
tankNode = osgDB::readNodeFile("Models/T72-tank/t72-tank_des.flt");
这就是加载数据库需要做的事。下一步我们把它作为scene graph的一部分加入。将模型加载到transform节点的子节点上,这样我们就可以重新定位它了。
// Declare a node which will serve as the root node
// for the scene graph. Since we will be adding nodes
// as ‘children‘ of this node we need to make it a ‘group‘
// instance.
// The ‘node‘ class represents the most generic version of nodes.
// This includes nodes that do not have children (leaf nodes.)
// The ‘group‘ class is a specialized version of the node class.
// It adds functions associated with adding and manipulating
// children.
osg::Group* root = new osg::Group();
root->addChild(cessnaNode);
// Declare transform, initialize with defaults.
osg::PositionAttitudeTransform* tankXform =
new osg::PositionAttitudeTransform();
// Use the ‘addChild‘ method of the osg::Group class to
// add the transform as a child of the root node and the
// tank node as a child of the transform.
root->addChild(tankXform);
tankXform->addChild(tankNode);
// Declare and initialize a Vec3 instance to change the
// position of the tank model in the scene
osg::Vec3 tankPosit(5,0,0);
tankXform->setPosition( tankPosit );
现在,我们的scene graph由一个根节点和两个子节点组成。一个是cessna的几何模型,另一个是一个右子树,由一个仅有一个tank的几何模型的transform节点组成。为了观察场景,需要建立一个viewer和一个仿真循环。就像这样做的:
#include <osgProducer/Viewer>
// Declare a ‘viewer‘
osgProducer::Viewer viewer;
// For now, we can initialize with ‘standard settings‘
// Standard settings include a standard keyboard mouse
// interface as well as default drive, fly and trackball
// motion models for updating the scene.
viewer.setUpViewer(osgProducer::Viewer::STANDARD_SETTINGS);
// Next we will need to assign the scene graph we created
// above to this viewer:
viewer.setSceneData( root );
// create the windows and start the required threads.
viewer.realize();
// Enter the simulation loop. viewer.done() returns false
// until the user presses the ‘esc‘ key.
// (This can be changed by adding your own keyboard/mouse
// event handler or by changing the settings of the default
// keyboard/mouse event handler)
while( !viewer.done() )
{
// wait for all cull and draw threads to complete.
viewer.sync();
// Initiate scene graph traversal to update nodes.
// Animation nodes will require update. Additionally,
// any node for which an ‘update‘ callback has been
// set up will also be updated. More information on
// settting up callbacks to follow.
viewer.update();
// initiate the cull and draw traversals of the scene.
viewer.frame();
}
你应当能编译并执行上面的代码(保证调用的顺序是正确的,已经添加了main等等)。执行代码的时候,按h键会弹出一个菜单选项(似乎没有这个功能——译者)。按‘esc’退出。
6.osg Text、HUD、RenderBins
6.1本章目标
添加文本到场景中——包括HUD风格的文本和作为场景一部分的文本。
本类继承自Drawable类。也就是说文本实例可以加到Geode类实例上并且可以和其它几何体一样被渲染。与文本类相关的方法的全部列在*这里*。‘osgExample Text’工程示范了许多方法。这个教程提供了文本类的几个有限的函数。绘制一个HUD牵扯到下面两个概念:
1、生成一个子树,它的根节点有合适的投影及观察矩阵...
2、将HUD子树中的几何体指定到合适的RenderBin上,这样HUD几何体就会在场景的其他部分之后按正确地状态设置绘制。
渲染HUD的子树涉及到一个投影矩阵和一个观察矩阵。对于投影矩阵,我们会使用相当于屏幕维数水平和垂直扩展的正投影。根据这种模式,坐标相当于象素坐标。为了简单起见,观察矩阵使用单位矩阵。
为了渲染HUD,我们把它里面的几何体附加到一个指定的RenderBin上。RenderBin允许用户在几何体绘制过程中指定顺序。这对于HUD几何体需要最后绘制来说很有用。
首先,声明我们需要的变量-osg::Text和osg::Projection。
osg::Group* root = NULL;
osg::Node* tankNode = NULL;
osg::Node* terrainNode = NULL;
osg::PositionAttitudeTransform* tankXform;
osgProducer::Viewer viewer;
// A geometry node for our HUD:
osg::Geode* HUDGeode = new osg::Geode();
// Text instance that wil show up in the HUD:
osgText::Text* textOne = new osgText::Text();
// Text instance for a label that will follow the tank:
osgText::Text* tankLabel = new osgText::Text();
// Projection node for defining view frustrum for HUD:
osg::Projection* HUDProjectionMatrix = new osg::Projection;
从文件里加载模型,和前面的教程一样建立scene graph(这里没什么新东东)。
// Initialize root of scene:
root = new osg::Group();
osgDB::FilePathList pathList = osgDB::getDataFilePathList();
pathList.push_back
("C:\\Projects\\OpenSceneGraph\\OpenSceneGraph-Data\\NPSData\\Models\\T72-Tank\\");
pathList.push_back
("C:\\Projects\\OpenSceneGraph\\OpenSceneGraph-Data\\NPSData\\Models\\JoeDirt\\");
pathList.push_back
("C:\\Projects\\OpenSceneGraph\\OpenSceneGraph-Data\\NPSData\\Textures\\");
osgDB::setDataFilePathList(pathList);
// Load models from files and assign to nodes:
tankNode = osgDB::readNodeFile("t72-tank_des.flt");
terrainNode = osgDB::readNodeFile("JoeDirt.flt");
// Initialize transform to be used for positioning the tank
tankXform = new osg::PositionAttitudeTransform());
tankXform->setPosition( osg::Vec3d(5,5,8) );
// Build the scene - add the terrain node directly to the root,
// connect the tank node to the root via the transform node:
root->addChild(terrainNode);
root->addChild(tankXform);
tankXform->addChild(tankNode);
下一步,建立场景来显示HUD组件。添加一个子树,它的根节点有一个投影和观察矩阵。
// Initialize the projection matrix for viewing everything we
// will add as descendants of this node. Use screen coordinates
// to define the horizontal and vertical extent of the projection
// matrix. Positions described under this node will equate to
// pixel coordinates.
HUDProjectionMatrix->setMatrix(osg::Matrix::ortho2D(0,1024,0,768));
// For the HUD model view matrix use an identity matrix:
osg::MatrixTransform* HUDModelViewMatrix = new osg::MatrixTransform;
HUDModelViewMatrix->setMatrix(osg::Matrix::identity());
// Make sure the model view matrix is not affected by any transforms
// above it in the scene graph:
HUDModelViewMatrix->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
// Add the HUD projection matrix as a child of the root node
// and the HUD model view matrix as a child of the projection matrix
// Anything under this node will be viewed using this projection matrix
// and positioned with this model view matrix.
root->addChild(HUDProjectionMatrix);
HUDProjectionMatrix->addChild(HUDModelViewMatrix);
现在建立几何体。我们根据屏幕坐标建立一个四边形,并设置颜色和纹理坐标。
// Add the Geometry node to contain HUD geometry as a child of the
// HUD model view matrix.
HUDModelViewMatrix->addChild( HUDGeode );
// Set up geometry for the HUD and add it to the HUD
osg::Geometry* HUDBackgroundGeometry = new osg::Geometry();
osg::Vec3Array* HUDBackgroundVertices = new osg::Vec3Array;
HUDBackgroundVertices->push_back( osg::Vec3( 0,0,-1) );
HUDBackgroundVertices->push_back( osg::Vec3(1024,0,-1) );
HUDBackgroundVertices->push_back( osg::Vec3(1024,200,-1) );
HUDBackgroundVertices->push_back( osg::Vec3(0,200,-1) );
osg::DrawElementsUInt* HUDBackgroundIndices =
new osg::DrawElementsUInt(osg::PrimitiveSet::POLYGON, 0);
HUDBackgroundIndices->push_back(0);
HUDBackgroundIndices->push_back(1);
HUDBackgroundIndices->push_back(2);
HUDBackgroundIndices->push_back(3);
osg::Vec4Array* HUDcolors = new osg::Vec4Array;
HUDcolors->push_back(osg::Vec4(0.8f,0.8f,0.8f,0.8f));
osg::Vec2Array* texcoords = new osg::Vec2Array(4);
(*texcoords)[0].set(0.0f,0.0f);
(*texcoords)[1].set(1.0f,0.0f);
(*texcoords)[2].set(1.0f,1.0f);
(*texcoords)[3].set(0.0f,1.0f);
HUDBackgroundGeometry->setTexCoordArray(0,texcoords);
osg::Texture2D* HUDTexture = new osg::Texture2D;
HUDTexture->setDataVariance(osg::Object::DYNAMIC);
osg::Image* hudImage;
hudImage = osgDB::readImageFile("HUDBack2.tga");
HUDTexture->setImage(hudImage);
osg::Vec3Array* HUDnormals = new osg::Vec3Array;
HUDnormals->push_back(osg::Vec3(0.0f,0.0f,1.0f));
HUDBackgroundGeometry->setNormalArray(HUDnormals);
HUDBackgroundGeometry->setNormalBinding(osg::Geometry::BIND_OVERALL);
HUDBackgroundGeometry->addPrimitiveSet(HUDBackgroundIndices);
HUDBackgroundGeometry->setVertexArray(HUDBackgroundVertices);
HUDBackgroundGeometry->setColorArray(HUDcolors);
HUDBackgroundGeometry->setColorBinding(osg::Geometry::BIND_OVERALL);
HUDGeode->addDrawable(HUDBackgroundGeometry);
为了正确的渲染HUD,我们建立带有深度检测和透明度混合的osg::stateSet。我们也要保证HUD几何体最后绘制。几何体在裁剪遍历时通过指定一个已编号的渲染箱可以控制渲染顺序。最后一行演示了这些:
// Create and set up a state set using the texture from above:
osg::StateSet* HUDStateSet = new osg::StateSet();
HUDGeode->setStateSet(HUDStateSet);
HUDStateSet->
setTextureAttributeAndModes(0,HUDTexture,osg::StateAttribute::ON);
// For this state set, turn blending on (so alpha texture looks right)
HUDStateSet->setMode(GL_BLEND,osg::StateAttribute::ON);
// Disable depth testing so geometry is draw regardless of depth values
// of geometry already draw.
HUDStateSet->setMode(GL_DEPTH_TEST,osg::StateAttribute::OFF);
HUDStateSet->setRenderingHint( osg::StateSet::TRANSPARENT_BIN );
// Need to make sure this geometry is draw last. RenderBins are handled
// in numerical order so set bin number to 11
HUDStateSet->setRenderBinDetails( 11, "RenderBin");
最后,使用文本时,由于osg::Text是继承自osg::Drawable的,osg::Text实例可以作为孩子加到osg::Geode类实例上。
// Add the text (Text class is derived from drawable) to the geode:
HUDGeode->addDrawable( textOne );
// Set up the parameters for the text we‘ll add to the HUD:
textOne->setCharacterSize(25);
textOne->setFont("C:/WINDOWS/Fonts/impact.ttf");
textOne->setText("Not so good");
textOne->setAxisAlignment(osgText::Text::SCREEN);
textOne->setPosition( osg::Vec3(360,165,-1.5) );
textOne->setColor( osg::Vec4(199, 77, 15, 1) );
// Declare a geode to contain the tank‘s text label:
osg::Geode* tankLabelGeode = new osg::Geode();
// Add the tank label to the scene:
tankLabelGeode->addDrawable(tankLabel);
tankXform->addChild(tankLabelGeode);
// Set up the parameters for the text label for the tank
// align text with tank‘s SCREEN.
// (for Onder: use XZ_PLANE to align text with tank‘s XZ plane.)
tankLabel->setCharacterSize(5);
tankLabel->setFont("/fonts/arial.ttf");
tankLabel->setText("Tank #1");
tankLabel->setAxisAlignment(osgText::Text::XZ_PLANE);
// Set the text to render with alignment anchor and bounding box around it:
tankLabel->setDrawMode(osgText::Text::TEXT |
osgText::Text::ALIGNMENT |
osgText::Text::BOUNDINGBOX);
tankLabel->setAlignment(osgText::Text::CENTER_TOP);
tankLabel->setPosition( osg::Vec3(0,0,8) );
tankLabel->setColor( osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f) );
最后,建立viewer并进入仿真循环:
viewer.setUpViewer(osgProducer::Viewer::STANDARD_SETTINGS);
viewer.setSceneData( root );
viewer.realize();
while( !viewer.done() )
{
viewer.sync();
viewer.update();
viewer.frame();
}
7.搜索并控制开关和DOF(自由度)节点
7.1搜索场景图形中的一个有名节点
模型文件可能包含了各种不同的节点类型,用户通过对这些节点的使用来更新和表达模型的各个部分。使用osgSim::MultiSwitch多重节点可以在多个模型渲染状态间进行选择。例如,对坦克模型使用多重节点,用户即可自行选择与完整的或者损坏的坦克相关联的几何体以及渲染状态。模型中还可以包含DOF节点,以便清晰表达坦克的某个部分。例如炮塔节点可以旋转,机枪节点可以升高。炮塔旋转时,炮塔体(包括机枪)的航向角(heading)与坦克的航向角相关联,而机枪抬升时,机枪的俯仰角(pitch)与炮塔的俯仰角相关联。
对这些节点进行更新时,我们需要一个指向节点的指针。而我们首先要获取节点的名字,才能得到该节点的指针。而获取节点的名称,主要有这样一些方法:咨询建模人员;使用其它文件浏览器(对于.flt文件,可以使用Creator或者Vega)浏览模型;或者使用OpenSceneGraph。用户可以根据自己的需要自由运用OSG的功能。例如在场景图形中载入flt文件,并且在仿真过程中将整个场景保存成.osg文件。osg文件使用ASCII格式保存,因此用户可以使用各种文本处理软件(写字板,记事本)对其进行编辑。在坦克模型文件中,你可以发现一个名为“sw1”的开关节点,它有两个子节点“good”和“bad”,分别指向坦克未损坏和损坏的状态。坦克模型的.osg文件可以从这里下载:
http://www.nps.navy.mil/cs/sullivan/osgTutorials/Download/T72Tank.osg
现在我们已经获得了需要控制的开关节点的名称(sw1),亦可获取其指针对象。获取节点指针的方法有两种:一是编写代码遍历整个场景图形;二是使用后面将会介绍的访问器(visitor)。在以前的教程中,我们已经知道如何加载flight文件,将其添加到场景并进入仿真循环的方法。
#include <osg/PositionAttitudeTransform>
#include <osg/Group>
#include <osg/Node>
#include <osgDB/ReadFile>
#include <osgViewer/Viewer>
#include <osgGA/TrackballManipulator>
int main()
{
osg::Node* tankNode = NULL;
osg::Group* root = NULL;
osgViewer::Viewer viewer;
osg::Vec3 tankPosit;
osg::PositionAttitudeTransform* tankXform;
tankNode = osgDB::readNodeFile("../NPS_Data/Models/t72-tank/t72-tank_des.flt");
root = new osg::Group();
tankXform = new osg::PositionAttitudeTransform();
root->addChild(tankXform);
tankXform->addChild(tankNode);
tankPosit.set(5,0,0);
tankXform->setPosition( tankPosit );
viewer.setCameraManipulator(new osgGA::TrackballManipulator());
viewer.setSceneData( root );
viewer.realize();
while( !viewer.done() )
{
viewer.frame();
}
}
现在我们需要修改上述代码,以添加查找节点的函数。下面的递归函数有两个参数值:用于搜索的字符串,以及用于指定搜索开始位置的节点。函数的返回值是指定节点子树中,第一个与输入字符串名称相符的节点实例。如果没有找到这样的节点,函数将返回NULL。特别要注意的是,使用访问器将提供更为灵活的节点访问方式。而下面的代码只用于展示如何手动编写场景图形的遍历代码。
osg::Node* findNamedNode(const std::string& searchName,
osg::Node* currNode)
{
osg::Group* currGroup;
osg::Node* foundNode;
// 检查输入的节点是否是合法的,
// 如果输入节点为NULL,则直接返回NULL。
if ( !currNode)
{ return NULL; }
// 如果输入节点合法,那么先检查该节点是否就是我们想要的结果。
// 如果确为所求,那么直接返回输入节点。
if (currNode->getName() == searchName)
{ return currNode; }
// 如果输入节点并非所求,那么检查它的子节点(不包括叶节点)情况。
// 如果子节点存在,则使用递归调用来检查每个子节点。
// 如果某一次递归的返回值非空,说明已经找到所求的节点,返回其指针。
// 如果所有的节点都已经遍历过,那么说明不存在所求节点,返回NULL。
currGroup = currNode->asGroup();
if ( currGroup )
{
for (unsigned int i = 0 ; i < currGroup->getNumChildren(); i ++)
{
foundNode = findNamedNode(searchName, currGroup->getChild(i));
if (foundNode)
return foundNode; // 找到所求节点。
}
return NULL; // 遍历结束,不存在所求节点。
}
else
{
return NULL; // 该节点不是组节点,返回NULL。
}
}
现在我们可以在代码中添加这个函数,用于查找场景中指定名称的节点并获取其指针。注意这是一种深度优先的算法,它返回第一个符合的节点指针。
我们将在设置场景之后,进入仿真循环之前调用该函数。函数返回的开关节点指针可以用于更新开关的状态。下面的代码用于模型载入后,执行查找节点的工作。
osg::Switch* tankStateSwitch = NULL;
osg::Node* foundNode = NULL;
foundNode = findNamedNode("sw1",root);
tankStateSwitch = (osg::Switch*) foundNode;
if ( !tankStateSwitch)
{
std::cout << "tank state switch node not found, quitting." << std::endl;
return -1;
}
7.2按照“访问器”模式搜索有名节点(Finding a named node using the Visitor pattern)
“访问器”的设计允许用户将某个特定节点的指定函数,应用到当前场景遍历的所有此类节点中。遍历的类型包括NODE_VISITOR,UPDATE_VISITOR,COLLECT_OCCLUDER_VISITOR和CULL_VISITOR。由于我们还没有讨论场景更新(updating),封闭节点(occluder node)和拣选(culling)的有关内容,因此这里首先介绍NODE_VISITOR(节点访问器)遍历类型。“访问器”同样允许用户指定遍历的模式,可选项包括TRAVERSE_NONE,TRAVERSE_PARENTS,TRAVERSE_ALL_CHILDREN和TRAVERSE_ACTIVE_CHILDREN。这里我们将选择TRAVERSE_ALL_CHILDREN(遍历所有子节点)的模式。
然后,我们需要定义应用到每个节点的函数。这里我们将会针对用户自定义的节点名称进行字符串比较。如果某个节点的名称与指定字符串相符,该节点将被添加到一个节点列表中。遍历过程结束后,列表中将包含所有符合指定的搜索字符串的节点。
为了能够充分利用“访问器”,我们可以从基类osg::NodeVisitor派生一个特定的节点访问器(命名为findNodeVisitor)。这个类需要两个新的数据成员:一个std::string变量,用于和我们搜索的有名节点进行字符串比较;以及一个节点列表变量(std::vector<osg::Node*>),用于保存符合搜索字符串的所有节点。为了实现上述的操作,我们需要重载“apply”方法。基类的“apply”方法已经针对所有类型的节点(所有派生自osg::Node的节点)作了定义。用户可以重载apply方法来操作特定类型的节点。如果我们希望针对所有的节点进行同样的操作,那么可以重载针对osg::Node类型的apply方法。findNodeVisitor的头文件内容在下表中列出,相关的源代码可以在这里下载:
http://www.nps.navy.mil/cs/sullivan/osgTutorials/Download/findNodeVisitor.zip
#include <osg/NodeVisitor>
#include <osg/Node>
#include <osgSim/DOFTransform>
#include <iostream>
#include <vector>
class findNodeVisitor : public osg::NodeVisitor {
public:
findNodeVisitor();
findNodeVisitor(const std::string &searchName) ;
virtual void apply(osg::Node &searchNode);
virtual void apply(osg::Transform &searchNode);
void setNameToFind(const std::string &searchName);
osg::Node* getFirst();
typedef std::vector<osg::Node*> nodeListType;
nodeListType& getNodeList() { return foundNodeList; }
private:
std::string searchForName;
nodeListType foundNodeList;
};
现在,我们创建的类可以做到:启动一次节点访问遍历,访问指定场景子树的每个子节点,将节点的名称与用户指定的字符串作比较,并建立一个列表用于保存名字与搜索字符串相同的节点。那么如何启动这个过程呢?我们可以使用osg::Node的“accept”方法来实现节点访问器的启动。选择某个执行accept方法的节点,我们就可以控制遍历开始的位置。(遍历的方向是通过选择遍历模式来决定的,而节点类型的区分则是通过重载相应的apply方法来实现)“accpet”方法将响应某一类的遍历请求,并执行用户指定节点的所有子类节点的apply方法。在这里我们将重载一般节点的apply方法,并选择TRAVERSE_ALL_CHILDREN的遍历模式,因此,触发accept方法的场景子树中所有的节点,均会执行这一apply方法。
在这个例子中,我们将读入三种不同状态的坦克。第一个模型没有任何变化,第二个模型将使用多重开关(multSwitch)来关联损坏状态,而第三个模型中,坦克的炮塔将旋转不同的角度,同时枪管也会升高。
下面的代码实现了从文件中读入三个坦克模型并将其添加到场景的过程。其中两个坦克将作为变换节点(PositionAttitudeTransform)的子节点载入,以便将其位置设置到坐标原点之外。
// 定义场景树的根节点,以及三个独立的坦克模型节点
osg::Group* root = new osg::Group();
osg::Group* tankOneGroup = NULL;
osg::Group* tankTwoGroup = NULL;
osg::Group* tankThreeGroup = NULL;
// 从文件中读入坦克模型
tankOneGroup = dynamic_cast<osg::Group*>
(osgDB::readNodeFile("t72-tank/t72-tank_des.flt"));
tankTwoGroup = dynamic_cast<osg::Group*>
(osgDB::readNodeFile("t72-tank/t72-tank_des.flt"));
tankThreeGroup = dynamic_cast<osg::Group*>
(osgDB::readNodeFile("t72-tank/t72-tank_des.flt"));
// 将第一个坦克作为根节点的子节点载入
root->addChild(tankOneGroup);
// 为第二个坦克定义一个位置变换
osg::PositionAttitudeTransform* tankTwoPAT =
new osg::PositionAttitudeTransform();
// 将第二个坦克向右移动5个单位,向前移动5个单位
tankTwoPAT->setPosition( osg::Vec3(5,5,0) );
// 将第二个坦克作为变换节点的子节点载入场景
root->addChild(tankTwoPAT);
tankTwoPAT->addChild(tankTwoGroup);
// 为第三个坦克定义一个位置变换
osg::PositionAttitudeTransform* tankThreePAT =
new osg::PositionAttitudeTransform();
// 将第二个坦克向右移动10个单位
tankThreePAT->setPosition( osg::Vec3(10,0,0) );
// 将坦克模型向左旋转22.5度(为此,炮塔的旋转应当与坦克的头部关联)
tankThreePAT->setAttitude( osg::Quat(3.14159/8.0, osg::Vec3(0,0,1) ));
// 将第三个坦克作为变换节点的子节点载入场景
root->addChild(tankThreePAT);
tankThreePAT->addChild(tankThreeGroup);
我们准备将第二个模型设置为损坏的状态,因此我们使用findNodeVisitor类获取控制状态的多重开关(multiSwitch)的句柄。这个节点访问器需要从包含了第二个坦克的组节点开始执行。下面的代码演示了声明和初始化一个findNodeVisitor实例并执行场景遍历的方法。遍历完成之后,我们即可得到节点列表中符合搜索字符串的第一个节点的句柄。这也就是我们准备使用multiSwitch来进行控制的节点句柄。
// 声明一个findNodeVisitor类的实例,设置搜索字符串为“sw1”
findNodeVisitor findNode("sw1");
// 开始执行访问器实例的遍历过程,起点是tankTwoGroup,搜索它所有的子节点
// 并创建一个列表,用于保存所有符合搜索条件的节点
tankTwoGroup->accept(findNode);
// 声明一个开关类型,并将其关联给搜索结果列表中的第一个节点。
osgSim::MultiSwitch* tankSwitch = NULL;
tankSwitch = dynamic_cast <osgSim::MultiSwitch*> (findNode.getFirst());
更新开关节点
当我们获取了一个合法的开关节点句柄后,下一步就是从一个模型状态变换到另一个状态。我们可以使用setSingleChildOn方法来实现这个操作。setSingleChildOn()方法包括两个参数:第一个无符号整型量相当于多重开关组(switchSet)的索引号;第二个无符号整型量相当于开关的位置。在这个例子中,我们只有一个多重开关,其值可以设置为未损坏状态或者损坏状态,如下所示:
// 首先确认节点是否合法,然后设置其中的第一个(也是唯一的)多重开关
if (tankSwitch)
{
//tankSwitch->setSingleChildOn(0,false); // 未损坏的模型
tankSwitch->setSingleChildOn(0,true); // 损坏的模型
}
更新DOF节点
坦克模型还包括了两个DOF(
以上是关于OSG开源教程(转)的主要内容,如果未能解决你的问题,请参考以下文章