第31节 AutoCAD操作器-正交操作器

功能

本节模拟一个AutoCAD的操作器,发现有很多网友是在使用OSG做CAD相关的操作,问题了很多关于使用正交操作器的问题。本文功能如下:

  • 绘制一个线框当背景
  • 正交投影
  • 鼠标滚轮向上向下,可以放大缩小线框
  • 点击a/d键向视点向左/右移动
  • 点击q键逆时针旋转线框

本节的内容在网盘中:

【击此打开网盘资源链接】

原理

视点

首先我们要明白,视点位置与投影是两码事。视点的位置由三个要素决定,_eye(眼睛的位置),_center(你在往哪个点看),_up(你头顶指向哪里,可以想象你歪脖子就是改变了up)

而投影就不同了,投影是你怎么看这个世界,人的眼睛是近大远小的透视投影,我们就不说了。本文要使用的投影要做正交2D投影,就是眼睛位置确定之后,只取眼睛左-右,下-上,这么大的范围的数据,且没有近大远小,这个范围内的全部叠加在一起。

想象一个从视点出发无限长的方形的管道,管道里的所有物体叠于一个面上,那就是正交投影显示的结果。当然正交投影也可以设置远-近,就是不是无限长的方形管道。此处不做过多说明。不设置默认是无限长的。

本文操作器我们仍然做了如下定义:

		_center = osg::Vec3(0.0, 0.0, 0.0);
		_up = osg::Vec3(0.0, 0.0, 1.0);
		_eye = osg::Vec3(0.0, -10.0, 0.0);

也就是默认的情况下我们站在y轴的-10的位置,看向原点。在这里我们要有空间的概念,x轴向屏幕右,y轴向屏幕里,z轴向屏幕上。

可以认为当前我们站在离屏幕10的距离朝向屏幕看,且头顶朝向屏幕的上方。如下图所示,x是屏幕右,z是屏幕上,狮子所在的位置是 负y轴的方向,也就是y轴朝向屏幕里。
在这里插入图片描述

投影

对于正交投影来说,参数特别的简单,就是left, right, bottom, top,这个是相对于视点来说的,想象视点在管道中间(0, 0)点, left就是管道左壁,right就是右壁,bottom就是下壁,top就是上壁。

因为我们绘制的网格是在xz平面上,1米划一条线,总共划20条,从(0, 0)开始划到(19, 19),视点又在(0, 0)点,因此我们把left, right, top, bottom这么设置:

		_l = -2.0;
		_r = 2.0;
		_b = -2.0;
		_t = 2.0;

会只看到两个网格:
在这里插入图片描述

鼠标滚轮

滚轮事件的处理是最简单的,我们只需要重新定义left, right, top, bottom的范围即可。让其更大或者更小。

旋转

旋转其实就是改变up的方向,我们定义了_theta,每点一次q我们就把其加1。然后将初始up = osg::Vec3(0.0, 0.0, 1.0) 绕y轴旋转_theta即可。

平移

本例只处理了a左移与d右移,当不旋转的时候,视点左移_deltaEye=0.3这么远。当鼠标滚轮时,因为视场变大了_deltaEye也会跟着left, right一起变大变小。这个不难理解。

假如我们不旋转,我们只点a向左平移是这样的:
在这里插入图片描述
我们只需要将eye的位置,沿x方向减去deltaEye就可以了。这就是我注释的代码:
//_eye += osg::Vec3(-_deltaEye, 0.0, 0.0);

那么现在旋转了,情况复杂了,当我们点击q的时候,旋转_theta度,假设在0~90度内,情况变这样了:
在这里插入图片描述
上图粗线是要行走的_deltaEye,旋转角度_theta后,红线画的是其在x方向上与y方向上的变化,在0~90度内,sin与cos都是正的,因此新eye的位置为:

_eye += osg::Vec3(-_deltaEye*std::cos(osg::inDegrees(_theta)), 0.0, _deltaEye*std::sin(osg::inDegrees(_theta)));

eye一改,center也要改

_center = osg::Vec3(_eye.x(), 0.0, _eye.z());

也就是永远站在屏幕外,看屏幕里,你怎么移都是这样。因为y值没有变化,eye的y永远是-10,永远看向y=0

其它的情况读者自己推吧,旋转[90, 360]的时候公式还一样吗?以及w向上s向下用户都可以自己写。或者鼠标拖动等,用户都可以自己写。

全部代码:

#include <osgViewer/Viewer>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/PrimitiveSet>
#include <osgGA/CameraManipulator>


class MyCameraManipulator : public osgGA::CameraManipulator
{
public:
	MyCameraManipulator()
	{
		_theta = 0.0;
		_center = osg::Vec3(0.0, 0.0, 0.0);
		_up = osg::Vec3(0.0, 0.0, 1.0);
		_eye = osg::Vec3(0.0, -10.0, 0.0);

		_deltaEye = 0.3;

		_l = -2.0;
		_r = 2.0;
		_b = -2.0;
		_t = 2.0;
	}

	//这三个纯虚函数本例不会使用
	virtual void setByMatrix(const osg::Matrixd& matrix) {};
	virtual void setByInverseMatrix(const osg::Matrixd& matrix) {};
	virtual osg::Matrixd getMatrix() const { return osg::Matrix::identity(); };

	//最关键的是这个,这个返回的就是ViewMatrix
	virtual osg::Matrixd getInverseMatrix() const
	{
		return osg::Matrix::lookAt(_eye, _center, _up);
	};

	//事件处理,我们要点击A就围着Z轴顺时针转动,点D就逆时针转动,转的时候始终朝0 0 0 点看着
	virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us)
	{
		if (ea.getEventType() == osgGA::GUIEventAdapter::FRAME)
		{
			us.asView()->getCamera()->setProjectionMatrixAsOrtho2D(_l, _r, _b, _t);
		}

		if (ea.getEventType() == osgGA::GUIEventAdapter::SCROLL)
		{
			//判断滚动的方向
			osgGA::GUIEventAdapter::ScrollingMotion sm = ea.getScrollingMotion();

			if (sm == osgGA::GUIEventAdapter::SCROLL_DOWN)
			{
				_deltaEye *= 1.1;

				_l *= 1.1;
				_t *= 1.1;
				_b *= 1.1;
				_r *= 1.1;

			}
			else
			{
				//范围变小0.1,正好与变大相反,左右同时往里缩
				_deltaEye *= 0.9;

				_l *= 0.9;
				_t *= 0.9;
				_b *= 0.9;
				_r *= 0.9;
			}
		}


		if (ea.getEventType() == osgGA::GUIEventAdapter::KEYDOWN)
		{

			//旋转视角
			if ((ea.getKey() == 'Q') || (ea.getKey() == 'q'))
			{
				//点q变1度
				_theta += 1;

				_up = osg::Vec3(0.0, 0.0, 1.0)*osg::Matrix::rotate(osg::inDegrees(_theta), osg::Y_AXIS);
			}

			//若是A键
			if ((ea.getKey() == 'A') || (ea.getKey() == 'a'))
			{

				//_eye += osg::Vec3(-_deltaEye, 0.0, 0.0);
				_eye += osg::Vec3(-_deltaEye*std::cos(osg::inDegrees(_theta)), 0.0, _deltaEye*std::sin(osg::inDegrees(_theta)));


				_center = osg::Vec3(_eye.x(), 0.0, _eye.z());
			}
			if ((ea.getKey() == 'D') || (ea.getKey() == 'd'))
			{
				//_eye += osg::Vec3(_deltaEye, 0.0, 0.0);

				_eye -= osg::Vec3(-_deltaEye*std::cos(osg::inDegrees(_theta)), 0.0, _deltaEye*std::sin(osg::inDegrees(_theta)));

				_center = osg::Vec3(_eye.x(), 0.0, _eye.z());
			}

		}
		return false;
	}

	//视点位置
	osg::Vec3d              _eye;

	//点击鼠标a键向左移的量度,随着鼠标滚轮的放大缩小,这个量度也在变化
	double _deltaEye; 


	//视点看向哪里
	osg::Vec3d              _center;
	//头顶的朝向
	osg::Vec3d              _up;

	//视点看向0 0 0的角度
	float              _theta;


	//二维投影参数, left, right, bottom, top
	double _l, _r, _b, _t;
};


osg::Geode* createNet()
{
	osg::Geode* gnode = new osg::Geode;

	osg::Geometry* geom = new osg::Geometry;
	gnode->addDrawable(geom);

	//设置线的颜色为白色
	osg::Vec4Array* color = new osg::Vec4Array;
	color->push_back(osg::Vec4(1.0, 1.0, 1.0, 1.0));
	geom->setColorArray(color, osg::Array::BIND_OVERALL);

	osg::Vec3Array* vertex = new osg::Vec3Array;
	geom->setVertexArray(vertex);

	//间隔1米,横20条,竖20条
	for (int i = 0; i < 20; i++)
	{
		//x方向
		vertex->push_back(osg::Vec3(0, 0, i));
		vertex->push_back(osg::Vec3(17, 0, i)); //设置为17,不画满

		//z方向
		vertex->push_back(osg::Vec3(i, 0, 0));
		vertex->push_back(osg::Vec3(i, 0, 17));
	}

	geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINES, 0, vertex->size()));
	geom->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);

	return gnode;
}


int main()
{
	osgViewer::Viewer viewer;
	viewer.setSceneData(createNet());
	viewer.setCameraManipulator(new MyCameraManipulator());
	return viewer.run();
}

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片