Section 9 examples - the simplest first person roaming operator

reason

Group Friends: challenge a high starting point. I asked such a question in the group:

So I think it's confused about the viewport defaults and rotation of the operator. If you don't make it clear, basically many things can't be done later. I'm going to write a small example to help you sort it out. The complete code is at the end of this section and in the network disk:

Note: be sure to use the browser to open:
Link: https://pan.baidu.com/s/13gwJLwo_LbRnN3Bl2NXXXw
Extraction code: xrf5

function

The function of this section is very simple. On a 20x20 chessboard grid, in a first person, click Q to turn left (counterclockwise), click E to turn right (clockwise), and click W to go forward. With these basic operations, other operations can be done.

Operator

This time, we plan to use two variables in the operator to control the viewport. One is the position of the viewpoint OSG:: vec3d_ eye; One is the orientation of the viewpoint OSG:: vec3d_ rotation; There is no need to explain the position. The orientation is the rotation component along xyz three axes. We use these two values to obtain the observation matrix as follows:

    //The key is this, which returns the 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));
    };

Default viewpoint orientation

_ eye needless to say, where is where. Now the first thing to figure out is when_ When rotation=0,0,0, which direction does it face. This is the default orientation, and then we can do our own operations according to where it is oriented. The default orientation is here, as shown in the following figure:

Red is the x-axis, green is the y-axis, and blue is the z-axis. We can see_ When rotation=0,0,0, we face the negative direction of the Z axis, and the top of the head is the Y axis and the right hand is the X axis.

Rotation operation

Now the first step is to move it towards the normal direction. We expect it to be like this:

Compared with the default orientation, we know that we only need to rotate 90 degrees counterclockwise along the x axis. The default rotation is counterclockwise, so we are_ Rotation is defined as follows:
_rotation = osg::Vec3(osg::inDegrees(90.0), 0.0, 0.0);

Rotate left to right

There is no special reason. We don't need to operate the rotation of the xy axis. Rotating left and right in the above figure is basically rotating around the z axis. Pay attention to the direction here. For example, rotate 45 degrees around the z axis to look here:

The angle shown in the figure is 45 degrees, and the red arrow near the z axis is the current orientation. Note that 10 degrees of rotation is 10 degrees counterclockwise, and - 10 degrees is 10 degrees clockwise.

At the event processing point q, we add 2 degrees (counterclockwise), and at the point e, we subtract 2 degrees (clockwise). The code is as follows:

            if ((ea.getKey() == 'q') || (ea.getKey() == 'Q'))//Turn left
            {
                _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'))//Turn right
            {
                _rotation.z() -= osg::inDegrees(2.0);
                if (_rotation.z() < osg::inDegrees(-180.0))
                {
                    _rotation.z() += osg::inDegrees(360.0);
                }
            }

Move on

Moving forward is a bit w, and it's also a big problem. It's easy to make people dizzy. Going forward does not involve the value of z, only the value of xy. If the step length is fixed in which direction to go, the change in xy direction is related to the direction, as shown in the following figure:

Look carefully at the figure above. Now we are standing at the origin and going to the other end of the red line. The length of the red line is the step size_ The z value of eye remains the same no matter how you go. As shown in the figure, if you go to the other end of the red line, the change in the x direction is the green line, the change in the Y direction is the yellow line, and the angle of the blue line in the figure is_ rotation.z(), the angle on the graph must be negative because it turns a little clockwise. The default orientation is in the positive direction of the y-axis. Turning a little clockwise is a negative value, which is a blue angle. Given the angle of blue and the step size of red, it takes minutes to find out the yellow and green.

One formula can be used in the first four quadrants and one formula can be used in the second and third quadrants. Angle has positive and negative, xy has positive and negative, positive and counterclockwise rotation has positive and negative. These three positive and negative are mixed together, which makes it difficult for you to clarify without the help of this article.

Take the first quadrant, the blue angle_ rotation. Z () is between [- 90, 0], stepSize * std::sin(-_rotation.z()) is the green line. stepSize * std::cos(-_rotation.z()); It's the yellow line. Take your time. Here is the code:

            if ((ea.getKey() == 'w') || (ea.getKey() == 'W'))//forward
            {
                float stepSize = 0.5;
                float zRot = _rotation.z();//[-180, 180]
                //Judge which quadrant is oriented with xy as the plane. Note that it is in the positive direction of each Y axis by default. You have to mention it from time to time
                //First quadrant 𞓜 fourth quadrant
                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 //Second and third quadrants
                {
                    _eye.x() -= stepSize * std::sin(zRot);
                    _eye.y() += stepSize * std::cos(zRot);
                }           
            }

The following are all the codes that can be copied directly in a cpp file:

#include <osgViewer/viewer>
#include <osgDB/ReadFile>
#include <osg/Geode>
#include <osg/Geometry>
#include <osgGA/CameraManipulator>

class TravelCameraManipulator : public osgGA::CameraManipulator
{
public:
    TravelCameraManipulator()
    {
        //The initial scene is a 20x20 chessboard, the center point is [0,0], the coordinate range of XY is [- 10,10], z=0
        //Set_ eye's birthplace
        _eye = osg::Vec3(0.0, -8, 1.0);
        //It's crucial here_ Where will the viewpoint face when rotation=(0,0,0)? This is a reference quantity, and the subsequent rotations are from
        //Here, so be sure to make it clear that the orientation at 000 is the negative direction of the Z axis, the top of the head is the positive direction of the Y axis, and naturally the right hand side is the positive direction of the X axis
        //There are pictures in the article of Jianshu. Search Yang Shixing and the shortest frame of osg3.6.5 for Jianshu
        //If we want to turn the viewing angle to the front, that is, stand at (0.0, - 8, 1.0) and look at (0,0,0), we only need to look at the Y axis
        //Positive direction is OK. You only need to turn 90 degrees counterclockwise in the x-axis direction, and the birth is here
        //Users can modify this value and feel it
        _rotation = osg::Vec3(osg::inDegrees(90.0), 0.0, 0.0);
        
    }

    //These three pure virtual functions will not be used in this example
    virtual void setByMatrix(const osg::Matrixd& matrix) {};
    virtual void setByInverseMatrix(const osg::Matrixd& matrix) {};
    virtual osg::Matrixd getMatrix() const { return osg::Matrix::identity(); };

    //The key is this, which returns the 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));
    };

    //For event handling, we need to click A to rotate clockwise around the Z axis, and point D to rotate counterclockwise. When we rotate, we always look at point 0
    virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us)
    {
        if (ea.getEventType() == osgGA::GUIEventAdapter::KEYDOWN)
        {
            //If it's A key
            if ((ea.getKey() == 'q') || (ea.getKey() == 'Q'))//Turn left
            {
                _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'))//Turn right
            {
                _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'))//forward
            {
                float stepSize = 0.5;
                float zRot = _rotation.z();//[-180, 180]
                //Judge which quadrant is oriented with xy as the plane. Note that it is in the positive direction of each Y axis by default. You have to mention it from time to time
                //First quadrant 𞓜 fourth quadrant
                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 //Second and third quadrants
                {
                    _eye.x() -= stepSize * std::sin(zRot);
                    _eye.y() += stepSize * std::cos(zRot);
                }           
            }
        }
        return false;
    }

    //Viewpoint position
    osg::Vec3d              _eye;
    //Viewpoint orientation
    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();
}

Keywords: OpenGL osg osgEarth OpenSceneGraph

Added by ZaphodQB on Sun, 23 Jan 2022 10:03:39 +0200