功能
本节模拟一个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();
}
暂无评论内容