让物体绕固定点做圆周运动 让物体到达目的点

28 篇文章 6 订阅
1 篇文章 0 订阅

考虑了很多思路,但是大部分实际实现与预想的有很大差异,终其原因,是因为每帧的所消耗的时间是不定的,且轨迹离散,所以每帧都可能需要做轨迹的稍微修正(这种修正帧率越低速度越快会导致越明显)。下面的思路是比较简洁和高效的一种方式。
对于3d中的物体运动到目标点或者运动到目标轨迹,都需要先考虑如何让物体到达目标点或者轨迹。这里只讲述如何到达目标轨迹。对于有需要考虑精确的加速度的运动,需要考虑路径积分等复杂的运算。这里只考虑匀速的方式到达轨迹。如下:


r1 为目标轨迹,r2为物体当前位置的向心运动轨迹。O为物体当前位置及运动朝向。绿色的线为当前轨迹与目标轨迹的切线,黄色的为目标物体从当前位置运动到目标轨迹将要走的预期轨迹。
假设物体速度是恒定的,为了让物体朝向运动轨迹,首先需要让物体方向转到朝向目标轨迹,考虑的第一个就是用向心力(运动轨迹计算中必不可少的一个量)来达到转向目的,假设有一个外力Fx(向心力) = Fl(离心力) ,向心力的加速度为ao = Fx/m,Fl = ma = m*v^2/r   =>   a = v^2/r 。r = Fx/(m*v^2) = Fx/m/(v^2) = ao/(v^2)。实际中,向心力是有最大值的且可以控制成恒定量和可收放,向心力有最大值就表示半径r有最小值,根据 r和向心运动的轨迹圆和当前位置 求出的圆心 就很方便计算轨迹了。实际运算中,因为每帧间隔计算是离散的,所以在进入预设轨迹之后实际轨迹应该是一直处于相交状态,这样就需要一直保持位移修正修正和角度修正。帧率越高,越能贴合实际预期的轨迹。

注意从一个圆轨迹硬切到另外一个圆轨迹,是有力的突变的!!!!要想平滑快速切入,会比较麻烦。

扩展:根据这个方式,也可以很快的判断出前方的目标点在当前速度下是否不绕圈的情况下可达(在向心圆内或圆外),甚至计算出如何最快入轨(比如降低速度,复杂运算就需要考虑积分问题)等等操作。(到达目的点如果容许绕圈的情况,可以用我这种方式,虽然可能不是最快速度或最短路径,但是比较简洁容易理解。)


下面简单粗暴的使用最小r值示例。 
整体思路:

    /*做圆周运动,每帧的angle应该是不为0的,因为每帧都会有转角。也就是每帧都需要进行方向的修正。但是这个方式的思路是(即上面图中的黄色线路图的描述):

1、让计算物体每帧的角度修正。
2、在物体转动到目标切线方向的时候(forward==targetdir),如果圆心位置偏移比较大,将角度修正屏蔽掉(设置为0),让只做向着目标轨迹切点方向的平移,可以想象这个直接适用于上面的三种情况。
*/

int AppWorldLogic::init() //循环体外,初始化
{
    App::get()->setUpdate(1);
    Visualizer::get()->setEnabled(1);
    m_nodeO = Editor::get()->getNodeByName("material_ball");
    m_node = m_nodeO->clone(); //当前控制运动的物体
    m_node->setWorldTransform(translate(Vec3(m_x, 0, 0))); //设置当前控制运动的物体的初始位置
    m_target = m_nodeO->getNodeWorldPosition();//目标轨迹圆心
    vec3 left = m_node->getWorldLeft();
    vec3 targetDir = getTvec3(vec3((m_target - m_node->getNodeWorldPosition())));//当前控制物体的与目标轨迹圆心的方向。等价于预期的切入到目标轨迹的切入点的切线方向。
    m_isleft = dot(targetDir, left) > -0.00001;//根据偏向左边还是右边来判定向左还是向右。当然可以设置只向左或向右
    return 1;
}

int AppWorldLogic::update() //循环体内,60fps
{//只考虑二维平面的圆周运动 ,对于三维空间的运动,要复杂一些,但是可以通过降维加修正来达到预期。
    //本方法直接设定物体的离心力固定,只绕特定半径来做圆周运动。步骤为先转动方向,再移动到轨道上,再在轨道上运行。
    float ifps = Game::get()->getIFps();//上一帧所消耗的时间
    Vec3 pos = m_node->getNodeWorldPosition();
    vec3 forward = m_node->getWorldForward(); //当前物体的前方
    vec3 left = m_node->getWorldLeft();
    vec3 right = m_node->getWorldRight();
    double currentAcce = m_speed*m_speed / m_radius; //获取向心力加速度,m_radius表示事先设定的半径
    vec3 up = vec3(0, 0, 1);//考虑在xy平面上做圆周运动
    float angle = 0;
    Vec3 centerDir = m_isleft? Vec3(left):Vec3(right);
    vec3 v = forward*m_speed;
    v += vec3(centerDir)*currentAcce*ifps;//当前运动方向为forward+向心力的加速度乘ifps。
    v.z = 0;
    v = v.normalize()*m_speed;//计算出当前物体在当前帧的运动方向,注意这是个离散值。
    angle = getAngle(forward, v, up);  //计算出这帧应该偏移的角度,angle between forward and new direction// / 2.0f    Mat4 mat = m_node->getWorldTransform();
    Vec3 centerPos = pos;
    if (m_isleft)
    {
        centerPos = (mat*translate(Vec3(1, 0, 0)*m_radius)).getTranslate();//当前运动轨迹的圆心
    }
    else
    {
        centerPos = (mat*translate(Vec3(-1, 0, 0)*m_radius)).getTranslate();
    }
    if((centerPos-m_target).length()>m_speed*ifps)//圆心偏移是几乎不可能为0的,在一个可接受的误差范围内就足够了。
    {
        vec3 targetDir = vec3(m_target - centerPos).normalize();//m_target表示目标圆心,此处获取到从当前的圆心到目标点的向量。这个正好是该修正的偏移值。
        targetDir = targetDir.normalize();
        forward = forward.normalize();
        Log::message("%f\n", dot(targetDir, forward));//dot算出两向量夹角的弧度
        if (dot(targetDir, forward) > 0.99)//目标方向和当前方向不可能是绝对的没有误差。所以此处判断如果两个方向夹角的余弦值为0.99即两个方向夹角接近0度了,则不用去修正角度了。将角度设置为0。(可以绘制一张图,目标方向与目标元切线方向一致。)
        {
            angle = 0;//方向正确了,接下来就只进行偏移的修正,将方向修正暂停。
        }
    }

    mat = mat*Mat4(quat(up, angle))*translate(Vec3(0, -1, 0)*m_speed*ifps);//将这帧之前的变换矩阵乘以这帧的变化矩阵(注意矩阵的组装和摆放的顺序,可参考我之前的关于矩阵的博客),求出这帧之后的变换矩阵。
    m_node->setWorldTransform(mat);
    return 1;
}
 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值