行为6

对象回避主题的完整意义是指,在机车行走的路线中存在一些障碍物,机车必须绕开、防止

触碰到它们。

听上去和碰撞检测有关,然而这仅仅是发生在预测阶段,也就是:“以我当前的速度行驶下去,可能就会撞到它了。” 

既然碰撞是预测的,就得长点脑子确保碰撞不会发生。

你可能正幼稚的想,那停下来或者调头不就行了嘛,你忘了有很多行为是在同时发生着的。

如果要躲避的是一个食肉动物,光靠停下来或者躲在树后面显然是不明智的。

凡有脑子的,此时会采取一切手段来躲避,而食肉动物也同样会绕开障碍物来追捕你。 


另外,如果要避开一个非常接近的东西,就必须改变路线。可能在沙漠中,发现远处有座金 字塔,稍作调整甚至不作调整的继续前进都不会碰到它。

而如果金字塔就在你面前,为了不撞上去,起码要转差不多90度左右。



  1目的

机车可以避开障碍物circle,自然的实现回避行为.
   2实现
l只看到前面的circle
l前面的circle挡着机车的路
l躲开挡着的circle
]  3讨论
l只看到前面的circle
可不就是只看到前面的circle,

看下示意图:
8-1分解1.jpg 
我们假设机车的视角为180(为什么要是180度啊?因为好算),那么如图所示,

当机车与circleposition矢量差difference与机车的速度矢量之间的夹角角a-90°~90°之间时,[-90 表示在右边,90表示在左边???????我的理解]

我们认为这个障碍物circle在机车的前面.



cos(a)=cos<differenc,heading>=(difference.x*heading.x+difference.y*heading.y)/difference.length;

这个是向量的点积,difference 是有大小的向量,heading 是标准化的向量       cos角度 = u 点积 v  /  |u| |v|  


cos(a)=difference.dotProd(heading)/difference.length;

当夹角 a -90 ° ~90 °之间时 cos(a)>0, 我们恰好可以利用这一点来判定 ,circle 是在机车的前方 .



l 前面的circle挡着机车的路
如何理解挡着机车的circle?

看下面的分解图:
8-1分解.jpg 
看着分解图讲究简单多了,

首先,看到前面有车我们要有足够的距离进行转弯,这个距离就存在avoidDistance;

然后circle和机车之间要有点间隙,贴着边儿走擦伤可能性肯定很大,这个间隙放在avoidBuffer里面;

所以如图所示,我们判断circle在机车前方,有相撞可能的条件是

projection<avoidDistance      &&       dist<  avoidBuffer+circle.radius;

下面的问题呢就是怎么计算projectiondist.
从上面的分解图中,我们可以很容易的看出,

projection.length=difference.length*cos(a),    // 这是三角函数 cos(a) 求领边的长度的公式。

上面一节中,我们已经讨论过cos(a)的值了,

cos(a)=difference.dotProd(heading)/difference.length;

直接把这个值代入到公式中,我们就得到了下面的代码:


var dotProd:Number = difference.dotProd(heading);
projection.length= dotProd;


上面的第一行代码是由这两个公式推出

projection.length=difference.length*cos(a),
<pre name="code" class="plain" style="font-size: 14px; line-height: 21px;">cos(a)=difference.dotProd(heading)/difference.length;

 

再加上 projection 的方向 , 这个向量就可以像下面这样表示 :

var projection:Vector2D = heading.multiply(dotProd);   // <span style="font-family: 'Times New Roman';">dotProd 是长度单位,计算之后变为向量。</span>

public function multiply(value:Number):Vector2D {
			return new Vector2D(_x * value, _y * value);
		}

然后的 dist 就好算了 ,

difference矢量减去projectiondist向量,

然而dist向量的方向对我们来说,没有什么实际的用处,所以,直接将dist定义为向量差的长度,如下:

var dist:Number = projection.substract(difference).length;  //<span style="font-size: 14px; line-height: 21px; font-family: 宋体; word-wrap: break-word; margin: 0px; padding: 0px;">直接将</span><span style="font-family: 'Times New Roman'; font-size: 14px; line-height: 21px; word-wrap: break-word; margin: 0px; padding: 0px;">dist</span><span style="font-size: 14px; line-height: 21px; font-family: 宋体; word-wrap: break-word; margin: 0px; padding: 0px;">定义为向量差的长度</span>

经过上面的讨论 , 判断相撞的语句应该如下 :

dist < circle.radius + _avoidBuffer      &&        projection.length < feeler.length 



l躲开挡着的circle
当判断到机车将与circle相撞时,应该怎么转呢?

哈哈,circle在左边机车就往右转,circle在右边就往左转,一般人我不告诉他.所以躲避又可以分为两步:
1.判断circle在左还是右,转向这一步完全可以仿照第一节判断circle在机车前面的方法计算,

只不过不是判断differencevelocity之间的夹角a,

而是与difference顺时针旋转90°后的向量与velocity的夹角.


代码如下:

8-1分解.jpg


 difference.sign(heading)
这个代码的解释是:difference的向量它的顺时针旋转90度得到的向量,点乘于 heading 向量,如果小于0 ,则sign为-1,否则sign为 1


下图 difference的向量它的顺时针旋转90度得到的向量,点乘于 heading 向量,可能点积之后得到????????,如果cos 角度是钝角的话,值小于0,我的理解





//最大的转向力.

var force:Vector2D = heading.multiply(_maxSpeed);

//如果circle在机车左边,机车就往右转,在右边,机车就往左转.

//difference顺时针转90(就是下面代码的perp方法返回的顺时针旋转90度的向量,顺时针体现在 -y )   与    heading    之间的夹角    取sign后返回1或-1,   可以参考一下Vector2D的sign方法

force.angle += difference.sign(heading) * Math.PI / 2;

public function sign(v2:Vector2D):int {
			return perp.dotProd(v2) < 0? -1:1;
		}
/**
		 * 返回垂直向量
		 */
		public function get perp():Vector2D {
			return new Vector2D( -y, x);
		}

/**
		 * 返回向量的长度
		 */
		public function get length():Number {
			return Math.sqrt(lengthSQ);
		}
		public function get lengthSQ():Number {
			return _x * _x + _y * _y;
		}
		public function set angle(value:Number):void {
			var len:Number = length;
			_x = Math.cos(value) * len;
			_y = Math.sin(value) * len;
		}
		public function get angle():Number {
			return Math.atan2(_y, _x);
		}


2.减速慢行


                            //离circle越近,刹车越猛,也没意见吧!
                            velocity = _velocity.multiply(projection.length / (feeler.length+circle.radius+_avoidBuffer));
 



                public function avoid(targets:Array):void {
                        
                        for (var i:Number = 0; i < targets.length;i++ ) {
                                var circle:Circle = targets as Circle;
                                //heading只表示机车的行驶方向,即单位话的速度velocity,
                                var heading:Vector2D = _velocity.clone().normalize();
                                //速度方向上的回避threshold,可以参考示意图中的avoidDistance.
                                var feeler:Vector2D = heading.multiply(_avoidDistance);
                                
                                //机车与circle位置的矢量差,参考示意图
                                var difference:Vector2D = circle.position.substract(_position);
                                //参考示意图中的projection
                                var dotProd:Number = difference.dotProd(heading);
                                
                                if (dotProd > 0) {//当cos(a)/length>0,表示circle在机车的前方.
                                        //下面这两个变量请参考示意图
                                        var projection:Vector2D = heading.multiply(dotProd);
                                        var dist:Number = projection.substract(difference).length;
                                        
                                        //在视角边界方向和速度方向都相撞,才会真正的撞在一起
                                        if (dist < circle.radius + _avoidBuffer && projection.length < feeler.length + circle.radius + _avoidBuffer) {
                                                //最大的转向力.
                                                var force:Vector2D = heading.multiply(_maxSpeed);
                                                //如果circle在机车左边,机车就往右转,在右边,机车就往左转.
                                                //difference顺时针转90与heading之间的夹角取sign后返回1或-1,可以参考一下Vector2D的sign方法
                                                force.angle += difference.sign(heading) * Math.PI / 2;
                                                

<span style="white-space:pre">						</span><strong><span style="font-size:14px;">//</span><span style="font-size:18px;">两种力,转向力,和 制动力</span></strong>

<pre name="code" class="plain"><strong><span style="font-size:18px;">//转向力</span></strong>
 
                                                //离circle越近,转向力越大,没意见吧
                                                force = force.multiply(1 - projection.length / feeler.length+circle.radius+_avoidBuffer);
                                                //转向力叠加
                                                steerForce = _steerForce.add(force);
                                               

<span style="font-size: 18px; font-weight: bold; font-family: Arial, Helvetica, sans-serif;"></span><pre name="code" class="plain"><strong><span style="font-size: 18px;">//</span></strong><span style="font-family: Arial, Helvetica, sans-serif;">制动力</span>
 
                                                //离circle越近,刹车越猛,也没意见吧!
                                                _velocity = _velocity.multiply(projection.length / (feeler.length+circle.radius+_avoidBuffer));
                                                _isAvoiding = true;
                                                _wanderAngle = force.angle;
                                        }else {
                                                _isAvoiding = false;
                                        }
                                }
                        }
                }

---------------------

C++ 版本

我们保持长方形区域(检测盒,从交通工具延伸出的)不被碰到,就可以躲避障碍了。

检测盒的宽度等于交通工具的包围半径,

       它的长度正比于交通工具的当前速度(他移动得越快,检测盒就越长)

      

//---------------------- ObstacleAvoidance 避开障碍-------------------------------
//
//  Given a vector of CObstacles, this method returns a steering force
//  that will prevent the agent colliding with the closest obstacle
//------------------------------------------------------------------------
Vector2D SteeringBehavior::ObstacleAvoidance(const std::vector<BaseGameEntity*>& obstacles)
{
  //检测盒的长度正比于智能体的速度
// 最小长度 + 最小速度 * <span style="font-family: Arial, Helvetica, sans-serif;">(m_pVehicle->Speed()/m_pVehicle->MaxSpeed())【这个是    当前速度 和 最大速度  的比率】</span>
  m_dDBoxLength = Prm.MinDetectionBoxLength + 
                  (m_pVehicle->Speed()/m_pVehicle->MaxSpeed()) *
                  Prm.MinDetectionBoxLength;

  //标记在范围内所有障碍物
// 相当于 调用下面的方法 <span style="font-family: Arial, Helvetica, sans-serif;">TagNeighbors</span>
  m_pVehicle->World()->TagObstaclesWithinViewRange(m_pVehicle, m_dDBoxLength);

  //跟踪最近的相交的障碍物 (CIB)
  BaseGameEntity* ClosestIntersectingObstacle = NULL;
 
  //跟踪到 CIB 的距离
  double DistToClosestIP = MaxDouble;

  //记录 CIB 被转化的局部坐标
  Vector2D LocalPosOfClosestObstacle;

  std::vector<BaseGameEntity*>::const_iterator curOb = obstacles.begin();

 //遍历被标记的邻居
  while(curOb != obstacles.end())
  {
    //如果该障碍物被标记在范围内
    if ((*curOb)->IsTagged())
    {
      //计算这个障碍物咋局部空间的位置
      Vector2D LocalPos = PointToLocalSpace((*curOb)->Pos(),
                                             m_pVehicle->Heading(),
                                             m_pVehicle->Side(),
                                             m_pVehicle->Pos());

      //如果局部空间位置有个负的x 值,那么它肯定在智能体后面。这个针对 智能体的局部坐标,所以负的就在后面了,如果 B 的情况
      //behind the agent. (in which case it can be ignored)
      if (LocalPos.x >= 0)
      {
        //如果到物体到x轴的距离小于它的半径+检查盒宽度的一半。那么可能相交
        
        double ExpandedRadius = (*curOb)->BRadius() + m_pVehicle->BRadius();

        if (fabs(LocalPos.y) < ExpandedRadius)    //如图 “局部y值小于扩大的半径” 的情况
        {
         
<img src="https://img-blog.csdn.net/20141026220509221?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjQxOTQxMA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />
           //现在做 线 / 圆周 相交测试。圆周的中心是 (cX , cY),如上图
         
          double cX = LocalPos.x;
          double cY = LocalPos.y;
          
          // (<span style="font-family: Arial, Helvetica, sans-serif;">它的半径+检查盒宽度的一半) 的平方  -  cY平方)开方后等于 </span><span style="font-family: Arial, Helvetica, sans-serif;">SqrtPart ,如上图</span><span style="font-family: Arial, Helvetica, sans-serif;">
</span>          double SqrtPart = sqrt(ExpandedRadius*ExpandedRadius - cY*cY);

          double ip = cX - SqrtPart;

<span style="white-space:pre">		</span>
          if (ip <= 0.0)
          {
<span style="white-space:pre">	</span>     //这相当于 A 的一个后面的相交点,cX长度比<span style="font-family: Arial, Helvetica, sans-serif;">SqrtPart长度短,所以小于0.0了,所以就取前面的点,我们不处理 相交于 交通工具后面的点</span>
            ip = cX + SqrtPart;
          }

          //测试是否这是目前为止最近的,如果是,记录这个障碍物和它的局部坐标。
          if (ip < DistToClosestIP)
          {
            DistToClosestIP = ip;

            ClosestIntersectingObstacle = *curOb;

            LocalPosOfClosestObstacle = LocalPos;
          } 
<span style="white-space:pre">		</span>

        
        }
      }
    }

    ++curOb;
  }

  //如果我们找到一个相交的障碍物,计算一个远离它的操控力
  Vector2D SteeringForce;

  if (ClosestIntersectingObstacle)
  {
    //智能体离物体越近,操控力就应该越强,因为智能体离障碍物越近,反应越快。
<h2>    //<strong>越近 用这个公式表示:</strong> (盒子长 - 碰撞物局部空间x值)/ 盒子长,就是说碰撞物肯定在盒子里,就是在盒子里面的位置有远近。</h2>    double multiplier = 1.0 + (m_dDBoxLength - LocalPosOfClosestObstacle.x) /
                        m_dDBoxLength;
<img src="https://img-blog.csdn.net/20141026220509221?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjQxOTQxMA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />
<h3>   <strong> // 图中 B 的半径 - cY  ,B</strong><h3 style="font-family: monospace; display: inline !important;">的半径<strong style="font-family: Arial, Helvetica, sans-serif; font-size: 12px;">大于0,cY小于0,相减之后 B加上cY的绝对值 ,再乘以 上面的系数 multiplier 。就得到了 侧向力</strong></h3></h3>
    SteeringForce.y = (ClosestIntersectingObstacle->BRadius()-
                       LocalPosOfClosestObstacle.y)  * multiplier;   

    //施加一个制动力,它正比于障碍物到智能体的距离。
    const double BrakingWeight = 0.2;

<h3>  <strong>  // 图中B 的半径 - cX</strong> ,     <span style="font-size: 12px;">B</span><span style="font-size: 12px;">的半径</span><span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px;">大于0,</span><span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px;">cX大于0,相减之后 B的半径 加上cX的绝对值 ,再乘以 上面的系数 </span><span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px;">BrakingWeight </span></h3>    SteeringForce.x = (ClosestIntersectingObstacle->BRadius() - 
                       LocalPosOfClosestObstacle.x) * 
                       BrakingWeight;
  }

  //最好,把操控向量 从 局部 转换 到 世界坐标。
  return VectorToWorldSpace(SteeringForce,
                            m_pVehicle->Heading(),
                            m_pVehicle->Side());
}


//----------------------- TagNeighbors 标记邻居----------------------------------
//
//  tags any entities contained in a std container that are within the
//  radius of the single entity parameter
// 标记容器内的任何实体在某个单一实体的半径范围内
//------------------------------------------------------------------------
template <class T, class conT>
void TagNeighbors(const T& entity, conT& ContainerOfEntities, double radius)
{
 
	//遍历所有实体检查范围
  for (typename conT::iterator curEntity = ContainerOfEntities.begin();
       curEntity != ContainerOfEntities.end();
       ++curEntity)
  {
   
	  //设置 m_bTag 属性为false
    (*curEntity)->UnTag();
    
	//m_vPos 属性 相减
    Vector2D to = (*curEntity)->Pos() - entity->Pos();

   
	//另一种是考虑的边界半径通过添加范围
	//BRadius 这个对象的边界半径的长度 m_dBoundingRadius
<span style="white-space:pre">	</span>//radius 表示 检测盒长度 ,这里是检测盒的长度加上 对象的半径
    double range = radius + (*curEntity)->BRadius();

   
	//(range*range 表示斜边的平方) 大于 (to的x,y坐标平方的和)表示在范围内,就做标记,设置m_bTag 为 true
    if ( ((*curEntity) != entity) && (to.LengthSq() < range*range))
    {
      (*curEntity)->Tag();
    }
    
  }//next entity
}











评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值