Section 4 operator and Trackball

Posted by kf on Sat, 22 Jan 2022 02:05:16 +0100

After the article was published, you have many questions, and I found some very basic. If you ask me to talk more carefully, I'll start again and write more carefully. Although you don't need to look at the code, you may still want to have a comparison when talking about the code. The author uses version 3.6.5, which I have uploaded to Baidu online disk. You can download it as follows:

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

For the simplest osg program with only three lines:

    osgViewer::Viewer viewer;
    viewer.setSceneData(osgDB::readNodeFile("glider.osg"));
    return viewer.run();

Let's go back to the Viewer::run() function. The call of this function is as follows:

int Viewer::run()
{
    if (!getCameraManipulator() && getCamera()->getAllowEventFocus())
    {
        setCameraManipulator(new osgGA::TrackballManipulator());
    }

    setReleaseContextAtEndOfFrameHint(false);

    return ViewerBase::run();
}

Literally, it is easy for us to understand. First, judge whether the current viewer is equipped with an operator, and then see whether the camera is allowed to obtain the focus. If it is not allowed to obtain the focus, the mouse and keyboard can't click, so the operator is not needed.

Then getCameraManipulator() we know that we have not called setCameraManipulator, so the result is null. But won't getCamera() return null? The answer is No. the application stage of this camera is in the constructor of osg::View. osgViewer::Viewer is derived from osg::View, so it is constructed naturally. The initialization code is in OSG / view Lines 27-40 of CPP are as follows:

View::View():Object(true)
{
...
    _camera = new osg::Camera;
    _camera->setView(this);

    double height = osg::DisplaySettings::instance()->getScreenHeight();
    double width = osg::DisplaySettings::instance()->getScreenWidth();
    double distance = osg::DisplaySettings::instance()->getScreenDistance();
    double vfov = osg::RadiansToDegrees(atan2(height/2.0f,distance)*2.0);

    _camera->setProjectionMatrixAsPerspective( vfov, width/height, 1.0f,10000.0f);

    _camera->setClearColor(osg::Vec4f(0.2f, 0.2f, 0.4f, 1.0f));

    osg::StateSet* stateset = _camera->getOrCreateStateSet();
    stateset->setGlobalDefaults();
...
}

If there is no operator, setcameramanipulator will be called (New osgga:: trackballmanipulator()); Trackball is set. How does the operator function in the scene? And what happened to trackball? How can the scene come up in the middle of the screen and drag it away? Let's answer them one by one.

How does the operator work?

All operators are derived from osgGA::CameraManipulator, and this CameraManipulator is derived from osgGA::GUIEventHandler. It can be seen that it is essentially an event processing class. Therefore, it will first receive events. For example, the scene moves as soon as the mouse is dragged. The scene movement and immobility are determined by the position and orientation of the viewpoint, that is, the observation matrix. Therefore, the CameraManipulator must have an interface for processing events and an interface for outputting matrices. The interface for processing events is the function bool handle (const osgga:: guieventadapter & EA, osgga:: guiactionadapter & AA); Therefore, each operator must write a special book on this function to achieve the purpose of achieving the interface according to its own operation. The place where this handle is called is in the event traversal phase of one frame drawing, in void Viewer::eventTraversal(), viewer Line 1126 of CPP:_ CameraManipulator->handle( event, 0, _eventVisitor.get()); Although the input parameters are different, you will find that the past will be called after tracing.

After the event is processed, the position of the viewpoint should be changed according to the processed event to achieve the purpose of operating the scene. This is implemented through the function virtual osg::Matrixd getInverseMatrix() of osgGA::CameraManipulator. Its call time is in the update phase void viewer:: updatetrversal(), line 1213:_ cameraManipulator->updateCamera(*_camera); It calls: camera setViewMatrix(getInverseMatrix()); At this time, some people don't understand why we should use the inverse Matrix? The reason is A little around here. Let's seriously understand that in world coordinates, A box is at point A (xa,ya,za) and the viewpoint is at point B (xb,yb,zb). First, regardless of the orientation or not. We want to output the image of point A seen by people at point B at this time. In fact, the graphics community does so. First convert point A into A coordinate system with point B as the local coordinate, and then carry out A series of operations. In fact, this transformation process is equivalent to finding the Matrix that moves point B back to the origin of the world coordinate. Point A can be multiplied by this Matrix. This Matrix is calculated in this way. The transformation that moves people from the origin of the world coordinate to point B is the Matrix, and the transformation that moves point B back to the origin of the world coordinate is the InverseMatrix. Now we want to put point A under the local coordinate of point B, The invertematrix needs to be transformed. It's A little windy, but it's important to understand this. It belongs to basic skills.

How did Trackball come up and look at objects
The first feature of Trackball is that it will look at the objects in the scene. How is this realized? First, setCameraManipulator actually has two parameters: void setCameraManipulator(osgGA::CameraManipulator* manipulator, bool resetPosition = true); The second parameter is true by default, which means that it should be set to the home position initially. In fact, it is to call the home function. Look at the code of this function:

void View::setCameraManipulator(osgGA::CameraManipulator* manipulator, bool resetPosition)
{
    _cameraManipulator = manipulator;

    if (_cameraManipulator.valid())
    {
        _cameraManipulator->setCoordinateFrameCallback(new ViewerCoordinateFrameCallback(this));

        if (getSceneData()) _cameraManipulator->setNode(getSceneData());

        if (resetPosition)
        {
            osg::ref_ptr<osgGA::GUIEventAdapter> dummyEvent = _eventQueue->createEvent();
            _cameraManipulator->home(*dummyEvent, *this);
        }
    }
}

How does home locate the object you just see? It's neither big nor small, neither near nor far. In the function home, it calls virtual void computeHomePosition(const osg::Camera *camera = NULL, bool useBoundingBox = false); First calculate the bounding box, and then call sethomeposition to set the position and orientation of the viewpoint according to the center point and radius of the bounding box. virtual void setHomePosition(const osg::Vec3d& eye, const osg::Vec3d& center, const osg::Vec3d& up, bool autoComputeHomePosition=false)

How does Trackball implement mouse inertial operation?
Trackball has two features:
1. Press the mouse, drag, stop, and the mouse pops up. The scene rotates and does not rotate when the mouse pops up.
2. Press the mouse and throw it out, and the scene will rotate all the time.

The key to the difference between the two is the pop-up operation of the mouse. One is to stop and the other is to throw. This is judged here. Bool standardmanipulator:: handlemouserelease (const guieventadapter & EA, guiactionadapter & US) is the mouse pop-up event of the operator: the key here is that if the time difference between the mouse pop-up and the previous mouse movement is > 0.02 seconds, it is considered as case 1, and if it is less than or equal to 0.02 seconds, it is considered as rejection.

bool StandardManipulator::handleMouseRelease( const GUIEventAdapter& ea, GUIActionAdapter& us )
{
    //Judge that it is DRAG, not MOVE. DRAG has a key pressed, Mask=0
    if( ea.getButtonMask() == 0 )
    {

        double timeSinceLastRecordEvent = _ga_t0.valid() ? (ea.getTime() - _ga_t0->getTime()) : DBL_MAX;
        //Judge the time difference between the previous event and the current event. If it is less than 0.02 seconds, it can be considered as rejection
        if( timeSinceLastRecordEvent > 0.02 )
            flushMouseEventStack();

        if( isMouseMoving() )
        {

            if( performMovement() && _allowThrow )
            {
                us.requestRedraw();
                us.requestContinuousUpdate( true );
                _thrown = true;
            }

            return true;
        }
    }

    flushMouseEventStack();
    addMouseEvent( ea );
    if( performMovement() )
        us.requestRedraw();
    us.requestContinuousUpdate( false );
    _thrown = false;

    return true;
}

Topics: OpenGL osg osgEarth OpenSceneGraph