(train whistle blows)
- Hello and welcome to a coding challenge.
In this coding challenge,
I should probably go eat lunch but I'm going to just
try this coding challenge.
I am going to attempt to program this.
This is a flocking simulation.
What you're seeing right now is an example
that I made many years ago
of a flocking simulation, a Boids system
running in processing.
I'm going to try to make exactly this or close to it
in JavaScript with the p5 library
starting from no code at all.
Obviously besides the dependencies.
Okay, so this is the example running.
Let me just give you a little bit of background here.
If you want to learn more about the flocking
algorithm as invented by Craig Reynolds,
you can go to this link to read this explanation of it.
And find many other links and resources
down here, whoa, that's a lot of stuff.
Have fun, see you in a few weeks.
You can also check out this YouTube video
of the original 1986 flocking simulation.
Wow, that's amazing.
This is so cool, oh my God, I love that.
You can also find an explanation of it
as part of my Nature of Code book online
with some diagrams and code written in Processing
and an exercise that you can try which I'll refer to
back at the end.
But I'm going to start from this, a blank slate.
I just have the canvas, you have some amount
of pixels in it.
There is nothing happening in the console.
I've got this code set up and draw.
So I'm going to write this code without worrying about
being perfectly organized and being able to scale
it very easily.
I just want to get the algorithm working but of course,
where is the button?
(pop music)
If you want to see a refactored example of it,
you can probably go and look and actually,
at my finished version in the Nature of Code
which I'll also link to.
The first thing that I want to do though is
I want to add another JavaScript
file called particle.js.
Actually, I should rename this to boid.js.
What is this term Boid?
When Craig Reynolds invented this algorithm
for simulating a flocking system like a flock of birds
or a swarm of bees, I suppose he didn't want
to use the term bird, but it is kind of like bird,
so Boid, it's kind of like droid but Boid.
Go read the original paper about steering behaviors
and flocking systems and you'll find out more
about the history about this.
But I'm going to make a class called Boid
and I am going to use the p5 vector object.
I'm going to make Boid objects and each time
I made a Boid object, I need a constructor
and I am going to give each Boid a position.
I'm going to give each Boid a velocity.
I'm going to give each Boid an acceleration.
All of those are going to be vectors.
I'm doing this is 2D.
You should, after you watch this, if you watch this,
you should make your own 3D version of this.
All of the math will work exactly the same way
but you'll just need to rethink how you're visualizing this.
Just to give things a start, let's just put
the position in the middle of the window
and let's write a function called show
which will just draw them as a circle right now.
Actually, I'll just even use point, like strokeWeight 16.
Stroke 255, point this.position.x.
I think Visual Studio code will do this for me.
No, where does it?
It fixed that for me once, whatever.
This PointerEvent, I'm not a PointerEvent,
I'm a point.
So now, if I were to then here make a flock,
which is an array, and I should probably make
a flock class, that might be a thing to refactor later.
But I'm just going to say a flock.push new Boid
and then I just want to say for every Boid in the flock
and I'm using of, I'm using a for-of loop
but I said in because that's the way I feel right now.
A boid.show and then I need to make sure
I am also referencing boid.js in my index HTML
so I have the basic Boid class, which just creates
a position, velocity, acceleration and draws it
as a point and then I have my sketch where I make
one Boid and I draw it in the center of the screen.
There it is, there's my boid.
(bell rings)
Step one complete.
I probably should plan this out
but I have one Boid, that's good.
Now, let's have that Boid move.
Let's actually write in the Boid object
a function called update and this I probably covered
ad nauseum in a lot of other videos
in my whole Nature of Code series.
But the idea here is that position is updated
based on the Boid's velocity and its velocity
is updated based on the Boid's acceleration.
If I now actually create a p5.vector, a random 2D.
I believe this is a function that will give me
a random velocity vector and I now go in here
and say boid.update.
Oh I just spelled it wrong.
I thought I missed the this dots.
Here we go, look, it's moving.
It's moving with a random velocity each time.
Okay, now let's just make a bunch of these.
Now I have a system of 100 Boids but it's interesting,
look at this, it fans out in the perfect circle.
Do you know why that is?
Because I make a Boid and I give it a random velocity.
That random vector is always of unit length one.
The direction is something different so they're all
actually moving with the same speed.
If I wanted them to have, and this is not an important
detail to the flocking stuff, but just to sort of
get a sense of how this vector stuff works,
I could say this.velocity.setMag, which is set the
magnitude to a random value between 0.5 and 1.5.
Now, you can see now they're all moving with a
slightly different velocity.
This is by the way, a nice little almost like explosion
motif and I could have them with a real big burst
then they slow down.
There's all sorts of physics-y stuff I could do
with this but I just want to do flocking.
How, how, how do I do flocking?
Let's return to this paper here.
This is the key behind Craig Reynolds' flocking algorithm.
Three rules.
Separation: steer to avoid crowding local flockmates.
Alignment: steer towards the average heading
of local flockmates.
Cohesion: steer to move toward the average position
of local flockmates.
Something so crucial in that description is the
word local, local.
Local, what does that mean?
Let's just do, I think the easiest one to do
actually is alignment.
Let's say there are five Boids.
Actually, let's make a lot more of them.
These are all of my Boids.
This is the one that I'm currently operating around.
It has a velocity vector like this.
Let me just make up, let's just pretend
like the ones around it are kind of moving
all in the same direction.
Because that'll be easier to think about.
This is the current one that I'm looking at.
And maybe some of these others are also moving
in other directions also.
I could take the approach to say this particular Boid
needs to align itself to everything.
But this is not really how complex systems
in nature actually work.
The emergent phenomena comes from this Boid having
a limited perception of its environment.
So maybe, it's actually only able to see things
that are in front of it or behind it
or to the right or what's probably the most simplest
thing to implement is within some radius.
So I only want to look at the Boids that are
within this radius, meaning this one, this one,
this one, this one, this one.
What if I iterate over all these ones that are
within some distance, average all of their velocities
and shift this velocity in the direction
of all those velocities.
That's what we want to do.
I am going to write a function now.
I'm going to put it in the Boid and I'm going to
call it align and it's going to get presumably
an array of other Boids.
The idea is this function align this Boid
with all the other Boids.
So what do I need to do?
I'm going to make a variable called average
which is going to be a vector then I am going to
iterate over and I could do all of these with
reduce probably in some higher order array functions.
That's a great thing for you to do.
(pop music)
Refactor it later but I'm just going to do it
this way, I'm actually going to use i because that's
maybe going to help me.
I'm not really sure but I'm going to use i right now.
Actually, no, I'll still use a for-of loop.
Let other, I'm going to call it other of Boids
and I'm going to just say average.add other.velocity.
So I'm adding up all of the velocities.
This is how you do an average, right?
Vectors, again you might want to go watch my videos
about how vectors work, but a vector, a p5 vector
just holds an x and a y or a x and y and a z.
So I want to average the vector, represented as an arrow
with an x component and a y component.
If I want to average, just like I might average
an array of numbers, I add them all up and divide
by the total.
Then I would say average.divide by Boids.length.
This would be a way of getting the average velocity
of all the Boids but remember I only want
the ones within some distance.
So actually, let's implement that right now.
Let's have some sort of distance like max.
I'll call it perception equals let's just make it
arbitrarily like 100 and then I'm going to now
say if the distance between this.position.x.
You know what?
I think there's a p5.
This is fine.
It's just going to be, it's kind of long the way I'm writing this
but I don't mind.
Let's actually make it in a variable.
D = the distance between this Boid's position
and the other Boid's position, x and y.
If that distance is less than 100,
look at that.
Look at this fancy thing that it's doing.
I have some prettier stuff that doesn't want
to let me write a long line of code.
I guess I'll keep that in there for right now.
So I'm calculating the distance between this Boid's
position and the other Boid's position.
If that distance is less than 100, I'm adding
it up and I should also probably have a total number,
total = zero.
Add it up.
I also should probably ignore myself and if other
does not equal this, right?
So I basically, I might as well put that first.
As long as the other thing is not me
and the distance is less than 100, add it up
and then divide by the total.
But obviously, I only want to divide by the total
if total is greater than zero.
All right, so this is kind of a little bit of
a long-winded algorithm now.
And perception.
If d is less than perception.
So I'm starting with a perception, a radius of 100.
Maybe that would be better if I called it
perceptionRadius, that's really what it is.
Starting with a perception radius of 100,
an empty vector, adding up the velocities
of any vector, any Boid that's near me,
dividing by the total and as long as I've found one,
dividing by that total.
Now here's the thing.
I could just say, for example I could do something
weird, this will be really weird.
I could just say this.velocity equals average.
What if I did that?
Then I said Boid, like here I said boid.align flock.
I don't know what's going to happen here,
but let's refresh this.
They just basically don't go anywhere.
Because they're all instantly equal
to each other's velocity.
That's not really a good way.
What I need to do now is if I don't want to actually
assign its velocity to that average directly.
I want to steer towards it.
This is where Craig Reynolds' steering formula comes in.
A steering force equals some desired velocity
minus the current actual velocity.
It's kind of like the error.
If I am moving this way.
Sorry, if I'm moving this way but I want to move this way,
the way I want to move minus the way I am moving
is the steering force meaning push me in that direction.
Apply a force.
Maybe I'm going to turn the steering wheel.
You can think of these as bees or cars
or birds or whatever.
This formula desired minus velocity will give me
the steering force and what is the desired velocity here?
The desired velocity is actually that average.
I'm actually going to rename this to desired
and I'm going to add it up
and divide by total and then I'm going to say
this.velocity equals, no, I'm not going to say that.
I'm going to say steering equals.
I should actually call this steering because I could
do all of this.
I don't need to save anything as I'm going.
I'm going to call that the steering force.
I'm going to add all the velocities.
I'm going to divide by the total and then,
I'm going to say steering force is the desired
subtract this.velocity.
And I'm going to say return steering.
I want this function to basically,
I want this function to return that force.
So really what I should be doing is I should have,
I'm going to write a function called flock.
That's maybe more like, yeah.
Flock with some number of Boids and then I'm going to say
alignment equals align with those Boids.
So I'm going to get this force and I suppose
steering is here, so you know what?
I should always return steering.
I want to always return a vector but if it didn't find
anything, I'll just return the zero vector.
Then, what I'm going to do is I'm going to say
acceleration add alignment.
The idea, the way that a physics engine works,
and this has to do with,
this is a very simple crude physics engine.
Force equals mass times acceleration.
This is Newton's Law of Motion.
Or acceleration equals force divided by mass
or in a world where the mass of everything is one,
acceleration equals force.
So if I want to apply a force to this object,
I just need to set the acceleration to that force.
Actually, that's what I'm going to do just starting out here.
I'm going to say acceleration equals alignment.
So now, if I go back to sketch and I say
Boid.flock Boids, we should now have all of Boids
doing that with just implemented with that alignment rule.
Let's see.
Boids is not defined.
Sketch.js line 14.
Oh, it's called flock.
Oh, this is my variable naming is terrible.
But I'm going to leave it that way right way.
Align is not defined.
Boid.js align Boids.
Oh, this .align, this .align Boids.
Acceleration is not defined, oh my goodness.
(pop music)
Let me actually not have them all start
in the same place because that is just to see
this effect happen, let me actually start them
in random places on the screen.
Let me change that.
Let me make the perception radius a little bit smaller
and I don't know why this matters, but let me draw them
like a little bit smaller.
Let me actually have them start out going
quite a bit faster.
You can see as they get near each other,
they start to get each other's velocity.
They start to average each velocity with their neighbor's.
You start to see these clumps moving together.
So much more to do on this.
Oh my goodness, but first a couple things.
One is I have now basically allowed these Boids
to steer with infinite power in a way.
We should probably have a variable.
I'm going to call it maxForce.
I'm going to set it equal to one for a second
and then what I'm going to do is here,
I'm going to say a steering limit this.maxForce.
What this does is it limits the magnitude,
the length of that vector to some maximum force
so this should be exactly the same.
We're not seeing anything different but if
I were to make maximum force 0.01, like really small
right now, you're going to see they're not actually
changing their velocity.
They are, but very, very slowly.
The other thing we're really going to need to do
is I just have to give up and do this.
I'm going to add something for the edges here.
I'm going to say if this.position.x is greater than
the width, this.position.x should equal zero.
Else, if this.position.x is less than zero,
this.position.x equals width.
Then I'm just going to do that for the y,
with, with, x, x, y, y, y, x, x, x, x,
with, with.
All right, and now here, let's do boid.edges.
We should see them reappear wrapping around
and now, let's go back to the Boid and
let's make this 0.2 is kind of reasonable-ish.
Interestingly enough, they're kind of like
it slows them down which is interesting.
But I'm only doing alignment right now,
but you can see how this works.
You can see how they're all starting to align
with each other.
I could keep them going at some minimum velocity
which might make sense.
You can see how these are going back and forth.
But you get the idea.
Why are they all slowing down?
Is that just a coincidence?
One thing I could do is
this is actually going to be worth it
because I should also give them a maximum speed.
This is going to be a parameter of a variable
that's going to allow me to control the system
pretty well and I could consider their desired
velocity in the alignment to not actually be
the actual average velocity but just the average
direction because I could then say steering.setMag
to this.maximum speed.
So basically I'm saying I always want to go in the
direction of my neighbor but at maximum speed.
I don't know that that's really an important detail,
but if I add that in here, we might get the effect
that I was hoping to get.
Now, this is what I was expecting to see.
As they get near each other,
they're all starting to align together.
Let me refresh that one more time.
You can see that they're clumping and as they group,
they all start to align.
This is the alignment rule.
This is a very simple rule.
It's predictable, it's obvious, this is alignment.
Now, all we need to do is add cohesion and separation.
Separation is going to be the hardest one to do.
So let's do cohesion next.
Same thing, we're going to look at our neighbors.
We should go back to what the rule was actually
defined as, let's go back to Craig Reynolds' original page.
Cohesion: steer to move toward the average position
of local flockmates.
This is actually not going to be that hard
because we've already calculated the average velocity
of local flockmates.
Now, let's just duplicate that code.
I know, I know we could refactor it.
I won't play the song.
Because we could probably do this all in one fell swoop.
There's so many possibilities but now I just want
to get the average location of my local flockmates
and steer towards that.
So, the way I'm going to do that is I'm just going to
go nuts and copy paste this whole thing
and call it cohesion and I'm going to keep
the perception radius, I'm going to keep this idea
of steering, keep this idea of total.
Go through all the Boids, check the distance
between myself and the other ones,
as long as I'm not myself and within the perception radius,
what am I doing?
Steering not the others' velocity but the others' position.
This should clearly be refactored into its own function
but whatever.
Then as long as I have at least one, I'm going
to divide by the total.
Now I don't want to set the magnitude to the maximum speed
so what I have now in steering is the average location.
So if this is the average location,
let's say it's over here, then what I need
is a vector that,
that's clearly not the average location.
But let's just say it was.
The average location is kind of like
where this Boid is.
But I want to steer in that direction
so to get a vector in that direction,
I take the average location minus the current position
of me which is basically saying
now what I want to do is say steering subtract
this.position so subtract my position now,
I've got a vector that's pointing from me
to the average location, remember which is in
the steering variable then let's say I want to
go at maximum speed.
Then, this is now my desired velocity.
I'm going to subtract out my current velocity,
limit it to max force and there we go.
That's cohesion.
(bell rings)
Now, in flock, let cohesion equal this.cohesion
Boids and then oh, we have a big problem.
This.acceleration equals alignment.
This.acceleration equals cohesion.
So how can I set the acceleration to two different forces?
But before I even answer that, there's an easy answer
to that question.
Let's just comment out alignment and let's watch
cohesion happen.
We can see they start to group together.
This is cohesion happening.
And that neighbor radius, by the way,
is a super important value.
Right?
That neighbor radius, if I were to change that
and I have a different, that perception radius,
if I were to change that to 10,000
they're all going to come together as one.
Because they all see everybody.
If I were to change that to 10,
there's really little pairs.
You can see they kind of get in groups of two.
The force though isn't so strong.
So if I were to change the maximum force to one,
now you see them, almost like these little
electron thingies that start, magnetic things
that start to spin around each other and then fan off.
There's so many possibilities here.
I could make the maximum speed two.
I'm not actually limiting it, you can see.
There you go, that's what I was looking for.
They get into these little groups
but let me go back and say maximum speed is four.
Maximum force is 22 and I realize
this maximum speed is not actually a maximum speed
because in update if I really wanted it to be
a maximum speed, I would want to say
this.velocity limit this.maxSpeed.
Let's actually add that in and see what we get.
Oh, and let's put the perception radius back
at 100 and there we go.
Now we have cohesion.
We have cohesion, what happens if we have cohesion
and alignment?
How do we get cohesion and alignment?
Okay, this is fun.
This is really working.
This is going to be a really long video.
All right, cohesion and alignment.
The problem rests here.
This is actually a really easy thing to solve
because this is called force,
the answer here is force accumulation.
In physics, if two forces are acting on an object,
the resulting acceleration is the sum of those forces.
So all I need to do is say acceleration.add alignment
and acceleration.add cohesion because
why is this not falling?
Because the force of gravity is pointed this way
and the other force of my hand holding it up
is pointed in the other direction with an equal magnitude.
Therefore, it is at rest.
Those two forces added together have a net acceleration
of zero.
But obviously, all you do is add them together
but there is this, if I actually do this right now,
we're going to see some really crazy behavior.
Looks like it's kind of working but it really isn't
because what it should also have is this acceleration
shouldn't accumulate over time.
Every moment in time, I start with a net acceleration
of zero and add all the forces together.
So what I should do right before I flock is say
this.acceleration set zero, zero.
This is setting its values to zero and zero.
Another way I could do that is just multiplying
it by zero, because multiplying a vector by zero
takes everything out.
It could make a new vector, whatever.
But this will work nicely and technically,
this also might make more sense here
because it's kind of like after I finish updating
the velocity then I can reset the acceleration
in case there's other things at play.
Now we can see both cohesion and alignment
are at play, look at this.
Eventually, they're just all going to become this one clump.
Come on, you can catch up, you got it, go go!
Go, catch up!
Yeah! One clump.
One thing I would like to do.
I really want to attach sliders to those parameters
but I got to resist because we got to have separation.
We've got alignment.
(bell rings) And cohesion.
(bell rings)
Separation, this one is a little bit hard.
Steer to avoid crowding local flockmates.
Here's the me.
That's me, here's my local flockmate.
If this local flockmate is too close, it's within
some sort of distance threshold, I want to steer
to avoid that.
What would be my desired velocity?
My desired velocity would be to move
in the opposite direction.
So the idea is my desired velocity is in the observable.
What if there is one here?
My desired velocity would also be opposite direction
so then my next desired velocity would be
the average of these two.
It even would make sense for the magnitude of that
desired velocity to be proportional to the distance.
So if this one is here, I maybe want to avoid it
but I only really need to think about avoiding it
a little bit.
If this one's really close, I need to avoid it a lot.
So the magnitude of the vector is inversely proportional
to the distance of the local flockmate.
This might end up with kind of like a desired velocity
of something like this if I'm averaging those two together.
So let's see if we can implement that.
Not going to be super easy but let's do it.
The nice thing is I'm going to start with cohesion
because we've kind of,
cohesion is close to separation.
Again, boy.
(pop music)
♪ I will refactor this later ♪
♪ You know I will refactor this later ♪
♪ I will refactor this later ♪
- I should show you that I'm wearing my
I will factor this later t-shirt, by the way.
Okay, so separation.
I'm going to leave this at 100.
Steering, I think I might need to use,
I like the idea of just having one vector
that ends up as, it's sort of the average here
then it's the difference but I think I need
to think, one step at a time.
I'm creating this vector.
This is the same.
If it's not me and it's within the perception radius,
what do I need?
I need a vector, I'm going to call it difference
which is the other's.
I want a vector, sorry, that points from
the other to me.
Because I want that to go to the opposite direction.
So it's my position minus the other.
I want a vector, the new vector which is
the subtraction between my position
and the other's position.
This is a way of calling the subtract function
which doesn't operate on a vector
but subtracts two vectors and gives us
a new vector which is difference.
Then, I want the difference to be inversely
proportional to the distance
so I'm going to multiply it or set its magnitude
something like that.
Or divide by distance.
I could just say divide by distance, right?
It's inversely proportional, the farther away
it is, the lower the magnitude.
The closer, the higher the magnitude.
The distance, I guess technically that distance
could be zero and that would be problematic
but with floating point math rule,
it's never going to get a value of zero.
Then I want to add.
Then that is the thing I add up.
That's the thing I want to average.
All those vectors that are pointed away
from the things near me, I think we're actually
in pretty good shape here.
This isn't as hard as I thought.
This was the tricky part here.
Get a vector that's pointing away
from the local flockmate, make it inversely proportional
to the distance, add it up, then this is the same.
Because, oh no.
I don't need to subtract position.
I kind of don't know if I want to.
Let's leave this whole always go at maximum speed thing
and then the rest is the same.
Once I have that, just average it,
set it to max speed, subtract out the velocity
and limit it to maximum force and that's the end.
Okay, just to be sure this is right,
let's go into Boid.
Let's go to flock.
Let's add let separation equal this.separation
and then let's add in separation.
I want to not bother adding cohesion and alignment.
Are these separating?
Yeah, it looks like they are.
It looks like when they get close to each other,
they're kind of moving away from each other.
Weird, weird behavior.
This doesn't look like exactly what I expected.
Let's give the maximum force, like really let
them be super strong about separating.
There we go.
So that's more like what I expected.
That's way too strong.
Yeah.
Why do they feel like they're, something is weird here.
Let's give it a much smaller perception radius.
Oh yeah, this is what I expected to see
and I think it wasn't really a bug in my code.
It's sort of a bug in my conceptual sense
of how this works.
This is pretty good, this is what I want to see.
This is separation only.
Now, we should be able to see and let's
I'm going to do this really quickly.
I'm going to not be thoughtful at all about the interface
but just so I can debug this effectively,
I am going to go into sketch.js.
I am going to say let alignSlider, cohesionSlider
and separationSlider.
AlignSlider equals createSlider.
I'm going to give it a range between one and five.
Actually, let's give it a range between zero,
sorry, zero and five, with a starting value of one
and increment value of 0.1.
I want to do this for all of these sliders.
Then in Boid, I want to say where I call in flock,
I want to just scale them.
I'm going to say separation.multiply separationSlider.value.
I'm going to do this also for cohesion and for alignment.
Now, by the way.
(bell rings)
(train whistle blows)
Flocking.
This is now the flocking simulation.
I didn't draw them as triangles.
They're not rotating, but this is the flocking simulation.
But just for the sake of argument, this should be
what order did I make them in?
They're not labeled.
So let's just turn all the way up to separation.
Oh boy.
Let's turn off.
This is only separation.
Now, turn that off.
Let's turn on only alignment.
Whoa, that's oh they're just the alignment
is so strong.
When the alignment is so strong, it makes them
go in circles around each other.
I have to talk about why.
Why did that happen that they were twirling in circles?
Well, look at this.
I'm updating them one at a time.
The next one is flocking with the previous ones
updated value, right?
Instead of taking a snapshot of all their current
velocities and then each one updates based
on that snapshot, I start with a set of velocities.
I update the first one based on the set of velocities
and now its new velocity is there.
So when the next one, it's actually updating itself
based on the previous one that I updated
which is not how the world works.
It's not how time works.
That's causing them to ripple into each other
based on the order and go in circles.
That is something that you might want to consider fixing.
I'm not going to fix that but now I can really play
with these, I can get different sort of qualities
of flocking based on how strong I make these rules.
So no alignment, a lot of cohesion.
No separation, add a lot of separation.
Less cohesion.
Separation, I shouldn't make too strong
but it's important and then let's add some alignment
back in and there we go.
Flocking.
(bell rings)
Okay, this coding challenge is complete.
It is done.
I will be uploading the code, if you want you can
look at the video description to find the code
as a snapshot, that's exactly what it is.
Let's go make a list.
Let's make a list on the board of things
you might try to do.
Number one, snapshot of all velocities.
That's going to be my code for that.
Number two, an optimization you can do
is called spatial subdivision or a quadtree optimization.
One of the things that makes this really slow.
And I'll just show you here for a second,
is if I were to try to do this with a 1,000 Boids.
Look how slow this is.
But you have to realize, it's not a big deal
for p5 to draw 1,000 things moving.
Why it's fine drawing 1,000 things moving
but as soon as I put the flock function in,
it's super slow.
The reason that is is because it's got to do
and every Boid, check every Boid against every
other Boid check.
So there's a way of subdividing the space,
spatial subdivision into bins or buckets
and have the Boids only check ones that are near
each other in the same buckets.
That's called, I probably have a lot of distance
calculation, I could reduce the number of times
I call it.
But for this, quadtree or just simple spatial subdivision.
These are things you could try.
These are just code refactoring, really.
Actually, the other thing you could try
is really build a more sophisticated interface
and there's lots of other parameters that you
could control and try.
There's the perception radius, there's maximum force,
there's maximum speed.
Each rule could have a different perception radius.
So many possibilities.
This is the stuff that you could do to really
control and tweak all the values and play with it.
Another thing you could do is just design,
visual design, like are they triangles,
circles, are they flapping butterflies.
How do you, design of the Boids?
Make your own beautiful visualization of how you
design and draw them.
Tadpole-like, there's so many ideas there.
Another thing you could try to do is 3D.
Can you extrapolate this into 3D?
If you see the word quaternian anywhere you start
to research, you might want to turn back.
But you could extrapolate this into 3D.
Another suggestion which came from the chat,
thank you, is Boids with different parameters.
There's no reason why every Boid has to have
the same max speed or maximum force.
Or same perception radius.
You could implement this thing called the view rule.
There's two things about this one.
Each Boid can see everything around it.
But what if the Boid itself could actually only
see if it's moving in this direction,
things that are within its particular view in front of it.
Maybe it doesn't deal with anything that's behind it.
It doesn't try to do cohesion or alignment or separation.
Then the view rule which is posited by Flake
in the book A Computational Beauty of Nature,
I'll link to that book also.
It's a wonderful book with tons,
probably like 90% of the things on my channel
are all from the Computational Beauty of Nature book.
But Flake posits this additional rule
that you always want to keep your view empty.
So if there is a Boid in front of you,
you want to steer that way to keep your view empty,
clear and this might result, the theory is
that this will result in something that looks
more like that pattern that you'll see of
actual birds flocking where they kind of
appear almost in this triangular pattern.
What's interesting about this, you see a pattern
like this, you think, "Ah ha."
This is a top-down behavior.
There is a leader, this bird, who is saying
to all the other birds, "Fan out from me and follow me."
But yet, this type of intelligent behavior
emergent phenomena can come actually only from
simple, local rules of interaction.
This by the way, this is what this whole video
was about, if you didn't gather that already.
Other ideas might be adding obstacles,
other forces, there's thinking about these
living in a world where they're interacting
with other things, maybe there's a predator
that comes in and is chasing them.
All sorts of unique and interesting possibilities.
So thank you everyone for watching this coding challenge.
Put your other ideas for things people could do
in the comments and then if you make your own
version of this flocking code, please go to the
codingtrain.com challenge page and submit a link
to what you made.
I will also make a version of this that runs
in the web editor and link to all those resources
like the Craig Reynolds original paper
about flocking, the video on YouTube and the chapter
in my Nature of Code book, okay?
Thanks for watching this coding challenge
on flocking and I'll see you soon.
(train whistle blows)
(upbeat music)