vsTASKER #6 – First Component : Boids Simulation

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.
Some code is provided below the video to help you build this demo in vsTASKER.
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!
Download the database (vsTASKER v7):
Download the zip database.
Unzip it in Data/Db/OpenGL
Load it with vsTASKER GUI v7 or newer