Section 10 example - double click to run to the operator

reason

Netizen: KeepSmile put forward an example of whether you can run after double clicking. If you need me to write some examples and analysis to realize a function in your study or work, you can clearly describe the function after this article, reply in the comment area or leave a message in the group, and I will help write some examples and analysis. My goal is to help the most with the simplest code_

The complete code is in the code block and network disk at the end of this article:

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

function

Scene: 20x20 meter grid. There is a coordinate axis in the middle.
Start the scene: there is a 5-second opening animation, which is slowly pushed from (0.0, - 18, 1.0) to (0.0, - 8, 1.0).
Operation: double click on the scene grid. After double clicking, a ball will be added at the intersection, and then the viewpoint will turn in two seconds and run in three seconds. In this process, you can also double-click elsewhere, turn elsewhere and run elsewhere. If the current orientation is consistent with the orientation of the ball at the point, that is, there is no need to turn your head, the viewpoint directly takes 5 seconds to run, and it does not take 2 seconds to turn your head.

Implementation key points

Operator: use osgGA::AnimationPathManipulator, which can use AnimationPath as input parameter. Therefore, the key is how to generate AnimationPath
Generate path: the path is generated by key points, which have three parameters: time, position and orientation. As follows:

    if (fromRotate != rotate1)//It means that there is no need to turn the direction of time. When the direction of the starting point is consistent with the direction of the end point, there is no need to turn the direction
    {
        //Give 2 seconds to turn the direction, the starting point remains unchanged, only the direction changes
        animationPath->insert(2, osg::AnimationPath::ControlPoint(from, rotate1));
    }
    animationPath->insert(5, osg::AnimationPath::ControlPoint(to, rotate1));

Where 5 is the time, that is, at the beginning of the path, the 5S should run to, and the direction is rotate1. Between the intervals of the two points, the position and direction will be interpolated automatically.

Event handling: when double clicking, find the intersection and draw the ball. We specially write an event handler MyEventHandler to handle events, then obtain the current viewpoint and orientation as the starting point, take the intersection as the end point, and use the createAnimationByTwoPoints function to generate a path. Set it to the AnimationPathManipulator. Note that the time of the AnimationPathManipulator is set to the negative of the current simulation time, that is:_ apm->setTimeOffset(-ea.getTime());. It is estimated that this is the BUG of OSG. Normally, setting settimeoffset to 0 means starting over. But the source code is simulation time + time offset, so if the animation wants to replay, the simulation time will be reduced. The corresponding code is:

                if (view->computeIntersections(ea, intersections))
                {
                    //There are intersections. Draw a ball first
                    osg::Vec3 hitPoint = intersections.begin()->getWorldIntersectPoint();
                    _root->addChild(CreateSphere(hitPoint));

                    //Switch the operator to the animation path operator, create the animation path and run directly
                    //Get current viewpoint information
                    osg::Vec3 eye, center, up;
                    view->getCamera()->getViewMatrixAsLookAt(eye, center, up);

                    //Get the orientation of the current viewpoint
                    osg::Matrix viewMatrix = view->getCamera()->getInverseViewMatrix();
                    _apm->setAnimationPath(createAnimationByTwoPoints(eye, viewMatrix.getRotate(),hitPoint));
                    //Subtract the current simulation time and start broadcasting again. It is unreasonable to stop a timeoffset setting
                    //It's supposed to be played from scratch when it's set to 0, but it's a little strange to add the current simulation time
                    _apm->setTimeOffset(-ea.getTime());
                }

In this way, the generated path always starts with the current viewpoint and orientation and ends with the small ball double clicking the ground. The orientation of the end point is determined by the connection between the starting point and the end point. You can look at the code. If you don't know, it is recommended to look at section 09 of the previous article, which is very clear.

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

//It is convenient to define global variables
osgGA::AnimationPathManipulator* _apm = new osgGA::AnimationPathManipulator;
osg::Group* _root = nullptr;
osg::Node* _base = nullptr;

osg::AnimationPath* createAnimationByTwoPoints(osg::Vec3 from, osg::Quat fromRotate, osg::Vec3 to)
{
    //Prevent the viewpoint from being too close to the ground
    if (from.z() < 1.0) from.z() = 1.0;
    if (to.z() < 1.0) to.z() = 1.0;

    osg::AnimationPath* animationPath = new osg::AnimationPath;
    animationPath->setLoopMode(osg::AnimationPath::NO_LOOPING);
    animationPath->insert(0, osg::AnimationPath::ControlPoint(from, fromRotate));
    osg::Quat rotate1(osg::inDegrees(90.0), osg::X_AXIS);

    osg::Vec3 tf = to - from;
    //After 90 degrees of X rotation, the viewpoint has been oriented in the positive direction of y axis
    //When x and y are greater than 0 in the first four quadrants, rotate theta clockwise
    float theta = 0;

    if (tf.x() > 0.000001)//One four quadrant
    {
        rotate1 *= osg::Quat(std::atan(tf.y() / tf.x()) - osg::inDegrees(90.0), osg::Z_AXIS);
    }
    else if (tf.x() < -0.000001)//Second and third quadrants
    {
        rotate1 *= osg::Quat(osg::inDegrees(90.0) + std::atan(tf.y() / tf.x()), osg::Z_AXIS);
    }
    else //x=0
    {
        if (tf.y() > 0)
        {
            //You don't have to do anything. The default is in the positive direction of the y axis
        }
        else
        {
            //Turn 180 to the negative direction of y-axis
            rotate1 *= osg::Quat(osg::PI, osg::Z_AXIS);
        }
    }

    if (fromRotate != rotate1)//It means that there is no need to turn the direction of time. When the direction of the starting point is consistent with the direction of the end point, there is no need to turn the direction
    {
        //Give 2 seconds to turn the direction, the starting point remains unchanged, only the direction changes
        animationPath->insert(2, osg::AnimationPath::ControlPoint(from, rotate1));
    }

    animationPath->insert(5, osg::AnimationPath::ControlPoint(to, rotate1));
    return animationPath;
}

//Draw a small ball
osg::Geode* CreateSphere(osg::Vec3 center)
{
    osg::Geode* gnode = new osg::Geode;
    osg::ShapeDrawable* sd = new osg::ShapeDrawable(new osg::Sphere(center, 0.1));
    gnode->addDrawable(sd);
    return gnode;
}

class MyEventHandler : public osgGA::GUIEventHandler
{
public:
    MyEventHandler() {}

    virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
    {
        //Double click to add a ball at the hit, and then find the way
        if (ea.getEventType() == ea.DOUBLECLICK)
        {
            if (ea.getButton() == ea.LEFT_MOUSE_BUTTON)
            {
                //Intersection with floor
                osgUtil::LineSegmentIntersector::Intersections intersections;
                osgViewer::View* view = dynamic_cast<osgViewer::View*>(&aa);
                if (view->computeIntersections(ea, intersections))
                {
                    //There are intersections. Draw a ball first
                    osg::Vec3 hitPoint = intersections.begin()->getWorldIntersectPoint();
                    _root->addChild(CreateSphere(hitPoint));

                    //Switch the operator to the animation path operator, create the animation path and run directly
                    //Get current viewpoint information
                    osg::Vec3 eye, center, up;
                    view->getCamera()->getViewMatrixAsLookAt(eye, center, up);

                    //Get the orientation of the current viewpoint
                    osg::Matrix viewMatrix = view->getCamera()->getInverseViewMatrix();
                    _apm->setAnimationPath(createAnimationByTwoPoints(eye, viewMatrix.getRotate(),hitPoint));
                    //Subtract the current simulation time and start broadcasting again. It is unreasonable to stop a timeoffset setting
                    //It's supposed to be played from scratch when it's set to 0, but it's a little strange to add the current simulation time
                    _apm->setTimeOffset(-ea.getTime());
                }
            }
        }

        return false;
    }
};

osg::Node* createBase(const osg::Vec3 center, float radius)
{
    osg::Group* root = new osg::Group;
    _root = root;

    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;
    _base = 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.addEventHandler(new MyEventHandler());

    osg::Quat rotate0(osg::inDegrees(90.0), osg::X_AXIS);
    _apm->setAnimationPath(createAnimationByTwoPoints(osg::Vec3(0.0, -18, 1.0), rotate0, osg::Vec3(0.0, -8, 1.0)));

    viewer.setCameraManipulator(_apm);
    viewer.setSceneData(createBase(osg::Vec3(0.0, 0.0, 0.0), 20.0));

    return viewer.run();
}

Keywords: OpenGL osg osgEarth OpenSceneGraph

Added by scald on Sun, 23 Jan 2022 11:27:05 +0200