In this video, I'm going to implement the steering behavior
path following.
This will depend entirely on everything
that I did in the previous video on scalar projection.
I'm just going to bring that code over here.
So you don't necessarily need to go back and watch that.
But if you want to understand that concept,
you can go back and watch the previous video.
I'm starting with the vehicle class
that I had at the end of the arrival video.
The first thing I want to do is define a path
for the vehicle to follow.
Eventually, I'm going to want a more sophisticated path.
But I think an easy way for me to begin
is just have it be-- a path be a line that
connects two points, a starting point and an ending point.
Then I can write a show function,
which will render that line.
Got to make sure I add path.js to index.html.
I've made a this dot error.
It's just something I will never stop doing.
And so now, I have a vehicle, and I have a path.
Just to make things sort of visually very simple right now,
my path is just a horizontal line bisecting the canvas.
Assuming I'm going to follow the model that I developed
for the previous steering behavior examples,
I then need to call a function to calculate a force
and then apply that force.
This tells me I need a new function in the vehicle class
itself called follow that receives a path object.
Now, in theory, this is the only place remaining
where I need to write code.
I need to read up on what the path following algorithm is
and implement it into that function,
returning a steering vector at the end.
I would encourage you to pause and read
the description of path following
from Craig Reynolds' paper.
But I'm going to describe it in my own words over
on the whiteboard.
Here's a diagram of all the elements
I have so far in the code.
One thing I haven't mentioned, which is an important element,
is Reynolds defines a radius for a path.
I didn't draw that perfectly.
This line should be directly in the center of these two
dotted lines.
But the idea is that if the vehicle is within the radius
of the path, it's on the path.
And remember the sort of driving principle here, lifelike
and improvisational manner.
If I wanted to, I could write some code
that puts this triangle exactly on this line
and just moves it along the line perfectly.
But that's not what I'm trying to do here.
I'm trying to get this vehicle to sort of veer
around, and steer, and attempt to stay within the boundaries
of this path.
Therefore, this idea of a radius is kind of important.
Step one, predict the future position of the vehicle.
So projecting out into the future, if the vehicle were
to continue at this velocity for some amount of time,
it would perhaps end up over here.
Step two-- I'm just going to call this future position
pos for simplicity.
Is that future position on the path?
Well, I can plainly see here that it is not.
The way I would determine that is
by calculating the distance between the point and the line.
I'll call that d.
This is everything that I worked out in the previous video,
how to do this.
This here, this is the path radius.
If d is less than the radius, it's on the path.
If it's on the path, do nothing.
I don't need a steering force.
The steering force is zero.
Assuming that point is not on the path,
find the projection point.
So if d is not less than radius, so yes, we just
go off and do nothing.
If no, step three, find this projection point.
I'll call that the target.
And guess what step four is.
Seek.
Did this fit onto the whiteboard?
It did.
Seek that as the target.
We've already written the seek algorithm in a previous video.
So that'll make things a lot easier when we get there.
OK.
Let me try to go and implement this in the code.
First, I'd like to add a radius to the path.
And I'm going to make it 20, just arbitrarily.
And I'm going to draw another line
with a thickness of the radius times 2, giving it some alpha.
So now we can visually see the path radius as well.
Now, there is one more interesting question
I've just been thinking about, which
is, does following the path mean move this way,
or does it mean move this way?
And there's no real answer to the question.
It's up to you to define that.
And ultimately, I think the way I'm going to implement it,
whatever its starting velocity is
going to ultimately determine which way it's following
the path, which is why, here in the sketch, for now,
I'm hard coding an initial starting velocity.
Step one, predict the future position.
To do that I just make a copy of the velocity,
multiply it by 50--
I'm going to look 50 frames ahead--
and then add that to the current position.
And that's my prediction of the future location.
There's still that thick stroke weight there.
So let's say noStroke.
Drawing it, of course, is unnecessary for the algorithm.
But I think it'll be helpful if I add visual annotations as we
work this out.
Step two, is this future position on the path?
Well, the way I determine that is by calculating the distance
and finding out if that's less than the radius.
How do I calculate the distance?
Think back to my previous video, scalar projection.
I'm going to grab this vector projection function
from the previous video.
And remember, the vector projection function, the way
as written, takes two vectors.
One would be defined as a vector that
points from the start of the path
to the vehicle's future location.
That's vector a.
Vector b is the start of the path
to the end of the path, the vector that
defines the path itself.
But what do I actually have?
In my code, I have three points--
this point, this point, and this point.
So I think I want to rewrite that vector projection function
to take as its input these three points,
calculate these two vectors, and then return this point here.
Let's call these points in the function pos, a, and b.
And then I'll call these vectors v1 and v2.
So v1 is a minus pos.
v2 is b minus pos.
So what was the vector b is now the vector v2.
I don't need to copy it anymore, because I've just
made it up right here.
But I do need to normalize it.
Then the scalar projection is v1 dot v2,
v2 multiplied by the scalar projection to get its length,
and return v2.
But all I really need in this case is this point.
So I don't actually need this vector.
I just need to find this point, which
is the position that I've started with,
adding that vector projection.
The function takes these three points, the start
and end of the line, as well as the future position
of the vehicle, and returns the projected point.
Let's call that findProjection.
And ultimately, that's going to be the target
to seek if, in fact, we need to move on to step four.
I kind of made these in a slightly weird order,
with a little bit odd naming.
I might refactor this in a moment to make it more clear.
But it's start, future, end.
path.start, future, end.
Let's just draw that point as well.
Oh, path.end.
I'll make that a different color.
And we can see, there's that point projected onto the path.
What was I trying to figure out again?
Right.
I need this distance.
I know this point.
That's the future.
I know the projected point.
I need to calculate the distance between those two points
and find out if it's less than the radius.
The distance between what?
The future and the target.
If that distance is less than the path of the radius,
what do I do?
Return, seek that target.
Otherwise, return, I don't know, a vector that's got nothing
in it, because do nothing.
Is that really everything?
[OMINOUS MUSIC]
I predicted the future position.
I determined whether it was within the radius and then--
determine whether this is--
I right kind of wrote this in a weird order,
I'm realizing, because in order to determine whether it's
within the radius, I need to find that projection point so
that I can find this distance.
And then once I have that projection point,
if it's not within the radius, then seek the target.
You got all that?
What's the chance that this works, right?
I'm calling that function.
I'm getting the force out.
I'm applying it.
Let's see what happens.
Well, that doesn't seem to be working at all.
Let's console.log that distance, see what I'm getting.
100, 100, 100, 100, 100.
That makes sense.
Let's console.log path.radius.
Oh my goodness.
Whoops!
Seek the target if the distance is greater than the radius.
Ah!
I'm really feeling like this is going to work now.
Oh, seek is not defined?
This dot, this dot, this dot!
[CHEERFUL MUSIC]
This dot, this dot, this dot, sorry.
Whoa!
It's working.
Let me get rid of the console.log.
Let me lower the maximum force and maximum speed.
I think I also might be looking too far
ahead with this vehicle.
This 50 should really be a parameter of the system.
Like, how far does it look ahead?
But let me look less far ahead, just to sort of see.
That's kind of fun.
So you can see, once it gets on the path, it stays.
It doesn't do any steering.
OK, let's at least make the path a little wonkier,
like not just a perfect horizontal line.
And I think it actually would be fun
if I could move the path with the mouse.
So I'm going to set the y position
of the endpoint of the path by moving the mouse.
Now, of course, the wraparound causes
it to not wrap around the path, but to reappear there, which
is kind of funny to see, but you can see now
this idea of it attempting to follow the path.
Now I might decide to like remove some of the annotation,
just to see what it looks like without that.
Obviously, I could have a variable or an interface
to turn that stuff on or off.
Let's make the canvas much wider.
And there, we have our little vehicle
trying to follow the path.
Boy, do I really want to keep going here.
I think I need to take a minute, take a minute and breathe.
This is a lot.
Just this much is a lot.
So I would encourage you to think
about how you might incorporate this
into some of the other examples that I've already created.
What happens if now, you had another vehicle that's
pursuing this vehicle that's following the path?
That could be something to try.
But ultimately, I think we're I would really
like to go with this is making the path a bit more complex.
You might remember, I think, back
at one of the videos I showed you,
the crowd path following example, which had
a more complex path like this.
It's made up of multiple line segments.
How do you know which line segment to look
for the projection point on?
You have all the tools to do this.
But it is a tricky problem.
So I'm going to leave that right now.
Obviously, I will leave a link to a solution
to this in the video's description.
But we'll leave implementing it as an exercise to you.
And if there's a lot of interest,
I can come back and add another video
to this playlist, where I go through augmenting the path
to be something that connects multiple line segments.
Additionally, the same technique applies
to wall following, which is like following a path,
but it's a wall instead of a path,
as well as containment, which is staying
within the bounds of something.
So I'd like to come back and make videos on those.
I don't, at present, have examples to show you of those.
But in a future time, I will.
Or if you make your own version of it,
please share it with me in the comments
or on the Coding Train website, where there's
a page that you can submit variations
on this path following example.
Hi.
Dan here from literally just like
five minutes in the future.
I'm about to say goodbye.
But I realized, after I said goodbye,
I missed a kind of crucial element
to the entire algorithm itself.
And it relates back to this idea of like, which way am I
going on the path?
Reynolds actually proposes that you find the projection point.
But that projection point isn't exactly the target
that you seek.
Rather, you could look ahead from the projection
point a little bit, some distance on the path,
and maybe choose this point to seek.
That will sort of guarantee that you're
sort of moving along in a particular direction
on the path.
You can actually see that in this other example
that I'm also including, where you can see the projection
point, as well as a point that it's actually seeking
a little bit ahead on the path of these two circles
that are following this path.
So ultimately, this is kind of another great exercise for you
to try.
How would you have that target not
be exactly where the green dot is, but a little bit ahead
of the path than the green dot?
And which way is ahead?
I'll make sure the code that I release with this video has
that built into it.
And if there's a lot of questions about it,
I can also come back and address it in the next video
that I eventually someday make about path following.
OK, back to me saying goodbye.
So what's next?
I actually have no idea.
I do want to eventually implement all of these.
And I've got a bunch so far.
But I think the real missing thing
that I will, at a minimum, get to
sooner than later is what does it
mean to have groups and combined behaviors, to some extent?
So what does it mean to have a separation steering behavior,
where vehicles want to separate from each other,
not run into each other?
So I want to get to that.
It'll lead to-- I have done a coding challenge
with the full flocking example already.
So that will be included in the playlist
at the time you're watching this right now.
But eventually, I'll be filling in some of the gaps
and missing pieces with some of these other steering behaviors.
Let me know what you think I should do next,
if it's not there already.
Thanks so much for watching.
And see you in a future Coding Train video.
[MUSIC PLAYING]