Resources for visualizing data using C# and the .NET platform

# ¶Boids in C#

This project implements the Boids flocking algorithm in C# to create an interesting procedural animation of bird-drones (boids) scurrying about the screen.

### ¶Simulation Rules

The Boids algorithm was created by Craig Reynolds in 1986 and is a term used to describe "bird-oid objects". In this simulation complex emergent behavior comes from simple rules:

• Rule 1: boids steer toward the center of mass of nearby boids
• Rule 2: boids adjust direction to match nearby boids
• Rule 3: boids steer away from very close boids

You can get fancy and apply additional rules to create even more complex and interesting behavior. In my example program I added 3 additional rules:

• Rule 4: boids speed up or slow down to match a target speed
• Rule 5: boids are repelled by the edge of the box
• Rule 6: boids steer away from boids marked as predators

## ¶Boids Model Code

### ¶Strategy

The velocity of voids is controlled by two variables, `Xvel` and `Yvel`. It's worth noting that trig functions can be used to convert these values to heading (in degrees) and speed (pixels per iteration), but this is typically not required.

All rules "steer" boids (adjusting their heading and speed) by acting on their X and Y velocities. Rules never move boids. After the application of all the rules, the position of each boid (`X` and `Y`) is moved by its velocity (`Xvel` and `Yvel`).

Each rule is given a `distance` that describes how far away it can act. Avoidance only acts on close boids, while flocking distances are much greater. Similarly, each rule is given a weight (termed `power` in this code) that describes how much it influences the final velocity. Typically rules with a larger distance have a smaller weight. Flocking weight is less than predator avoidance weight.

#### ¶The `Boid` Class

Some helper functions have been omitted, but this is the gist of the `Boid` class. This class only stores position and velocity of one boid, and any information about the outside world must be passed-in.

``````public class Boid
{
public double X;
public double Y;
public double Xvel;
public double Yvel;

public Boid(double x, double y, double xVel, double yVel)
{
(X, Y, Xvel, Yvel) = (x, y, xVel, yVel);
}
}``````

#### ¶The `Field` Class

The `Field` class contains a `List` of `Boid` objects and is responsible for applying the rules to each `Boid`. It is instantiated with a set of dimensions and a number of initial boids, and random boids (with random positions and velocities) are placed upon instantiation.

``````public readonly double Width;
public readonly List<Boid> Boids = new List<Boid>();
private readonly Random Rand = new Random();

public Field(double width, double height, int boidCount = 100)
{
(Width, Height) = (width, height);
for (int i = 0; i < boidCount; i++)
}``````

This method applies all the rules and advances the boids model in time. Distances and weights for each rule are defined in arguments.

``````public void Advance(bool bounceOffWalls = true, bool wrapAroundEdges = false)
{
// update void speed and direction (velocity) based on rules
foreach (var boid in Boids)
{
(double flockXvel, double flockYvel) = Flock(boid, 50, .0003);
(double alignXvel, double alignYvel) = Align(boid, 50, .01);
(double avoidXvel, double avoidYvel) = Avoid(boid, 20, .001);
(double predXvel, double predYval) = Predator(boid, 150, .00005);
boid.Xvel += flockXvel + avoidXvel + alignXvel + predXvel;
boid.Yvel += flockYvel + avoidYvel + alignYvel + predYval;
}

// move all boids forward in time
foreach (var boid in Boids)
{
boid.MoveForward();
if (bounceOffWalls)
BounceOffWalls(boid);
if (wrapAroundEdges)
WrapAround(boid);
}
}``````

#### ¶Rule 1: Steer Toward Center of Mass of Nearby Boids

Return the velocity adjustment needed to point toward the center of the flock (mean flock boid position). Notice that we define a flock (and neighbors) as boids within the given distance.

``````private (double xVel, double yVel) Flock(Boid boid, double distance, double power)
{
var neighbors = Boids.Where(x => x.GetDistance(boid) < distance);
double meanX = neighbors.Sum(x => x.X) / neighbors.Count();
double meanY = neighbors.Sum(x => x.Y) / neighbors.Count();
double deltaCenterX = meanX - boid.X;
double deltaCenterY = meanY - boid.Y;
return (deltaCenterX * power, deltaCenterY * power);
}``````

#### ¶Rule 2: Mimic Direction and Speed of Nearby Boids

Return the velocity adjustment needed to approach the mean speed and direction of nearby boids.

``````private (double xVel, double yVel) Align(Boid boid, double distance, double power)
{
var neighbors = Boids.Where(x => x.GetDistance(boid) < distance);
double meanXvel = neighbors.Sum(x => x.Xvel) / neighbors.Count();
double meanYvel = neighbors.Sum(x => x.Yvel) / neighbors.Count();
double dXvel = meanXvel - boid.Xvel;
double dYvel = meanYvel - boid.Yvel;
return (dXvel * power, dYvel * power);
}``````

#### ¶Rule 3: Steer Away from Extremely Close Boids

Return the velocity adjustment needed to avoid very close boids. This method doesn't use the center of the close flock, but instead summates the "closeness" of all close birds to generate the velocities.

``````private (double xVel, double yVel) Avoid(Boid boid, double distance, double power)
{
var neighbors = Boids.Where(x => x.GetDistance(boid) < distance);
(double sumClosenessX, double sumClosenessY) = (0, 0);
foreach (var neighbor in neighbors)
{
double closeness = distance - boid.GetDistance(neighbor);
sumClosenessX += (boid.X - neighbor.X) * closeness;
sumClosenessY += (boid.Y - neighbor.Y) * closeness;
}
return (sumClosenessX * power, sumClosenessY * power);
}``````

#### ¶Rule 4: Speed Limit

After the first three rules are applied, the new velocity is calculated for each boid. An operation can then be performed to scale these velocities (keeping their ratio the same) to adjust speed. I accomplish this inside the advancement method in the `Boid` class.

Notice the `IsNan` method has to be used to accommodate cases where speed is zero so as not to break the trig functions which calculate heading later.

``````public void MoveForward(double minSpeed = 1, double maxSpeed = 5)
{
X += Xvel;
Y += Yvel;

var speed = GetSpeed();
if (speed > maxSpeed)
{
Xvel = (Xvel / speed) * maxSpeed;
Yvel = (Yvel / speed) * maxSpeed;
}
else if (speed < minSpeed)
{
Xvel = (Xvel / speed) * minSpeed;
Yvel = (Yvel / speed) * minSpeed;
}

if (double.IsNaN(Xvel))
Xvel = 0;
if (double.IsNaN(Yvel))
Yvel = 0;
}``````

#### ¶Rule 5: Avoid Edges

This code accelerates boids away from walls with each iteration. Originally it just slows them down as they approach, but with more time they reverse course and travel away from the edge. This method is safe to use with fast boids that may travel off the screen for a brief period of time.

``````private void BounceOffWalls(Boid boid)
{
double turn = .5;
boid.Xvel += turn;
if (boid.X > Width - pad)
boid.Xvel -= turn;
boid.Yvel += turn;
if (boid.Y > Height - pad)
boid.Yvel -= turn;
}``````

#### ¶Alternate Rule 5: Wrap the Universe

This code isn't used in my example, but it could be used instead of the "avoid edges" method above. In this method boids that fall off the screen on one edge reappear on the opposite edge.

``````private void WrapAround(Boid boid)
{
if (boid.X < 0)
boid.X += Width;
if (boid.X > Width)
boid.X -= Width;
if (boid.Y < 0)
boid.Y += Height;
if (boid.Y > Height)
boid.Y -= Height;
}``````

#### ¶Rule 6: Avoid Predators

Return the velocity adjustment needed to steer away from predators. In this example predators are simply defined as the first N boids using a class-level variable. Similar to the earlier boid avoidance method, this one summates avoidances based on each predator's position instead of responding to the mean position of all predators.

``````public int PredatorCount = 3;
private (double xVel, double yVel) Predator(Boid boid, double distance, double power)
{
(double sumClosenessX, double sumClosenessY) = (0, 0);
for (int i = 0; i < PredatorCount; i++)
{
Boid predator = Boids[i];
double distanceAway = boid.GetDistance(predator);
if (distanceAway < distance)
{
double closeness = distance - distanceAway;
sumClosenessX += (boid.X - predator.X) * closeness;
sumClosenessY += (boid.Y - predator.Y) * closeness;
}
}
return (sumClosenessX * power, sumClosenessY * power);
}``````

## ¶Rendering the Boids Model

### ¶Graphics Transformation and Rotation

The outline of a boid is defined as a `Point[]` array. Instead of rotating the points to match the direction of each boid, the entire canvas is rotated around the boid, then the boid is drawn right-side-up. This method greatly simplifies the act of drawing rotated shapes with System.Drawing.

``````private void RenderBoid(Graphics gfx, Boid boid)
{
// drawing of a boid centered at (0, 0)
var boidOutline = new Point[]
{
new Point(0, 0),
new Point(-5, -1),
new Point(0, 10),
new Point(5, -1),
new Point(0, 0),
};

using (var brush = new SolidBrush(Color.LightGreen))
{
// translate and rotate the canvas around the boid
gfx.TranslateTransform((float)boid.X, (float)boid.Y);
gfx.RotateTransform((float)boid.GetAngle());

// draw the boid at (0, 0)
gfx.FillClosedCurve(brush, boidOutline);

// reset before drawing the next object
gfx.ResetTransform();
}
}``````

Rendering is triggered using a timer set to 1 ms. The first 3 boids are predators so they are colored differently.

``````Field field = new Field(pictureBox1.Width, pictureBox1.Height, 100);
private void timer1_Tick(object sender, EventArgs e)
{
pictureBox1.Image?.Dispose();
pictureBox1.Image = RenderField(field);
}

public static Bitmap RenderField(Field field)
{
Bitmap bmp = new Bitmap((int)field.Width, (int)field.Height);
using (Graphics gfx = Graphics.FromImage(bmp))
{
gfx.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
gfx.Clear(ColorTranslator.FromHtml("#003366"));
for (int i = 0; i < field.Boids.Count(); i++)
{
if (i < 3)
RenderBoid(gfx, field.Boids[i], Color.White);
else
RenderBoid(gfx, field.Boids[i], Color.LightGreen);
}
}
return bmp;
}``````