目录

我的学习分享

记录精彩的程序人生

【Box2D】21.Box2D C++ 教程-射线投射

https://blog.csdn.net/Const_Gong/article/details/51464597

声明:本教程翻译自:Box2D C++ tutorials - Ray casting,仅供学习参考。

射线投射经常用来找出部分物理世界当中已经存在的物件。所谓射线就是一条直线,我们可以通过使用Box2D当中提供的方法来检测是否与某个定制器(fixture)有交点。我们也可以找出与定制器交点的法线。

下面就是上面我提到的方法,如果射线与某个定制器发生碰撞则返回true。这里需要注意的是此方法是b2Fixture类的一个成员,这也就是说首先我们满足一个射线对儿这个基本条件才行。

  bool b2Fixture::RayCast(b2RayCastOutput* output, const b2RayCastInput& input);

现在让我们看一下输入、输出参数。直接从源代码中可以看出b2RayCastInput包含了哪些参数:

  // Ray-cast input data. The ray extends from p1 to p1 + maxFraction * (p2 - p1).
  struct b2RayCastInput
  {
      b2Vec2 p1, p2;
      float32 maxFraction;
  };

p1和p2点确定了射线的方向。maxFraction表示了射线检测到交点的距离。下面的图可能能够让人一目了然。maxFraction等于1意味着射线段是从p1点到p2点,在此例子中显然不能与多边形有交点,但是maxFraction等于2就可以。

raycastinginput.png

下面是b2RayCastOutput包括的参数:

  // Ray-cast output data. The ray hits at p1 + fraction * (p2 - p1), where p1 and p2
  // come from b2RayCastInput.
  struct b2RayCastOutput
  {
      b2Vec2 normal;
      float32 fraction;
  };

如果射线确实与多边形相交,b2Fixture::RayCast将会返回true,而且我们可以通过这个输出结构体里的fraction参数找出交点段值(详见上图),还有在多边形定制器表面生成的法线:

raycastingoutput.png

例子

为了尝试一下这个很方便实用的功能,让我们创建一个场景,场景的条件为失重状态,其中漂浮着一些多边形。现在创建这些你应该很顺手了,接下来我们在四周创建一些墙来代替四方盒子作为边界,为的是看起来更好看。

    FooTest() {
  
      // a static body
      b2BodyDef myBodyDef;
      myBodyDef.type = b2_staticBody;
      myBodyDef.position.Set(0, 0);
      b2Body* staticBody = m_world->CreateBody(&myBodyDef);
  
      // shape definition
      b2PolygonShape polygonShape;
    
      // fixture definition
      b2FixtureDef myFixtureDef;
      myFixtureDef.shape = &polygonShape;
      
      // add four walls to the static body
      b2Vec2 bl(-20, 0);
      b2Vec2 br( 20, 0);
      b2Vec2 tl(-20,40);
      b2Vec2 tr( 20,40);
      polygonShape.SetAsEdge( bl, br ); // ground
      staticBody->CreateFixture(&myFixtureDef);
      polygonShape.SetAsEdge( tl, tr);// ceiling
      staticBody->CreateFixture(&myFixtureDef);
      polygonShape.SetAsEdge( bl, tl );// left wall
      staticBody->CreateFixture(&myFixtureDef);
      polygonShape.SetAsEdge( br, tr );// right wall
      staticBody->CreateFixture(&myFixtureDef);
  
      myBodyDef.type = b2_dynamicBody;
      myBodyDef.position.Set(0,20);
      polygonShape.SetAsBox(2,2);
      myFixtureDef.density = 1;
      for (int i = 0; i < 5; i++)
          m_world->CreateBody(&myBodyDef)->CreateFixture(&myFixtureDef);
  
      // circles
      b2CircleShape circleShape;
      circleShape.m_radius = 2;
      myFixtureDef.shape = &circleShape;
      for (int i = 0; i < 5; i++)
          m_world->CreateBody(&myBodyDef)->CreateFixture(&myFixtureDef);
  
      // turn gravity off
      m_world->SetGravity( b2Vec2(0,0) );
  
    }

raycastingsceneshapes.png

现在我们需要有一个射线与这些多边形发生碰撞。让我们在屏幕的中心生成一条四周投射的射线,并且缓慢旋转。我们需要唯一做的就是保持住射线的旋转角度,那么我们就不要为此特别封装一个类了,我们只需要有一个全局变量就OK了。

  // at global scope
  float currentRayAngle = 0;
  
  // in Step() function
  currentRayAngle += 360 / 20.0 / 60.0 * DEGTORAD; // one revolution every 20 seconds
  
  // calculate points of ray
  float rayLength = 25; // long enough to hit the walls
  b2Vec2 p1( 0, 20 ); // center of scene
  b2Vec2 p2 = p1 + rayLength * b2Vec2( sinf(currentRayAngle), cosf(currentRayAngle) );
  
  // draw a line
  glColor3f(1,1,1); //white
  glBegin(GL_LINES);
  glVertex2f( p1.x, p1.y );
  glVertex2f( p2.x, p2.y );
  glEnd();

raycastingsceneray.png

现在在场景中你应该能够看到一条旋转的白线。现在我们可以就用RayCast方法来获取离射线最近的多边形,并画出线段的长度。我们将会对每个多边形中的每个定制器进行检测,当然这并不是最有效的办法,但是可以作为一个学习的例子(详见查询world话题)来学习。这样的话,我们就可以了解一下世界里的物件:

      // in Step() function, continuing on from section above
  
      // set up input
      b2RayCastInput input;
      input.p1 = p1;
      input.p2 = p2;
      input.maxFraction = 1;
  
      // check every fixture of every body to find closest
      float closestFraction = 1; // start with end of line as p2
      b2Vec2 intersectionNormal(0,0);
      for (b2Body* b = m_world->GetBodyList(); b; b = b->GetNext()) {
          for (b2Fixture* f = b->GetFixtureList(); f; f = f->GetNext()) {
  
              b2RayCastOutput output;
              if ( ! f->RayCast( &output, input ) )
                  continue;
              if ( output.fraction < closestFraction ) {
                  closestFraction = output.fraction;
                  intersectionNormal = output.normal;
              }            
          }
      }
  
      b2Vec2 intersectionPoint = p1 + closestFraction * (p2 - p1);
  
      // draw a line
      glColor3f(1,1,1); // white
      glBegin(GL_LINES);
      glVertex2f(p1.x, p1.y);
      glVertex2f(intersectionPoint.x, intersectionPoint.y);
      glEnd();
      
      // draw a point at the intersection point
      glPointSize(5);
      glBegin(GL_POINTS);
      glVertex2f(intersectionPoint.x, intersectionPoint.y);
      glEnd();

你可能注意到现在我们在每个上方画出了两条线…为了清晰起见,删除第一条你就会看到类似下面的画面:

raycastingintersect.png

上面所说的就是找出交点。我们也可以在输出结构中使用法线来实现一些有趣的想法,现在我们就可以试试看。首先,让我们把法线长什么样儿简单的画出来:

      // draw intersection normal
      b2Vec2 normalEnd = intersectionPoint + intersectionNormal;
      glColor3f(0,1,0); // green
      glBegin(GL_LINES);
      glVertex2f(intersectionPoint.x, intersectionPoint.y);
      glVertex2f(normalEnd.x, normalEnd.y);
      glEnd();

raycastingnormal.png

作为最后的压轴戏,我们可以把投射代码放到一个自有方法中,然后每次不断的递归调用直到射线的耗尽。这并不意味着Box2D怎么写都行,我只是觉得这么做代码看起来更整洁而已:)。

raycastingrecurse.png

代码:

  // new function for FooTest class
  void drawReflectedRay(b2Vec2 p1, b2Vec2 p2)
  {
      // set up input
      b2RayCastInput input;
      input.p1 = p1;
      input.p2 = p2;
      input.maxFraction = 1;
  
      // check every fixture of every body to find closest
      float closestFraction = 1; // start with end of line as p2
      b2Vec2 intersectionNormal(0,0);
      for (b2Body* b = m_world->GetBodyList(); b; b = b->GetNext()) {
          for (b2Fixture* f = b->GetFixtureList(); f; f = f->GetNext()) {
  
              b2RayCastOutput output;
              if (!f->RayCast( &output, input))
                  continue;
              if (output.fraction < closestFraction) {
                  closestFraction = output.fraction;
                  intersectionNormal = output.normal;
              }
          }
      }
      b2Vec2 intersectionPoint = p1 + closestFraction * (p2 - p1);
  
      // draw this part of the ray
      glBegin(GL_LINES);
      glVertex2f(p1.x, p1.y);
      glVertex2f( intersectionPoint.x, intersectionPoint.y );
      glEnd();
  
      if (closestFraction == 1)
          return; // ray hit nothing so we can finish here
      if (closestFraction == 0)
          return;
  
      // still some ray left to reflect
      b2Vec2 remainingRay = (p2 - intersectionPoint);
      b2Vec2 projectedOntoNormal = b2Dot(remainingRay, intersectionNormal) * intersectionNormal;
      b2Vec2 nextp2 = p2 - 2 * projectedOntoNormal;
  
      // recurse
      drawReflectedRay(intersectionPoint, nextp2);
  }

…然后在Step()方法中添加下面几行代码:

      // calculate points of ray
      float rayLength = 25;
      b2Vec2 p1(0, 20); // center of scene
      b2Vec2 p2 = p1 + rayLength * b2Vec2(sinf(currentRayAngle), cosf(currentRayAngle));
  
      glColor3f(1,1,1); // white
      drawReflectedRay(p1, p2);

  1. Box2D C++ 教程-简介
  2. Box2D C++ 教程-环境设置
  3. Box2D C++ 教程-Testbed结构
  4. Box2D C++ 教程-创建测试
  5. Box2D C++ 教程-物体
  6. Box2D C++ 教程-定制器
  7. Box2D C++ 教程-设置世界
  8. Box2D C++ 教程-力和冲量
  9. Box2D C++ 教程-自定义重力
  10. Box2D C++ 教程-匀速运动
  11. Box2D C++ 教程-旋转到指定角度
  12. Box2D C++ 教程-跳跃
  13. Box2D C++ 教程-使用debug Draw
  14. Box2D C++ 教程-画自己的图像
  15. Box2D C++ 教程-用户数据
  16. Box2D C++ 教程-碰撞剖析
  17. Box2D C++ 教程-源代码
  18. Box2D C++ 教程-碰撞回调
  19. Box2D C++ 教程-碰撞过滤
  20. Box2D C++ 教程-传感器
  21. Box2D C++ 教程-射线投射
  22. Box2D C++ 教程-查询 World
  23. Box2D C++ 教程-安全地移除物体
  24. Box2D C++ 教程-跳跃问题
  25. Box2D C++ 教程-幽灵顶点
  26. Box2D C++ 教程-连接器-概述
  27. Box2D C++ 教程-连接器-旋转
  28. Box2D C++ 教程-连接器-平移
  29. Box2D C++ 教程-开发环境设置(iPhone)