用OpenSceneGraph实现的NeHe OpenGL教程 - 第二十八课

  • 简介

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();
					}
				}

最后编译运行程序:

用OpenSceneGraph实现的NeHe OpenGL教程 - 第二十八课_第1张图片

附:本课源码(源码中可能存在错误和不足,仅供参考)

#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();
}

你可能感兴趣的:(C++,qt,OpenGL,nehe,OSG)