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