美文网首页
第09节 实例-最简单的第一人称漫游操作器

第09节 实例-最简单的第一人称漫游操作器

作者: 杨石兴 | 来源:发表于2021-06-16 00:45 被阅读0次

    缘由

    群友:挑战高起点 在群里问了这么一个问题:

    image.png

    因此我觉得它是对操作器的视口默认值以及旋转方式搞糊涂了。如果不理理清楚,基本上后面很多事情都没法办。我准备写个小例子帮助大家理一理。完整代码在本节最后和在网盘中:

    注意:务必使用浏览器打开:
    链接:https://pan.baidu.com/s/13gwJLwo_LbRnN3Bl2NXXXw
    提取码:xrf5

    功能

    这一节的功能很简单,在一个20x20的棋盘格子上,以一第人称,点击Q是向左转(逆时针),点击E是向右转(正时针),点击W是向前走。 会了这些基本操作,其它的操作都可以做出来。

    操作器

    这次我们打算在操作器中用两个变量来控制视口,一个是视点的位置osg::Vec3d _eye;一个是视点的朝向osg::Vec3d _rotation;其中位置不用解释了,朝向是沿xyz三个轴各自的旋转分量。我们使用这两个值如下得到观察矩阵:

        //最关键的是这个,这个返回的就是ViewMatrix
        virtual osg::Matrixd getInverseMatrix() const
        {
            return osg::Matrix::inverse(osg::Matrix::rotate(_rotation.x(), osg::X_AXIS, _rotation.y(), osg::Y_AXIS,
                _rotation.z(), osg::Z_AXIS) * osg::Matrix::translate(_eye));
        };
    

    默认视点朝向

    _eye不用说了,放哪是哪,现在首先要弄明白的是当_rotation=0,0,0的时候,它朝哪。这是个默认朝向,然后我们根据它朝哪,我们才能做自己的操作。默认朝向是朝向这里,如下图:

    image.png

    其中红色是x轴,绿色是y轴,蓝色是z轴。我们可以看到_rotation=0,0,0的时候,我们朝向z轴负方向,且头顶是y轴,右手是x轴。

    旋转操作

    现在第一步,我们要把它移向正常的朝向,我们期望是这样:


    image.png

    对比默认朝向,我们知道,只需要沿x轴逆时针旋转90度就可以了。默认的旋转都是逆时针的,因此我们在_rotation的时候这样定义了:
    _rotation = osg::Vec3(osg::inDegrees(90.0), 0.0, 0.0);

    向左向右旋转

    没有什么特别的原因,旋转我们就不需要操作xy轴的旋转了,向左向右旋转在上图的基本上其实就是绕z轴旋转。这里要注意方向,比如绕z轴旋转45度,是看向这里:


    image.png

    图中示意的角度是45度,靠近z轴的红色箭头就是当前的朝向。注意旋转10度,是逆时针旋转10度,-10度是正时针旋转10度。

    在事件处理中点q我们就加2度(逆时针),点e我们就减两度(正时针)。代码如下:

                if ((ea.getKey() == 'q') || (ea.getKey() == 'Q'))//左转
                {
                    _rotation.z() += osg::inDegrees(2.0);
                    if (_rotation.z() > osg::inDegrees(180.0))
                    {
                        _rotation.z() -= osg::inDegrees(360.0);
                    }
                }
                if ((ea.getKey() == 'E') || (ea.getKey() == 'e'))//右转
                {
                    _rotation.z() -= osg::inDegrees(2.0);
                    if (_rotation.z() < osg::inDegrees(-180.0))
                    {
                        _rotation.z() += osg::inDegrees(360.0);
                    }
                }
    

    向前走

    向前走是点w,也是个大难题,容易把人绕晕乎。向前走不涉及z的值,只涉及xy的值。而具体往哪个方向走,将步长固定下来,则xy方向上的变化量就是与朝向有关系了,如下图所示:


    image.png

    上图看仔细了啊,现在我们站在原点,要走向红线的另一头,红线的长度是步长,_eye的z值怎么走都不变,则如图走到红线另一头则x方向上的变化是绿线,y方向上的变化是黄线,图中蓝线的角度就是_rotation.z(),图上这个角度肯定是负的,因为是顺时针转了一点点。默认朝向是朝y轴正方向的,顺时针转了一点点就是负值,是蓝色的角度。已知蓝色的角度,和红色的步长,那求出来黄色和绿色就是分分钟的事情了。

    这里面在第一四象限可以用一个公式,在二三象限可以用一个公式。角度有正负、xy有正负、正逆时针旋转有正负,这三个正负搅和在一起,让你没有本文辅佐实难理清呀。

    拿第一象限来说,蓝色角度_rotation.z()在[-90, 0]之间,stepSize * std::sin(-_rotation.z()) 就是绿线。stepSize * std::cos(-_rotation.z());就是黄线。慢慢理吧,以下是代码:

                if ((ea.getKey() == 'w') || (ea.getKey() == 'W'))//前进
                {
                    float stepSize = 0.5;
                    float zRot = _rotation.z();//[-180, 180]
                    //判断朝向以xy为平面的哪个象限,注意默认是朝各Y轴正方向的,时不时就得提一下
                    //第一象限||第四象限
                    if (((zRot >= -osg::inDegrees(90.0)) && (zRot <= -osg::inDegrees(0.0)))|| ((zRot <= -osg::inDegrees(90.0)) && (zRot >= osg::inDegrees(-180.0))))
                    {
                        _eye.x() += stepSize * std::sin(-zRot);
                        _eye.y() += stepSize * std::cos(-zRot);
                    }
                    else //二三象限
                    {
                        _eye.x() -= stepSize * std::sin(zRot);
                        _eye.y() += stepSize * std::cos(zRot);
                    }           
                }
    

    以下是所有的代码,在一个cpp文件中,直接拷走可用:

    #include <osgViewer/viewer>
    #include <osgDB/ReadFile>
    #include <osg/Geode>
    #include <osg/Geometry>
    #include <osgGA/CameraManipulator>
    
    class TravelCameraManipulator : public osgGA::CameraManipulator
    {
    public:
        TravelCameraManipulator()
        {
            //初始的场景是个20x20的棋盘,中心点在[0,0],xy的坐标范围是从[-10,10],z=0
            //设置_eye的出生点
            _eye = osg::Vec3(0.0, -8, 1.0);
            //这里很关键,_rotation=(0,0,0)的情况下视点会朝向哪里呢,这是个基准参考量,后面的旋转都是从
            //这里来,所以务必要弄清楚,000时的朝向是Z轴负方向,头顶向Y轴正方向,自然右手边就是X轴正方向
            //在简书的文章里有图,简书搜杨石兴,《osg3.6.5最短的一帧》等找找
            //我们要想让视角转着朝向前方,也即站在(0.0, -8, 1.0)看向(0,0,0),则只需要看向Y轴
            //正方向就可以,则只需要x轴方向逆时针转90度,则出生就是朝向这里了
            //用户可以自己修改这个值感受一下
            _rotation = osg::Vec3(osg::inDegrees(90.0), 0.0, 0.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::inverse(osg::Matrix::rotate(_rotation.x(), osg::X_AXIS, _rotation.y(), osg::Y_AXIS,
                _rotation.z(), osg::Z_AXIS) * osg::Matrix::translate(_eye));
        };
    
        //事件处理,我们要点击A就围着Z轴顺时针转动,点D就逆时针转动,转的时候始终朝0 0 0 点看着
        virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us)
        {
            if (ea.getEventType() == osgGA::GUIEventAdapter::KEYDOWN)
            {
                //若是A键
                if ((ea.getKey() == 'q') || (ea.getKey() == 'Q'))//左转
                {
                    _rotation.z() += osg::inDegrees(2.0);
                    if (_rotation.z() > osg::inDegrees(180.0))
                    {
                        _rotation.z() -= osg::inDegrees(360.0);
                    }
                }
                if ((ea.getKey() == 'E') || (ea.getKey() == 'e'))//右转
                {
                    _rotation.z() -= osg::inDegrees(2.0);
                    if (_rotation.z() < osg::inDegrees(-180.0))
                    {
                        _rotation.z() += osg::inDegrees(360.0);
                    }
                }
                if ((ea.getKey() == 'w') || (ea.getKey() == 'W'))//前进
                {
                    float stepSize = 0.5;
                    float zRot = _rotation.z();//[-180, 180]
                    //判断朝向以xy为平面的哪个象限,注意默认是朝各Y轴正方向的,时不时就得提一下
                    //第一象限||第四象限
                    if (((zRot >= -osg::inDegrees(90.0)) && (zRot <= -osg::inDegrees(0.0)))|| ((zRot <= -osg::inDegrees(90.0)) && (zRot >= osg::inDegrees(-180.0))))
                    {
                        _eye.x() += stepSize * std::sin(-zRot);
                        _eye.y() += stepSize * std::cos(-zRot);
                    }
                    else //二三象限
                    {
                        _eye.x() -= stepSize * std::sin(zRot);
                        _eye.y() += stepSize * std::cos(zRot);
                    }           
                }
            }
            return false;
        }
    
        //视点位置
        osg::Vec3d              _eye;
        //视点朝向
        osg::Vec3d              _rotation;
    };
    
    osg::Node* createBase(const osg::Vec3 center, float radius)
    {
        osg::Group* root = new osg::Group;
    
        int numTilesX = 10;
        int numTilesY = 10;
    
        float width = 2 * radius;
        float height = 2 * radius;
    
        osg::Vec3 v000(center - osg::Vec3(width * 0.5f, height * 0.5f, 0.0f));
        osg::Vec3 dx(osg::Vec3(width / ((float)numTilesX), 0.0, 0.0f));
        osg::Vec3 dy(osg::Vec3(0.0f, height / ((float)numTilesY), 0.0f));
    
        // fill in vertices for grid, note numTilesX+1 * numTilesY+1...
        osg::Vec3Array* coords = new osg::Vec3Array;
        int iy;
        for (iy = 0; iy <= numTilesY; ++iy)
        {
            for (int ix = 0; ix <= numTilesX; ++ix)
            {
                coords->push_back(v000 + dx * (float)ix + dy * (float)iy);
            }
        }
    
        //Just two colours - black and white.
        osg::Vec4Array* colors = new osg::Vec4Array;
        colors->push_back(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f)); // white
        colors->push_back(osg::Vec4(0.0f, 0.0f, 0.0f, 1.0f)); // black
    
        osg::ref_ptr<osg::DrawElementsUShort> whitePrimitives = new osg::DrawElementsUShort(GL_QUADS);
        osg::ref_ptr<osg::DrawElementsUShort> blackPrimitives = new osg::DrawElementsUShort(GL_QUADS);
    
        int numIndicesPerRow = numTilesX + 1;
        for (iy = 0; iy < numTilesY; ++iy)
        {
            for (int ix = 0; ix < numTilesX; ++ix)
            {
                osg::DrawElementsUShort* primitives = ((iy + ix) % 2 == 0) ? whitePrimitives.get() : blackPrimitives.get();
                primitives->push_back(ix + (iy + 1) * numIndicesPerRow);
                primitives->push_back(ix + iy * numIndicesPerRow);
                primitives->push_back((ix + 1) + iy * numIndicesPerRow);
                primitives->push_back((ix + 1) + (iy + 1) * numIndicesPerRow);
            }
        }
    
        // set up a single normal
        osg::Vec3Array* normals = new osg::Vec3Array;
        normals->push_back(osg::Vec3(0.0f, 0.0f, 1.0f));
    
        osg::Geometry* geom = new osg::Geometry;
        geom->setVertexArray(coords);
    
        geom->setColorArray(colors, osg::Array::BIND_PER_PRIMITIVE_SET);
    
        geom->setNormalArray(normals, osg::Array::BIND_OVERALL);
    
        geom->addPrimitiveSet(whitePrimitives.get());
        geom->addPrimitiveSet(blackPrimitives.get());
    
        osg::Geode* geode = new osg::Geode;
        geode->addDrawable(geom);
        geode->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
        root->addChild(geode);
    
        root->addChild(osgDB::readNodeFile("axes.osgt"));
    
        return root;
    }
    
    int main()
    {
        osgViewer::Viewer viewer;
    
        viewer.setCameraManipulator(new TravelCameraManipulator);
        viewer.setSceneData(createBase(osg::Vec3(0.0, 0.0, 0.0), 10.0));
    
        return viewer.run();
    }
    
    

    相关文章

      网友评论

          本文标题:第09节 实例-最简单的第一人称漫游操作器

          本文链接:https://www.haomeiwen.com/subject/gqztyltx.html