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

FAST-LIO2实战:如何在无人机上部署并实现厘米级定位(附ROS配置全流程) 本文详细介绍了如何在无人机上部署FAST-LIO2激光雷达惯性里程计系统,以实现厘米级精确定位。内容涵盖从硬件选型(如激光雷达与机载计算机)、软件环境配置(ROS Noetic)、传感器标定,到FAST-LIO2参数调优、与PX4飞控集成以及实际飞行测试的全流程。重点解决了实时定位中的外参标定、数据同步等关键问题,为无人机在无GPS或复杂光照环境下提供稳定可靠的定位解决方案。 阅读详情
  • 简介

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

一般来说,场景都是通过外部建模工具生成的,然后再通过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();
}



Python爬虫实战:5分钟搞定大众点评评论数据抓取(附完整代码) 本文详细介绍了使用Python爬虫高效获取大众点评评论数据的5个关键技巧,包括环境配置、智能页面解析、反反爬策略、数据存储与容错设计以及性能优化。通过实战案例和完整代码,帮助开发者快速掌握文本挖掘技术,适用于市场分析和商业决策。 阅读详情

相关推荐

海思MIPI屏调试实战:从参数计算到点亮屏幕的完整指南

本文提供了一份海思平台MIPI屏幕调试的完整实战指南。文章以Hi3516DV300调试800x1280分辨率屏幕为例,详细讲解了从参数计算、配置文件修改到最终点亮屏幕的全流程,并分享了常见问题排查与进阶优化技巧,旨在帮助嵌入式开发者高效解决MIPI屏调试难题。

weixin_29190573的博客 736

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

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

ALLENJIAO的专栏 812

【超简单】一文入门在Ubuntu系统使用GitHub

Github是基于git的代码仓库,对于开发项目的科研者或者工程师,代码仓库都是不可多得的版本控制与多端编程工具,今天这篇文章带你轻松Github入门,简单的项目本文的操作足矣。

a13337649640的博客 5236

电力电子矢量输出总结

扇区图 扇区确定 时间计算

zhaopeng的博客 953

【HAL库】STM32CubeMX开发----STM32F407----SPI实验

【HAL库】STM32CubeMX开发----STM32F407----SPI实验

MQ0522的博客 3020

【AutoSAR】【WdgM】WdgM安全管理模块

一、WdgM在AutoSAR里的整体架构 WdgM是在autosar架构里位于服务层的一个基础软件模块;主要监督程序的执行; 主要包括三个模块: 看门狗管理器(WdgM) 看门狗接口(WdgIf) 看门狗驱动(Wdg) 二、三种监督机制 Alive监控-------主要监控周期性软件程序的时间 DeadLine监控-------主要监控非周期软件程序 程序流监控-------主要监控程序流执行的正确性 三、基于全局状态的检测流程 受监管实体(SE):Supervised Ent

专注汽车软件开发、AutoSAR、车载以太网、SOA、EE架构。 2993

OpenGl中模拟走动效果

模拟走动效果;1、相应VK_UP按键消息,并作出如下处理    if (keys[VK_UP])    {     xpos -= (float)sin(heading*piover180) * 0.05f;     zpos -= (float)cos(heading*piover180) * 0.05f;     if (walkbiasangle >= 359.0f)     {    

skyspark的专栏 1464

QML 果冻抖动:用 Scale 动画模拟 Q 弹反馈

介绍果冻抖动效果:点击后卡片通过多段 ParallelAnimation 实现 Scale X/Y 反向缩放和逐步衰减,模拟果冻弹性形变;进阶版本加入小幅 Rotation 增加活泼感,适合游戏、儿童应用等趣味反馈场景。

u011186532的专栏 426

Nehe教程第10课3D世界

Nehe教程第10课4D世界源程序及资源 #include &lt;stdlib.h&gt; #include &lt;math.h&gt; #include &lt;stdio.h&gt; #include &lt;windows.h&gt; // Windows的头文件 #include &lt;gl/glew.h&gt; // 包含最新的gl.h,glu.h库 #include &l...

java开发指南博客 【转载】 201

Python时间序列:PaddleTS-时间序列分类

评估的指标采用F1,Precision,Recall,PaddleTS已经基于TSDataset实现了这三个指标的计算,直接调用即可。使用内置的API:get_dataset加载BasicMotions数据集。模型训练完成之后,可以对测试集进行预测并评估。模型保存之后,可以加载模型用于新的数据预测。可以将训练完成的模型保存到指定路径。概率预测 可以返回每个分类的概率。安装paddlets。

斌擎科技 1293

lammps蒙特卡洛MC模拟案例

在lammps中,和MC相关的模拟命令有三个:fix gcmc、fix atom/swap、fix sgcmc。在一个lammps模拟中,mc和md可同时存在,间隔运行,所以设置fix sgcmc命令的同时,可以同时设置系综(nve、nvt或者npt)。lammps官网自带案例中也有一个fix sgcmc的案例,主要模拟FeCu合金的MC建模。sgcmc命令可应用到合金的模拟中,如短程有序模拟。MC运行间隔为100步,最终的浓度比例为0.02,也就是2%的Fe原子交换为Cu原子。大家好,我是小马老师。

lammps_jiayou的博客 1825

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

简介 OpenSceneGraph是一个开放源码,跨平台的图形开发包,它为诸如飞行器仿真,游戏,虚拟现实,科学计算可视化这样的高性能图形应用程序开发而设计。它基于场景图的概念,它提供一个在OpenGL之上的面向对象的框架,从而能把开发者从实现和优化底层图形的调用中解脱出来,并且它为图形应用程序的快速开发提供很多附加的实用工具。 NeHe教程是目前针对初学者来说最

Frank的专栏 1万+

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

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

简介 NeHe教程在这节课中向我们介绍了如何读取显卡支持的OpenGL的扩展,如何使用Targa(TGA)图像文件作为纹理,以及如何利用OpenGL的剪裁区域来滚动屏幕文字。 在OpenInventor中没有相关的函数来获取OpenGL的扩展信息,我们只能通过直接调用OpenGL来获得这些信息。OpenInventor的纹理节点自动支持使用Targa(TGA)图像文件作为纹理,所以我们

Frank的专栏 2317

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

OpenSceneGraph实现的NeHe OpenGL教程 - 第一课

目录(?)[-] 简介创建一个osg窗口 简介 OpenSceneGraph是一个开放源码,跨平台的图形开发包,它为诸如飞行器仿真,游戏,虚拟现实,科学计算可视化这样的高性能图形应用程序开发而设计。它基于场景图的概念,它提供一个在OpenGL之上的面向对象的框架,从而能把开发者从实现和优化底层图形的调用中解脱出来,并且它为图形应用程序的快速

ALLENJIAO的专栏 1201

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

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

简介 NeHe教程在这节课中向我们介绍了如何读取显卡支持的OpenGL的扩展,如何使用Targa(TGA)图像文件作为纹理,以及如何利用OpenGL的剪裁区域来滚动屏幕文字。osg支持tga格式的文件读取,因此不需要像NeHe课程那样解析TGA格式,有兴趣的读者可以查看osgPlugins库中的ReaderWriterTGA.cpp文件了解osg是如何读取TGA图片格式的。

ALLENJIAO的专栏 1110

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

简介 这节课我们将创建一个以正弦波方式飘动的旗帜。本课所用到的知识在前面的课程中都有讲解,并没有什么新的内容 实现 首先创建我们的场景,关于旗帜的顶点坐标在NeHe教程中已经有非常详细的介绍,本文就不在赘述了。 float points[45][45][3]; for(int x=0; x<45; x++) { for(int y=0; y<45; y++

Frank的专栏 1579

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

简介 这节课NeHe教我们怎样把图片

Frank的专栏 2303

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

这节课主要工作是在场景中添加一个三角形和一个四边形。本节新增加一个函数buildScene,这个函数将在场景中创建一个三角形和一个四边形。 osg场景介绍 在添加场景之前,先对osg的场景结果有一个了解,osg采用场景树的方式来组织场景,如下图所示: 场景已节点(Node)的方式来组织,包括组节点Group(用来管理节点),空间变换节点(MatrixTransform),细节

Frank的专栏 3860

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

简介 这节课我们将创建一个以正弦波方式飘动的旗帜。本课所用到的知识在前面的课程中都有讲解,并没有什么新的内容 实现 首先创建我们的场景,关于旗帜的顶点坐标在NeHe教程中已经有非常详细的介绍,本文就不在赘述了。 [cpp] view plain copy   float points[45][45][3];

ALLENJIAO的专栏 717

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

简介 NeHe教程在这节课中向我们介绍了如何从文件加载3D模型,并且平滑的从一个模型变换为另一个模型。两个模型之间平滑过渡的算法其实是很简单的,首先我们要保证两个模型要有相同数目的顶点,然后我们分别计算每个点从一个模型移动到另外一个模型时,中间的位置数据。我们将这个过程显示出来就会产生模型平滑过渡的效果。

Frank的专栏 1747

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

简介 本课NeHe教程主要是使用了OpenGL中的显示列表。OpenGL的显示列表是加速OpenGL性能的一种重要的手段。OpenGl的几何体绘制方法主要包括立即模式、顶点数组、显示列表和VBO。在OSG中显示列表是大部分OSG程序所使用的默认绘制优化行为,因此不需要我们去像OpenGL那样进行设置。 实现 首先创建整个场景,本课中我使用了OSG中预定义的几种基本形体,(os

Frank的专栏 7883

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

简介 本节课NeHe课程主要想我们介绍了OpenGL中的拾取(Selection)的方式来和场景中的物体进行交换,在OSG中并没有封装OpenGL中的拾取方式,而是用了更高效的一种方式来实现与场景中的模型进行求交的操作,对于OSG这样处理的原因可以参考OSG中的这个MailList: New to OSG, some questions (selection buffer, para

ALLENJIAO的专栏 541

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

简介 这节课NeHe课程实现了在场景中进行碰撞检测以及简单的物理作用(碰撞之后的反弹),碰撞检测三维图形学中一个比较高级的主题,本课只涉及了简单的规则几何体之间的碰撞检测,更复杂的实例需要读者去更深入的学习。 实现 本课中碰撞检测的算法包括线与平面的求交检测、线与圆柱体的求交、以及球体之间的碰撞检测,这些碰撞检测的原理可以参考NeHe中的介绍。 具体实现在如下几

ALLENJIAO的专栏 722

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

简介 这节课NeHe

Frank的专栏 1753

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

简介 这节课NeHe课程教我们如何读取一张raw格式的图片,并且对两张raw格式图片进行处理生成了一张纹理图片。整个过程并不是十分复杂,文中大部分内容涉及到图片数据的操作。 实现 首先我们创建需要显示的立方体: [cpp] view plain copy osg::Geode* createCubeGeode(osg

ALLENJIAO的专栏 686

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

简介 NeHe这节课主要讨论OpenGL怎样给文字赋予纹理贴图。OpenGL使用glTexGen函数给文字自动生成纹理坐标。在OSG中,使用osg::TexGen这个继承自StateAttribute的类来实现相同的效果 实现 首先将创建3D的字体,代码如下: osg::Geode *fontGeode = new osg::Geode;     osg::I

Frank的专栏 1780
上一篇: 用OpenSceneGraph实现的NeHe OpenGL教程 - 第九课
下一篇: 用OpenSceneGraph实现的NeHe OpenGL教程 - 第十一课
csxiaoshui
博客等级 码龄16年 873粉丝 158原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值