这节课NeHe课程主要向我们展示了将物理运动规律引入到三维场景中,模拟真实物体的位置变化过程。这节课分别模拟了如下几种运动方式:
(1)在重力作用下的抛物线运动;
(2)匀速运动
(3)一种类似于弹簧一样的来回运动
这些运动的方程在Physics.h头文件中描述的很清楚,稍有物理知识应该不难理解。
首先我们在场景中添加各种几何体:
root->addChild(createBackGroundGeode()); //网状背景 root->addChild(constantVelocityNode()); //匀速模拟 root->addChild(underGravitationNode()); // 重力模拟 root->addChild(massConnectedWithSpringNode()); //弹簧来回运动模拟另外需要添加场景中的说明文字:
osg::Node *timeEllapsedNode = glPrint(-15.0f, 14, 0, osg::Vec4(1,1,1,1), "Time elapsed (seconds): %.2f", timeElapsed); timeEllapsedNode->addUpdateCallback(new TimeEllapsedUpdateCallback); root->addChild(timeEllapsedNode); osg::Node *ratioNode = glPrint(-15.0f, 13, 0, osg::Vec4(1,1,1,1),"Slow motion ratio: %.2f", slowMotionRatio); ratioNode->addUpdateCallback(new RatioUpdateCallback); root->addChild(ratioNode); root->addChild(glPrint(-15.0f, 12, 0, osg::Vec4(1,1,1,1),"Press F2 for normal motion")); root->addChild(glPrint(-15.0f, 11, 0, osg::Vec4(1,1,1,1),"Press F3 for slow motion"));
接下来便是一系列的更新回调,按照物理运动的规律更新节点位置:
//Constant Callback class ConstantPointUpdateCallback : public osg::Drawable::UpdateCallback { public: virtual void update(osg::NodeVisitor*, osg::Drawable* drawable) { osg::Geometry *geometry = dynamic_cast<osg::Geometry*>(drawable); if (!geometry) return; osg::Vec3Array *vertexArray = dynamic_cast<osg::Vec3Array*>(geometry->getVertexArray()); if (!vertexArray) return; vertexArray->clear(); for (int a = 0; a < constantVelocity->numOfMasses; ++a) { Mass* mass = constantVelocity->getMass(a); Vector3D* pos = &mass->pos; vertexArray->push_back(osg::Vec3(pos->x, pos->y, pos->z)); } geometry->setVertexArray(vertexArray); vertexArray->dirty(); } }; class ConstantWordUpdateCallback : public osg::NodeCallback { public: virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) { osg::MatrixTransform *mt = dynamic_cast<osg::MatrixTransform*>(node); if (!mt) return; for (int a = 0; a < constantVelocity->numOfMasses; ++a) { Mass* mass = constantVelocity->getMass(a); Vector3D* pos = &mass->pos; mt->setMatrix(osg::Matrix::translate(pos->x, pos->y + 1, pos->z)); } traverse(node, nv); } };为了篇幅限制,这里只给出匀速运动的回调代码,其他部分参看附录中的源码。
我们需要在每帧中计算时间以及在每帧中更新物理运动的参数,这部分在交互的代码FRAME(帧循环事件)中给出:
case (osgGA::GUIEventAdapter::FRAME): { double dt = 0.0; _currentTick = osg::Timer::instance()->tick(); if (_currentTick != _lastTick) { dt = osg::Timer::instance()->delta_s(_lastTick, _currentTick); _lastTick = _currentTick; } dt /= slowMotionRatio; timeElapsed += dt; float maxPossible_dt = 0.1f; int numOfIterations = (int)(dt / maxPossible_dt) + 1; if (numOfIterations != 0) dt = dt / numOfIterations; for (int a = 0; a < numOfIterations; ++a) { constantVelocity->operate(dt); motionUnderGravitation->operate(dt); massConnectedWithSpring->operate(dt); } }此外在按下F2和F3时调整运动的速度:
if (ea.getKey() == osgGA::GUIEventAdapter::KEY_F2) { slowMotionRatio = 1.0f; } if (ea.getKey() == osgGA::GUIEventAdapter::KEY_F3) { slowMotionRatio = 10.0f; }编译运行程序:
附:本课源码(源码中可能存在错误和不足,仅供参考)
#include "../osgNeHe.h" #include "Physics1.h" #include <QtCore/QTimer> #include <QtGui/QApplication> #include <QtGui/QVBoxLayout> #include <osgViewer/Viewer> #include <osgDB/ReadFile> #include <osgQt/GraphicsWindowQt> #include <osg/MatrixTransform> #include <osgText/Text> #include <osg/Point> #include <osgGA/TrackballManipulator> ////////////////////////////////////////////////////////////////////////// ConstantVelocity* constantVelocity = new ConstantVelocity(); MotionUnderGravitation* motionUnderGravitation = new MotionUnderGravitation(Vector3D(0.0f, -9.81f, 0.0f)); MassConnectedWithSpring* massConnectedWithSpring = new MassConnectedWithSpring(2.0f); float slowMotionRatio = 10.0f; float timeElapsed = 0; osg::Node* glPrint(GLfloat x, GLfloat y, GLfloat z, const osg::Vec4& fontColor, const char *string, ...) { char text[256]; va_list ap; if (string == NULL) return NULL; va_start(ap, string); vsprintf(text, string, ap); va_end(ap); osg::MatrixTransform *posMT = new osg::MatrixTransform; posMT->setMatrix(osg::Matrix::translate(x, y, z)); osgText::Text *textWords = new osgText::Text; textWords->setColor(fontColor); textWords->setText(text); textWords->setFont("Fonts/Arial.ttf"); textWords->setCharacterSize(1.0f); osg::Geode *fontGeode = new osg::Geode; fontGeode->addDrawable(textWords); posMT->addChild(fontGeode); return posMT; } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// class ManipulatorSceneHandler : public osgGA::GUIEventHandler { public: ManipulatorSceneHandler() { _lastTick = osg::Timer::instance()->tick(); _currentTick = _lastTick; } public: 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::FRAME): { double dt = 0.0; _currentTick = osg::Timer::instance()->tick(); if (_currentTick != _lastTick) { dt = osg::Timer::instance()->delta_s(_lastTick, _currentTick); _lastTick = _currentTick; } dt /= slowMotionRatio; timeElapsed += dt; float maxPossible_dt = 0.1f; int numOfIterations = (int)(dt / maxPossible_dt) + 1; if (numOfIterations != 0) dt = dt / numOfIterations; for (int a = 0; a < numOfIterations; ++a) { constantVelocity->operate(dt); motionUnderGravitation->operate(dt); massConnectedWithSpring->operate(dt); } } break; case(osgGA::GUIEventAdapter::KEYDOWN): { if (ea.getKey() == osgGA::GUIEventAdapter::KEY_F2) { slowMotionRatio = 1.0f; } if (ea.getKey() == osgGA::GUIEventAdapter::KEY_F3) { slowMotionRatio = 10.0f; } } break; default: break; } return false; } osg::Timer_t _lastTick; osg::Timer_t _currentTick; }; ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// //Constant Callback class ConstantPointUpdateCallback : public osg::Drawable::UpdateCallback { public: virtual void update(osg::NodeVisitor*, osg::Drawable* drawable) { osg::Geometry *geometry = dynamic_cast<osg::Geometry*>(drawable); if (!geometry) return; osg::Vec3Array *vertexArray = dynamic_cast<osg::Vec3Array*>(geometry->getVertexArray()); if (!vertexArray) return; vertexArray->clear(); for (int a = 0; a < constantVelocity->numOfMasses; ++a) { Mass* mass = constantVelocity->getMass(a); Vector3D* pos = &mass->pos; vertexArray->push_back(osg::Vec3(pos->x, pos->y, pos->z)); } geometry->setVertexArray(vertexArray); vertexArray->dirty(); } }; class ConstantWordUpdateCallback : public osg::NodeCallback { public: virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) { osg::MatrixTransform *mt = dynamic_cast<osg::MatrixTransform*>(node); if (!mt) return; for (int a = 0; a < constantVelocity->numOfMasses; ++a) { Mass* mass = constantVelocity->getMass(a); Vector3D* pos = &mass->pos; mt->setMatrix(osg::Matrix::translate(pos->x, pos->y + 1, pos->z)); } traverse(node, nv); } }; //Gravity Callback class GravitationPointUpdateCallback : public osg::Drawable::UpdateCallback { public: virtual void update(osg::NodeVisitor*, osg::Drawable* drawable) { osg::Geometry *geometry = dynamic_cast<osg::Geometry*>(drawable); if (!geometry) return; osg::Vec3Array *vertexArray = dynamic_cast<osg::Vec3Array*>(geometry->getVertexArray()); if (!vertexArray) return; vertexArray->clear(); for (int a = 0; a < motionUnderGravitation->numOfMasses; ++a) { Mass* mass = motionUnderGravitation->getMass(a); Vector3D* pos = &mass->pos; vertexArray->push_back(osg::Vec3(pos->x, pos->y, pos->z)); } geometry->setVertexArray(vertexArray); vertexArray->dirty(); } }; class GravitationWordUpdateCallback : public osg::NodeCallback { public: virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) { osg::MatrixTransform *mt = dynamic_cast<osg::MatrixTransform*>(node); if (!mt) return; for (int a = 0; a < motionUnderGravitation->numOfMasses; ++a) { Mass* mass = motionUnderGravitation->getMass(a); Vector3D* pos = &mass->pos; mt->setMatrix(osg::Matrix::translate(pos->x, pos->y + 1, pos->z)); } traverse(node, nv); } }; //SpringCallback class SpringPointUpdateCallback : public osg::Drawable::UpdateCallback { public: virtual void update(osg::NodeVisitor*, osg::Drawable* drawable) { osg::Geometry *geometry = dynamic_cast<osg::Geometry*>(drawable); if (!geometry) return; osg::Vec3Array *vertexArray = dynamic_cast<osg::Vec3Array*>(geometry->getVertexArray()); if (!vertexArray) return; vertexArray->clear(); for (int a = 0; a < massConnectedWithSpring->numOfMasses; ++a) { Mass* mass = massConnectedWithSpring->getMass(a); Vector3D* pos = &mass->pos; vertexArray->push_back(osg::Vec3(pos->x, pos->y, pos->z)); } geometry->setVertexArray(vertexArray); vertexArray->dirty(); } }; class SpringWordUpdateCallback : public osg::NodeCallback { public: virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) { osg::MatrixTransform *mt = dynamic_cast<osg::MatrixTransform*>(node); if (!mt) return; for (int a = 0; a < massConnectedWithSpring->numOfMasses; ++a) { Mass* mass = massConnectedWithSpring->getMass(a); Vector3D* pos = &mass->pos; mt->setMatrix(osg::Matrix::translate(pos->x, pos->y + 1, pos->z)); } traverse(node, nv); } }; class SpringLineUpdateCallback : public osg::Drawable::UpdateCallback { public: virtual void update(osg::NodeVisitor*, osg::Drawable* drawable) { osg::Geometry *geometry = dynamic_cast<osg::Geometry*>(drawable); if (!geometry) return; osg::Vec3Array *vertexArray = dynamic_cast<osg::Vec3Array*>(geometry->getVertexArray()); if (!vertexArray) return; vertexArray->clear(); for (int a = 0; a < massConnectedWithSpring->numOfMasses; ++a) { Mass* mass = massConnectedWithSpring->getMass(a); Vector3D* pos = &mass->pos; vertexArray->push_back(osg::Vec3(pos->x, pos->y, pos->z)); Vector3D *tmp = &massConnectedWithSpring->connectionPos; vertexArray->push_back(osg::Vec3(tmp->x, tmp->y, tmp->z)); } geometry->setVertexArray(vertexArray); vertexArray->dirty(); } }; class TimeEllapsedUpdateCallback : public osg::NodeCallback { public: virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) { osg::MatrixTransform *mt = dynamic_cast<osg::MatrixTransform*>(node); if (!mt) return; osg::Geode *geode = dynamic_cast<osg::Geode*>(mt->getChild(0)); if (!geode) return; osgText::Text *text = dynamic_cast<osgText::Text*>(geode->getDrawable(0)); if (!text) return; std::stringstream os; std::string str; os.precision(2); os << std::fixed << "Time elapsed (seconds): " << timeElapsed; str = os.str(); text->setText(str); traverse(node, nv); } }; class RatioUpdateCallback : public osg::NodeCallback { public: virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) { osg::MatrixTransform *mt = dynamic_cast<osg::MatrixTransform*>(node); if (!mt) return; osg::Geode *geode = dynamic_cast<osg::Geode*>(mt->getChild(0)); if (!geode) return; osgText::Text *text = dynamic_cast<osgText::Text*>(geode->getDrawable(0)); if (!text) return; std::stringstream os; std::string str; os.precision(2); os << std::fixed << "Slow motion ratio: " << slowMotionRatio; str = os.str(); text->setText(str); traverse(node, nv); } }; //绘制背景网格 osg::Geode* createBackGroundGeode() { osg::Geode *geode = new osg::Geode; osg::Geometry *geometry = new osg::Geometry; osg::Vec3Array *vertexArray = new osg::Vec3Array; osg::Vec3Array *colorArray = new osg::Vec3Array; colorArray->push_back(osg::Vec3(0, 0, 1.0)); geometry->setColorArray(colorArray, osg::Array::BIND_OVERALL); for (float x = -20; x <= 20; x += 1.0f) { vertexArray->push_back(osg::Vec3(x, 20, 0)); vertexArray->push_back(osg::Vec3(x,-20, 0)); } for (float y = -20; y <= 20; y += 1.0f) { vertexArray->push_back(osg::Vec3(20, y, 0)); vertexArray->push_back(osg::Vec3(-20, y, 0)); } geometry->setVertexArray(vertexArray); geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); geometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINES, 0, vertexArray->size())); geode->addDrawable(geometry); return geode; } osg::Geode* createPoint(const osg::Vec3& pos, const osg::Vec3& color, float size) { osg::Geode *geode = new osg::Geode; osg::Geometry *geometry = new osg::Geometry; osg::Vec3Array *vertexArray = new osg::Vec3Array; osg::Vec3Array *colorArray = new osg::Vec3Array; colorArray->push_back(color); geometry->setColorArray(colorArray, osg::Array::BIND_OVERALL); vertexArray->push_back(pos); geometry->setVertexArray(vertexArray); geometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::POINTS, 0, vertexArray->size())); osg::Point *point = new osg::Point; point->setSize(size); geometry->getOrCreateStateSet()->setAttributeAndModes(point); geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); geode->addDrawable(geometry); return geode; } osg::Geode* createLine(const osg::Vec3& posBeg, const osg::Vec3& posEnd, const osg::Vec3& color) { osg::Geode *geode = new osg::Geode; osg::Geometry *geometry = new osg::Geometry; osg::Vec3Array *vertexArray = new osg::Vec3Array; osg::Vec3Array *colorArray = new osg::Vec3Array; vertexArray->push_back(posBeg); vertexArray->push_back(posEnd); geometry->setVertexArray(vertexArray); colorArray->push_back(color); geometry->setColorArray(colorArray, osg::Array::BIND_OVERALL); geometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINES, 0, vertexArray->size())); geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); geode->addDrawable(geometry); return geode; } //绘制Constant Velocity osg::Node* constantVelocityNode() { osg::Group *constantVelocityGroup = new osg::Group; for (int a = 0; a < constantVelocity->numOfMasses; ++a) { Mass* mass = constantVelocity->getMass(a); Vector3D* pos = &mass->pos; osg::Node *node = glPrint(pos->x, pos->y + 1, pos->z, osg::Vec4(1,0,0,1), "Mass with constant vel"); osg::Geode *geode = createPoint(osg::Vec3(pos->x, pos->y, pos->z), osg::Vec3(1,0,0), 4); geode->getDrawable(0)->setUpdateCallback(new ConstantPointUpdateCallback); node->addUpdateCallback(new ConstantWordUpdateCallback); constantVelocityGroup->addChild(node); constantVelocityGroup->addChild(geode); } return constantVelocityGroup; } //绘制motionUnderGravitation osg::Node* underGravitationNode() { osg::Group *group = new osg::Group; for (int a = 0; a < motionUnderGravitation->numOfMasses; ++a) { Mass* mass = motionUnderGravitation->getMass(a); Vector3D* pos = &mass->pos; osg::Node *wordNode = glPrint(pos->x, pos->y + 1, pos->z, osg::Vec4(1,1,0,1), "Motion under gravitation"); osg::Geode *pointNode = createPoint(osg::Vec3(pos->x, pos->y, pos->z), osg::Vec3(1,1,0), 4); wordNode->addUpdateCallback(new GravitationWordUpdateCallback); pointNode->getDrawable(0)->setUpdateCallback(new GravitationPointUpdateCallback); group->addChild(wordNode); group->addChild(pointNode); } return group; } //绘制massConnectedWithSpring osg::Node* massConnectedWithSpringNode() { osg::Group *group = new osg::Group; for (int a = 0; a < massConnectedWithSpring->numOfMasses; ++a) { Mass* mass = massConnectedWithSpring->getMass(a); Vector3D* pos = &mass->pos; osg::Node *wordNode = glPrint(pos->x, pos->y + 1, pos->z, osg::Vec4(0,1,0,1), "Mass connected with spring"); osg::Geode *pointNode = createPoint(osg::Vec3(pos->x, pos->y, pos->z), osg::Vec3(0,1,0), 8); group->addChild(wordNode); group->addChild(pointNode); wordNode->addUpdateCallback(new SpringWordUpdateCallback); pointNode->getDrawable(0)->setUpdateCallback(new SpringPointUpdateCallback); Vector3D *tmp = &massConnectedWithSpring->connectionPos; osg::Geode *lineNode = createLine(osg::Vec3(pos->x, pos->y, pos->z), osg::Vec3(tmp->x, tmp->y, tmp->z), osg::Vec3(0,1,0)); //Force the lineNode Draw Upon Background lineNode->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); group->addChild(lineNode); lineNode->getDrawable(0)->setUpdateCallback(new SpringLineUpdateCallback); } return group; } 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, 40), osg::Vec3d(0, 0, 0), osg::Vec3d(0, 1, 0)); //OSG默认会将点裁剪掉 camera->setCullingMode( camera->getCullingMode() & ~osg::CullSettings::SMALL_FEATURE_CULLING); addEventHandler(new ManipulatorSceneHandler); this->setSceneData( scene ); 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() { osg::Group *root = new osg::Group; root->addChild(createBackGroundGeode()); root->addChild(constantVelocityNode()); root->addChild(underGravitationNode()); root->addChild(massConnectedWithSpringNode()); osg::Node *timeEllapsedNode = glPrint(-15.0f, 14, 0, osg::Vec4(1,1,1,1), "Time elapsed (seconds): %.2f", timeElapsed); timeEllapsedNode->addUpdateCallback(new TimeEllapsedUpdateCallback); root->addChild(timeEllapsedNode); osg::Node *ratioNode = glPrint(-15.0f, 13, 0, osg::Vec4(1,1,1,1),"Slow motion ratio: %.2f", slowMotionRatio); ratioNode->addUpdateCallback(new RatioUpdateCallback); root->addChild(ratioNode); root->addChild(glPrint(-15.0f, 12, 0, osg::Vec4(1,1,1,1),"Press F2 for normal motion")); root->addChild(glPrint(-15.0f, 11, 0, osg::Vec4(1,1,1,1),"Press F3 for slow motion")); 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(); }