第10节 实例-双击跑过去操作器

缘由

网友:KeepSmile 提出来能否做一个双击之后,跑过去的例子。大家如果在学习或工作中实现某个功能上需要我写一些例子和分析,可以在本文之后把功能描述清楚在评论区回复或在群里留言,我会帮忙写一些例子和解析。我的目标是用最简单的代码帮最大的忙 _

完整代码在本文最后的代码块和网盘中:

【击此打开网盘资源链接】

功能

场景: 20×20米的格子。中间放着一个坐标轴。
启动场景: 有一个5秒的开场动画,从(0.0, -18, 1.0)缓缓推到(0.0, -8, 1.0)。
操作: 在场景格子上双击,双击后会在交点处加个球,然后视点会花两秒钟转过去,花三秒钟跑过去。在这个过程中你还可以双击别处,又会转往别处,跑往别处。如果当前朝向和点的球的朝向是一致的,也就是不需要转脑袋,则视点直接花5秒跑过去,不需要花2秒钟头。

实现关键点

操作器: 使用osgGA::AnimationPathManipulator,这个操作器可以使用AnimationPath做为入参。因此最关键的就是如何生成AnimationPath
生成路径: 路径的生成靠关键点,关键点有三个参数,一个是时间,一个是位置,一个是朝向。如下:

    if (fromRotate != rotate1)//代表不需要给时间转方向,当起点的方向和终点的方向是一致的时候,就不需要转方向
    {
        //给2秒钟转方向,起点不变,只有方向变
        animationPath->insert(2, osg::AnimationPath::ControlPoint(from, rotate1));
    }
    animationPath->insert(5, osg::AnimationPath::ControlPoint(to, rotate1));

其中5就是时间,也就是路径开始的时候第5S要跑到to, 朝向是rotate1,在两个点的间隔之间,会自动的位置和朝向都进行插值。

事件处理: 在双击时,求交点,画球。我们专门写了个事件处理器MyEventHandler来做事件处理,然后获取当前视点和朝向做为起点,将交点做为终点,使用createAnimationByTwoPoints函数生成一个路径。设置到AnimationPathManipulator当中,注意将AnimationPathManipulator的时间设置为当前仿真时间的取负,也即:_apm->setTimeOffset(-ea.getTime());。这里估计是OSG的BUG,按说setTimeOffset设置成0就代表重新开始。但是源码里是仿真时间+时间偏移,因此动画要想重播,仿真时间就要被减掉。对应代码是:

                if (view->computeIntersections(ea, intersections))
                {
                    //有交点,先画个球
                    osg::Vec3 hitPoint = intersections.begin()->getWorldIntersectPoint();
                    _root->addChild(CreateSphere(hitPoint));

                    //操作器切换成animationPath操作器,创建animationPath直接跑过去
                    //获取当前视点信息
                    osg::Vec3 eye, center, up;
                    view->getCamera()->getViewMatrixAsLookAt(eye, center, up);

                    //得到当前视点的朝向
                    osg::Matrix viewMatrix = view->getCamera()->getInverseViewMatrix();
                    _apm->setAnimationPath(createAnimationByTwoPoints(eye, viewMatrix.getRotate(),hitPoint));
                    //减去当前仿真时间重头开始播,停个timeoffset设置的不太合理
                    //按说设置成0就应该是从头播了,但是其实现加了当前仿真时间,有点怪怪的
                    _apm->setTimeOffset(-ea.getTime());
                }

这样就实现了生成的路径始终以当前视点和朝向为起点,以双击地面的小球为终点。终点时的朝向是由起点和终点的连线决定,可以看看代码,弄不清楚的话建议看看上一篇第09节,里面讲的非常清晰。

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

//定义全局变量用着方便
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)
{
    //防止视点贴地面太近
    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;
    //经X旋转90度以后,视点已经朝向y轴正方向
    //当x,y都大于0在第一四象限,要顺时针旋转theta
    float theta = 0;

    if (tf.x() > 0.000001)//一四象限
    {
        rotate1 *= osg::Quat(std::atan(tf.y() / tf.x()) - osg::inDegrees(90.0), osg::Z_AXIS);
    }
    else if (tf.x() < -0.000001)//二三象限
    {
        rotate1 *= osg::Quat(osg::inDegrees(90.0) + std::atan(tf.y() / tf.x()), osg::Z_AXIS);
    }
    else //x=0
    {
        if (tf.y() > 0)
        {
            //啥也不用做,默认朝y轴正方向
        }
        else
        {
            //转180,朝y轴负方向
            rotate1 *= osg::Quat(osg::PI, osg::Z_AXIS);
        }
    }

    if (fromRotate != rotate1)//代表不需要给时间转方向,当起点的方向和终点的方向是一致的时候,就不需要转方向
    {
        //给2秒钟转方向,起点不变,只有方向变
        animationPath->insert(2, osg::AnimationPath::ControlPoint(from, rotate1));
    }

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

//画一个小球
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)
    {
        //双击则加个球在击处,然后寻路过去
        if (ea.getEventType() == ea.DOUBLECLICK)
        {
            if (ea.getButton() == ea.LEFT_MOUSE_BUTTON)
            {
                //与地板求交
                osgUtil::LineSegmentIntersector::Intersections intersections;
                osgViewer::View* view = dynamic_cast<osgViewer::View*>(&aa);
                if (view->computeIntersections(ea, intersections))
                {
                    //有交点,先画个球
                    osg::Vec3 hitPoint = intersections.begin()->getWorldIntersectPoint();
                    _root->addChild(CreateSphere(hitPoint));

                    //操作器切换成animationPath操作器,创建animationPath直接跑过去
                    //获取当前视点信息
                    osg::Vec3 eye, center, up;
                    view->getCamera()->getViewMatrixAsLookAt(eye, center, up);

                    //得到当前视点的朝向
                    osg::Matrix viewMatrix = view->getCamera()->getInverseViewMatrix();
                    _apm->setAnimationPath(createAnimationByTwoPoints(eye, viewMatrix.getRotate(),hitPoint));
                    //减去当前仿真时间重头开始播,停个timeoffset设置的不太合理
                    //按说设置成0就应该是从头播了,但是其实现加了当前仿真时间,有点怪怪的
                    _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();
}

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片