-
简介
NeHe教程在这节课中向我们介绍了如何从文件加载3D模型,并且平滑的从一个模型变换为另一个模型。这个过程又称为逐点动画,点之间的变化过程算法十分简单,采用的是线性的算法。
-
实现
首先我们仿照NeHe课程中定义了表示点的结构体以及读取文件的结构体OBJECT并读取文件
void initMorphObject()
{
objload("Data/sphere.txt",&morph1);
objload("Data/torus.txt",&morph2);
objload("Data/tube.txt",&morph3);
objallocate(&morph4,486);
morph4.verts = 486;
for(int i=0;i<486;i++)
{
morph4.points[i].x=((float)(rand()%14000)/1000)-7;
morph4.points[i].y=((float)(rand()%14000)/1000)-7;
morph4.points[i].z=((float)(rand()%14000)/1000)-7;
}
morphGeometry1 = new osg::Geometry;
osg::Vec3Array *vertexArray1 = new osg::Vec3Array;
for (unsigned i = 0; i < morph1.verts; ++i)
{
vertexArray1->push_back(osg::Vec3(morph1.points[i].x, morph1.points[i].y, morph1.points[i].z));
}
morphGeometry1->setVertexArray(vertexArray1);
morphGeometry1->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::POINTS, 0, 486));
morphGeometry1->setUseDisplayList(false);
srcMorphGeometry = dynamic_cast<osg::Geometry*>(morphGeometry1->clone(osg::CopyOp::DEEP_COPY_ALL));
destMorphGeometry = morphGeometry1;
...
}
读取文件之后构建4个几何体对象,接着在几何体更新回调中处理逐个点的变化
上述的处理过程在几何体的更新回调MorphUpdateCallback之中
virtual void update(osg::NodeVisitor*, osg::Drawable* drawable)
{
osg::Geometry *geom = dynamic_cast<osg::Geometry*>(drawable);
if (!geom)
return;
osg::Vec3Array *vertices = dynamic_cast<osg::Vec3Array*>(geom->getVertexArray());
osg::Vec3Array *destVertices = dynamic_cast<osg::Vec3Array*>(_destGeometry->getVertexArray());
if (vertices && destVertices)
{
for (unsigned i = 0; i < vertices->size(); ++i)
{
osg::Vec3 delta = (destVertices->at(i) - vertices->at(i)) / _steps;
vertices->at(i) += delta;
}
vertices->dirty();
}
}
在与键盘的交互中设置变换的几何体目标对象:
if (ea.getKey() == osgGA::GUIEventAdapter::KEY_1)
{
MorphUpdateCallback *muc = dynamic_cast<MorphUpdateCallback*>(srcMorphGeometry->getUpdateCallback());
if (!muc)
return false;
muc->setDestGeometry(morphGeometry1);
}
if (ea.getKey() == osgGA::GUIEventAdapter::KEY_2)
{
MorphUpdateCallback *muc = dynamic_cast<MorphUpdateCallback*>(srcMorphGeometry->getUpdateCallback());
if (!muc)
return false;
muc->setDestGeometry(morphGeometry2);
}
通过设置几何体变换的开始形状(srcGeometry)和最终变换到的目标几何体(destGeometry),在回调中不断调整顶点位置完成动画效果。
编译运行程序:
附:本课源码(源码中可能存在错误和不足,仅供参考)
/************************************************************************\
* osgNeHe - Copyright (C) 2013-2014 Frank He
*
* This library is open source and may be redistributed and/or modified under
* the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
* (at your option) any later version. The full license is in LICENSE file
* included with this distribution, and on the openscenegraph.org website.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* A HUGE thanks to NeHe for the OpenGL tutorials< http://nehe.gamedev.net >
*
* If you've found this code useful, Please let me know.
* My E-mail: < hzhxfrank@gmail.com >
* My Blog: < http://blog.csdn.net/csxiaoshui >
*
\************************************************************************/
#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/Geometry>
GLfloat cx,cy,cz = -15.0f;
int step=0;
int steps = 200;
bool morph = false;
osg::MatrixTransform *g_transMT = NULL;
osg::MatrixTransform *g_rotateX = NULL;
osg::MatrixTransform *g_rotateY = NULL;
osg::MatrixTransform *g_rotateZ = NULL;
struct VERTEX
{
float x, y, z;
};
struct OBJECT
{
int verts;
VERTEX *points;
} ;
OBJECT morph1,morph2,morph3,morph4;
osg::Geometry *morphGeometry1, *morphGeometry2, *morphGeometry3, *morphGeometry4;
osg::Geometry *srcMorphGeometry, *destMorphGeometry;
void objallocate(OBJECT *k,int n)
{
k->points=(VERTEX*)malloc(sizeof(VERTEX)*n);
}
void objfree(OBJECT *k)
{
free(k->points);
}
void readstr(FILE *f,char *string)
{
do
{
fgets(string, 255, f);
} while ((string[0] == '/') || (string[0] == '\n'));
return;
}
void objload(char *name,OBJECT *k)
{
int ver;
float rx,ry,rz;
FILE *filein;
char oneline[255];
filein = fopen(name, "rt");
readstr(filein,oneline);
sscanf(oneline, "Vertices: %d\n", &ver);
k->verts=ver;
objallocate(k,ver);
for (int i=0;i<ver;i++)
{
readstr(filein,oneline);
sscanf(oneline, "%f %f %f", &rx, &ry, &rz);
k->points[i].x = rx;
k->points[i].y = ry;
k->points[i].z = rz;
}
fclose(filein);
}
//
class MorphUpdateCallback : public osg::Drawable::UpdateCallback
{
public:
MorphUpdateCallback(osg::Geometry *dest, int steps) : _destGeometry(dest), _steps(steps)
{
}
void setDestGeometry(osg::Geometry *destGeometry)
{
_destGeometry = destGeometry;
}
virtual void update(osg::NodeVisitor*, osg::Drawable* drawable)
{
osg::Geometry *geom = dynamic_cast<osg::Geometry*>(drawable);
if (!geom)
return;
osg::Vec3Array *vertices = dynamic_cast<osg::Vec3Array*>(geom->getVertexArray());
osg::Vec3Array *destVertices = dynamic_cast<osg::Vec3Array*>(_destGeometry->getVertexArray());
if (vertices && destVertices)
{
for (unsigned i = 0; i < vertices->size(); ++i)
{
osg::Vec3 delta = (destVertices->at(i) - vertices->at(i)) / _steps;
vertices->at(i) += delta;
}
vertices->dirty();
}
}
int _steps;
osg::Geometry* _destGeometry;
};
void initMorphObject()
{
objload("Data/sphere.txt",&morph1);
objload("Data/torus.txt",&morph2);
objload("Data/tube.txt",&morph3);
objallocate(&morph4,486);
morph4.verts = 486;
for(int i=0;i<486;i++)
{
morph4.points[i].x=((float)(rand()%14000)/1000)-7;
morph4.points[i].y=((float)(rand()%14000)/1000)-7;
morph4.points[i].z=((float)(rand()%14000)/1000)-7;
}
morphGeometry1 = new osg::Geometry;
osg::Vec3Array *vertexArray1 = new osg::Vec3Array;
for (unsigned i = 0; i < morph1.verts; ++i)
{
vertexArray1->push_back(osg::Vec3(morph1.points[i].x, morph1.points[i].y, morph1.points[i].z));
}
morphGeometry1->setVertexArray(vertexArray1);
morphGeometry1->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::POINTS, 0, 486));
morphGeometry1->setUseDisplayList(false);
srcMorphGeometry = dynamic_cast<osg::Geometry*>(morphGeometry1->clone(osg::CopyOp::DEEP_COPY_ALL));
destMorphGeometry = morphGeometry1;
morphGeometry2 = new osg::Geometry;
osg::Vec3Array *vertexArray2 = new osg::Vec3Array;
for (unsigned i = 0; i < morph2.verts; ++i)
{
vertexArray2->push_back(osg::Vec3(morph2.points[i].x, morph2.points[i].y, morph2.points[i].z));
}
morphGeometry2->setVertexArray(vertexArray2);
morphGeometry2->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::POINTS, 0, 486));
morphGeometry2->setUseDisplayList(false);
morphGeometry3 = new osg::Geometry;
osg::Vec3Array *vertexArray3 = new osg::Vec3Array;
for (unsigned i = 0; i < morph3.verts; ++i)
{
vertexArray3->push_back(osg::Vec3(morph3.points[i].x, morph3.points[i].y, morph3.points[i].z));
}
morphGeometry3->setVertexArray(vertexArray3);
morphGeometry3->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::POINTS, 0, 486));
morphGeometry3->setUseDisplayList(false);
morphGeometry4 = new osg::Geometry;
osg::Vec3Array *vertexArray4 = new osg::Vec3Array;
for (unsigned i = 0; i < morph4.verts; ++i)
{
vertexArray4->push_back(osg::Vec3(morph4.points[i].x, morph4.points[i].y, morph4.points[i].z));
}
morphGeometry4->setVertexArray(vertexArray4);
morphGeometry4->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::POINTS, 0, 486));
morphGeometry4->setUseDisplayList(false);
}
//
class RotAxisCallback : public osg::NodeCallback
{
public:
RotAxisCallback(const osg::Vec3& axis, double rotSpeed = 0.0, double currentAngle = 0.0)
: _rotAxis(axis), _rotSpeed(rotSpeed), _currentAngle(currentAngle){ }
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
osg::MatrixTransform *rotMT = dynamic_cast<osg::MatrixTransform*>(node);
if (!rotMT)
return;
rotMT->setMatrix(osg::Matrix::rotate(_currentAngle, _rotAxis));
_currentAngle += _rotSpeed;
traverse(node, nv);
}
void setRotateSpeed(double speed)
{
_rotSpeed = speed;
}
double getRotateSpeed() const
{
return _rotSpeed;
}
private:
osg::Vec3 _rotAxis;
double _currentAngle;
double _rotSpeed;
};
//
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_Left)
{
if (!g_rotateX)
return false;
RotAxisCallback *rotCallback = dynamic_cast<RotAxisCallback*>(g_rotateX->getUpdateCallback());
if (!rotCallback)
return false;
double speed = rotCallback->getRotateSpeed();
speed -= 0.02;
rotCallback->setRotateSpeed(speed);
}
if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Right)
{
if (!g_rotateX)
return false;
RotAxisCallback *rotCallback = dynamic_cast<RotAxisCallback*>(g_rotateX->getUpdateCallback());
if (!rotCallback)
return false;
double speed = rotCallback->getRotateSpeed();
speed += 0.02;
rotCallback->setRotateSpeed(speed);
}
if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Up)
{
if (!g_rotateY)
return false;
RotAxisCallback *rotCallback = dynamic_cast<RotAxisCallback*>(g_rotateY->getUpdateCallback());
if (!rotCallback)
return false;
double speed = rotCallback->getRotateSpeed();
speed -= 0.02;
rotCallback->setRotateSpeed(speed);
}
if (ea.getKey()== osgGA::GUIEventAdapter::KEY_Down)
{
if (!g_rotateY)
return false;
RotAxisCallback *rotCallback = dynamic_cast<RotAxisCallback*>(g_rotateY->getUpdateCallback());
if (!rotCallback)
return false;
double speed = rotCallback->getRotateSpeed();
speed += 0.02;
rotCallback->setRotateSpeed(speed);
}
if (ea.getKey()== osgGA::GUIEventAdapter::KEY_Page_Up)
{
if (!g_rotateZ)
return false;
RotAxisCallback *rotCallback = dynamic_cast<RotAxisCallback*>(g_rotateZ->getUpdateCallback());
if (!rotCallback)
return false;
double speed = rotCallback->getRotateSpeed();
speed -= 0.02;
rotCallback->setRotateSpeed(speed);
}
if (ea.getKey()== osgGA::GUIEventAdapter::KEY_Page_Down)
{
if (!g_rotateZ)
return false;
RotAxisCallback *rotCallback = dynamic_cast<RotAxisCallback*>(g_rotateZ->getUpdateCallback());
if (!rotCallback)
return false;
double speed = rotCallback->getRotateSpeed();
speed += 0.02;
rotCallback->setRotateSpeed(speed);
}
if (ea.getKey()== osgGA::GUIEventAdapter::KEY_A)
{
if (!g_transMT)
return false;
osg::Matrix transMatrix = g_transMT->getMatrix();
transMatrix.setTrans(transMatrix.getTrans() + osg::Vec3(-0.1, 0, 0));
g_transMT->setMatrix(transMatrix);
}
if (ea.getKey()== osgGA::GUIEventAdapter::KEY_D)
{
if (!g_transMT)
return false;
osg::Matrix transMatrix = g_transMT->getMatrix();
transMatrix.setTrans(transMatrix.getTrans() + osg::Vec3(0.1, 0, 0));
g_transMT->setMatrix(transMatrix);
}
if (ea.getKey()== osgGA::GUIEventAdapter::KEY_W)
{
if (!g_transMT)
return false;
osg::Matrix transMatrix = g_transMT->getMatrix();
transMatrix.setTrans(transMatrix.getTrans() + osg::Vec3(0, 0.1, 0));
g_transMT->setMatrix(transMatrix);
}
if (ea.getKey()== osgGA::GUIEventAdapter::KEY_S)
{
if (!g_transMT)
return false;
osg::Matrix transMatrix = g_transMT->getMatrix();
transMatrix.setTrans(transMatrix.getTrans() + osg::Vec3(0, -0.1, 0));
g_transMT->setMatrix(transMatrix);
}
if (ea.getKey()== osgGA::GUIEventAdapter::KEY_Q)
{
if (!g_transMT)
return false;
osg::Matrix transMatrix = g_transMT->getMatrix();
transMatrix.setTrans(transMatrix.getTrans() + osg::Vec3(0, 0, -0.1));
g_transMT->setMatrix(transMatrix);
}
if (ea.getKey()== osgGA::GUIEventAdapter::KEY_Z)
{
if (!g_transMT)
return false;
osg::Matrix transMatrix = g_transMT->getMatrix();
transMatrix.setTrans(transMatrix.getTrans() + osg::Vec3(0, 0, 0.1));
g_transMT->setMatrix(transMatrix);
}
if (ea.getKey() == osgGA::GUIEventAdapter::KEY_1)
{
MorphUpdateCallback *muc = dynamic_cast<MorphUpdateCallback*>(srcMorphGeometry->getUpdateCallback());
if (!muc)
return false;
muc->setDestGeometry(morphGeometry1);
}
if (ea.getKey() == osgGA::GUIEventAdapter::KEY_2)
{
MorphUpdateCallback *muc = dynamic_cast<MorphUpdateCallback*>(srcMorphGeometry->getUpdateCallback());
if (!muc)
return false;
muc->setDestGeometry(morphGeometry2);
}
if (ea.getKey() == osgGA::GUIEventAdapter::KEY_3)
{
MorphUpdateCallback *muc = dynamic_cast<MorphUpdateCallback*>(srcMorphGeometry->getUpdateCallback());
if (!muc)
return false;
muc->setDestGeometry(morphGeometry3);
}
if (ea.getKey() == osgGA::GUIEventAdapter::KEY_4)
{
MorphUpdateCallback *muc = dynamic_cast<MorphUpdateCallback*>(srcMorphGeometry->getUpdateCallback());
if (!muc)
return false;
muc->setDestGeometry(morphGeometry4);
}
}
default: break;
}
return false;
}
};
class ViewerWidget : public QWidget, public osgViewer::Viewer
{
public:
ViewerWidget(osg::Node *scene = NULL)
{
QWidget* renderWidget = getRenderWidget( createGraphicsWindow(0,0,640,480), 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()
{
initMorphObject();
osg::Group *root = new osg::Group;
root->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);
osg::MatrixTransform *mt = new osg::MatrixTransform;
g_transMT = mt;
mt->setMatrix(osg::Matrix::translate(0, 0, cz));
osg::MatrixTransform *rotX = new osg::MatrixTransform;
g_rotateX = rotX;
rotX->addUpdateCallback(new RotAxisCallback(osg::X_AXIS));
osg::MatrixTransform *rotY = new osg::MatrixTransform;
g_rotateY = rotY;
rotY->addUpdateCallback(new RotAxisCallback(osg::Y_AXIS));
osg::MatrixTransform *rotZ = new osg::MatrixTransform;
g_rotateZ = rotZ;
rotZ->addUpdateCallback(new RotAxisCallback(osg::Z_AXIS));
osg::Geode *geode = new osg::Geode;
geode->addDrawable(srcMorphGeometry);
srcMorphGeometry->setUpdateCallback(new MorphUpdateCallback(destMorphGeometry,200));
root->addChild(mt);
mt->addChild(rotX);
rotX->addChild(rotY);
rotY->addChild(rotZ);
rotZ->addChild(geode);
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();
}