So this is just quick post to finish up some details of my last post on steering. Nothing here is earth shattering (simple mathematical function manipulation), but it is handy stuff to consider when you’re trying to polish a steering system.
First off, let’s revisit Reynold’s classic repulsion force for the separation behaviour. He does a nifty trick that I found rather clever. Basically he wanted a 1/x repulsive force that was inexpensive to compute.
Conceptually you should normalize the vector to the entity and scale it by the force you want to apply (in this case 1/x). He saves the square root (the normalize) by just dividing by the squared distance! One divide for the normalize and another for the 1/x. Neat. But this optimization comes at a cost. We lose a lot of control over this force. I personally feel that the normalize isn’t really a performance concern on modern hardware unless you’re simulating very large numbers of entities and the nearest neighbor calculations are orders of magnitude more expensive than the square root. But regardless, my first tweak to the force can be done with Reynold’s divide by the squared distance trick.
Previously we came to the conclusion that discontinuities in steering forces were pure unadulterated evil. If the distance cutoff for your nearest neighbors is large enough the 1/x force will be essentially zero when entities enter the neighborhood. This however is rarely the case. Let’s look at the 1/x and 1/x2 functions.
Notice that with a cutoff of 4 the 1/x force is 0.25. To prevent discontinuities we’d like the value of the function at the cutoff to be pretty flat and zero. We can make it zero by simply moving the function down a bit! Specifically we can just apply negative correction of the value of whatever function we use at the cutoff point. In this case -1/4. If we were using a 1/x2 force we’d apply a -1/42 correction.
One of the nice things about the x2 curve is that it flattens out quickly and is more suitable for some uses because of this. But to use a 1/x2 curve I need to normalize the vector and compute the force separately. I like the flexibility of this approach for other reasons though. I can now manipulate this force in any way I see fit. Besides changing the fundamental shape of the curve (like from 1/x to 1/x2) I can also simply shift it left or right. Why would I do this? Well 1/x type curves get pretty big when the distance drops below 1. I’m pretty sure I don’t need a repulsive force with a magnitude of 16 if the distance is 0.25m. I can either just clamp the max force or I can slide the function left so I can smoothly ramp up to the max force at zero distance. This is a simple matter of just fiddling with x (distance) as the input to the function. Here’s the repulsive force shifted left by 0.3 and corrected to be zero at 4 which maxes out at 3 instead of infinity.
Now I this is simple stuff for anyone that has taken high school mathematics. The purpose being really just to remind you of the simple strings you can pull to get some basic functions to do what you want. Besides shifting I also find scaling functions quite useful. Notice that regardless of whether I use 1/x or 1/x4 the force is always 1 at 1. I can scale a 1/x function though by applying a scale to x and then multiplying the result of the function by the reciprocal of this scaling factor (basically scrunch it towards the origin or expand away from it) . Here is the old 1/x function scaled down by a factor of 4 (1/(x*4))/4. Such a force can be useful if you only want an effect to manifest when entities are very close to each other.
So what about something besides repulsive forces? One of my favourite ways to get entities to stand at a position is to have them approach the point with a standard seek behaviour (linear ramp down in speed as they approach the point). This will move the entity quite accurately up to the point, but it is overly sensitive and if the entity overshoots it just a little bit or has other forces acting on it that push it slightly off the goal position they can be stuck eternally shuffling back and forth trying to get right on top of the point. We also want to avoid a binary dead zones on the point because there will be a big discontinuity on the edge of the dead zone. What we do want is a gentle trough of forces – kind of a flattened basket centered at the point. With our ability to tweak the functions defining the shape of this basket we can tailor the forces being applied to the entity however we like (how wide the flat portion of the basket is, how steeply the walls climb, etc). So once the entity approaches within some tolerance of the point I switch from linear ramp seek to “basket mode”. The result is that entities rarely have to readjust their position once at their destination. The basket below is simply an x3 curve shifted out by 0.1 creating a nice smooth deadzone in the middle.
A good graphing calculator or website is invaluable for testing out your changes. As is rendering a sample of all the forces at varying distances to validate your in-game code is doing what you think it’s doing. I find having debug rendering of all the various forces that can act on an entity hooked up to a series of debug switches very useful. It can facilitate a kind of unit testing for steering behaviours. Designers can tweak numbers into crazy town, and changes in dependent systems can result in the forces being applied to an entity no longer being valid or appropriate. Being able to correct these problems when they first start occurring is invaluable. Even simple steering systems can generate quite complicated interactions and large errors in the underlying forces can go unnoticed for a surprisingly long time. Many would argue that resolving these complex interactions are the biggest deficiency of steering systems – and they would probably be right. Steering behaviours are not a panacea and should be used judiciously – and never for collision avoidance. That said, I still think they are awesome.