用OpenSceneGraph实现的NeHe OpenGL教程 - 第十课

52 篇文章 38 订阅
  • 简介

本文实现从外部文件中加载场景,并进行简单的场景漫游操作。

一般来说,场景都是通过外部建模工具生成的,然后再通过osg进行读取加载,一方面直接通过顶点生成场景对程序员来说十分困难,另一方面通过外部建模的方式加载场景使得程序变得十分灵活。像我们玩的CS游戏就是通过加载一些地图文件实现在不同的场景之中战斗。

  • 实现

在NeHe教程中实现的过程介绍的非常清楚,不熟悉的读者可以参考NeHe教程第十课。在这里我们同样使用NeHe中读取文件的方式以及行进的方式,代码如下

typedef struct tagVERTEX
{
	float x,y,z;
	float u,v;
}VERTEX;

typedef struct tagTRIANGLE
{
	VERTEX	vertex[3];
}TRIANGLE;

typedef	struct tagSECTOR
{
	int	iNumTriangle;
	TRIANGLE *pTriangle;
}SECTOR;
SECTOR	Sector;

void readstr(FILE *f,char *string)
{
	do
	{
		fgets(string, 255, f);
	} while ((string[0] == '/') || (string[0] == '\n'));
}

void SetupWorld()
{
	float x, y, z, u, v;
	int numtriangles;
	FILE *filein;
	char oneline[255];
	filein = fopen("Data/world.txt", "rt");				// File To Load World Data From

	readstr(filein,oneline);
	sscanf(oneline, "NUMPOLLIES %d\n", &numtriangles);

	Sector.iNumTriangle = numtriangles;
	Sector.pTriangle = new TRIANGLE[numtriangles];
	for(int i = 0; i < numtriangles; i++)
	{
		for(int j = 0; j < 3; j++)
		{
			readstr(filein,oneline);
			sscanf(oneline, "%f %f %f %f %f", &x, &y, &z, &u, &v);
			Sector.pTriangle[i].vertex[j].x = x;
			Sector.pTriangle[i].vertex[j].y = y;
			Sector.pTriangle[i].vertex[j].z = z;
			Sector.pTriangle[i].vertex[j].u = u;
			Sector.pTriangle[i].vertex[j].v = v;
		}
	}
	fclose(filein);
}

读取文件的方式有很多种,(在我们的Qt框架中可以通过QFile和QTextStream等方式读取)为了节省时间此处借用了NeHe源码中的读取方式

const float piover180 = 0.0174532925f;
float heading; 
float xpos;
float zpos;
GLfloat	yrot;

GLfloat walkbias = 0;
GLfloat walkbiasangle = 0;
GLfloat lookupdown = 0.0f;
同样的定义了一些变量用来控制角色。之后为了实现在每一帧中修改角色的姿态,我们定义了三个UpdateCallback类用来响应向前行进、左右旋转(绕Y轴旋转)以及上下旋转
(绕X轴旋转)

class TransUpdateCallback : public osg::NodeCallback
{
public:

	TransUpdateCallback()
	{
		GLfloat xtrans = -xpos;
		GLfloat ztrans = -zpos;
		GLfloat ytrans = -walkbias-0.25f;
		_trans = osg::Vec3d(xtrans, ytrans, ztrans);
	}

	virtual void operator()(Node* node, NodeVisitor* nv)
	{
		if (dynamic_cast<osg::MatrixTransform*>(node))
		{
			osg::MatrixTransform *moveMT = dynamic_cast<osg::MatrixTransform*>(node);
			moveMT->setMatrix(osg::Matrix::translate(_trans));
		}

		traverse(node, nv);
	}

	void setTrans(osg::Vec3d trans)
	{
		_trans = trans;
	}

	osg::Vec3d _trans;
};

class RotateXUpdateCallback : public osg::NodeCallback
{

public:

	RotateXUpdateCallback()
	{
		_pitch = lookupdown;
	}

	virtual void operator()(Node* node, NodeVisitor* nv)
	{
		if (dynamic_cast<osg::MatrixTransform*>(node))
		{
			osg::MatrixTransform *rotateX = dynamic_cast<osg::MatrixTransform*>(node);
			rotateX->setMatrix(osg::Matrix::rotate(_pitch, osg::X_AXIS));
		}

		traverse(node, nv);
	}

	void setPitch(double pitch)
	{
		_pitch = pitch;
	}

	double _pitch;
};


class RotateYUpdateCallback : public osg::NodeCallback
{

public:

	RotateYUpdateCallback()
	{
		_heading = osg::DegreesToRadians(360.0f) - yrot;
	}

	virtual void operator()(Node* node, NodeVisitor* nv)
	{
		if (dynamic_cast<osg::MatrixTransform*>(node))
		{
			osg::MatrixTransform *rotateY = dynamic_cast<osg::MatrixTransform*>(node);
			rotateY->setMatrix(osg::Matrix::rotate(_heading, osg::Y_AXIS));
		}

		traverse(node, nv);
	}

	void setHeading(double heading)
	{
		_heading = heading;
	}

	double _heading;
};

接着定义了三个全局的变量,用来记录场景中角色的姿态,在键盘的响应中修改它们实现角色的正确移动和旋转

TransUpdateCallback *g_moveMTCB;
RotateXUpdateCallback *g_xRotMTCB;
RotateYUpdateCallback *g_yRotMTCB;
接下来构建我们的场景

	osg::Group *root = new osg::Group;

	osg::Vec3Array *vertexArray = new osg::Vec3Array;
	osg::Vec2Array *textureArray = new osg::Vec2Array;

	unsigned int num = Sector.iNumTriangle;

	for (unsigned i = 0; i < num; ++i)
	{
		vertexArray->push_back(osg::Vec3(Sector.pTriangle[i].vertex[0].x, Sector.pTriangle[i].vertex[0].y, Sector.pTriangle[i].vertex[0].z));
		vertexArray->push_back(osg::Vec3(Sector.pTriangle[i].vertex[1].x, Sector.pTriangle[i].vertex[1].y, Sector.pTriangle[i].vertex[1].z));
		vertexArray->push_back(osg::Vec3(Sector.pTriangle[i].vertex[2].x, Sector.pTriangle[i].vertex[2].y, Sector.pTriangle[i].vertex[2].z));

		textureArray->push_back(osg::Vec2(Sector.pTriangle[i].vertex[0].u, Sector.pTriangle[i].vertex[0].v));
		textureArray->push_back(osg::Vec2(Sector.pTriangle[i].vertex[1].u, Sector.pTriangle[i].vertex[1].v));
		textureArray->push_back(osg::Vec2(Sector.pTriangle[i].vertex[2].u, Sector.pTriangle[i].vertex[2].v));
	}

	osg::MatrixTransform *moveMT = new osg::MatrixTransform;
	TransUpdateCallback *tucb = new TransUpdateCallback;
	moveMT->setUpdateCallback(tucb);
	g_moveMTCB = tucb;

	osg::MatrixTransform *headingMT = new osg::MatrixTransform;
	RotateYUpdateCallback *ryuc = new RotateYUpdateCallback;
	headingMT->setUpdateCallback(ryuc);
	g_yRotMTCB = ryuc;

	osg::MatrixTransform *pitchMT = new osg::MatrixTransform;
	RotateXUpdateCallback *rxuc = new RotateXUpdateCallback;
	pitchMT->setUpdateCallback(rxuc);
	g_xRotMTCB = rxuc;

	headingMT->addChild(pitchMT);
	pitchMT->addChild(moveMT);

	osg::Geometry *triangleGeometry = new osg::Geometry;
	triangleGeometry->setVertexArray(vertexArray);

	triangleGeometry->setTexCoordArray(0, textureArray);
	triangleGeometry->getOrCreateStateSet()->setTextureAttributeAndModes(0, createTexture(0));

	triangleGeometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, 108));
	osg::Geode *triangleGeode = new osg::Geode;
	triangleGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
	triangleGeode->addDrawable(triangleGeometry);
	moveMT->addChild(triangleGeode);
	root->addChild(headingMT);
构建场景中我们将Callback记录在全局变量中,在EventHandler中修改它们

		switch(ea.getEventType())
		{
		case(osgGA::GUIEventAdapter::KEYDOWN):
			{
				if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Up)
				{
					xpos -= (float)sin(heading*piover180) * 0.05f;
					zpos -= (float)cos(heading*piover180) * 0.05f;
					if (walkbiasangle >= 359.0f)
					{
						walkbiasangle = 0.0f;
					}
					else
					{
						walkbiasangle+= 10;
					}
					walkbias = (float)sin(walkbiasangle * piover180)/20.0f;
					GLfloat ytrans = -walkbias-0.25f;
					g_moveMTCB->setTrans(osg::Vec3d(-xpos, ytrans, -zpos));
				}

				if (ea.getKey()== osgGA::GUIEventAdapter::KEY_Down)
				{	
					xpos += (float)sin(heading*piover180) * 0.05f;
					zpos += (float)cos(heading*piover180) * 0.05f;
					if (walkbiasangle <= 1.0f)
					{
						walkbiasangle = 359.0f;
					}
					else
					{
						walkbiasangle-= 10;
					}
					walkbias = (float)sin(walkbiasangle * piover180)/20.0f;
					GLfloat ytrans = -walkbias-0.25f;
					g_moveMTCB->setTrans(osg::Vec3d(-xpos, ytrans, -zpos));
				}

				if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Left)
				{
					heading -= 1.0f;
					yrot = heading;
					double direction = osg::DegreesToRadians(360.0f) - osg::DegreesToRadians(yrot);
					g_yRotMTCB->setHeading(direction);
				}
				
				if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Right)
				{
					heading += 1.0f;
					yrot = heading;
					double direction = osg::DegreesToRadians(360.0f) - osg::DegreesToRadians(yrot);
					g_yRotMTCB->setHeading(direction);
				}

				if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Page_Up)
				{
					lookupdown += 1.0f;
					double direction = osg::DegreesToRadians(lookupdown);
					g_xRotMTCB->setPitch(direction);
				}

				if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Page_Down)
				{
					lookupdown -= 1.0f;
					double direction = osg::DegreesToRadians(lookupdown);
					g_xRotMTCB->setPitch(direction);
				}

			}
		default: break;
		}
行进以及旋转的方式参考了NeHe教程中的描述。

编译运行程序


附:本课源码(源码中可能存在错误和不足,仅供参考)

#include "../osgNeHe.h"

#include <QtCore/QTimer>
#include <QtGui/QApplication>
#include <QtGui/QVBoxLayout>

#include <osgViewer/Viewer>
#include <osgDB/ReadFile>
#include <osgQt/GraphicsWindowQt>

#include <osg/MatrixTransform>
#include <osg/Texture2D>
#include <osgGA/TrackballManipulator>

using namespace osg;

//
//Copied From NeHe Tutorial

typedef struct tagVERTEX
{
	float x,y,z;
	float u,v;
}VERTEX;

typedef struct tagTRIANGLE
{
	VERTEX	vertex[3];
}TRIANGLE;

typedef	struct tagSECTOR
{
	int	iNumTriangle;
	TRIANGLE *pTriangle;
}SECTOR;
SECTOR	Sector;

void readstr(FILE *f,char *string)
{
	do
	{
		fgets(string, 255, f);
	} while ((string[0] == '/') || (string[0] == '\n'));
}

void SetupWorld()
{
	float x, y, z, u, v;
	int numtriangles;
	FILE *filein;
	char oneline[255];
	filein = fopen("Data/world.txt", "rt");				// File To Load World Data From

	readstr(filein,oneline);
	sscanf(oneline, "NUMPOLLIES %d\n", &numtriangles);

	Sector.iNumTriangle = numtriangles;
	Sector.pTriangle = new TRIANGLE[numtriangles];
	for(int i = 0; i < numtriangles; i++)
	{
		for(int j = 0; j < 3; j++)
		{
			readstr(filein,oneline);
			sscanf(oneline, "%f %f %f %f %f", &x, &y, &z, &u, &v);
			Sector.pTriangle[i].vertex[j].x = x;
			Sector.pTriangle[i].vertex[j].y = y;
			Sector.pTriangle[i].vertex[j].z = z;
			Sector.pTriangle[i].vertex[j].u = u;
			Sector.pTriangle[i].vertex[j].v = v;
		}
	}
	fclose(filein);
}

//End
//

const float piover180 = 0.0174532925f;
float heading;
float xpos;
float zpos;
GLfloat	yrot;

GLfloat walkbias = 0;
GLfloat walkbiasangle = 0;
GLfloat lookupdown = 0.0f;

//
//TransUpdateCallback

class TransUpdateCallback : public osg::NodeCallback
{
public:

	TransUpdateCallback()
	{
		GLfloat xtrans = -xpos;
		GLfloat ztrans = -zpos;
		GLfloat ytrans = -walkbias-0.25f;
		_trans = osg::Vec3d(xtrans, ytrans, ztrans);
	}

	virtual void operator()(Node* node, NodeVisitor* nv)
	{
		if (dynamic_cast<osg::MatrixTransform*>(node))
		{
			osg::MatrixTransform *moveMT = dynamic_cast<osg::MatrixTransform*>(node);
			moveMT->setMatrix(osg::Matrix::translate(_trans));
		}

		traverse(node, nv);
	}

	void setTrans(osg::Vec3d trans)
	{
		_trans = trans;
	}

	osg::Vec3d _trans;
};


//End
//


//
//RotateUpdateCallback 

class RotateXUpdateCallback : public osg::NodeCallback
{

public:

	RotateXUpdateCallback()
	{
		_pitch = lookupdown;
	}

	virtual void operator()(Node* node, NodeVisitor* nv)
	{
		if (dynamic_cast<osg::MatrixTransform*>(node))
		{
			osg::MatrixTransform *rotateX = dynamic_cast<osg::MatrixTransform*>(node);
			rotateX->setMatrix(osg::Matrix::rotate(_pitch, osg::X_AXIS));
		}

		traverse(node, nv);
	}

	void setPitch(double pitch)
	{
		_pitch = pitch;
	}

	double _pitch;
};


class RotateYUpdateCallback : public osg::NodeCallback
{

public:

	RotateYUpdateCallback()
	{
		_heading = osg::DegreesToRadians(360.0f) - yrot;
	}

	virtual void operator()(Node* node, NodeVisitor* nv)
	{
		if (dynamic_cast<osg::MatrixTransform*>(node))
		{
			osg::MatrixTransform *rotateY = dynamic_cast<osg::MatrixTransform*>(node);
			rotateY->setMatrix(osg::Matrix::rotate(_heading, osg::Y_AXIS));
		}

		traverse(node, nv);
	}

	void setHeading(double heading)
	{
		_heading = heading;
	}

	double _heading;
};


//End
//

TransUpdateCallback *g_moveMTCB;
RotateXUpdateCallback *g_xRotMTCB;
RotateYUpdateCallback *g_yRotMTCB;



osg::Texture2D*  createTexture(int mode)
{
	osg::Image *textureImage = osgDB::readImageFile("Data/Mud.bmp");
	osg::Texture2D *texture2D = new osg::Texture2D;
	texture2D->setImage(textureImage);

	if (mode == 0)
	{
		texture2D->setFilter(osg::Texture::MIN_FILTER, osg::Texture::NEAREST);
		texture2D->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST);
	}
	else if (mode == 1)
	{
		texture2D->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
		texture2D->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
	}
	else if (mode == 2)
	{
		texture2D->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_NEAREST);
		texture2D->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
	}

	texture2D->setWrap(osg::Texture::WRAP_R, osg::Texture::REPEAT);
	texture2D->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
	texture2D->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);

	return texture2D;
}

//

class ManipulatorSceneHandler : public osgGA::GUIEventHandler
{
public:
	ManipulatorSceneHandler()
	{
	}

	virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
	{
		osgViewer::Viewer *viewer = dynamic_cast<osgViewer::Viewer*>(&aa);
		if (!viewer)
			return false;
		if (!viewer->getSceneData())
			return false;
		if (ea.getHandled()) 
			return false;

		osg::Group *root = viewer->getSceneData()->asGroup();

		switch(ea.getEventType())
		{
		case(osgGA::GUIEventAdapter::KEYDOWN):
			{
				if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Up)
				{
					xpos -= (float)sin(heading*piover180) * 0.05f;
					zpos -= (float)cos(heading*piover180) * 0.05f;
					if (walkbiasangle >= 359.0f)
					{
						walkbiasangle = 0.0f;
					}
					else
					{
						walkbiasangle+= 10;
					}
					walkbias = (float)sin(walkbiasangle * piover180)/20.0f;
					GLfloat ytrans = -walkbias-0.25f;
					g_moveMTCB->setTrans(osg::Vec3d(-xpos, ytrans, -zpos));
				}

				if (ea.getKey()== osgGA::GUIEventAdapter::KEY_Down)
				{	
					xpos += (float)sin(heading*piover180) * 0.05f;
					zpos += (float)cos(heading*piover180) * 0.05f;
					if (walkbiasangle <= 1.0f)
					{
						walkbiasangle = 359.0f;
					}
					else
					{
						walkbiasangle-= 10;
					}
					walkbias = (float)sin(walkbiasangle * piover180)/20.0f;
					GLfloat ytrans = -walkbias-0.25f;
					g_moveMTCB->setTrans(osg::Vec3d(-xpos, ytrans, -zpos));
				}

				if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Left)
				{
					heading -= 1.0f;
					yrot = heading;
					double direction = osg::DegreesToRadians(360.0f) - osg::DegreesToRadians(yrot);
					g_yRotMTCB->setHeading(direction);
				}
				
				if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Right)
				{
					heading += 1.0f;
					yrot = heading;
					double direction = osg::DegreesToRadians(360.0f) - osg::DegreesToRadians(yrot);
					g_yRotMTCB->setHeading(direction);
				}

				if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Page_Up)
				{
					lookupdown += 1.0f;
					double direction = osg::DegreesToRadians(lookupdown);
					g_xRotMTCB->setPitch(direction);
				}

				if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Page_Down)
				{
					lookupdown -= 1.0f;
					double direction = osg::DegreesToRadians(lookupdown);
					g_xRotMTCB->setPitch(direction);
				}

			}
		default: break;
		}
		return false;
	}
};

//


class ViewerWidget : public QWidget, public osgViewer::Viewer
{
public:
	ViewerWidget(osg::Node *scene = NULL)
	{
		QWidget* renderWidget = getRenderWidget( createGraphicsWindow(0,0,100,100), scene);

		QVBoxLayout* layout = new QVBoxLayout;
		layout->addWidget(renderWidget);
		layout->setContentsMargins(0, 0, 0, 1);
		setLayout( layout );

		connect( &_timer, SIGNAL(timeout()), this, SLOT(update()) );
		_timer.start( 10 );
	}

	QWidget* getRenderWidget( osgQt::GraphicsWindowQt* gw, osg::Node* scene )
	{
		osg::Camera* camera = this->getCamera();
		camera->setGraphicsContext( gw );

		const osg::GraphicsContext::Traits* traits = gw->getTraits();

		camera->setClearColor( osg::Vec4(0.0, 0.0, 0.0, 1.0) );
		camera->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) );
		camera->setProjectionMatrixAsPerspective(45.0f, static_cast<double>(traits->width)/static_cast<double>(traits->height), 0.1f, 100.0f );
		camera->setViewMatrixAsLookAt(osg::Vec3d(0, 0, 1), osg::Vec3d(0, 0, 0), osg::Vec3d(0, 1, 0));

		this->setSceneData( scene );
		this->addEventHandler(new ManipulatorSceneHandler);

		return gw->getGLWidget();
	}

	osgQt::GraphicsWindowQt* createGraphicsWindow( int x, int y, int w, int h, const std::string& name="", bool windowDecoration=false )
	{
		osg::DisplaySettings* ds = osg::DisplaySettings::instance().get();
		osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
		traits->windowName = name;
		traits->windowDecoration = windowDecoration;
		traits->x = x;
		traits->y = y;
		traits->width = w;
		traits->height = h;
		traits->doubleBuffer = true;
		traits->alpha = ds->getMinimumNumAlphaBits();
		traits->stencil = ds->getMinimumNumStencilBits();
		traits->sampleBuffers = ds->getMultiSamples();
		traits->samples = ds->getNumMultiSamples();

		return new osgQt::GraphicsWindowQt(traits.get());
	}

	virtual void paintEvent( QPaintEvent* event )
	{ 
		frame(); 
	}

protected:

	QTimer _timer;
};



osg::Node*	buildScene()
{
	SetupWorld();
	osg::Group *root = new osg::Group;

	osg::Vec3Array *vertexArray = new osg::Vec3Array;
	osg::Vec2Array *textureArray = new osg::Vec2Array;

	unsigned int num = Sector.iNumTriangle;

	for (unsigned i = 0; i < num; ++i)
	{
		vertexArray->push_back(osg::Vec3(Sector.pTriangle[i].vertex[0].x, Sector.pTriangle[i].vertex[0].y, Sector.pTriangle[i].vertex[0].z));
		vertexArray->push_back(osg::Vec3(Sector.pTriangle[i].vertex[1].x, Sector.pTriangle[i].vertex[1].y, Sector.pTriangle[i].vertex[1].z));
		vertexArray->push_back(osg::Vec3(Sector.pTriangle[i].vertex[2].x, Sector.pTriangle[i].vertex[2].y, Sector.pTriangle[i].vertex[2].z));

		textureArray->push_back(osg::Vec2(Sector.pTriangle[i].vertex[0].u, Sector.pTriangle[i].vertex[0].v));
		textureArray->push_back(osg::Vec2(Sector.pTriangle[i].vertex[1].u, Sector.pTriangle[i].vertex[1].v));
		textureArray->push_back(osg::Vec2(Sector.pTriangle[i].vertex[2].u, Sector.pTriangle[i].vertex[2].v));
	}

	osg::MatrixTransform *moveMT = new osg::MatrixTransform;
	TransUpdateCallback *tucb = new TransUpdateCallback;
	moveMT->setUpdateCallback(tucb);
	g_moveMTCB = tucb;

	osg::MatrixTransform *headingMT = new osg::MatrixTransform;
	RotateYUpdateCallback *ryuc = new RotateYUpdateCallback;
	headingMT->setUpdateCallback(ryuc);
	g_yRotMTCB = ryuc;

	osg::MatrixTransform *pitchMT = new osg::MatrixTransform;
	RotateXUpdateCallback *rxuc = new RotateXUpdateCallback;
	pitchMT->setUpdateCallback(rxuc);
	g_xRotMTCB = rxuc;

	headingMT->addChild(pitchMT);
	pitchMT->addChild(moveMT);

	osg::Geometry *triangleGeometry = new osg::Geometry;
	triangleGeometry->setVertexArray(vertexArray);

	triangleGeometry->setTexCoordArray(0, textureArray);
	triangleGeometry->getOrCreateStateSet()->setTextureAttributeAndModes(0, createTexture(0));

	triangleGeometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, 108));
	osg::Geode *triangleGeode = new osg::Geode;
	triangleGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
	triangleGeode->addDrawable(triangleGeometry);
	moveMT->addChild(triangleGeode);
	root->addChild(headingMT);

	return root;
}


int main( int argc, char** argv )
{
	QApplication app(argc, argv);
	ViewerWidget* viewWidget = new ViewerWidget(buildScene());
	viewWidget->setGeometry( 100, 100, 640, 480 );
	viewWidget->show();
	return app.exec();
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值