接着上次的来,我们在群体算法之前把基本的个体运动解决掉。
9、WallAvoidance避开墙壁
此处的墙被抽象为一条线段,不论你的游戏使用的是一条线段作为墙面的碰撞检测,或者用一个几何形状作为墙面,几何形状我们可以看作多条线段的集合,都可以用此方法。
墙类的实现
首先是线段类,作为基类,拥有几种几何计算的方法,便于计算平面线段的交点,不多说。
struct Seg
{
Seg(Point p1, Point p2):
_from(p1), _to(p2)
{
}
#define eps 1e-6
//static math functions
static int sgn(double x)
{
return x<-eps ? -1 : (x>eps);
}
static double Cross(const Point& p1, const Point& p2, const Point& p3, const Point& p4)
{
return (p2.x - p1.x)*(p4.y - p3.y) - (p2.y - p1.y)*(p4.x - p3.x);
}
static double Area(const Point& p1, const Point& p2, const Point& p3)
{
return Cross(p1, p2, p1, p3);
}
static double fArea(const Point& p1, const Point& p2, const Point& p3)
{
return fabs(Area(p1, p2, p3));
}
static bool Meet(const Point& p1, const Point& p2, const Point& p3, const Point& p4)
{
return max(min(p1.x, p2.x), min(p3.x, p4.x)) <= min(max(p1.x, p2.x), max(p3.x, p4.x))
&& max(min(p1.y, p2.y), min(p3.y, p4.y)) <= min(max(p1.y, p2.y), max(p3.y, p4.y))
&& sgn(Cross(p3, p2, p3, p4) * Cross(p3, p4, p3, p1)) >= 0
&& sgn(Cross(p1, p4, p1, p2) * Cross(p1, p2, p1, p3)) >= 0;
}
static Point Inter(const Point& p1, const Point& p2, const Point& p3, const Point& p4)
{
double s1 = fArea(p1, p2, p3), s2 = fArea(p1, p2, p4);
return Point((p4.x*s1 + p3.x*s2) / (s1 + s2), (p4.y*s1 + p3.y*s2) / (s1 + s2));
}
static double PointToSegDist(double x, double y, double x1, double y1, double x2, double y2)
{
double cross = (x2 - x1) * (x - x1) + (y2 - y1) * (y - y1);
if (cross <= 0) return sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
double d2 = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
if (cross >= d2) return sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
double r = cross / d2;
double px = x1 + (x2 - x1) * r;
double py = y1 + (y2 - y1) * r;
return sqrt((x - px) * (x - px) + (py - y1) * (py - y1));
}
Point _from;
Point _to;
};
墙类保护继承于线段类,我们并不把Seg类的几何计算方法暴露出来。
class Wall :public BaseEntity,protected Seg//inherite math functions as protected functions
{
public:
Wall(Vec2, Vec2);
virtual ~Wall();
public:
virtual void update();
virtual bool handleMsg(const Telegram&);
public:
Vec2 from()const { return _from; }
Vec2 to()const { return _to; }
Vec2 normal()const
{
Vec2 normalLine(_to.x - _from.x, _from.x - _to.x);
return (normalLine.getNormalized());
}
static bool lineIntersection(const Vec2 entPo, const Vec2 fleer, const Wall wall, double& entity2wall, Vec2& intersection);
};
bool Wall::lineIntersection(const Vec2 entPo, const Vec2 feeler, const Wall wall, double& entity2wall, Vec2& intersection)
{
if (!Seg::Meet(entPo, feeler, wall.from(), wall.to()))
{
return false;
}
else
{
entity2wall = Seg::PointToSegDist(entPo.x, entPo.y, wall.from().x, wall.from().y, wall.to().x, wall.to().y);
intersection = Seg::Inter(entPo, feeler, wall.from(), wall.to());
return true;
}
}
碰撞的避免
类似于上一篇,这次我们用“触须”来作为辅助图形
在猫科动物运动过程中,触须会帮助它们判断前方物体是否会碰撞,例如洞口能否钻过,此处仿真了这一点。
一般我们使用三根触须,触须从实体坐标出发,方向分别为沿朝向,然后两个45°角,沿朝向的长度为两侧的两倍。
触须会提前插入墙壁,产生的斥力作用于交点,斥力大小与插入深度成正比。
触须长度满足这样的规则:
类型一:永不碰撞规则
物体以最大速度运动,垂直于墙壁时,从触须接触墙壁开始,产生的斥力刚好让其在墙壁前停止运动。
m·v=∫f·dt 冲量的积分刚好等于其动量,此处f表示斥力的计算公式,在此处,f=插入墙壁深度。
类型二:允许碰撞
如果允许碰撞,表示你设定的最大速度超过了“安全速度”,我们需要使用基本刚体碰撞来避免其重叠(这步在游戏逻辑层的最后实现)。
触须
我们先来创建触须,添加成员变量:
std::vector<Vec2> _feelers;
double _wallDetectionFeelerLength;
void SteeringBehaviors::createFeelers()
{
//feeler pointing straight in front
_feelers[0] = _ownerVehicle->position() + _wallDetectionFeelerLength * _ownerVehicle->heading();
//feeler to left
Vec2 temp = _ownerVehicle->heading();
//Vec2DRotateAroundOrigin(temp, halfPI * 3.5f);
temp.rotate(Vec2::ZERO, halfPI*3.5f);
_feelers[1] = _ownerVehicle->position() + _wallDetectionFeelerLength / 2.0f * temp;
//feeler to right
temp = _ownerVehicle->heading();
temp.rotate(Vec2::ZERO, halfPI * 0.5f);
_feelers[2] = _ownerVehicle->position() + _wallDetectionFeelerLength / 2.0f * temp;
}
其实就是设定好大小和角度而已。
紧接着我们开始计算_wallDetectionFeelerLength
在总时间t内,动量等于冲量的积分: