第36.4节 动画-路径动画中的角度控制问题

本节功能

本节创建了一个高高低低的三维的路径,在楼顶和地面之间穿梭,一个飞机沿着这个路径进行飞行。如下图所示:
在这里插入图片描述
请使用浏览器打开,平时遇到问题或加群也可以加我微信:13324598743:
【击此打开网盘资源链接】

关键点

在上一节的基础上:第36.3节 动画-路径动画生成要点,我们很容易得出结论,其中关于路径两点之间的角度计算是个问题。

如果像上一节,是二维的,那么只需要在一个平面内计算角度就可以了,如果是三维的呢?想一想是不是就有点复杂。二维的情况下,模型是绕着Z轴旋转。而三维的情况下,可以认为是绕着任意轴旋转,大家可以看到本节的关于计算角度的代码其实并没有改变,还是这个:

osg::Quat rotate;
rotate.makeRotate(osg::Y_AXIS, keyPoint->at(i + 1) - keyPoint->at(i));

也就是仍然的飞机默认朝向Y轴正方向,我们将其旋转到路径上的两点朝向的向量。只是这个向量并不是指向XY平面的,而是任意的指向。一个向量旋转到另一个向量,其实是:向量沿任意轴的旋转问题。我们想象有一个向量垂直于当前向量,和旋转后的向量,那么就可以认为这个垂直的向量是轴,当前向量就是绕着这个轴向量旋转到达旋转后向量的。

绕任意轴的旋转其实还真不是个容易的命题,我在这里有一篇文章大家可以详细看一下计算公式:【数学】三维向量绕任意轴的旋转公式

可以看到,旋转之后,飞机的翅膀始终是水平的。这也许不是我们想要的,我们看osganimate这个例子(只需要命令行中输入osganimate就可以运行),它的飞机翅膀有一定的角度。假如说我们要飞机沿着某个方向要旋转着前进,那么我们就要在关键点处用自己的算法来指定旋转量,而不是由上面的makeRotate公式来计算。

现实中,常常的,飞机或其它航天器的轨迹点除了位置以外,还会包含yaw, pitch, roll这三个量,其实就是旋转的量,这样值就给定了,就不需要我们计算了。

第18.4节 OE3.1实例-导弹在地球上沿路径飞行中,我使用了较为笨拙的方法严丝合缝的实现了一个叫做GetFlyPosture的方法,可以根据起点和终点,返回旋转的三个分量pitch, yaw, roll,大家需要也可以参考一下:

void GetFlyPosture(osg::Vec3d First, osg::Vec3d Second, double& PitchAngle, double& yAngleHengGun, double& YawAngle)

所有代码

#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>
#include <osg/io_utils>

//给定顶点,和行进的速度,创建一个路径
osg::AnimationPath* CreateAnimate(osg::Vec3Array* keyPoint, float speed)
{
    //只有1个点,是形成不了路径的
    if (keyPoint->size() <= 1)
    {
        return NULL;
    }

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

    //关键点对应的时间
    float t = 0.0;
    //朝向
    osg::Quat rotate;
    for (int i = 0; i < keyPoint->size(); i++)
    {
        //最后一个点,就把第一个点也压进去,形成环路
        if ((keyPoint->size() - 1) == i)
        {
            //最后一个点的朝向不计算了,就用上一个点的朝向
            animationPath->insert(t, osg::AnimationPath::ControlPoint(keyPoint->at(i), rotate));

            //计算最后一个点的时间
            t += ((keyPoint->at(0) - keyPoint->at(i)).length() / speed);

            //第一个点的朝向还是指向第二个点
            rotate.makeRotate(osg::Y_AXIS, keyPoint->at(1) - keyPoint->at(0));
            animationPath->insert(t, osg::AnimationPath::ControlPoint(keyPoint->at(0), rotate));
            //循环结束
            break;
        }
        else
        {
            //其它点
            //如果是第一个点,时间上是t=0.0, 朝向上由第一个点和第二个点的朝向决定
            //计算当前点的朝向,因为我们的朝向只在一个轴上水平的变化,所以可以用简单的方法
            //视口默认朝Y轴,我们要将其转到由第一个点看向第二个点的向量
            rotate.makeRotate(osg::Y_AXIS, keyPoint->at(i + 1) - keyPoint->at(i));
            animationPath->insert(t, osg::AnimationPath::ControlPoint(keyPoint->at(i), rotate));
        }

        //两点间的距离除以速度
        t += ((keyPoint->at(i + 1) - keyPoint->at(i)).length() / speed);

    }

    return animationPath;
}

//创建一个红球做标志,给定圆心和半径
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));//红色
    gnode->addDrawable(sd);
    return gnode;
}

//创建线,给定顶点和红宽,
osg::Node* CreateLine(osg::Vec3Array* vertex, float lineWidth)
{
    osg::Geode* gnode = new osg::Geode;
    osg::Geometry* geom = new osg::Geometry;
    gnode->addDrawable(geom);

    //设置顶点
    geom->setVertexArray(vertex);

    //设置颜色
    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);

    //设置线宽
    osg::LineWidth* lw = new osg::LineWidth(lineWidth);
    geom->getOrCreateStateSet()->setAttribute(lw, osg::StateAttribute::ON | osg::StateAttribute::PROTECTED);
    //关闭灯光显得亮一点
    geom->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED);

    //设置添加为线
    geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_LOOP, 0, vertex->size()));

    return gnode;
}

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

    //添加红球做关键点
    for (int i = 0; i < keyPoint->size(); i++)
    {
        markGroup->addChild(CreateSphere(keyPoint->at(i), 1.0));
    }

    //添加线
    markGroup->addChild(CreateLine(keyPoint, 1.0));

    return markGroup;
}


//添加运动的飞机
osg::Node* loadFly(osg::AnimationPath* animation)
{
    //用于旋转飞机,使其朝向合适
    osg::MatrixTransform* mt = new osg::MatrixTransform;

    osg::Node* glider = osgDB::readNodeFile("glider.osg");
    glider->getOrCreateStateSet()->setMode(GL_RESCALE_NORMAL, osg::StateAttribute::ON);
    //飞机默认朝X轴负方向,我们绕Z轴转-90度,使其朝Y轴正方向
    mt->setMatrix(osg::Matrix::scale(5.0, 5.0, 5.0) * osg::Matrix::rotate(osg::inDegrees(-90.0), osg::Z_AXIS));
    mt->addChild(glider);

    //实际的路径控制
    osg::MatrixTransform* persionMT = new osg::MatrixTransform;
    persionMT->setUpdateCallback(new osg::AnimationPathCallback(animation));
    persionMT->addChild(mt);

    return persionMT;
}

int main()
{
    osgViewer::Viewer viewer;
    //里面存放的是路径上的关键点
    osg::Vec3Array* keyPoint = new osg::Vec3Array;
    keyPoint->push_back(osg::Vec3(-5.15, -88.89, 8.0));
    keyPoint->push_back(osg::Vec3(50.1688, -62.9621, 39.0));
    keyPoint->push_back(osg::Vec3(79.1318, -46.6088, 15.0));
    keyPoint->push_back(osg::Vec3(80.03, -26.95, 29.0));
    keyPoint->push_back(osg::Vec3(81.41, -6.42, 12.0));
    keyPoint->push_back(osg::Vec3(70.11, 12.47, 29.0));
    keyPoint->push_back(osg::Vec3(57.51, 28.49, 12.0));
    keyPoint->push_back(osg::Vec3(38.83, 45.23, 29.0));
    keyPoint->push_back(osg::Vec3(6.98, 61.87, 12.0));
    keyPoint->push_back(osg::Vec3(-23.46, 43.42, 29.0));
    keyPoint->push_back(osg::Vec3(-36.27, 22.40, 15.0));
    keyPoint->push_back(osg::Vec3(-42.94, -13.22, 29.0));
    keyPoint->push_back(osg::Vec3(-60.78, -41.55, 8.0));
    keyPoint->push_back(osg::Vec3(-41.95, -49.65, 29.0));
    osg::Node* ceep = osgDB::readNodeFile("ceep.ive");

    //场景ceep
    osg::Group* root = new osg::Group;
    root->addChild(ceep);
    //关键点和路径的标记
    root->addChild(createMark(keyPoint));

    //添加运动的小人
    root->addChild(loadFly(CreateAnimate(keyPoint, 3.0)));

    viewer.setSceneData(root);
    //viewer.addEventHandler(new MyEvent);

    return viewer.run();
}

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

昵称

取消
昵称表情代码图片

    暂无评论内容