This bird flock simulation is the implementation of the Boids algorithm from Craig Reynolds (1986) with some modifications. Like in the Reynold algorithm, only 3 rules are used by all the
boids, to maintain cohesion, separation and alignment.
- Cohesion: try to get close to the boids seen around;
- Separation; try to avoid collision with other boids;
- Alignment: try to follow the boids seen in front.
The database can also be downloaded.
GroupFollow Component:
Declaration
// Method Section:
public:
protected:
//! prepare the list of all surrounding entities that can be seen
void prepareList();
//! change the heading when approaching an obstacle
int avoidObstacles(float dist);
//! bounce on obstacle when hitting it (birds are not that clever)
int slideOnObstacle(float dist);
//! change the heading and speed according to birds around so that
//! not hit them
void autoDirection(float& hdg, float& spd);
//! return the mean speed and heading of birds around
void getMeanValues(float& hdg, float& spd);
private:
// Data & Interface Section:
public:
int display; //&& LIST[ON,OFF] TYPE[Boolean]
int split; //&& LBL[Split At] DEF[200] TYPE[value]
double min_dist; //&& LBL[Minimum Distance] DEF[3] TYPE[Value] UNIT[m]
float view_ap; //&& LBL[View Angle] DEG[50] TYPE[Value] UNIT[deg]
float prox_ap; //&& LBL[Proximity Angle] DEG[100] TYPE[Value] UNIT[deg]
char obs_s[NS]; //&& LBL[Obstacles] TYPE[zone]
protected:
ZoneRep* obs; //!< obstacles if any
private:
KArray viewlist;
int escape;
Initialization
case RESET: {
proximity = scen()->flock_size;
view_ap = scen()->eye_aperture;
prox_ap = 100;
split = 200;
obs = (ZoneRep*) scen()->findFeature(obs_s);
autofill = true;
display = false;
escape = 0;
} break;
Methods
// ==========================================
void GroupFollow::prepareList()
{
float pap = DEG2RAD(prox_ap);
float vap = DEG2RAD(view_ap);
viewlist.clear();
for (int i = 0; i < proximatelist.count(); i++) {
Vt_Entity* ngb = proximatelist[i];
float bear = fabs(ent()->dyn->getBearingTo(ngb->pos));
if (bear > pap) {
proximatelist.delElem(i);
i--;
}
if (bear < vap) {
viewlist.addElem(ngb);
}
}
}
// =======================================================
void GroupFollow::getMeanValues(float& hdg, float& spd)
{
hdg = spd = 0;
for (int i = 0; i < viewlist.count(); i++) {
Vt_Entity* oth = viewlist[i]; hdg += oth->getHeading();
spd += oth->getSpeed();
}
if (viewlist.count()) {
hdg /= viewlist.count();
spd /= viewlist.count();
}
}
// ==============================================================
void GroupFollow::autoDirection(float& delta_h, float& delta_s)
{
float dst;
delta_h = 0;
delta_s = 1;
// process all around
for (int i = 0; i < proximatelist.count(); i++) {
Vt_Entity* oth = proximatelist[i];
dst = ent()->pos.distanceTo2D(oth->pos);
if (dst < min_dist / 2) { // do something
delta_s = frand(0.5, 2);
if (display) {
ogl_draw->setColor(clRed);
Coord2D f = scenario->terrain.db->posToScreen(ent()->pos);
Coord2D t = scenario->terrain.db->posToScreen(oth->pos);
ogl_draw->drawLine(f, t);
}
}
if (dst < min_dist) { // space yourself
if (ent()->dyn->getBearingTo(oth->pos) < 0) delta_h += DEG2RAD(5);
else delta_h -= DEG2RAD(5);
if (display) {
ogl_draw->setColor(clBlue);
Coord2D f = scenario->terrain.db->posToScreen(ent()->pos);
Coord2D t = scenario->terrain.db->posToScreen(oth->pos);
ogl_draw->drawLine(f, t);
}
}
}
}
// =========================================
int GroupFollow::avoidObstacles(float dist)
{
if (obs) {
float agl = ent()->dyn->heading.getTarget();
WCoord npos = ent()->pos.getNewPosAt(agl, 0, dist);
ShapeRep* block = obs->getIn(npos);
if (block) { // avoid
for (int i = 10; i < 90; i += 10) {
float da = DEG2RAD(i);
npos = ent()->pos.getNewPosAt(agl + da, 0, dist);
if (block->isIn(npos)) {
npos = ent()->pos.getNewPosAt(agl - da, 0, dist);
if (block->isIn(npos)) continue;
else { // seems ok, let's check at 1/2 the distance
npos = ent()->pos.getNewPosAt(agl - da, 0, dist / 2);
if (block->isIn(npos)) continue;
else {
ent()->dyn->setHeading(agl - da);
return 1;
}
}
}
else { // seems ok, let's check at 1/2 the distance
npos = ent()->pos.getNewPosAt(agl + da, 0, dist / 2);
if (block->isIn(npos)) continue;
else {
ent()->dyn->setHeading(agl + da);
return 1;
}
}
}
}
}
return 0;
}
// =========================================================
int GroupFollow::slideOnObstacle(float dist)
{
if (obs) {
ShapeRep* block = obs->getClose(ent()->pos, dist);
if (block) { // avoid
float agl1 = ent()->pos.azimuthTo(block->getBarycenter());
float agl2 = ent()->getHeading();
float agl = MIN_AGL_SGN(agl1, agl2);
if (fabs(agl) < PI_2) {
agl = (PI_2 - fabs(agl))*SGN(agl);
ent()->dyn->forceHeading(ent()->getHeading() - agl);
return 1;
}
}
}
return 0;
}
Runtime
if (chrono.lesser("activate", 5)) return AGAIN;
if (escape) {
escape--;
avoidObstacles(100);
slideOnObstacle(10);
return AGAIN;
}
prepareList();
if (display) {
ogl_draw->setColor(clGreen);
Coord2D f = scenario->terrain.db->posToScreen(ent()->pos);
for (int i = 0; i<proximatelist.count(); i++) {
Vt_Entity* oth = proximatelist[i]; Coord2D t = scenario->terrain.db->posToScreen(oth->pos);
ogl_draw->drawLine(f, t);
}
}
float mean_heading, mean_speed;
float auto_heading, auto_speed;
// compute the mean heading
getMeanValues(mean_heading, mean_speed);
// position yourself
autoDirection(auto_heading, auto_speed);
if (cnode && cnode->elist && cnode->elist->count() > split) escape = 1;
else escape = 0;
if (viewlist.count() && !escape)
{
float alea = DEG2RAD(frand(-1, 1));
ent()->setHeading(mean_heading + auto_heading + alea);
mean_speed *= auto_speed;
ent()->setSpeed(BOUND(mean_speed, 5, 30));
}
else
if (escape) {
if (RANDOM(1, 10) < 5) { // half of them must evade ent()->setHeading(DEG2RAD(RANDOM(-180,180)));
ent()->setSpeed(RANDOM(5, 15));
escape = RANDOM(1, 10); // more or less far
}
}
else { // other wise, some leaders can change direction
if (RANDOM(1, 20) == 1) ent()->setHeading(DEG2RAD(RANDOM(-20, 20)));
}
// check obstacle
if (obs) {
avoidObstacles(100);
slideOnObstacle(20);
}
if (error) { // return center
WCoord center;
ent()->dyn->setHeading(center);
}
ObstaclesMotion Component:
Declaration
// Data & Interface Section:
public:
float dx; //&&
float dy; //&&
char obs_s[NS]; //&& LBL[Obstacles] TYPE[zone]
protected:
ZoneRep* obs;
private:
int *s_dx, *s_dy;
Initialization
case INIT: { // at first start
s_dx = NULL;
s_dy = NULL;
} break;
case RESET: {
obs = (ZoneRep*) scen()->findFeature(obs_s);
if (obs) {
int nb = obs->nb();
if (!s_dx) s_dx = (int*) malloc(sizeof(int)*nb);
if (!s_dy) s_dy = (int*) malloc(sizeof(int)*nb);
for (int i=0; i < nb; i++) {
s_dx[i] = COF()? -1:1;
s_dy[i] = COF()? -1:1;
}
}
} break;
Runtime
if (obs) {
for (int i=0; inb(); i++) {
ShapeRep* shape = obs->getShape(i);
shape->data.center.x += dx * s_dx[i];
shape->data.center.y += dy * s_dy[i];
if (shape->data.center.x > 400) s_dx[i] = -1;
if (shape->data.center.x < -400) s_dx[i] = +1;
if (shape->data.center.y > 200) s_dy[i] = -1;
if (shape->data.center.y < -360) s_dy[i] = +1;
shape->resize();
}
}
Download the standalone demo:
Download the zip file.
Unzip it wherever you want (keep all in a directory).
Launch: boids_simulation.exe
Enjoy!
Unzip it wherever you want (keep all in a directory).
Launch: boids_simulation.exe
Enjoy!
Download the database (vsTASKER v7):