OSG獲取模型座標點、索引、法向量、紋理等數據

(文章很長,如果想直接看源碼不想聽我囉嗦的話,請直接跳到最後下載源碼)

如果要做針對模型的算法,獲取模型中所有圖元(由於OSG多邊形只支持三角形圖元,而且自己做的科研也是針對三角網模型的,本文就針對三角形模型來說明)的座標、紋理、法向量等數據,然後再進行處理是非常重要的,但是網上關於獲取圖元屬性的教程實在太少,而且有的說的也不是很清楚,自己在做科研的過程中,探索以後,記錄在此,希望能幫助到有需要的同學。

首先要知道,在osg中幾何圖元是被幾何體(osg::Geometry)管理的,osg::Geometry提供了管理幾何圖元osg::PrimitiveSet的數組,用來添加和刪除這些圖元,這些圖元共同構成了幾何體對象。要訪問圖元的屬性,應該就要在geometry中找,查看geometry的函數,發現有兩個訪問者函數:

virtual void accept(PrimitiveFunctor& pf) const;
virtual void accept(PrimitiveIndexFunctor& pf) const;

根據命名我們可以看出,這兩個函數對應着圖元的訪問和圖元索引的訪問。Geometry的父類Drawable也有這兩個函數。因爲在Node中,我們很容易拿到Drawable,所以我們要獲取圖元的屬性,應該是要給Drawable傳入訪問器,然後在訪問器的apply中獲取圖元的屬性了。

通過查文檔,我們發現Drawale支持四個visitor來獲取Drawable的信息,分別是:

virtual void accept(AttributeFunctor&);
virtual void accept(ConstAttributeFunctor&) const;
virtual void accept(PrimitiveFunctor&) const;
virtual void accept(PrimitiveIndexFunctor&) const;

我們要訪問圖元的屬性和索引,主要用到的是AttributeFunctor還有PrimitiveIndexFunctor。首先看AttributeFunctor類的定義:

        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*) {}
        };

不出所料,就是在不同的apply函數中,可以獲取不同類型的屬性信息。

再看PrimitiveIndexFunctor的實現:

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;
};

可以看到,裏面有函數drawElements函數,有openGL知識的通過應該知道這個函數時用來使用索引繪圖的,裏面通過各類參數應該可以拿到頂點的信息。但是在文檔中我們看發現這個類是一個類中的函數都沒有實現(如下圖所示)

所以我們再去看看他的子類是否有實現,進一步看文檔我們可以發現,PrimitiveIndexFunctor類有以下三個子類:

我們要處理的是三角形,再去看看TriangleIndexFunctor,

virtual void  setVertexArray (unsigned int, const Vec2 *) 
  
virtual void  setVertexArray (unsigned int, const Vec3 *) 
  
virtual void  setVertexArray (unsigned int, const Vec4 *) 
  
virtual void  setVertexArray (unsigned int, const Vec2d *) 
  
virtual void  setVertexArray (unsigned int, const Vec3d *) 
  
virtual void  setVertexArray (unsigned int, const Vec4d *) 
  
virtual void  begin (GLenum mode) 
  
virtual void  vertex (unsigned int vert) 
  
virtual void  end () 
  
virtual void  drawArrays (GLenum mode, GLint first, GLsizei count) 
  
virtual void  drawElements (GLenum mode, GLsizei count, const GLubyte *indices) 
  
virtual void  drawElements (GLenum mode, GLsizei count, const GLushort *indices) 
  
virtual void  drawElements (GLenum mode, GLsizei count, const GLuint *indices) 

我們發現裏面仍然有drawElements函數,所以我們就直接用這個類來獲取模型中三角形索引的信息。從文檔中得知:TriangleIndexFunctor是一個類模板,而且它繼承自自身模板參數T,在模板參數中必須實現T::operator()(const unsigned& v1, const unsigned& v2, const unsigned& v3)這個函數,每繪製一個三角形,都會調用一次這個函數。這個函數中,很明顯可以猜到,v1,v2,v3就是三角形三個頂點的索引。現在好辦了。

1.首先定義類TriangleIndex作爲TriangleIndexFunctor的模板參數T。

頭文件:

#include <osg/ref_ptr>
#include <osg/Array>
#include <osg/TriangleIndexFunctor>

class TriangleIndex
{
public:
	osg::ref_ptr<osg::UIntArray> indexs;//所有的索引
	int triangleNum;//三角形的數量
	TriangleIndex();
	~TriangleIndex();
	void operator()(const unsigned int& v1, const unsigned int& v2, const unsigned int& v3);
};

cpp文件:

#include "stdafx.h"
#include "TriangleIndex.h"
#include <iostream>


TriangleIndex::TriangleIndex()
{
	indexs = new osg::UIntArray;
	triangleNum = 0;
}


TriangleIndex::~TriangleIndex()
{
}

void TriangleIndex::operator()(const unsigned& v1, const unsigned& v2, const unsigned& v3)
{
	if (v1 == v2 || v1 == v3 || v2 == v3)
		return;
	indexs->push_back(v1);
	indexs->push_back(v2);
	indexs->push_back(v3);
	triangleNum++;
}

這個函數在operator()函數中存儲了所有的三角形索引的值以及三角形的數量。

2.定義類ModelAttributeFunctor繼承自osg::Drawable::AttributeFunctor來獲取頂點屬性

上面已經說過,要獲取頂點的屬性,應該在AttributeFunctor函數中來獲取。我們要獲取頂點座標位置和紋理座標,頂點位置是osg::Vec3類型,而紋理座標是osg::Vec2類型(此處不考慮三維和一維紋理)。那就應該重寫以下兩個函數:
 

virtual void apply(osg::Drawable::AttributeType, unsigned, osg::Vec2*) ;
virtual void apply(osg::Drawable::AttributeType, unsigned, osg::Vec3*) ;

OK,我們定義類ModelAttributeFunctor繼承自osg::Drawable::AttributeFunctor,然後重寫上面的兩個函數,然後將得到的信息保存下來。

頭文件:

#include <osg/Drawable>

class ModelAttributeFunctor
	:public osg::Drawable::AttributeFunctor
{
public:
	osg::ref_ptr<osg::Vec3Array> vertexList;//存儲頂點的數組
	osg::ref_ptr<osg::Vec3Array> normalList;//存儲法向量
	osg::ref_ptr<osg::Vec2Array> textCoordList;//紋理座標
	virtual void apply(osg::Drawable::AttributeType, unsigned, osg::Vec2*) override;
	virtual void apply(osg::Drawable::AttributeType, unsigned, osg::Vec3*) override;
	ModelAttributeFunctor();
	~ModelAttributeFunctor();
};

cpp文件:

#include "stdafx.h"
#include "ModelAttributeFunctor.h"
#include <iostream>
using namespace std;


ModelAttributeFunctor::ModelAttributeFunctor()
{
	vertexList = new osg::Vec3Array;
	normalList = new osg::Vec3Array;
	textCoordList = new osg::Vec2Array;
}


ModelAttributeFunctor::~ModelAttributeFunctor()
{
}

void ModelAttributeFunctor::apply(osg::Drawable::AttributeType type, unsigned size, osg::Vec2* front)
{
	if (type==osg::Drawable::TEXTURE_COORDS_0)
	{
		for (unsigned i=0;i<size;i++)
		{
			textCoordList->push_back(*(front + i));
		}
	}
}

void ModelAttributeFunctor::apply(osg::Drawable::AttributeType type, unsigned size, osg::Vec3* front)
{
	if (type == osg::Drawable::VERTICES)
	{
		for (unsigned i = 0; i<size; i++)
		{
			vertexList->push_back(*(front + i));
		}
	}
	else if (type == osg::Drawable::NORMALS)
	{
		for (unsigned i = 0; i<size; i++)
		{
			normalList->push_back(*(front + i));
		}
	}
}

可以看到,在不同類型的apply函數中,獲取了不同的數據,並分別存儲在vertexList,normalList,textCoordList中。此處需要注意的是,apply函數中,有一個參數是osg::Drawable::AttributeType,利用這個參數可以判斷是哪種類型的數據,比如法向量和頂點座標都是osg::Vec3類型,所以在上面的void ModelAttributeFunctor::apply(osg::Drawable::AttributeType type, unsigned size, osg::Vec3* front)函數中做了判斷。

3.定義存儲頂點信息,三角形信息以及Node信息的類

爲了存儲讀取得到的頂點,三角形以及三角形構成的模型部件,我定義了三個類:Vertex類用來存儲頂點信息,Triangle類用來存儲三角形信息,Geom類用來存儲模型部件(這個在4中詳細說)。

3.1定義Vertex類

頭文件:

#include <osg/Vec3>
#include <osg/Vec2>
#include <osg/Vec4>
#include <vector>
using namespace std;

/**
 * 頂點類,記錄模型中頂點的各類信息,包括頂點座標、法向量、紋理座標等
 */
class Vertex
{
public:
	osg::Vec3 coor;//頂點座標
	osg::Vec3 normal;//法向量
	osg::Vec2 texCoor;//紋理座標
	int index;//該頂點在數組中的下標
	vector<int> neighborTriangle;//頂點相鄰的三角形
	Vertex();
	~Vertex();
};

cpp文件:

#include "stdafx.h"
#include "Vertex.h"


Vertex::Vertex()
{
}


Vertex::~Vertex()
{
}

3.2定義Triangle類存儲三角形信息

頭文件:

#include <osg/Vec3>
#include <vector>
#include <osg/Vec4>

class Trianngle
{
public:
	void init();
	Trianngle();
	~Trianngle();
	int vertexIndexs[3];//頂點索引
	osg::Vec3 normal;//法向量
	int index;//該三角形在數組中的索引
	std::vector<int> neighborTriangles;//相鄰的三角形的索引
};

cpp文件:

#include "stdafx.h"
#include "Trianngle.h"


Trianngle::Trianngle()
{
	init();
}


Trianngle::~Trianngle()
{
}

void Trianngle::init()
{
	this->vertexIndexs[0] = this->vertexIndexs[1] = this->vertexIndexs[2] = -1;
	this->normal.set(0.0f, 0.0f, 0.0f);
}

3.3定義Geom類存儲模型中的部件

Geom類中自己以前寫了自己科研過程中創建三角形及頂點之間索引的一些函數,這裏就不刪除了。此外,Geom中自己寫了一個函數osg::ref_ptr<osg::Geode> Geom::createOsgNode(osg::Vec4 color),這個函數是將得到的數據用純色重新繪製出來並返回一個osg的Geod節點的函數,自己要將模型的不同部件用不同的顏色顯示,以觀察部件之間的聯繫和分別時,這個函數還是很有用的。

頭文件:

#include <vector>
#include "Vertex.h"
#include "Trianngle.h"
#include <osg/ref_ptr>
#include <osg/Geode>
using namespace std;


class Geom
{
public:
	vector<Vertex*> vertices;//一個geom中所有的頂點信息
	vector<Trianngle*> trianngles;//一個geom中的所有三角形信息
	osg::BoundingBox  boundingBox;//包圍盒
	bool isTwoTriangleNeighbor(int triangle1Index,int triangle2Index);//兩個三角形是否相鄰
	void createTriangleTopo();//創建三角形之間的拓撲關係
	void createVertexTopo();//創建頂點之間的拓撲
	osg::ref_ptr<osg::Geode> createOsgNode(osg::Vec4 color);
	Geom();
	~Geom();
};

cpp文件:

#include "stdafx.h"
#include "Geom.h"
#include <iostream>
#include <queue>
#include "Utility.h"
#include <osg/Geometry>
using namespace std;

Geom::Geom()
{
}

Geom::~Geom()
{
	for (Vertex* vertex : vertices)
		delete vertex;
	for (Trianngle* trianngle : trianngles)
		delete trianngle;
}

/**
 * 判斷兩個三角形是否相鄰
 */
bool Geom::isTwoTriangleNeighbor(int triangle1Index, int triangle2Index)
{
	Trianngle* trianngle1 = trianngles.at(triangle1Index);
	Trianngle* trianngle2 = trianngles.at(triangle2Index);

	osg::Vec3 pnt11 = vertices.at(trianngle1->vertexIndexs[0])->coor;
	osg::Vec3 pnt12 = vertices.at(trianngle1->vertexIndexs[1])->coor;
	osg::Vec3 pnt13 = vertices.at(trianngle1->vertexIndexs[2])->coor;

	osg::Vec3 pnt21 = vertices.at(trianngle2->vertexIndexs[0])->coor;
	osg::Vec3 pnt22 = vertices.at(trianngle2->vertexIndexs[1])->coor;
	osg::Vec3 pnt23 = vertices.at(trianngle2->vertexIndexs[2])->coor;

	if ((Utility::isVec3Same(pnt11, pnt21) && Utility::isVec3Same(pnt12, pnt22))//第一條邊
		|| (Utility::isVec3Same(pnt11, pnt22) && Utility::isVec3Same(pnt12, pnt21))
		|| (Utility::isVec3Same(pnt11, pnt22) && Utility::isVec3Same(pnt12, pnt23))//第二條邊
		|| (Utility::isVec3Same(pnt11, pnt23) && Utility::isVec3Same(pnt12, pnt22))
		|| (Utility::isVec3Same(pnt11, pnt21) && Utility::isVec3Same(pnt12, pnt23))//第三條邊
		|| (Utility::isVec3Same(pnt11, pnt23) && Utility::isVec3Same(pnt12, pnt21))

		|| (Utility::isVec3Same(pnt12, pnt21) && Utility::isVec3Same(pnt13, pnt22))//第一條邊
		|| (Utility::isVec3Same(pnt12, pnt22) && Utility::isVec3Same(pnt13, pnt21))
		|| (Utility::isVec3Same(pnt12, pnt22) && Utility::isVec3Same(pnt13, pnt23))//第二條邊
		|| (Utility::isVec3Same(pnt12, pnt23) && Utility::isVec3Same(pnt13, pnt22))
		|| (Utility::isVec3Same(pnt12, pnt21) && Utility::isVec3Same(pnt13, pnt23))//第三條邊
		|| (Utility::isVec3Same(pnt12, pnt23) && Utility::isVec3Same(pnt13, pnt21))

		|| (Utility::isVec3Same(pnt11, pnt21) && Utility::isVec3Same(pnt13, pnt22))//第一條邊
		|| (Utility::isVec3Same(pnt11, pnt22) && Utility::isVec3Same(pnt13, pnt21))
		|| (Utility::isVec3Same(pnt11, pnt22) && Utility::isVec3Same(pnt13, pnt23))//第二條邊
		|| (Utility::isVec3Same(pnt11, pnt23) && Utility::isVec3Same(pnt13, pnt22))
		|| (Utility::isVec3Same(pnt11, pnt21) && Utility::isVec3Same(pnt13, pnt23))//第三條邊
		|| (Utility::isVec3Same(pnt11, pnt23) && Utility::isVec3Same(pnt13, pnt21)))
		return true;
	return  false;
}

/**
 * 創建模型的三角形之間的拓撲
 */
void Geom::createTriangleTopo()
{
	cout << "開始創建三角形之間的拓撲關係:" << endl;
	for (size_t i = 0; i<trianngles.size(); ++i)
	{
		Trianngle* triannglei = trianngles.at(i);
		for (size_t j = i+1; j<trianngles.size(); ++j)
		{
			Trianngle* triannglej = trianngles.at(j);
			if (isTwoTriangleNeighbor(i, j))
			{
				triannglei->neighborTriangles.push_back(j);
				triannglej->neighborTriangles.push_back(i);
			}
		}
		cout << "\t當前進度" << int(i*100.0 / trianngles.size()) << "%\r";
	}
	cout << endl;
}

/**
 * 創建頂點之間的拓撲
 */
void Geom::createVertexTopo()
{
	//點周圍的三角形
	for (size_t i=0;i<trianngles.size();++i)
	{
		Vertex *vertex1 =(Vertex*) vertices.at(trianngles.at(i)->vertexIndexs[0]);
		vertex1->neighborTriangle.push_back(i);
		Vertex *vertex2 = (Vertex*)vertices.at(trianngles.at(i)->vertexIndexs[1]);
		vertex2->neighborTriangle.push_back(i);
		Vertex *vertex3 = (Vertex*)vertices.at(trianngles.at(i)->vertexIndexs[2]);
		vertex3->neighborTriangle.push_back(i);
		cout << "	點周圍的三角形:" << int(i*1.0 / trianngles.size() * 100) << "%\r";
	}
}

/**
 * 將Geom中的數據創建成osg節點
 */
osg::ref_ptr<osg::Geode> Geom::createOsgNode(osg::Vec4 color)
{
	osg::ref_ptr<osg::Geode> geode = new osg::Geode;
	osg::ref_ptr<osg::Geometry> geometry = new osg::Geometry;
	//頂點、法向量
	osg::ref_ptr<osg::Vec3Array> vertexArray = new osg::Vec3Array;
	osg::ref_ptr<osg::Vec3Array> normalArray = new osg::Vec3Array;
	osg::ref_ptr<osg::Vec4Array> colorArray = new osg::Vec4Array;
	for (Vertex* vertex : vertices)
	{
		vertexArray->push_back(vertex->coor);
		normalArray->push_back(vertex->normal);
	}
	//顏色
	colorArray->push_back(color);
	//索引
	osg::ref_ptr<osg::DrawElementsUInt> indexs = new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, 0);
	for (Trianngle* trianngle : trianngles)
	{
		indexs->push_back(trianngle->vertexIndexs[0]);
		indexs->push_back(trianngle->vertexIndexs[1]);
		indexs->push_back(trianngle->vertexIndexs[2]);
	}
	geometry->setVertexArray(vertexArray);
	geometry->setNormalArray(normalArray, osg::Array::BIND_PER_VERTEX);
	geometry->setColorArray(colorArray, osg::Array::BIND_OVERALL);
	geometry->addPrimitiveSet(indexs);
	geode->addDrawable(geometry);
	return geode;
}

4.創建節點訪問器遍歷節點,獲取模型的各類數據

OK,在3中定義了那麼多類,終於要開始獲取節點的信息了。我們知道osg中,其實說到底,有兩類節點非常重要:一個是Geode,另一個是Group。其他類的節點基本都是從這兩類中繼承或者封裝得到的。我們在上面說過,Drawable使用accept函數,傳入訪問器參數,在訪問器中就可以拿到模型的座標等各類信息。那獲取Drawable的話,我們知道Geode有一個函數是getDrawable,所以我們應該拿到osg場景中的所有Geode節點,然後獲取其Drawable,然後應用訪問器即可。獲取所有Geode節點的話,就要用到OSG中的節點訪問器了。關於節點訪問器,我這裏就不贅述了,自己可以查資料,我以後如果有時間,也想寫一個關於節點訪問器的博客,感興趣的話,可以關注我的博客。我在這裏定義了一個類PositionVisitor函數繼承自osg::NodeVisitor,直接貼代碼:

頭文件:

#include <osg/NodeVisitor>
#include <vector>
#include "ModelAttributeFunctor.h"
#include <osg/TriangleIndexFunctor>
#include "TriangleIndex.h"
#include "Geom.h"
#include <osgText/Text>
using namespace std;
class PositionVisitor
	:public osg::NodeVisitor
{
protected:
	vector<Geom*> allGeom;//所有的geom
	osg::Vec4 geomColor;//geom的顏色
	string modelName;//模型名稱
	osg::BoundingBox boundingBox;//包圍盒
public:
	virtual  void apply(osg::Geode& node) override;
	void dealTriangleInfo(ModelAttributeFunctor attributeFunctor,osg::TriangleIndexFunctor<TriangleIndex> indexFunctor);//處理訪問器得到的信息,構建三角形關係
	osg::ref_ptr<osg::Node> createOsgNode(osg::Vec4 color,int order);//根據指定的顏色,將geom中的數據創建成osg節點
	osg::ref_ptr<osg::Node> createRandomColorOsgNode(int order);//將geom中的數據創建成osg節點,顏色隨機
	osg::ref_ptr<osgText::Text> createTipText(short direction);//創建提示文字
	PositionVisitor(string ModelName);
	~PositionVisitor();
};

cpp文件:

#include "stdafx.h"
#include "PositionVisitor.h"
#include <osg/Drawable>
#include <osg/Geode>
#include <iostream>
#include "Geom.h"
#include <valarray>
#include "Trianngle.h"
#include "Vertex.h"
#include <cstdlib>
#include <ctime>
#include <osg/ComputeBoundsVisitor>

PositionVisitor::PositionVisitor(string ModelName)
{
	this->modelName = ModelName;
	setTraversalMode(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN);
}

PositionVisitor::~PositionVisitor()
{
	for (Geom* geom:allGeom)
	{
		delete geom;
	}
}

void PositionVisitor::apply(osg::Geode& node)
{
	for (size_t i=0;i<node.getNumDrawables();i++)
	{
		osg::ref_ptr<osg::Drawable> drawable = node.getDrawable(i);
		ModelAttributeFunctor functor;
		drawable->accept(functor);
		osg::TriangleIndexFunctor<TriangleIndex> triangleIndex;
		drawable->accept(triangleIndex);
		dealTriangleInfo(functor, triangleIndex);
	}
}

void PositionVisitor::dealTriangleInfo(ModelAttributeFunctor attributeFunctor, osg::TriangleIndexFunctor<TriangleIndex> indexFunctor)
{
	Geom *geom = new Geom;
	if (attributeFunctor.textCoordList->size()!=0
		&&attributeFunctor.textCoordList->size()!=attributeFunctor.vertexList->size())
	{
		cout << "紋理座標和頂點數量不匹配" << endl;
		return;
	}
	//處理頂點信息
	for (size_t i=0;i<attributeFunctor.vertexList->size();i++)
	{
		Vertex* vertex=new Vertex;
		vertex->coor = attributeFunctor.vertexList->at(i);
		vertex->index = i;
		vertex->normal = attributeFunctor.normalList->at(i);
		if (i< attributeFunctor.textCoordList->size())
			vertex->texCoor = attributeFunctor.textCoordList->at(i);
		geom->vertices.push_back(vertex);
	}
	//處理三角形信息
	for (int i=0;i<indexFunctor.triangleNum;i++)
	{
		Trianngle* trianngle=new Trianngle;
		trianngle->index = i;
		trianngle->vertexIndexs[0] = indexFunctor.indexs->at(i * 3);
		trianngle->vertexIndexs[1] = indexFunctor.indexs->at(i * 3+1);
		trianngle->vertexIndexs[2] = indexFunctor.indexs->at(i * 3+2);
		//計算法向量
		osg::Vec3 edge1 = geom->vertices.at(trianngle->vertexIndexs[1])->coor - geom->vertices.at(trianngle->vertexIndexs[0])->coor;
		osg::Vec3 edge2 = geom->vertices.at(trianngle->vertexIndexs[2])->coor - geom->vertices.at(trianngle->vertexIndexs[0])->coor;
		osg::Vec3 triangleNormal = edge1^edge2;
		triangleNormal.normalize();
		trianngle->normal = triangleNormal;
		geom->trianngles.push_back(trianngle);
	}
	allGeom.push_back(geom);
}

osg::ref_ptr<osg::Node> PositionVisitor::createOsgNode(osg::Vec4 color, int order)
{
	this->geomColor = color;
	short direction = order % 4;
	osg::ref_ptr<osg::Group> result = new osg::Group;
	if (allGeom.size()>0&&allGeom.size()==1)
	{
		osg::ref_ptr<osg::Geode> geode= allGeom[0]->createOsgNode(color);
		this->boundingBox = geode->getBoundingBox();
		result->addChild(geode);
	}
	else
	{
		for (Geom* geom : allGeom)
			result->addChild(geom->createOsgNode(color));
		osg::ComputeBoundsVisitor boundsVisitor;
		result->accept(boundsVisitor);
		this->boundingBox = boundsVisitor.getBoundingBox();
	}
	result->addChild(createTipText(direction));
	return result;
}

osg::ref_ptr<osg::Node> PositionVisitor::createRandomColorOsgNode(int order)
{
	//創建一個隨機顏色
	osg::Vec4 color = osg::Vec4(rand()%10*0.1, rand() % 10 * 0.1, rand() % 10 * 0.1, 1.0f);
	this->geomColor = color;
	return createOsgNode(color,order);
}

osg::ref_ptr<osgText::Text> PositionVisitor::createTipText(short direction)
{
	osg::ref_ptr<osgText::Font> font = osgText::readFontFile("fonts/simhei.ttf");
	osg::ref_ptr<osgText::Text> text = new osgText::Text;
	text->setFont(font);//設置字體
	text->setCharacterSize(5);//字體大小

	//對每個組件設置不同的朝向,避免所有的提示文字都在一個朝向
	osg::Vec3 tipPosition;
	float halfX = (boundingBox.xMax() + boundingBox.xMin()) / 2;
	float halfY = (boundingBox.yMax() + boundingBox.yMin()) / 2;
	float halfZ = (boundingBox.zMax() + boundingBox.zMin()) / 2;
	switch (direction)
	{
	case 0://左
		tipPosition =osg::Vec3 (halfX, boundingBox.yMin()-1, halfZ);
		text->setAxisAlignment(osgText::Text::XZ_PLANE);//文字對稱方式
		break;
	case 1://右
		tipPosition = osg::Vec3(halfX, boundingBox.yMax()+1, halfZ);
		text->setAxisAlignment(osgText::Text::REVERSED_XZ_PLANE);//文字對稱方式
		break;
	case 2://前
		tipPosition = osg::Vec3(boundingBox.xMax()+1, halfY, halfZ);
		text->setAxisAlignment(osgText::Text::YZ_PLANE);//文字對稱方式
		break;
	case 3://後
		tipPosition = osg::Vec3(boundingBox.xMin()-1, halfY, halfZ);
		text->setAxisAlignment(osgText::Text::REVERSED_YZ_PLANE);//文字對稱方式
		break;
	}
	text->setPosition(tipPosition);//字體位置
	text->setColor(this->geomColor);//字體顏色
	text->setAutoRotateToScreen(false);//跟隨視角不斷變化,距離越遠,文字越小
	text->setBackdropType(osgText::Text::OUTLINE);//文件描邊
	text->setBackdropColor(osg::Vec4(1.0, 1.0, 1.0, 1.0));//文字描邊的顏色
	text->setDrawMode(osgText::Text::TEXT | osgText::Text::BOUNDINGBOX);//添加文字邊框
	text->setText(modelName);
	return text;
}

PositionVisitor函數中,我定義了vector<Geom*> allGeom這個成員變量用來存儲所有的geom。同樣,這裏也有一部分我寫的用作其他用途的函數,如果用到的話就用,用不到無視就好了。上面的代碼,有兩個地方需要特別注意:

1.在構造函數中一定要寫上setTraversalMode(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)這一句,這樣節點訪問器才能遍歷所有節點。

2.在PositionVisitor::apply(osg::Geode& node)函數中,可以拿到所有的Geode節點(其實說到其,一個場景,到最後,都是Geode節點組成的,所以我們只需要寫這一個apply函數,就可以拿到模型的所有部分了)。因爲一個Geode節點中,可能有多個Drawable,所以我定義了Geom類來存儲一個Drawable。

5.工具類

上面的代碼中,有一些是我寫的工具類中的函數,故把工具類的定義也貼在這裏吧。

頭文件:

#include <osg/Vec3>
#include <string>
using namespace std;

class Utility
{
public:
	static bool isVec3Same(osg::Vec3 v1, osg::Vec3 v2);//比較兩個三維向量是否相等
	static string getFileNameFromPath(string path);//從模型路徑中獲取明名稱
	static void string_replace(std::string &strBig, const std::string &strsrc, const std::string &strdst);
	Utility();
	~Utility();
};

cpp文件:

 #include "stdafx.h"
#include "Utility.h"
#include <iostream>

Utility::Utility()
{
}


Utility::~Utility()
{
}

/**
 * 比較兩個三維向量是否相同
 */
bool Utility::isVec3Same(osg::Vec3 v1, osg::Vec3 v2)
{
	return (v1.x() == v2.x()) && (v1.y() == v2.y()) && (v1.z() == v2.z());
}

/**
 * 用一個字符替換原字符中的另一個字符
 */
void Utility::string_replace(std::string& strBig, const std::string& strsrc, const std::string& strdst)
{
	std::string::size_type pos = 0;
	std::string::size_type srclen = strsrc.size();
	std::string::size_type dstlen = strdst.size();

	while ((pos = strBig.find(strsrc, pos)) != std::string::npos)
	{
		strBig.replace(pos, srclen, strdst);
		pos += dstlen;
	}
}

/**
 * 從路徑中獲取文件名(不包括後綴名)
 */
string Utility::getFileNameFromPath(string path)
{
	if(path.empty())
	{
		return "";
	}
	string_replace(path, "/", "\\");
	std::string::size_type iPos = path.find_last_of('\\') + 1;
	std::string::size_type dPos = path.find_last_of('.') + 1;
	if (dPos == 0)
		dPos = path.length();
	return path.substr(iPos, dPos - iPos-1);
}

6.調用

調用就很簡單了,只需要給節點傳入Visitor就好了,然後在visitor的allGeom函數中拿到Geom對象,就可以獲取模型的諸如頂點座標,法向量,紋理座標等各類屬性了。調用示例如下:

string name = "此處是模型的路徑”;
osg::ref_ptr<osg::Node> node = osgDB::readNodeFile(name);
string modelName = Utility::getFileNameFromPath(name);
PositionVisitor visitor = PositionVisitor(modelName);
node->accept(visitor);
root->addChild(visitor.createRandomColorOsgNode(i));

OK,因爲我要同時加載多個模型並且查看模型之間的關係,但是osg中的cmd的osgviewer命令一次只能查看一個模型,非常不方便,所以這個程序是自己寫的可以同時查看多個模型的一個小工具。程序會將每個模型用一個隨機的顏色純色繪製出來,而且用相同顏色的文字顯示模型的名稱。效果如下:

 

7.源代碼

源碼爲自己做的這個小工具的源碼,如果自己想讀取模型的各類屬性然後自己使用的話,直接把我的類拷貝到自己的項目中使用即可。下載後,記得修改osg庫目錄的位置爲自己的位置。

源碼地址:https://github.com/MeteorCh/ModelViewer(覺得好的話記得給個star)

8.問題

我的代碼應該是可以滿足大部分的讀取模型座標、法向量的問題了。但是,由於自己水平有限,有些東西肯定還是會存在考慮不到的情況(比如紋理類型沒有考慮完全,比如有些頂點沒有紋理座標只有顏色,本文沒有考慮),有意見建議或者問題可以在評論下說明,我會盡力解答。

9. 更新日記

  • 2019.08.30更新:針對模型大小不同,統一設置提示字體大小可能會出現的提示文字過大或過小的問題,添加進入程序先輸入文字大小,再繪製模型的功能。當文字不合適時,可以關閉程序重新啓動,多測試幾次直到找到合適的文字大小。
  • 2019.09.01更新:增加座標軸顯示功能。如下所示:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章