Autonomous Steering Behaviours and Navigator

Module containing AiBoid steering behavior functions (and Navigator).

Many behaviours use constant values, imported from steering_constants.py as the STEERING_DEFAULTS dictionary. These are chosen based on the physics defaults (also in steering_constants) to give reasonable results.

Todo

Document keyword arguments; these are usually for overriding default values from steering.constants.py.

Todo

See Navigator.update_neighbors() docstring notes for possible updates.

class steering.SteeringBehaviour(owner)

Abstract Base Class for all steering behaviours.

Parameters

owner (vehicle) – Steering is computed for this vehicle.

Subclass __init__() methods should first call:

SteeringBehaviour.__init__(self, owner)

Where owner is the vehicle that will use the behaviour. This sets the owner locally (and may do other things in the future).

Subclasses must implement the force() method with self as the only argument; this is used by the Navigator class for updates.

See the Seek class for a simple example.

class steering.Seek(owner, target)

SEEK towards a fixed point at maximum speed.

Parameters
  • owner (SimpleVehicle2d) – The vehicle computing this force.

  • target (Point2d) – The point to SEEK towards.

Note

Use ARRIVE for a more graceful approach and to prevent jittering.

class steering.Flee(owner, target, panic_dist=inf)

FLEE from a fixed point at maximum speed.

Parameters
  • owner (SimpleVehicle2d) – The vehicle computing this force.

  • target (Point2d) – The point to FLEE from.

  • panic_dist (float, optional) – If specified, FLEE only when the distance to the target is less than this value.

class steering.Arrive(owner, target, hesitance=2.0, *, decel_tweak=10.0)

Gracefully ARRIVE at a target point.

Parameters
  • owner (SimpleVehicle2d) – The vehicle computing this force.

  • target (Point2d) – The target point that owner is to arrive at.

  • hesistance (float) – Controls the time it takes to deccelerate; higher values give more gradual (and slow) decceleration. Suggested values are 1.0 - 10.0; default is 2.0.

This works like SEEK, except the vehicle gradually deccelerates as it nears the target position.

Todo

Stability analysis suggested that hesistance should be set above a ceratin threshold, based on the owner’s mass. Implement this as the default value and/or scale based on this threshold.

class steering.Wander(owner, distance=30.0, radius=25.0, jitter=15.0)

Pseudo-randomly WANDER about.

Parameters
  • owner (SimpleVehicle2d) – The vehicle computing this force.

  • distance (float) – Distance to center of WANDER circle; see below.

  • radius (float) – Radius of the WANDER circle; see below.

  • jitter (float) – Maximum jitter magnitude; see below.

WANDER projects an imaginary circle in directly front of the owner and will SEEK towards a randomly-moving target on that circle. The center and radius of this circle are determined by distance and radius.

We displace the target point each update by a random vector (whose size is limited by jitter) and rescale so the target remains on our circle.

Todo

Give each instance its own random number generator for jitter; this will make testing more reliable.

class steering.SideSlip(owner, forward_dir, left_slip, max_slope, start_prop=0.01)

Steering force for SIDESLIP behaviour (experimental).

Parameters
  • owner (SimpleVehicle2d) – The vehicle computing this force.

  • forward_dir (Point2d) – Intended forward direction of travel.

  • left_slip (float) – Horizontal slip distance; left = posistive.

  • max_slope (positive float) – Maximum desired slope during slip.

  • start_prop (positive float) – Proportion of total progress; see below.

The vehicle aims towards a given direction (forward_dir) along a line that is parallel to the one through its current position; (left_slip) gives the distance between these lines. Negative values will shift right.

This is accomplished using a logistic curve satisfying y’ = ry(k-y) where y is in the direction orthogonal to forward_dir; this is an S-curve that will be asymptotic to the shifted (final) line of travel. (max_slope) controls the severity of the slip (on the ideal S-curve, it is the slope at the halfway point y=k/2). Because y=0 is a fixed point, we assume that we’ve started at some positive proportion (start_prop) of the total y range so that the math behaves. However, the horizontal distance from our current position to the final line of travel will still be abs(left_slip); this behaviour makes the necessary adjustments automatically.

class steering.ObstacleAvoid(owner, obstacle_list, *, min_length=35.0, brake_weight=0.01)

AVOID stationary obstacles by steering around them.

Parameters

This looks for an obstacle in front of the owner for which collision is most imminent (not always the closest obstacle). If such an obstacle is found, steer around it using a combination of lateral and braking forces.

class steering.ObstacleSkim(owner, obstacle_list, *, react_time=200.0)

SKIM off of a stationary obstacle by steering along a tangent path.

Parameters

Uses the same detection method as ObstacleAvoid, but attempts to steer on a course that is tangent to the most imminent obstacle. Still experimental.

class steering.TakeCover(owner, target, obstacle_list, max_range, *, hesitance=1.0, evade_mult=1.5, stalk=False, stalk_dsq=90000.0, stalk_cos=- 0.5, stalk_prox=2.0)

TAKECOVER from another target vehicle behind a nearby obstacle.

Parameters
  • owner (SimpleVehicle2d) – The vehicle computing this force.

  • target (BasePointMass2d) – The vehicle we try to hide from.

  • obs_list (list of BasePointMass2d) – List of obstacles for hiding.

  • max_range (float) – Hiding points further than this value are ignored.

Keyword Arguments
  • hesitance (positive float) – Hesitance for arriving at the hiding spot.

  • evade_mult (float) – Used to compute EVADE panic distance; see below.

  • stalk (boolean) – If True, only hide when we’re detectable; see below.

  • stalk_dsq (positive float) – See stalk description below.

  • stalk_cos (float) – See stalk description below.

Owner attempts to find a spot that puts an obstacle between itself and the target vehicle, then ARRIVEs at that position. If a spot cannot be found nearby (within max_range of the owner), EVADE the target instead, using a panic distance of evade_mult*max_range.

If stalk is set to True, the owner takes cover only when close enough to the target and within a maximum angle of the target’s front vector. These values are controlled by STALK_DSQ and STALK_COS, respectively.

class steering.WallAvoid(owner, whisker_list, wall_list=None)

WALLAVOID behaviour using simulated whiskers.

Parameters
  • owner (SimpleVehicle2d) – The vehicle computing this force.

  • whikser_list (list of Point2d) – Whisker vectors in owner local space.

  • wall_list (list, BaseWall2d) – List of walls to test against.

For each whisker, we find the wall having its point of intersection closest to the base of the whisker. If such a wall is detected, it contributes a force in the direction of the wall normal, proportional to the penetration depth of the whisker.

class steering.Pursue(owner, prey, pcos=0.966, pdist=100.0)

PURSUE a moving object; the opposite of EVADE.

Parameters
  • owner (SimpleVehicle2d) – The vehicle computing this force.

  • prey (BasePointMass2d) – The object we’re pursuing.

  • pcos (float, optional) – Cosine of the pounce angle, see below.

  • pdist (float, optional) – Pounce distance, see below.

If the prey is heading our way (we are within a certain angle of the prey’s heading) and within a certain distance, simply “pounce” on the prey by SEEKing to its current position. Otherwise, predict the future position of they prey, based on current velocities, and SEEK to that location.

class steering.Follow(owner, leader, offset, hesitance=1.5)

Attempt to FOLLOW a leader at some fixed offset.

Parameters
  • owner (SimpleVehicle2d) – The vehicle computing this force.

  • leader (BasePointMass2d) – The object to be followed.

  • offset (Point2d) – Offset vector from leader; see below.

  • (positive float (hesitance) – Hesistance for ARRIVE; see below.

  • optional – Hesistance for ARRIVE; see below.

This vehicle will attempt to predict the future position of its leader, and uses ARRIVE-style behaviour to steer towards a given offset (given in the leader’s local coordinate space).

class steering.Evade(owner, predator, panic_dist=160.0)

EVADE a moving object; the opposite of PURSUE.

Parameters
  • owner (SimpleVehicle2d) – The vehicle computing this force.

  • predator (BasePointMass2d) – The object we’re evading.

  • panic_dist (float) – Ignore the predator beyond this distance.

class steering.Guard(owner, guard_this, guard_from, aggro=0.5, *, hesitance=1.0)

GUARD one object from another by moving between them.

Parameters
  • owner (SimpleVehicle2d) – The vehicle computing this force.

  • guard_this (BasePointMass2d) – The object to be guarded.

  • guard_from (BasePointMass2d) – The object to guard against.

  • aggro (float) – Value from 0 to 1 for aggressiveness; see below.

This is a more general version of INTERPOSE. The vehicle attempts to keep itself between guard_this and guard_from, at a relative distance controlled by aggro. An aggro near zero will position close to guard_this; near 1.0 will position close to guard_from.

Note

This allows setting aggro outside of the interval [0,1]; as the formula for computing position is the standard parameterization of the line segment from guard_this to guard_from.

class steering.Brake(owner, decay=0.5)

Steering force opposite of current forward velocity.

Parameters
  • owner (SimpleVehicle2d) – The vehicle computing this force.

  • decay (float) – Discrete exponential decay constant; 0 < decay < 1.

Warning

Haven’t sufficiently tested this. Values of decay close to 0 should give more gradual braking; close to 1 should be more severe. The actual performance may depend on delta_t for time.

Todo

Once tested, add a BRAKE_DECAY constant to steering.defaults.

class steering.WaypointPath(waypoints, is_cyclic=False, *, epsilon_sq=100.0, waypt_radius=10.0)

Helper class for managing path-related behaviour, using waypoints.

Parameters
  • waypoints (list of Point2d) – At least two waypoints; see below.

  • is_cyclic (boolean) – If True, path will automatically cycle, returning to waypoints[1] after the last waypoint.

When used for vehicle steering, an instance of this class should be owned by a vehicle Navigator instance, but all path-management code is controlled from here.

waypoints[0] is intended as the starting point of the owner vehicle, and is ignored when the path is reset or cycled. To include this point in a cyclic path (for example, a patrol route that returns to to start), add the starting point as the last element of waypoints.

The waypoints list is pre-processed so that any point that is close to its predecessor (measured by _EPSILON_SQ) is ignored. If all points are close, we raise a ValueError.

Raises

ValueError – If waypoints has fewer than two entries, or all points are close together (as described above).

restart_from(new_start_pos, start_index=0)

Explicitly set the next waypoint and follow the path from there.

Parameters
  • start_pos (Point2d) – Starting location for path following.

  • start_index (positive int) – Index of the next waypoint.

As with __init__(), start_pos is intended as the current location of some vehicle, and is not explicitly added to the waypoint list.

Note

If start_pos is close (measured by _EPSILON_SQ) to the given waypoint, we ignore that waypoint and start from the next one.

resume_at_nearest_from(from_pos, ignore_visited=True)

Find the closest waypoint and follow the path from there; see notes.

Parameters
  • from_pos (Point2d) – The new starting point for the path.

  • ignore_visited (boolean) – If True (default), only consider those waypoints not yet visited when finding the closest. Has no effect on cyclic paths.

advance()

Update our waypoint to the next one in the path.

Note

When we advance() from the last waypoint in a non-cyclic path, the value of self.newway is set to None. This can be used elsewhere??

num_left()

Returns the number of waypoints remaining in this path.

Note

For cyclic paths, we always return the total number of waypoints, regardless of where we are in the list.

class steering.WaypathVisit(owner, waypath, wayradius=None)

Visit a series of waypoints in order.

Parameters
  • owner (SimpleVehicle2d) – The vehicle computing this force.

  • waypath (WaypointPath) – The path to be followed.

  • wayradius (float, optional) – Waypoint radius; see below.

A waypoint is visited once the distance to owner is less than wayradius. In this version; we steer directly at the next waypoint, even if we are knocked off-course. See WayPathResume for an alternative.

For the final waypoint, we ARRIVE at it and stay there as long as this behaviour remains active. For previous waypoints, we use SEEK.

class steering.WaypathResume(owner, waypath, expk=0.075, wayradius=None, *, decel_tweak=10.0)

Visit waypoints in order, trying to stay close to the path between them.

Parameters
  • owner (SimpleVehicle2d) – The vehicle computing this force.

  • waypath (WaypointPath) – The path to be followed.

  • expk (positive float) – Exponential decay constant; see below.

  • wayradius (float) – Waypoint radius; see below.

If the vehicle is knocked off-course, this will give a balance between returning directly to the current path edge and progressing towards the next waypoint; this is controlled by expk. Larger values give a more immediate return to the path.

Notes

If the vehicle has already overshot the next waypoint, we head directly to that waypoint, ignoring the path. Otherwise, follow an exponential decay curve asymptotic to the path; although this curve doesn’t truly pass through the waypoint, it makes computations very quick, especially since we store invk. Since, a waypoint is visited once the we’re withint wayradius, we don’t need to hit the waypoint exactly.

class steering.FlowFollow(owner, vel_field, dt=1.0)

Steering force for FLOWFOLLOW behaviour.

Parameters
  • owner (SimpleVehicle2d) – The vehicle computing this force.

  • vel_field (function Point2d(Point2d)) – A velocity vector field; owner will attempt to follow this.

  • dt (positive float) – Time between steering updates.

Warning

Still experimental. We should probably allow for a variable time step.

class steering.FlockSeparate(owner, scale=2.1)

SEPARATE from our neighbors to avoid collisions (flocking).

Parameters

owner (SimpleVehicle2d) – The vehicle computing this force.

Notes

For each neighbor, include a force away from that neighbor of magnitude proportional to the neighbor radius and inversely proprotional to distance. This gave nicer results and allows us to cleverly avoid computing a sqrt.

class steering.FlockAlign(owner)

ALIGN with our neighbors by matching their average velocity (flocking).

Parameters

owner (SimpleVehicle2d) – The vehicle computing this force.

class steering.FlockCohesion(owner, hesitance=3.5)

COHESION with our neighbors towards their average position (flocking).

Parameters

owner (SimpleVehicle2d) – The vehicle computing this force.

class steering.Navigator(vehicle, use_budget=True)

Helper class for managing steering behaviours.

Parameters
  • vehicle (SimpleVehicle2d) – The vehicle to be steered.

  • use_budget (boolean) – If True (default), use prioritized budgeted force, with budget set to vehicle.maxforce

set_steering(behaviour, *args, **kwargs)

Add a new steering behaviour or change existing targets.

Parameters

behaviour (string) – Name of the behaviour to be added/modified.

Additional positional and keyword arguments are passed as-is to the __init__() method of the given behaviour.

pause_steering(behaviour)

Temporarilily turns off a steering behaviour, to be resumed later.

Parameters

behaviour (string) – Name of the behaviour to be paused.

resume_steering(behaviour)

Reactivates a previously paused behaviour with prior targets.

Parameters

behaviour (string) – Name of the behaviour to be resumed.

update(delta_t=1.0)

Update neighbors (if needed) and set our new steering force.

Parameters

delta_t (float, optional) – Time since last update; currently unused.

compute_force_simple()

Compute/update steering force using all active behaviors.

Note

Since the BasePoint classes are expected to limit the maximum force applied, this function no longer does so.

compute_force_budgeted()

Compute/update prioritized steering force within a given budget.

sort_budget_priorities()

Sort our currently-active behaviours by priority; see below.

This is intended to work with budgeted force. When a new behaviour is added, the Navigator’s force_update method is set to this function, and the new list of behaviours gets sorted just before the actual update. One can also call this manually to immediately perform the sort. In any case, the force_update is then reset to compute_force_budgeted.

update_neighbors(vehlist, *, radius_scale=5.0)

Populates a list of nearby vehicles, for use with flocking.

Parameters
  • vehlist (List of BasePointMass2d) – Objects to check; see notes.

  • radius_scale (positive float) – Check for neighbors within the owner’s radius times this value.

Notes

This function checks object based on their distance to owner and includes only those in front of the owner. Maximum distance is the owner’s radius times radius_scale. We may use more sophisticated sensing of neighbors in the future.

Any pre-processing (spatial partitioning, sensory perception, or flocking with certain vehicles only) should be done before calling this function; with those results passed in as vehlist.

Results are stored as owner.neighbor_list to be read later by any steering force() functions (mostly flocking) that require neighbor information. Run this function before each steering update.