Section 36.3 animation - key points of path animation generation

Keywords: osg

This function

In this section, we have created three key points in the scene to form a line. A villain will run from the first point to the last point, as shown in the figure:

The contents of this section are in the online disk, and the directory is the attachment / in the / osgChina webmaster's collection / file. If there are attachments, they will be stored in this directory according to the section number:

Please use the browser to open it. If you encounter problems or add groups, you can also add me on wechat: 13324598743:
Link: https://pan.baidu.com/s/13gwJLwo_LbRnN3Bl2NXXXw
Extraction code: xrf5

Key points of realization

Path generation

OSG uses the class osg::AnimationPath, which has a method called insert:
animationPath->insert(t, osg::AnimationPath::ControlPoint(keyPoint->at(i), rotate));

  • The first parameter t represents the time when the path passes through the current point
  • The second parameter ControlPoint contains two quantities, one is the position and the other is the orientation

When these two parameters are combined, it means that at a certain time, it passes through a certain point and faces in a certain direction. The time is easy to find. The distance between the two key points can be divided by the speed. We have realized a function here, which is called:
osg::AnimationPath* CreateAnimate(osg::Vec3Array* keyPoint, float speed)

Its input parameter osg::Vec3Array contains a series of points, and speed represents the speed of the path.

Now we should pay attention to two problems. One is how to calculate the orientation. The current scene ceep.ive is expanded along the XY plane, so the orientation calculation is mainly the angle of rotation along the Z axis. By default, our head is facing the Z axis and the positive direction of the Y axis, so we use a simple function to calculate this angle:
Quqt.makeRotate(osg::Y_AXIS, keyPoint->at(i+1) - keyPoint->at(i));

Because we know that the point in the keyPoint is expanded along the XY plane, the angle between its vector and the Y axis is its rotation angle.

The second problem is the interpolation of orientation,

As shown in the figure, the blue is the key point and orientation of the pressing, which is reasonable at first. The starting point faces the second point, and the second point faces the third point, but the value between them may not be reasonable. For the position, it is formed by interpolation. The position from the first point to the middle of the second point is interpolated according to time, but the orientation is also reasonable, such as the yellow arrow, It transitions from the orientation of the starting point to the orientation of the second point. This effect may not be what we want. Maybe we want him to turn when he runs to the front. This requires artificial interpolation of a key point at the turning point when calculating the path. This is what we often do.

Character loading

The animation control of this character is in Section 36.2 animation - control of bone animation , we take the 22nd action and play it to get the running person, but the default orientation of the person is not facing Z and y, and it is too large. We modify it. First scale it by 0.02 times, then rotate it 90 degrees along the X axis, then rotate it 180 degrees along the Z axis, and then raise it 4 meters in the positive direction of the Z axis, and then put the figure on the ground with its head facing the Z axis, Looking at the positive direction of the Y axis ensures that he is running along the path, not running backwards, head down or across.

These are all the key points.

Here are all the codes

#include <osgViewer/Viewer>
#include <osgDB/ReadFile>
#include <osg/NodeVisitor>
#include <osg/Transform>
#include <osg/AnimationPath>
#include <osgGA/GUIEventHandler>
#include <osgUtil/LineSegmentIntersector>
#include <osg/ShapeDrawable>
#include <osg/LineWidth>
#include <osgAnimation/BasicAnimationManager>
#include <osg/MatrixTransform>


//Given the vertex and the speed of travel, create a path
osg::AnimationPath* CreateAnimate(osg::Vec3Array* keyPoint, float speed)
{
    //Only one point cannot form a path
    if (keyPoint->size() <= 1)
    {
        return NULL;
    }

    osg::AnimationPath* animationPath = new osg::AnimationPath;
    animationPath->setLoopMode(osg::AnimationPath::LOOP);

    //Time corresponding to key point
    float t = 0.0;
    //orientation
    osg::Quat rotate;
    for (int i = 0; i < keyPoint->size(); i++)
    {
        //Last point
        if ((keyPoint->size()-1) == i)
        {
            //The orientation is not calculated, so the orientation of the previous point is used
            animationPath->insert(t, osg::AnimationPath::ControlPoint(keyPoint->at(i), rotate));
            //End of cycle
            break;
        }
        else
        {
            //Other points
            //If it is the first point, the time is t=0.0, and the orientation is determined by the orientation of the first point and the second point
            //Calculate the orientation of the current point. Because our orientation changes horizontally only on one axis, we can use a simple method
            //By default, the viewport is facing the Y axis, and we want to turn it to the vector from the first point to the second point
            rotate.makeRotate(osg::Y_AXIS, keyPoint->at(i+1) - keyPoint->at(i));
            animationPath->insert(t, osg::AnimationPath::ControlPoint(keyPoint->at(i), rotate));
        }

        //Distance between two points divided by speed
        t += ((keyPoint->at(i+1) - keyPoint->at(i)).length()/speed);

    }

    return animationPath;
}

//Create a red ball as a sign and give the center and radius of the circle
osg::Node* CreateSphere(osg::Vec3 center, float r)
{
    osg::Geode* gnode = new osg::Geode;
    osg::ShapeDrawable* sd = new osg::ShapeDrawable(new osg::Sphere(center, r));
    sd->setColor(osg::Vec4(1.0, 0.0, 0.0, 1.0));//gules
    gnode->addDrawable(sd);
    return gnode;
}

//Create lines, given vertices and red width,
osg::Node* CreateLine(osg::Vec3Array* vertex, float lineWidth)
{
    osg::Geode* gnode = new osg::Geode;
    osg::Geometry* geom = new osg::Geometry;
    gnode->addDrawable(geom);
    
    //Set vertex
    geom->setVertexArray(vertex);

    //Set color
    osg::Vec4Array* color = new osg::Vec4Array();
    color->push_back(osg::Vec4(1.0, 0.0, 0.0, 1.0));
    geom->setColorArray(color, osg::Array::BIND_OVERALL);

    //Set lineweight
    osg::LineWidth* lw = new osg::LineWidth(lineWidth);
    geom->getOrCreateStateSet()->setAttribute(lw, osg::StateAttribute::ON | osg::StateAttribute::PROTECTED);
    //Turn off the light to make it brighter
    geom->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED);

    //Add settings as lines
    geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_STRIP, 0, vertex->size()));

    return gnode;
}

osg::Group* createMark(osg::Vec3Array* keyPoint)
{
    osg::Group* markGroup = new osg::Group();

    //Add red ball as key
    for (int i = 0; i < keyPoint->size(); i++)
    {
        markGroup->addChild(CreateSphere(keyPoint->at(i), 1.0));
    }

    //Add line
    markGroup->addChild(CreateLine(keyPoint, 1.0));

    return markGroup;
}

//Add a moving villain and move along the animation
osg::Node* loadPersion(osg::AnimationPath* animation)
{
    //Used to rotate the villain so that its default orientation is correct
    osg::MatrixTransform* mt = new osg::MatrixTransform;

    osg::Node* persion = osgDB::readNodeFile("Naruto.fbx");
    //Because the orientation of the villain is uncertain during modeling, we need to look at the orientation loaded into the OSG, and then adjust it to the Y axis and the Z axis of the head,
    //When we build this path, we calculate the included angle based on the Z axis of the head and 0 degrees towards the Y axis
    //First reduce it by 0.02 times, then rotate the villain 90 degrees along the x axis, and then rotate the villain 180 degrees along the Z axis, so that its orientation is towards the positive direction of the Y axis, and then lift it for 4 meters along the positive direction of the Z axis
    mt->setMatrix(osg::Matrix::scale(0.02, 0.02, 0.02)*osg::Matrix::rotate(osg::inDegrees(90.0), osg::X_AXIS) * osg::Matrix::rotate(osg::inDegrees(180.0), osg::Z_AXIS)*osg::Matrix::translate(osg::Vec3(0.0, 0.0, 4.0)));
    mt->addChild(persion);

    //Play its 22nd action, which is running
    osgAnimation::BasicAnimationManager* apc = dynamic_cast<osgAnimation::BasicAnimationManager*>(persion->getUpdateCallback());
    apc->playAnimation(apc->getAnimationList().at(22));

    //Actual path control
    osg::MatrixTransform* persionMT = new osg::MatrixTransform;
    persionMT->setUpdateCallback(new osg::AnimationPathCallback(animation));
    persionMT->addChild(mt);

    return persionMT;
}


int main()
{
    osgViewer::Viewer viewer;
    //It stores the key points on the path
    osg::Vec3Array* keyPoint = new osg::Vec3Array;
    keyPoint->push_back(osg::Vec3(-28.22, -63.42, 1.0));
    keyPoint->push_back(osg::Vec3(3.38, -32, 1.0));
    keyPoint->push_back(osg::Vec3(40.34, -38.40, 1.0));

    osg::Node* ceep = osgDB::readNodeFile("ceep.ive");

    //Scenario ceep
    osg::Group* root = new osg::Group;
    root->addChild(ceep);
    //Marking of keys and paths
    root->addChild(createMark(keyPoint));

    //Add sports villain
    root->addChild(loadPersion(CreateAnimate(keyPoint, 2.5)));

    viewer.setSceneData(root);

    return viewer.run();
}

Posted by sigmadog on Mon, 13 Sep 2021 20:41:39 -0700