Chapters

Hide chapters

Metal by Tutorials

Second Edition · iOS 13 · Swift 5.1 · Xcode 11

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section I: The Player

Section 1: 8 chapters
Show chapters Hide chapters

Section III: The Effects

Section 3: 10 chapters
Show chapters Hide chapters

17. Particle Behavior
Written by Marius Horga

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

As you learned in the previous chapter, particles have been at the foundation of computer animation for years. In computer graphics literature, three major animation paradigms are well defined and have rapidly evolved in the last two decades:

  • Keyframe animation: Starting parameters are defined as initial frames, and then an interpolation procedure is used to fill the remaining values for in-between frames. This topic was covered in Chapter 8, “Character Animation.”
  • Physically based animation: Starting values are defined as animation parameters, such as a particle’s initial position and velocity, but intermediate values are not specified externally. This topic was covered in Chapter 16, “Particle Systems.”
  • Behavioral animation: Starting values are defined as animation parameters. In addition, a cognitive process model describes and influences the way intermediate values are later determined.

In this chapter, you’ll focus on the last paradigm as you work through:

  • Behavioral animation.
  • Swarming behavior.
  • Velocity and bounds checking.
  • Behavioral rules.

By the end of the chapter, you’ll build and control a swarm exhibiting basic behaviors you might see in nature.

Behavioral animation

You can broadly split behavioral animation into two major categories:

  • Cognitive behavior: This is the foundation of artificial life which differs from artificial intelligence in that AI objects do not exhibit behaviors or have their own preferences. It can range from a simple cause-and-effect based system to more complex systems, known as agents, that have a psychological profile influenced by the surrounding environment.
  • Aggregate behavior: Think of this as the overall outcome of a group of agents. This behavior is based on the individual rules of each agent and can influence the behavior of neighbors.

In this chapter, you’ll keep your focus on aggregate behavior.

There’s a strict correlation between the various types of aggregate behavior entities and their characteristics. In the following table, notice how the presence of a physics system or intelligence varies between entity types.

  • Particles are the largest aggregate entities and are mostly governed by the laws of physics, but they lack intelligence.
  • Flocks are an entity that’s well-balanced between size, physics and intelligence.
  • Crowds are smaller entities that are rarely driven by physics rules and are highly intelligent.

Working with crowd animation is both a challenging and rewarding experience. However, the purpose of this chapter is to describe and implement a flocking-like system, or to be more precise, a swarm of insects.

Swarming behavior

Swarms are gatherings of insects or other small-sized beings. The swarming behavior of insects can be modeled in a similar fashion as the flocking behavior of birds, the herding behavior of animals or the shoaling behavior of fish.

The project

The starting project is the same as the final one from the previous chapter except this time around, the boids:

output.write(color, location + uint2( 1, 0));
output.write(color, location + uint2( 0, 1));
output.write(color, location - uint2( 1, 0));
output.write(color, location - uint2( 0, 1));
output.write(color, location + uint2(-1, 1));
output.write(color, location - uint2(-1, 1));
output.write(color, location + uint2( 1,-1));
output.write(color, location - uint2( 1,-1));

Velocity

Velocity is a vector made up of two other vectors: direction and speed. The speed is the magnitude or length of the vector, and the direction is given by the linear equation of the line on which the vector lies.

var velocity: float2
let velocity = float2(random(10), random(10))
pointer.pointee.velocity = velocity
float2 velocity;
float2 velocity = boid.velocity;
position += velocity;
boid.position = position;
boid.velocity = velocity;
boids[id] = boid;

if (position.x < 0 || position.x > output.get_width()) {
  velocity.x *= -1;
}

if (position.y < 0 || position.y > output.get_height()) {
  velocity.y *= -1;
}

Behavioral rules

There’s a basic set of steering rules that swarms and flocks can adhere to, and it includes:

Cohesion

Cohesion is a steering behavior that causes the boids to stay together as a group. To determine how cohesion works, you need to find the average position of the group, known as the center of gravity. Each neighboring boid will then apply a steering force in the direction of this center and converge near the center.

constant float average = 100;
constant float attenuation = 0.1;
constant float cohesionWeight = 2.0;
float2 cohesion(uint index, device Boid* boids, 
                uint particleCount) {
  // 1
  Boid thisBoid = boids[index];
  float2 position = float2(0);
  // 2
  for (uint i = 0; i < particleCount; i++) {
    Boid boid = boids[i];
    if (i != index) {
      position += boid.position;
    }
  }
  // 3
  position /= (particleCount - 1);
  position = (position - thisBoid.position) / average;
  return position;
}
float2 cohesionVector = 
    cohesion(id, boids, particleCount) * attenuation;

// velocity accumulation
velocity += cohesionVector * cohesionWeight;

Separation

Separation is another steering behavior that allows a boid to stay a certain distance from nearby neighbors. This is accomplished by applying a repulsion force to the current boid when the set threshold for proximity is reached.

constant float limit = 20;
constant float separationWeight = 1.0;
float2 separation(uint index, device Boid* boids, 
                  uint particleCount) {
  // 1
  Boid thisBoid = boids[index];
  float2 position = float2(0);
  // 2
  for (uint i = 0; i < particleCount; i++) {
    Boid boid = boids[i];
    if (i != index) {
      if (abs(distance(boid.position, thisBoid.position))
            < limit) {
        position = 
            position - (boid.position - thisBoid.position);
      }
    }
  }
  return position;
}
float2 separationVector = separation(id, boids, particleCount)
  * attenuation;
velocity += cohesionVector * cohesionWeight 
  + separationVector * separationWeight;

Alignment

Alignment is the last of the three steering behaviors Reynolds used for his flocking simulation. The main idea is to calculate an average of the velocities for a limited number of neighbors. The resulting average is often referred to as the desired velocity.

constant float neighbors = 8;
constant float alignmentWeight = 3.0;
float2 alignment(uint index, device Boid* boids, 
                 uint particleCount) {
  // 1
  Boid thisBoid = boids[index];
  float2 velocity = float2(0);
  // 2
  for (uint i = 0; i < particleCount; i++) {
    Boid boid = boids[i];
    if (i != index) {
      velocity += boid.velocity;
    }
  }
  // 3
  velocity /= (particleCount - 1);
  velocity = (velocity - thisBoid.velocity) / neighbors;
  return velocity;
}
float2 alignmentVector = alignment(id, boids, particleCount)
  * attenuation;
velocity += cohesionVector * cohesionWeight
  + separationVector * separationWeight 
  + alignmentVector * alignmentWeight;

Escaping

Escaping is a new type of steering behavior that introduces an agent with autonomous behavior and slightly more intelligence — the predator.

constant float escapingWeight = 0.01;
float2 escaping(Boid predator, Boid boid) {
  return -attenuation * (predator.position - boid.position) 
            / average;
}
Boid boid = boids[id];
Boid predator = boids[0];
Boid boid;
if (id != 0) {
  boid = boids[id];
}
if (id == 0) {
  color = half4(1.0, 0.0, 0.0, 1.0);
  location = uint2(boids[0].position);
}
// 1
if (predator.position.x < 0 
     || predator.position.x > output.get_width()) {
  predator.velocity.x *= -1;
}
if (predator.position.y < 0 
     || predator.position.y > output.get_height()) {
  predator.velocity.y *= -1;
}
// 2
predator.position += predator.velocity / 2.0;
boids[0] = predator;
float2 escapingVector = escaping(predator, boid) * attenuation;
velocity += cohesionVector * cohesionWeight 
  + separationVector * separationWeight 
  + alignmentVector * alignmentWeight 
  + escapingVector * escapingWeight;

Dampening

Dampening is the last steering behavior you’ll looking at in this chapter. Its purpose is to dampen the effect of the escaping behavior, because at some point, the predator will stop its pursuit.

constant float dampeningWeight = 1.0;
float2 dampening(Boid boid) {
  // 1
  float2 velocity = float2(0);
  // 2
  if (abs(boid.velocity.x) > limit) {
    velocity.x += boid.velocity.x / abs(boid.velocity.x) 
                       * attenuation;
  }
  if (abs(boid.velocity.y) > limit) {
    velocity.y = boid.velocity.y / abs(boid.velocity.y) 
                       * attenuation;
  }
  return velocity;
}
float2 dampeningVector = dampening(boid) * attenuation;
velocity += cohesionVector * cohesionWeight 
  + separationVector * separationWeight 
  + alignmentVector * alignmentWeight 
  + escapingVector * escapingWeight 
  + dampeningVector * dampeningWeight;

Where to go from here?

In this chapter, you learned how to construct basic behaviors and apply them to a small flock. Continue developing your project by adding a colorful background and textures for the boids. Or make it a 3D flocking app by adding projection to the scene. When you’re done, add the flock animation to your engine. Whatever you do, the sky is the limit!

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now