首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > C++ >

Box2D C++课程11-旋转指定角度

2012-09-09 
Box2D C++教程11-旋转指定角度Box2D C教程11-旋转指定角度 转载自:http://www.ohcoder.com/post/2012-06-1

Box2D C++教程11-旋转指定角度

Box2D C++教程11-旋转指定角度

 

转载自:http://www.ohcoder.com/post/2012-06-18/40027953487

旋转指定角度 

这个话题就像之前那个话题,只不过把直线运动改成旋转运动。旋转一个物体既可以直接设置角度也可以通过设置扭矩/冲量方法来实现,对一个点直接改变其旋转角度,如果是这么做,那么其实并不算是真正的模拟物理场景。

为了做这个实验我们需要一个动态物体,最好没有重力的影响,那样物体可以停留在半空中。我们会给物体附加一个具有特定方向的定制器,我们可以查看物体是否面向右侧。让我们就像设置“顶点”一样先设置一下这个多边形吧:

b2Body* body;

      

FooTest() {

  //body definition

  b2BodyDef myBodyDef;

  myBodyDef.type = b2_dynamicBody;

      

  //hexagonal shape definition

  b2PolygonShape polygonShape;

  b2Vec2 vertices[6];

  for (int i = 0; i < 6; i++) {

    float angle = -i/6.0 * 360 * DEGTORAD;

    vertices[i].Set(sinf(angle), cosf(angle));

  }

  vertices[0].Set( 0, 4 ); //change one vertex to be pointy

  polygonShape.Set(vertices, 6);

      

  //fixture definition

  b2FixtureDef myFixtureDef;

  myFixtureDef.shape = &polygonShape;

  myFixtureDef.density = 1;

      

  //create dynamic body

  myBodyDef.position.Set(0, 10);

  body = m_world->CreateBody(&myBodyDef);

  body->CreateFixture(&myFixtureDef);

      

  //zero gravity

  m_world->SetGravity( b2Vec2(0,0) );

}

Box2D C++课程11-旋转指定角度

设置物体的点,使其朝向我们,然后设置鼠标输入方法:

//class member variable

//class member variable

b2Vec2 clickedPoint;

    

//in class constructor

clickedPoint = b2Vec2(0,20);//initial starting point

    

//override parent class

void MouseDown(const b2Vec2& p)

{

  //store last mouse-down position

  clickedPoint = p;

    

  //do normal behaviour

  Test::MouseDown( p );

}

    

//inside Step()

glPointSize(4);

glBegin(GL_POINTS);

glVertex2f( clickedPoint.x, clickedPoint.y );

glEnd();

如果你不明白Step()方法里的代码也不要紧,这段代码只是在屏幕上画出了鼠标单击的点。

Box2D C++课程11-旋转指定角度

这个点将会移到鼠标最后一次单击的地方。在场景中这也是一种默认的掠夺行为,虽然有一点点怪异但是还是比较好的满足了我们的需求。

直接设置角度

使用SetTransform方法来设置角度非常简单,但是首先我们需要知道设置到哪个角度,给定物体的坐标点以及需要设置的目标点。添加每帧都会调用的Step()方法:

//in Step() function

float bodyAngle = body->GetAngle();

b2Vec2 toTarget = clickedPoint - body->GetPosition();

float desiredAngle = atan2f( -toTarget.x, toTarget.y );

  

//view these in real time

m_debugDraw.DrawString(5, m_textLine, "Body angle %.3f", bodyAngle * RADTODEG);

m_textLine += 15;

m_debugDraw.DrawString(5, m_textLine, "Target angle %.3f", desiredAngle * RADTODEG);

m_textLine += 15;

现在用鼠标单击屏幕并进行旋转,看看在物体本身和目标点之间的角度是如何旋转的。注意如果物体在某个方向上持续旋转,那么角度会不停的变大甚至会超过360度,但是我们计算的目标点的角度会保持在-180和180之间。

Box2D C++课程11-旋转指定角度

现在我们看到是如何计算行为角度的,尝试使用SetTransform方法可以让物体指向目标点,而且你会看到物体自己会立刻指向目标点。

body->SetTransform( body->GetPosition(), desiredAngle );

Box2D C++课程11-旋转指定角度

试着抛弃物体,之后它会慢慢接近目标点,然后你会看到这个方法所带来负面影响,物体不会像真实的物理世界一样进行模拟。我发现物体要么会远离目标点,要么以某种轨迹绕着目标点进行旋转。这是因为质心稍稍偏离了的缘故,而且我认为当我们在上一帧直接设置角度并计算的时候,角速度已经变成了无效的。在任何情况下,我们都应该注意,通过设置角速度为零来消除上一帧所引起的问题,例如:

body->SetAngularVelocity(0);

让物体逐渐改变,只要在每一帧更新中限制角度的变更即可:

float totalRotation = desiredAngle - bodyAngle;

float change = 1 * DEGTORAD; //allow 1 degree rotation per time step

float newAngle = bodyAngle + min( change, max(-change, totalRotation));

body->SetTransform( body->GetPosition(), newAngle );

当目标位置在物体下面的时候,你发现异常了吗?角度在左边是正值,在右边是负值,刻度范围在-180~180之间。这也就意味着当目标在物体下方并直接横跨物体的时候,物体在179度到-179之间进行变动,几乎可以看做是在整圈的旋转(358度),之间的范围甚至可以是2度!我们可以通过一些变换让它旋转的范围永远都不能超过180度。当总的旋转角度超过180度的时候我们可以这样变换:

while ( totalRotation < -180 * DEGTORAD ) totalRotation += 360 * DEGTORAD;

while ( totalRotation >  180 * DEGTORAD ) totalRotation -= 360 * DEGTORAD;

这也可以防止旋转角度由于旋转很多次而变的很大。

使用扭矩

为了达到一个更真实的物理世界,可以使用扭矩来作用于物体让其旋转:我们可以这样开始:

float totalRotation = desiredAngle - bodyAngle;

while ( totalRotation < -180 * DEGTORAD ) totalRotation += 360 * DEGTORAD;

while ( totalRotation >  180 * DEGTORAD ) totalRotation -= 360 * DEGTORAD;

body->ApplyTorque( totalRotation < 0 ? -10 : 10 );

呃...不能正常工作吗?当物体面向正右方时,就会出现问题,当物体刚好转过目标点时就会立刻退回来,然后再过去再回来,就像荡秋千一样,永远都会这样。我们能做的就是减小作用力让其无限制的接近正确的角度...但是即便缩小力也不会从根本上解决这个问题,你可以试试。

既然问题是由当前角速度引起,并通过以后的时间步长所影响的。我们可以在没有力矩作用的情下,计算下一帧物体的角度-这就可以知道-在没有外界作用的情况下物体会怎样变化-然后将其用在当前角度(testbed默认是60Hz):

1

2

3

float nextAngle = bodyAngle + body->GetAngularVelocity() / 60.0;

float totalRotation = desiredAngle - nextAngle;//use angle in next time step

body->ApplyTorque( totalRotation < 0 ? -10 : 10 );

乍一看和之前差不多,但是如果你一下当物体面向正右方的时候,就会发现至少物体会立刻停住而不是永远的上下摇摆。虽然技术上完全解决了,但是对于大多数的应用来说还是不能让人满意。解决方案中,当物体到达正确角度的时候仍然超过了一帧的计算时间。我发现它就像磁罗盘针头一样,大概1/3秒的时间调整一次。

float nextAngle = bodyAngle + body->GetAngularVelocity() / 3.0;// 1/3 second

那么进行瞬间转动如何?就像之前所谈论的话题一样,在一个时间步长中用一个非常大的力矩,沿用之前那个具有“预见性”的方法。所使用的方程式可以由原来线性版本替换成扭矩版本,不同点是使用角速度和角质量。角质量的本质是转动惯量,通常用I来表示。

使用公式T=Iv/t,其中T是我们所知道的扭矩,I是物体的转动惯量,v是角速度以及t是我们将要使用扭矩作用的时间,像之前一样:

float nextAngle = bodyAngle + body->GetAngularVelocity() / 60.0;

float totalRotation = desiredAngle - nextAngle;

while ( totalRotation < -180 * DEGTORAD ) totalRotation += 360 * DEGTORAD;

while ( totalRotation >  180 * DEGTORAD ) totalRotation -= 360 * DEGTORAD;

float desiredAngularVelocity = totalRotation * 60;

float torque = body->GetInertia() * desiredAngularVelocity / (1/60.0);

body->ApplyTorque( torque );

注意虽然这不会非常精确的瞬间完成,但是要想让物体做正确的旋转通常会在2~3个时间步长范围内,对于实际应用来说,这种解决方案已经足够满足要求了。

更新:如果物体的质心没有在其原点上,那么这种方法将不能正确的工作,例如这里所实现的方式:(。当前Box2D API只能允许访问原点作为物体的中心,我们只能在质心上对物体施加扭矩。希望在Box2D以后的版本中API可以灵活设置质心,以此来调整惯性。

使用冲量

就像最近话题中所说的那样,类似于上面的代码使用冲量达到瞬时作用的效果,但是不加入时间因素:

float nextAngle = bodyAngle + body->GetAngularVelocity() / 60.0;

float totalRotation = desiredAngle - nextAngle;

while ( totalRotation < -180 * DEGTORAD ) totalRotation += 360 * DEGTORAD;

while ( totalRotation >  180 * DEGTORAD ) totalRotation -= 360 * DEGTORAD;

float desiredAngularVelocity = totalRotation * 60;

float impulse = body->GetInertia() * desiredAngularVelocity;// disregard time factor

body->ApplyAngularImpulse( impulse );

为了达到逐步改变的效果,只要允许每帧中对旋转角度做一定限制:

float nextAngle = bodyAngle + body->GetAngularVelocity() / 60.0;

float totalRotation = desiredAngle - nextAngle;

while ( totalRotation < -180 * DEGTORAD ) totalRotation += 360 * DEGTORAD;

while ( totalRotation >  180 * DEGTORAD ) totalRotation -= 360 * DEGTORAD;

float desiredAngularVelocity = totalRotation * 60;

float change = 1 * DEGTORAD; //allow 1 degree rotation per time step

desiredAngularVelocity = min( change, max(-change, desiredAngularVelocity));

float impulse = body->GetInertia() * desiredAngularVelocity;

body->ApplyAngularImpulse( impulse );

再次出现物体摆动现象的原因是由nextAngle变量所控制的,具体可以查看这个变量。

 

热点排行