在osg的可绘制场景对象里,使用最广泛的是称为几何体的对象,也就是osg::Geometry,本文主要介绍如何使用osg提倡的方式来遍历和更新这一几何对象。
1. osg中的遍历方式
osg中广泛使用 访问者模式(Visitor Pattern),大量使用这种模式的优点在于:场景的节点类型相对稳定,但是有大量针对场景中节点的操作,使用这种设计模式在扩展针对场景操作时,减少了对场景结构的影响。
从最原始的数组开始,osg就使用了这种模式,可以参考osg::Array类的实现:osg::Array是所有数组类的基类(数组类提供了用来保存几何体顶点位置、法线、颜色、纹理坐标等信息的数组),它提供了下面的访问器接口:
inline virtual void accept(ArrayVisitor& av);
inline virtual void accept(ConstArrayVisitor& av) const;
inline virtual void accept(unsigned int index,ValueVisitor& vv);
inline virtual void accept(unsigned int index,ConstValueVisitor& vv) const;
在访问者模式中,包括访问者和被访问者。
-
被访问者具有以下的特点:
- 这些被访问的类都是osg::Array的子类,并且这些继承自osg::Array的子类结构相对的稳定。【三维场景中的几何体对象满足这一特点:几何体中包含的数据就那几种:顶点位置(一般就是一个osg::Vec3d的数组)顶点法线(osg::Vec3d),顶点颜色(osg::Vec4d)等】
- 被访问者的类都实现了accept方法,参考包含访问者(一般是反问者对象的基类引用),在函数实现中调用访问者对象的操作方法(apply)
-
访问者具有以下的特点:
- 访问者实现在被访问者上的操作(实现了针对所有被访问者的apply方法)
- 一般来说osg提供的访问者都是基类,需要我们去继承来做想做的事情,也不一定是实现所有被访问者类型的apply操作(实现部分感兴趣的被访问者的apply方法)
上文中提到的4个访问器便是4个访问者的基类,包括:
1. ArrayVisitor:用来访问整个数组(可以修改数组内容)
2. ConstArrayVisitor:用来访问整个数组(无法修改数组内容)
3. ValueVisitor:用来访问数组中的某个索引的对象(可以修改)
4. ConstValueVisitor:用来访问数组中的某个索引的对象(无法修改)
针对这个最基本的数组类型,osg也采用了这种模式,初看起来osg仿佛是把简单的问题复杂化了,例如:
假设我们需要将一个保存顶点的数组打印出来,假设这个数组包含3个顶点:
#include <iostream>
#include <osg/Array>
//继承自ArrayVisitor的访问器
class PrintArrayVisitor : public osg::ArrayVisitor
{
public:
PrintArrayVisitor(){}
~PrintArrayVisitor(){}
//知道数组的类型是osg::Vec3dArray,因此只重载实现这个函数
//其他的函数使用osg::ArrayVisitor默认的实现(空函数体)
virtual void apply(osg::Vec3dArray& v) override;
};
void PrintArrayVisitor::apply(osg::Vec3dArray& v)
{
for (osg::Vec3Array::size_type i = 0; i < v.size(); ++i)
{
std::cout << v.at(i).x() << " " << v.at(i).y() << " " << v.at(i).z() << std::endl;
}
}
int main()
{
osg::Vec3dArray *vertexArray = new osg::Vec3dArray;
vertexArray->push_back(osg::Vec3d(1, 2, 3));
vertexArray->push_back(osg::Vec3d(3, 4, 5));
vertexArray->push_back(osg::Vec3d(10, 2, 9));
//直接访问数组中的对象
for (osg::Vec3Array::size_type i = 0; i < vertexArray->size(); ++i)
{
std::cout << vertexArray->at(i).x() << " " << vertexArray->at(i).y() << " " << vertexArray->at(i).z() << std::endl;
}
//使用访问器的方式
PrintArrayVisitor pav;
vertexArray->accept(pav);
}
看起来似乎直接操作数组的方式更加简单和直观一点。应该是osg为了整个遍历的统一性而选择这种处理方式。
2. 几何图元的访问
构成几何体的基本单元是几何图元,在OSG中的几何图元的绘制方式有以下几种:
enum Mode
{
POINTS = GL_POINTS,
LINES = GL_LINES,
LINE_STRIP = GL_LINE_STRIP,
LINE_LOOP = GL_LINE_LOOP,
TRIANGLES = GL_TRIANGLES,
TRIANGLE_STRIP = GL_TRIANGLE_STRIP,
TRIANGLE_FAN = GL_TRIANGLE_FAN,
QUADS = GL_QUADS,
QUAD_STRIP = GL_QUAD_STRIP,
POLYGON = GL_POLYGON,
LINES_ADJACENCY = GL_LINES_ADJACENCY,
LINE_STRIP_ADJACENCY = GL_LINE_STRIP_ADJACENCY,
TRIANGLES_ADJACENCY = GL_TRIANGLES_ADJACENCY,
TRIANGLE_STRIP_ADJACENCY = GL_TRIANGLE_STRIP_ADJACENCY,
PATCHES = GL_PATCHES
};
几何图元的类型告知OpenGL如何处理顶点数组中的数据。在OpenGL中,我们一般使用glDrawArrays、glDrawElements这样的调用来绘制几何图元,OSG将这些函数调用封装在继承自osg::PrimitiveSet的几个子类中,实现和OpenGL类似的操作
在OSG中提供了一个用来访问图元的类:osg::PrimitiveFunctor,它的继承关系如下:
从实现上来说,它也采用了访问者模式,但是实现过程和上文中讨论的访问者模式略有差别。一般来说访问者模式实现中的访问者操作被访问者的函数名称是一样的,根据不同的被访问者参数提供了不同的函数重载的方法,比如上文中说到的数组遍历器ArrayVisitor的实现:
class ArrayVisitor
{
public:
ArrayVisitor() {}
virtual ~ArrayVisitor() {}
virtual void apply(Array&) {}
virtual void apply(ByteArray&) {}
virtual void apply(ShortArray&) {}
virtual void apply(IntArray&) {}
virtual void apply(UByteArray&) {}
virtual void apply(UShortArray&) {}
virtual void apply(UIntArray&) {}
virtual void apply(FloatArray&) {}
virtual void apply(DoubleArray&) {}
virtual void apply(Vec2bArray&) {}
virtual void apply(Vec3bArray&) {}
virtual void apply(Vec4bArray&) {}
virtual void apply(Vec2sArray&) {}
virtual void apply(Vec3sArray&) {}
virtual void apply(Vec4sArray&) {}
virtual void apply(Vec2iArray&) {}
virtual void apply(Vec3iArray&) {}
virtual void apply(Vec4iArray&) {}
virtual void apply(Vec2ubArray&) {}
virtual void apply(Vec3ubArray&) {}
virtual void apply(Vec4ubArray&) {}
virtual void apply(Vec2usArray&) {}
virtual void apply(Vec3usArray&) {}
virtual void apply(Vec4usArray&) {}
virtual void apply(Vec2uiArray&) {}
virtual void apply(Vec3uiArray&) {}
virtual void apply(Vec4uiArray&) {}
virtual void apply(Vec2Array&) {}
virtual void apply(Vec3Array&) {}
virtual void apply(Vec4Array&) {}
virtual void apply(Vec2dArray&) {}
virtual void apply(Vec3dArray&) {}
virtual void apply(Vec4dArray&) {}
virtual void apply(MatrixfArray&) {}
virtual void apply(MatrixdArray&) {}
};
所有的函数名称是一样的,都是apply,更加具体被访问者的不同而重载了不同的函数。
但是在osg::PrimitiveFunctor
却不是这样的,被访问者的类关系如下图:
注意图中红色的标记,osg::PrimitiveFunctor中的针对不同的被访问者提供的函数名并不相同。osg::DrawArrays和osg::DrawElementsUByte中accept函数的实现是直接调用osg::PrimitveFuctor中的drawArrays和drawElemtnsUByte函数的。也就是说不同的被访问者的子类调用了访问者中不同的函数。这种处理方式严格来说应该不能算是访问者模式了。
3. 几何体的访问
在osg中几何图元是被几何体管理的,osg::Geometry提供了管理几何图元osg::PrimitiveSet的数组,用来添加和删除这些图元,这些图元共同构成了几何体对象。在查看几何体对象osg::Geometry类中发现它也提供了一下两个函数:
virtual void accept(PrimitiveFunctor& pf) const;
virtual void accept(PrimitiveIndexFunctor& pf) const;
同时在osg::Geometry的基类函数osg::Drawable中也包含这两个函数。(函数的实现是空的)
这两个函数的实现同样也在osg::Geometry之中,它的实现仍然是调用osg::PrimitiveFunctor中的某些函数来完成的
osg::Geometry的accept函数的实现都是通过调用PrimitiveFunctor的函数来完成的,从这里可以知道osg::PrimitveFunctor组合了osg::PrimtiveSet和osg::Drawable中accept的实现,osg只是将一部分的实现转移到osg::PrimtiveFuctor中来实现,但是这种编码方式由于使用了accept这样的函数命令,一定程度上误导读者,让读者以为采用了访问者模式,事实上只是一种实现的模块化,将一部分对几何体的操作转移到其他类中实现而已。
4. 与Drawable相关的Functor
与Drawable相关的Functor主要有PrimitiveFunctor和PrimitiveIndexFunctor定义的接口和由它们派生的子类:
PrimitiveFunctor及其派生类
PrimitiveIndexFunctor及其派生类
5. 以Geometry为例解析Functor
本文的接下来部分会通过一个简单的示例分析,了解osg这种处理可绘制对象的方式(在Text和ShapeDrawable中也是通过这种方式实现的)
在osg::Drawable中支持使用下面4个Functor来获取Drawable的信息,分别是:
virtual void accept(AttributeFunctor&);
virtual void accept(ConstAttributeFunctor&) const;
virtual void accept(PrimitiveFunctor&) const;
virtual void accept(PrimitiveIndexFunctor&) const;
四个Functor自身提供了接口,我们可以通过重写这些接口来实现自定义的功能,接口分别是:
class AttributeFunctor
{
public:
virtual ~AttributeFunctor() {}
virtual void apply(AttributeType,unsigned int,GLbyte*) {}
virtual void apply(AttributeType,unsigned int,GLshort*) {}
virtual void apply(AttributeType,unsigned int,GLint*) {}
virtual void apply(AttributeType,unsigned int,GLubyte*) {}
virtual void apply(AttributeType,unsigned int,GLushort*) {}
virtual void apply(AttributeType,unsigned int,GLuint*) {}
virtual void apply(AttributeType,unsigned int,float*) {}
virtual void apply(AttributeType,unsigned int,Vec2*) {}
virtual void apply(AttributeType,unsigned int,Vec3*) {}
virtual void apply(AttributeType,unsigned int,Vec4*) {}
virtual void apply(AttributeType,unsigned int,Vec4ub*) {}
virtual void apply(AttributeType,unsigned int,double*) {}
virtual void apply(AttributeType,unsigned int,Vec2d*) {}
virtual void apply(AttributeType,unsigned int,Vec3d*) {}
virtual void apply(AttributeType,unsigned int,Vec4d*) {}
};
class ConstAttributeFunctor
{
public:
virtual ~ConstAttributeFunctor() {}
virtual void apply(AttributeType,unsigned int,const GLbyte*) {}
virtual void apply(AttributeType,unsigned int,const GLshort*) {}
virtual void apply(AttributeType,unsigned int,const GLint*) {}
virtual void apply(AttributeType,unsigned int,const GLubyte*) {}
virtual void apply(AttributeType,unsigned int,const GLushort*) {}
virtual void apply(AttributeType,unsigned int,const GLuint*) {}
virtual void apply(AttributeType,unsigned int,const float*) {}
virtual void apply(AttributeType,unsigned int,const Vec2*) {}
virtual void apply(AttributeType,unsigned int,const Vec3*) {}
virtual void apply(AttributeType,unsigned int,const Vec4*) {}
virtual void apply(AttributeType,unsigned int,const Vec4ub*) {}
virtual void apply(AttributeType,unsigned int,const double*) {}
virtual void apply(AttributeType,unsigned int,const Vec2d*) {}
virtual void apply(AttributeType,unsigned int,const Vec3d*) {}
virtual void apply(AttributeType,unsigned int,const Vec4d*) {}
};
class PrimitiveFunctor
{
public:
virtual ~PrimitiveFunctor() {}
virtual void setVertexArray(unsigned int count, const Vec2* vertices) = 0;
virtual void setVertexArray(unsigned int count, const Vec3* vertices) = 0;
virtual void setVertexArray(unsigned int count, const Vec4* vertices) = 0;
virtual void setVertexArray(unsigned int count, const Vec2d* vertices) = 0;
virtual void setVertexArray(unsigned int count, const Vec3d* vertices) = 0;
virtual void setVertexArray(unsigned int count, const Vec4d* vertices) = 0;
virtual void drawArrays(GLenum mode, GLint first, GLsizei count) = 0;
virtual void drawElements(GLenum mode, GLsizei count, const GLubyte* indices) = 0;
virtual void drawElements(GLenum mode, GLsizei count, const GLushort* indices) = 0;
virtual void drawElements(GLenum mode, GLsizei count, const GLuint* indices) = 0;
virtual void begin(GLenum mode) = 0;
virtual void vertex(const Vec2& vert) = 0;
virtual void vertex(const Vec3& vert) = 0;
virtual void vertex(const Vec4& vert) = 0;
virtual void vertex(float x, float y) = 0;
virtual void vertex(float x, float y, float z) = 0;
virtual void vertex(float x, float y, float z, float w) = 0;
virtual void end() = 0;
void useVertexCacheAsVertexArray()
{
setVertexArray(_vertexCache.size(), &_vertexCache.front());
}
std::vector<Vec3> _vertexCache;
bool _treatVertexDataAsTemporary;
};
class PrimitiveIndexFunctor
{
public:
virtual ~PrimitiveIndexFunctor() {}
virtual void setVertexArray(unsigned int count,const Vec2* vertices) = 0;
virtual void setVertexArray(unsigned int count,const Vec3* vertices) = 0;
virtual void setVertexArray(unsigned int count,const Vec4* vertices) = 0;
virtual void setVertexArray(unsigned int count,const Vec2d* vertices) = 0;
virtual void setVertexArray(unsigned int count,const Vec3d* vertices) = 0;
virtual void setVertexArray(unsigned int count,const Vec4d* vertices) = 0;
virtual void drawArrays(GLenum mode,GLint first,GLsizei count) = 0;
virtual void drawElements(GLenum mode,GLsizei count,const GLubyte* indices) = 0;
virtual void drawElements(GLenum mode,GLsizei count,const GLushort* indices) = 0;
virtual void drawElements(GLenum mode,GLsizei count,const GLuint* indices) = 0;
virtual void begin(GLenum mode) = 0;
virtual void vertex(unsigned int pos) = 0;
virtual void end() = 0;
void useVertexCacheAsVertexArray()
{
setVertexArray(_vertexCache.size(),&_vertexCache.front());
}
std::vector<Vec3> _vertexCache;
bool _treatVertexDataAsTemporary;
};
按照上文讨论的说法,在osg::Drawable的派生类中(如osg::Geometry),应该都会实现这几个函数,并且函数的实现中会调用这4个类定义的接口函数,只有了解了在osg::Geometry等派生类中使用这些接口的方式,才能了解到这些接口的作用。
5.1 AttributeFunctor
osg::Geometry对其实现如下:
void Geometry::accept(AttributeFunctor& af)
{
AttributeFunctorArrayVisitor afav(af);
if (_vertexArray.valid())
{
afav.applyArray(VERTICES, _vertexArray.get());
}
else if (_vertexAttribList.size() > 0)
{
OSG_INFO << "Geometry::accept(AttributeFunctor& af): Using vertex attribute instead" << std::endl;
afav.applyArray(VERTICES, _vertexAttribList[0].get());
}
afav.applyArray(NORMALS, _normalArray.get());
afav.applyArray(COLORS, _colorArray.get());
afav.applyArray(SECONDARY_COLORS, _secondaryColorArray.get());
afav.applyArray(FOG_COORDS, _fogCoordArray.get());
for (unsigned unit = 0; unit < _texCoordList.size(); ++unit)
{
afav.applyArray((AttributeType)(TEXTURE_COORDS_0 + unit), _texCoordList[unit].get());
}
for (unsigned int index = 0; index < _vertexAttribList.size(); ++index)
{
afav.applyArray(index, _vertexAttribList[index].get());
}
}
osg::Geometry接受AttributeFunctor的实现使用了一个辅助类AttributeFunctorArrayVisitor来实现,通过调用这个辅助类可以获取到osg::Geometry中的各种类型的数组(顶点位置、顶点颜色、顶点法线等等),然后对这些数组调用applyArray方法,这个方法最终会调用_af.apply(_type,array.size(),&(array.front()));
也就是AttributeFunctor的apply方法。这样就将几何体Geomtry中的顶点根据它的属性传递到了AttributeFunctor的接口方法中,也就是说我们需要实现的AttributeFunctor方法的传入参数
virtual void apply(AttributeType,unsigned int,const GLbyte*) {}
AttributeType是传入给我们的顶点属性信息(可以是顶点位置、颜色、法线等)
unsigned int 是第三个参数数组的个数
const T(类型)*是传入数组的指针(配合第二个参数,可以很容易遍历整个数组)
5.2 ConstAttributeFunctor
实现与AttributeFunctor几乎一致,不过使用它遍历不能修改osg::Geometry顶点数组的值
5.3 PrimitiveFunctor
osg::Geometry针对它的实现是将顶点的位置设置到setVertex函数中
void Geometry::accept(PrimitiveFunctor& functor) const
{
const osg::Array* vertices = _vertexArray.get();
if (!vertices && _vertexAttribList.size()>0)
{
OSG_INFO<<"Using vertex attribute instead"<<std::endl;
vertices = _vertexAttribList[0].get();
}
if (!vertices || vertices->getNumElements()==0) return;
if (_containsDeprecatedData && dynamic_cast<const osg::IndexArray*>(vertices->getUserData())!=0)
{
OSG_WARN<<"Geometry::accept(PrimitiveFunctor& functor) unable to work due to deprecated data, call geometry->fixDeprecatedData();"<<std::endl;
return;
}
switch(vertices->getType())
{
case(Array::Vec2ArrayType):
functor.setVertexArray(vertices->getNumElements(),static_cast<const Vec2*>(vertices->getDataPointer()));
break;
case(Array::Vec3ArrayType):
functor.setVertexArray(vertices->getNumElements(),static_cast<const Vec3*>(vertices->getDataPointer()));
break;
case(Array::Vec4ArrayType):
functor.setVertexArray(vertices->getNumElements(),static_cast<const Vec4*>(vertices->getDataPointer()));
break;
case(Array::Vec2dArrayType):
functor.setVertexArray(vertices->getNumElements(),static_cast<const Vec2d*>(vertices->getDataPointer()));
break;
case(Array::Vec3dArrayType):
functor.setVertexArray(vertices->getNumElements(),static_cast<const Vec3d*>(vertices->getDataPointer()));
break;
case(Array::Vec4dArrayType):
functor.setVertexArray(vertices->getNumElements(),static_cast<const Vec4d*>(vertices->getDataPointer()));
break;
default:
OSG_WARN<<"Warning: Geometry::accept(PrimitiveFunctor&) cannot handle Vertex Array type"<<vertices->getType()<<std::endl;
return;
}
for(PrimitiveSetList::const_iterator itr=_primitives.begin();
itr!=_primitives.end();
++itr)
{
(*itr)->accept(functor);
}
}
我们在继承PrimitiveFunctor类,并重写函数时virtual void setVertexArray(unsigned int count, const Vec2* vertices) = 0;
类似于setVertexArray的传入参数传入的是Geometry顶点位置数组的值,第一个参数是顶点数组的个数,第二个是它的指针。Geometry的实现还将PrimitiveFunctor传到了它的PrimtiveSet列表中,PrimtiveSet会调用accept的方法,这个方法前面已经讨论到了,它的实现是再次调用Functor自己的drawArrays或者drawElements方法。
5.4 PrimitiveIndexFunctor
在osg::Geometry中函数体的实现和PrimtiveFunctor是一样的
6. 小结
在重写Functor和了解Functor功能的时候,需要查看具体接受(accept)Functor的类是如何使用Functor中的成员函数的,这样我们才能知道重载的函数到底传入的参数含义是什么,我们可以对传入的参数做什么操作。本文只是分析了Geometry的实现方式,针对Drawable派生的其他类(如Text、ShapeDrawable)它们也有自己的实现方法,需要查看相应的代码了解清楚。