NeHe教程在这节课中向我们介绍了贝塞尔曲面,贝塞尔曲面是一种可以只使用很少的参数就可以描述出复杂曲面的一种数学工具。关于贝塞尔曲线的内容可以参考Bézier surface
本课的实现过程非常简单,在理解了贝塞尔曲面之后利用贝塞尔曲面的计算公式计算出控制点和插值点坐标:
控制点线条的代码如下:
osg::Geode* createBezierControlLineGeode() { 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(1.0, 0.0, 0.0)); geometry->setColorArray(colorArray, osg::Array::BIND_OVERALL); geometry->setVertexArray(vertexArray); geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); for(int i=0;i<4;i++) { // draw the horizontal lines for(int j=0;j<4;j++) vertexArray->push_back(osg::Vec3(mybezier.anchors[i][j].x, mybezier.anchors[i][j].y, mybezier.anchors[i][j].z)); } for(int i=0;i<4;i++) { // draw the vertical lines for(int j=0;j<4;j++) vertexArray->push_back(osg::Vec3(mybezier.anchors[j][i].x, mybezier.anchors[j][i].y, mybezier.anchors[j][i].z)); } geometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_STRIP, 0, vertexArray->size() / 2)); geometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_STRIP, vertexArray->size() / 2, vertexArray->size() / 2)); geode->addDrawable(geometry); return geode; }利用控制点插值出曲面中的其他坐标点,绘制曲面:
osg::Geode* createBezierGeode(BEZIER_PATCH patch, int divs) { int u = 0, v; float py, px, pyold; POINT_3D temp[4]; POINT_3D *last = (POINT_3D*)malloc(sizeof(POINT_3D)*(divs+1)); // array of points to mark the first line of polys temp[0] = patch.anchors[0][3]; // the first derived curve (along x axis) temp[1] = patch.anchors[1][3]; temp[2] = patch.anchors[2][3]; temp[3] = patch.anchors[3][3]; for (v=0;v<=divs;v++) { // create the first line of points px = ((float)v)/((float)divs); // percent along y axis // use the 4 points from the derives curve to calculate the points along that curve last[v] = Bernstein(px, temp); } osg::Geode *geode = new osg::Geode; osg::Geometry *geometry = new osg::Geometry; osg::Vec3Array *vertexArray = new osg::Vec3Array; osg::Vec2Array *textureArray = new osg::Vec2Array; geometry->setVertexArray(vertexArray); geometry->setTexCoordArray(0, textureArray); osg::Vec3Array *colorArray = new osg::Vec3Array; colorArray->push_back(osg::Vec3(1, 1, 1)); geometry->setColorArray(colorArray, osg::Array::BIND_OVERALL); geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); for (u=1;u<=divs;u++) { py = ((float)u)/((float)divs); // Percent along Y axis pyold = ((float)u-1.0f)/((float)divs); // Percent along old Y axis temp[0] = Bernstein(py, patch.anchors[0]); // Calculate new bezier points temp[1] = Bernstein(py, patch.anchors[1]); temp[2] = Bernstein(py, patch.anchors[2]); temp[3] = Bernstein(py, patch.anchors[3]); for (v=0;v<=divs;v++) { px = ((float)v)/((float)divs); // Percent along the X axis textureArray->push_back(osg::Vec2(pyold, px)); // Apply the old texture coords vertexArray->push_back(osg::Vec3(last[v].x, last[v].y, last[v].z)); // Old Point last[v] = Bernstein(px, temp); // Generate new point textureArray->push_back(osg::Vec2(py, px)); // Apply the new texture coords vertexArray->push_back(osg::Vec3(last[v].x, last[v].y, last[v].z)); // New Point } } osg::Texture2D *bezierTex = new osg::Texture2D; bezierTex->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); bezierTex->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); bezierTex->setImage(osgDB::readImageFile("Data/NeHe.bmp")); geometry->getOrCreateStateSet()->setTextureAttributeAndModes(0, bezierTex); geometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLE_STRIP, 0, vertexArray->size())); geode->addDrawable(geometry); return geode; }最后将二者添加到Switch节点中,在交互操作中切换控制点线条的显示和隐藏:
osg::Switch *switchLineAndGeodeSwitch = new osg::Switch; g_Swith = switchLineAndGeodeSwitch; rotZMT->addChild(switchLineAndGeodeSwitch); switchLineAndGeodeSwitch->addChild(createBezierControlLineGeode()); switchLineAndGeodeSwitch->addChild(createBezierGeode(mybezier, divs)); switchLineAndGeodeSwitch->setAllChildrenOn();交互部分代码如下
if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Space) { if (!g_Swith) return false; if (g_Swith->getValue(0)){ g_Swith->setSingleChildOn(1); }else{ g_Swith->setAllChildrenOn(); } }
附:本课源码(源码中可能存在错误和不足,仅供参考)
#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 <osg/Switch> osg::MatrixTransform *g_ZRotMT = NULL; osg::Switch *g_Swith = NULL; typedef struct point_3d { double x, y, z; } POINT_3D; typedef struct bpatch { POINT_3D anchors[4][4]; } BEZIER_PATCH; BEZIER_PATCH mybezier; int divs = 7; POINT_3D pointAdd(POINT_3D p, POINT_3D q) { p.x += q.x; p.y += q.y; p.z += q.z; return p; } POINT_3D pointTimes(double c, POINT_3D p) { p.x *= c; p.y *= c; p.z *= c; return p; } POINT_3D makePoint(double a, double b, double c) { POINT_3D p; p.x = a; p.y = b; p.z = c; return p; } // Calculates 3rd degree polynomial based on array of 4 points // and a single variable (u) which is generally between 0 and 1 POINT_3D Bernstein(float u, POINT_3D *p) { POINT_3D a, b, c, d, r; a = pointTimes(pow(u,3), p[0]); b = pointTimes(3*pow(u,2)*(1-u), p[1]); c = pointTimes(3*u*pow((1-u),2), p[2]); d = pointTimes(pow((1-u),3), p[3]); r = pointAdd(pointAdd(a, b), pointAdd(c, d)); return r; } ////////////////////////////////////////////////////////////////////////// void initBezier(void) { mybezier.anchors[0][0] = makePoint(-0.75, -0.75, -0.5); mybezier.anchors[0][1] = makePoint(-0.25, -0.75, 0.0); mybezier.anchors[0][2] = makePoint(0.25, -0.75, 0.0); mybezier.anchors[0][3] = makePoint(0.75, -0.75, -0.5); mybezier.anchors[1][0] = makePoint(-0.75, -0.25, -0.75); mybezier.anchors[1][1] = makePoint(-0.25, -0.25, 0.5); mybezier.anchors[1][2] = makePoint(0.25, -0.25, 0.5); mybezier.anchors[1][3] = makePoint(0.75, -0.25, -0.75); mybezier.anchors[2][0] = makePoint(-0.75, 0.25, 0.0); mybezier.anchors[2][1] = makePoint(-0.25, 0.25, -0.5); mybezier.anchors[2][2] = makePoint(0.25, 0.25, -0.5); mybezier.anchors[2][3] = makePoint(0.75, 0.25, 0.0); mybezier.anchors[3][0] = makePoint(-0.75, 0.75, -0.5); mybezier.anchors[3][1] = makePoint(-0.25, 0.75, -1.0); mybezier.anchors[3][2] = makePoint(0.25, 0.75, -1.0); mybezier.anchors[3][3] = makePoint(0.75, 0.75, -0.5); } ////////////////////////////////////////////////////////////////////////// //创建贝塞尔曲线 osg::Geode* createBezierControlLineGeode() { 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(1.0, 0.0, 0.0)); geometry->setColorArray(colorArray, osg::Array::BIND_OVERALL); geometry->setVertexArray(vertexArray); geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); for(int i=0;i<4;i++) { // draw the horizontal lines for(int j=0;j<4;j++) vertexArray->push_back(osg::Vec3(mybezier.anchors[i][j].x, mybezier.anchors[i][j].y, mybezier.anchors[i][j].z)); } for(int i=0;i<4;i++) { // draw the vertical lines for(int j=0;j<4;j++) vertexArray->push_back(osg::Vec3(mybezier.anchors[j][i].x, mybezier.anchors[j][i].y, mybezier.anchors[j][i].z)); } geometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_STRIP, 0, vertexArray->size() / 2)); geometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_STRIP, vertexArray->size() / 2, vertexArray->size() / 2)); geode->addDrawable(geometry); return geode; } osg::Geode* createBezierGeode(BEZIER_PATCH patch, int divs) { int u = 0, v; float py, px, pyold; POINT_3D temp[4]; POINT_3D *last = (POINT_3D*)malloc(sizeof(POINT_3D)*(divs+1)); // array of points to mark the first line of polys temp[0] = patch.anchors[0][3]; // the first derived curve (along x axis) temp[1] = patch.anchors[1][3]; temp[2] = patch.anchors[2][3]; temp[3] = patch.anchors[3][3]; for (v=0;v<=divs;v++) { // create the first line of points px = ((float)v)/((float)divs); // percent along y axis // use the 4 points from the derives curve to calculate the points along that curve last[v] = Bernstein(px, temp); } osg::Geode *geode = new osg::Geode; osg::Geometry *geometry = new osg::Geometry; osg::Vec3Array *vertexArray = new osg::Vec3Array; osg::Vec2Array *textureArray = new osg::Vec2Array; geometry->setVertexArray(vertexArray); geometry->setTexCoordArray(0, textureArray); osg::Vec3Array *colorArray = new osg::Vec3Array; colorArray->push_back(osg::Vec3(1, 1, 1)); geometry->setColorArray(colorArray, osg::Array::BIND_OVERALL); geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); for (u=1;u<=divs;u++) { py = ((float)u)/((float)divs); // Percent along Y axis pyold = ((float)u-1.0f)/((float)divs); // Percent along old Y axis temp[0] = Bernstein(py, patch.anchors[0]); // Calculate new bezier points temp[1] = Bernstein(py, patch.anchors[1]); temp[2] = Bernstein(py, patch.anchors[2]); temp[3] = Bernstein(py, patch.anchors[3]); for (v=0;v<=divs;v++) { px = ((float)v)/((float)divs); // Percent along the X axis textureArray->push_back(osg::Vec2(pyold, px)); // Apply the old texture coords vertexArray->push_back(osg::Vec3(last[v].x, last[v].y, last[v].z)); // Old Point last[v] = Bernstein(px, temp); // Generate new point textureArray->push_back(osg::Vec2(py, px)); // Apply the new texture coords vertexArray->push_back(osg::Vec3(last[v].x, last[v].y, last[v].z)); // New Point } } osg::Texture2D *bezierTex = new osg::Texture2D; bezierTex->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); bezierTex->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); bezierTex->setImage(osgDB::readImageFile("Data/NeHe.bmp")); geometry->getOrCreateStateSet()->setTextureAttributeAndModes(0, bezierTex); geometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLE_STRIP, 0, vertexArray->size())); geode->addDrawable(geometry); return geode; } ////////////////////////////////////////////////////////////////////////// ////////////////////场景交互代码//////////////////////////////// ////////////////////////////////////////////////////////////////////////// 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)); traverse(node, nv); } void setRotateAngle(double speed) { _currentAngle = speed; } double getRotateAngle() const { return _currentAngle; } 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_ZRotMT) return false; RotAxisCallback *rotCallback = dynamic_cast<RotAxisCallback*>(g_ZRotMT->getUpdateCallback()); if (!rotCallback) return false; double speed = rotCallback->getRotateAngle(); speed -= 0.02; rotCallback->setRotateAngle(speed); } if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Right) { if (!g_ZRotMT) return false; RotAxisCallback *rotCallback = dynamic_cast<RotAxisCallback*>(g_ZRotMT->getUpdateCallback()); if (!rotCallback) return false; double speed = rotCallback->getRotateAngle(); speed += 0.02; rotCallback->setRotateAngle(speed); } if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Space) { if (!g_Swith) return false; if (g_Swith->getValue(0)){ g_Swith->setSingleChildOn(1); }else{ g_Swith->setAllChildrenOn(); } } } 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 ); 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() { initBezier(); osg::Group *root = new osg::Group; osg::MatrixTransform *zoomMT = new osg::MatrixTransform; zoomMT->setMatrix(osg::Matrix::translate(0.0f,0.0f,-4.0f)); osg::MatrixTransform *rotXMT = new osg::MatrixTransform; rotXMT->setMatrix(osg::Matrix::rotate(osg::DegreesToRadians(-75.0), osg::X_AXIS)); osg::MatrixTransform *rotZMT = new osg::MatrixTransform; rotZMT->addUpdateCallback(new RotAxisCallback(osg::Z_AXIS)); g_ZRotMT = rotZMT; osg::Switch *switchLineAndGeodeSwitch = new osg::Switch; g_Swith = switchLineAndGeodeSwitch; rotZMT->addChild(switchLineAndGeodeSwitch); switchLineAndGeodeSwitch->addChild(createBezierControlLineGeode()); switchLineAndGeodeSwitch->addChild(createBezierGeode(mybezier, divs)); switchLineAndGeodeSwitch->setAllChildrenOn(); root->addChild(zoomMT); zoomMT->addChild(rotXMT); rotXMT->addChild(rotZMT); 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(); }