How to steer away from a plane

I thought I’d put this question out on the forums for a while before I decide on an approach.

The question is about my steering behaviours code.

My moving characters use a CollisionTube to detect upcoming collisions, like so:

Perhaps it’s a little hard to see, but the CollisionTube is placed around the character and extends out ahead of the character. The tube increases and decreases in length with the character’s velocity. So if the character is moving toward an obstacle (like the CollisionSphere in the screenshot) the tube will collide with the sphere before the character does. The character then uses the point and normal information from this collision to safely steer away from or around the sphere. You can see it in action if you download the code from the link above. It is modelled on this obstacle avoidance steering demo.

Panda’s CollisionTube is so perfect for this task that I don’t want to give it up. But there is a problem: in Panda, the CollisionTube can only be used as an “into” object in the collision system. This means that tubes can only collide with “from” objects: spheres, rays, lines and segments.

I have my characters running around on an island which is square in shape. I need to put some kind of collision wall around the borders of the island, so that the characters will steer to remain on the island in the same way that they steer to avoid obstacles represented by CollisionSpheres. Four CollisionPlanes or CollisionPolygons could represent this wall very well, but they cannot act as “from” objects in Panda so they don’t work. Panda seems to detect the tube-plane collision but is not able to construct the point and normal for the collision and prints out an error instead.

So how to keep the characters on the island? I’ve considered three possibilities so far:

  1. Place a lot of CollisionSpheres around the borders of the island to approximate a wall, like buoys. Since tubes can collide with spheres, the characters will be able to steer away from this wall. A wall made of spheres would look something like this:

This has the advantage of being quick and easy to do. The disadvantage is that it would use a very large number of spheres and might be very inefficient.

  1. Replace the characters CollisionTube with a wall of spheres (as above) approximating a CollisionTube. It could vary in length by adding or removing spheres. This sphere-tube would be able to collide with spheres, inverse spheres, tubes, polygons, planes, any “into” object in the panda collision system, as the sphere-tube itself would be a collection of “from” objects.

This seems like it might be worth doing as it affords a lot more freedom to represent obstacles for collision avoidance – you can now use tubes, planes and polygons as obstacles. But it would take some work from me to make a sphere-tube class, and I already feel like I’m running behind. Also I think it’d be a little awkward, especially compared to the perfect fit of the CollisionSphere.

A problem with both of these solutions is that a wall or tube constructed out of spheres is only approximately the right shape. As you can see from the image there are little bumps where one sphere goes in and the next goes out. I’m worried that this might cause erratic steering.

  1. Don’t use Panda’s collision system for keeping characters on the island at all, but instead fake it. To steer away from an obstacle a character needs a point on the surface of the obstacle where a predicted collision will occur and a surface normal on the obstacle at this point.

If the obstacle is a plane then the surface normal is the same everywhere.

So I could write a task that each frame looks for characters that are within a certain range of the edge of the island. If one is found, the task projects the characters forward direction onto the plane representing the island boundary to get a point of collision which is combined with the surface normal of the plane and passed to the character, the character steers away from the boundary exactly like it would steer to avoid any other obstacle.

Assuming projecting the forward direction to get the point on the plane would be easy (I don’t actually know how to do it, but think it should be simple, just computing a line-plane intersection) this approach might work nicely.

Edit: An alternative way to do this one would be to add a CollisionSegment to each character as well as the CollisionTube, and point it out ahead of the character like the tube. The segment can be used as a from object only to detect collisions with CollisionPlanes marking the boundaries of the island as into objects. The original CollisionTube can be used for colliding with spherical obstacles.

So, any comments or advice on the problem and/or the suggested solutions? Any better solutions?

Thanks in advance

P.S. I tried forcing my island to be circular in shape, so I could wrap an inverse sphere around it. But it turns out that tubes cannot collide with inverse spheres either: inverse spheres can be used as from objects only. I got the same error message from Panda as I got when I tried to use a Plane or a Tube as a from object:

P.P.S. I suppose a fourth solution would be to use CollisionSegments (or lines, or rays) to make walls surrounding the island. Several lines at different heights not too far apart ought to catch any character approaching the wall.

P.P.P.S. Lines don’t work very well either. They catch the collisions, but the wall they make is too thin. If the character doesn’t steer sharp enough (and they don’t) then as soon as the tube is on the other side of the line it is no longer in collision and the character passes through the wall. A plane that divides the space so that everything behind the plane is in collision is needed (or better steering).

Perhaps a stupid suggestion, but did you know you can stretch collisionspheres just over 1 axis? So can’t you just make a stretched collisionsphere?
Like this:

For the walls around the island you could use CollisionPlanes. If that doesnt work either, you can create your own CollisionSolid for that.
Or, you can just use good old Geometry, with transparent texture.

Thanks pro-rsoft, but I think scaling the nodepath that the collisionsphere is attached to like that just makes the sphere look stretched when rendered, it’s not actually stretching it as far as the collision detection system is concerned (I tested it and this seems to be the case).

I tried the thing with the CollisionSegment and the CollisionPlane, but again the characters seem able to pass through the wall and no longer be colliding with it. It seems that if both the start point and the end point of a CollisionSegment are behind a CollisionPlane then the segment is not in collision with the plane, despite what the manual says about CollisionPlane:

Oh boy. We def. need CollisionBox’es :slight_smile:

Try creating your own solids for the walls around the island. For more information how to do this, see the Collision Detection sample in your Panda3D folder, or click here.
Or, as I said before, just add the walls into your terrain model and make the walls invisible, by either doing model.find(’**/wall’).hide() or by giving them a transparent texture.

Most of your woes come from the fact that you’re trying to use a CollisionTube around your actively moving object, which means you have to reverse the sense of “from” and “into”, which in turn severely limits your choice of into objects.

Although a CollisionTube is such a perfect fit, probably it would be best to abandon this perfection since it leads to so many other problems. I think having an extrusion of CollisionSpheres, or really even just two spheres: one at the back and one at the front, would do the job just as well, and then everything else would just work the way it was supposed to.


Another crazy idea, for the walls: create your own kinda CollisionPlane.
Like this:

#call this in some task

Another thing: Wall of spheres is def. not a good idea. If I have 15 chars walking around, with 5 spheres each, that would be 75 spheres, which would be terribly slow. Maybe two or so, like drwr said, would be a good idea.

That’s quite a good idea pro-rsoft, by defining a task like that, I effectively get the behaviour that I wanted from the CollisionPlane (as quoted from the manual above).

Then if colliding=True I need to compute the point of collision and surface normal to get the character to steer away from the boundary.

To do that I could employ a CollisionPlane as the bounday and a CollisionLine (extends infinitely in both directions) attached to the character…

Ultimately drwr is probably right though, we should not abuse the poor CollisionTube and use a Sphere instead, then we’ll avoid messes like this in future.

Actually I think I got the approach to keeping steering characters inside a volume fundamentally wrong. I was trying to setup obstacles surrounding the characters and use generalised collision avoidance (otherwise called “containment”) to keep the characters within the obstacles, as in this demo:

But in that demo, the obstacles don’t completely surround the vehicles, and I’m not sure but I don’t think containment steering is meant to deal with that case.

I’ve been looking at the source code for opensteer, specifically their boids demo. The boids stay within a spherical boundary, and the way they do it is with an additional “Stay within boundary” steering behaviour that has maximum priority. If the “stay within boundary” function returns False for a given frame then another steering function is given a chance (as I already do with obstacle avoidance).

If the boid is outside the boundary though, then the function computes the steering force required to seek towards the centre of the boundary sphere, then takes the perpendicular component of that vector projected onto the forward direction of the boid and applies to as the steering force. The result is that whenever the boid leaves the boundary it turns back towards the boundary and on entering it again returns to its normal steering behaviour.

This approach could be used with any boundary shape that has a defined centre: sphere, cube, whatever, just implement a getCentre() and a isOutside() function.

I think perhaps this is what I will try, it seems a smarter approach, but I’m going to take a break to think about it.