本文实现从外部文件中加载场景,并进行简单的场景漫游操作。
一般来说,场景都是通过外部建模工具生成的,然后再通过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); }
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轴旋转)以及上下旋转
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(); }