触碰到它们。
听上去和碰撞检测有关,然而这仅仅是发生在预测阶段,也就是:“以我当前的速度行驶下去,可能就会撞到它了。”
既然碰撞是预测的,就得长点脑子确保碰撞不会发生。
你可能正幼稚的想,那停下来或者调头不就行了嘛,你忘了有很多行为是在同时发生着的。
如果要躲避的是一个食肉动物,光靠停下来或者躲在树后面显然是不明智的。
凡有脑子的,此时会采取一切手段来躲避,而食肉动物也同样会绕开障碍物来追捕你。
而如果金字塔就在你面前,为了不撞上去,起码要转差不多90度左右。
1目的
机车可以避开障碍物circle,自然的实现回避行为.
2实现
l只看到前面的circle
l前面的circle挡着机车的路
l躲开挡着的circle
] 3讨论
l只看到前面的circle
可不就是只看到前面的circle嘛,
看下示意图:
我们假设机车的视角为180度(为什么要是180度啊?因为好算),那么如图所示,
当机车与circle的position矢量差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呢?
看下面的分解图:
看着分解图讲究简单多了,
首先,看到前面有车我们要有足够的距离进行转弯,这个距离就存在avoidDistance里;
然后circle和机车之间要有点间隙,贴着边儿走擦伤可能性肯定很大,这个间隙放在avoidBuffer里面;
所以如图所示,我们判断circle在机车前方,有相撞可能的条件是
projection<avoidDistance && dist< avoidBuffer+circle.radius;
下面的问题呢就是怎么计算projection和dist.
从上面的分解图中,我们可以很容易的看出,
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矢量减去projection即dist向量,
然而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在机车前面的方法计算,
只不过不是判断difference和velocity之间的夹角a了,
而是与difference顺时针旋转90°后的向量与velocity的夹角.
代码如下:
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
}