BOID/Vehicle Code

Two-dimensional Reynolds-style BOIDS and steering behaviours.


vehicle2d.py

steering.py

Module containing PyBOID steering behavior functions.

Each type of behaviour needs a force_foo() function to compute the actual steering force. The first argument (“owner”) is the vehicle that is being steered. Any number of additional arguments are permitted. This allows the SteeringBehaviour.compute_force functions to automatically call each active behaviour’s force_foo() function with appropriate arguments.

Each behaviour also needs a activate_foo() function. The first argument (“steering”) is an instance of SteeringBehaviour owned by the given vehicle; additional arguments are intended to be stored within the SteeringBehaviour instance and then passed to the corresponding force_foo() each update. See the SEEK code for a simple example.

Many behaviours use constant values, imported from steering_constants.py and assigned to local constants within this module. These are chosen based on the the physics defaults (also in steering_constants) to give reasonable results, but can be overridden with steering.FOO_CONSTANT = new_value. See the sheepdog demo for an example of this.

Importing this module will automatically generate a BEHAVIOUR_LIST containing all behaviours that follow the conventions above. This makes it very easy to add additional behaviours with minimal changes to the existing code (besides writing the force/activate functions, SteeringBehaviour.PRIORITY_LIST would need to be modified with any new behaviours if we use budgeted force).

TODO: set/pause/resume/stop behaviour functions always call set_priorities() regardless of whether budgeted force is actually used. Since we’re currently using budgeted force all the time, this issue is pretty much unimportant.

TODO: Updates to self.flocking are handled through set_priorities(), which is a sensible thing, since set_priorities() is the function that gets called when there is any kind of behaviour change. Make up our minds whether this is truly the right approach and change documentation appropriately.

steering.force_seek(owner, target)

Steering force for SEEK behaviour.

This is a simple behaviour that directs the owner towards a given point. Other, more complex behaviours make use of this.

Parameters:
  • owner (SimpleVehicle2d) – The vehicle computing this force.
  • position (Point2d) – The target point that owner is seeking to.
steering.activate_seek(steering, target)

Activate SEEK behaviour.

steering.force_flee(owner, target, panic_squared=inf)

Steering force for FLEE behaviour.

Another simple behaviour that directs the owner away from a given point.

Parameters:
  • owner (SimpleVehicle2d) – The vehicle computing this force.
  • position (Point2d) – The target point that owner is fleeing from.
  • panic_squared (float) – If specified, only compute a flee_force if squared distance to the target is less than this value.
steering.activate_flee(steering, target)

Activate FLEE behaviour.

steering.force_arrive(owner, target, hesitance=2.0)

Steering force for ARRIVE behaviour.

This works like SEEK, except the vehicle gradually deccelerates as it nears the target position. The optional third parameter controls the amount of decceleration.

Parameters:
  • owner (SimpleVehicle2d) – The vehicle computing this force.
  • position (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.
steering.activate_arrive(steering, target)

Activate ARRIVE behaviour.

steering.force_pursue(owner, prey)

Steering force for PURSUE behaviour.

Similar to SEEK, but lead the prey by estimating its future location, based on current velocities.

Parameters:
  • owner (SimpleVehicle2d) – The vehicle computing this force.
  • prey (BasePointMass2d) – The vehicle that owner will pursue.
steering.activate_pursue(steering, prey)

Activate PURSUE behaviour.

steering.force_evade(owner, predator)

Steering force for EVADE behaviour.

Similar to FLEE, but try to get away from the predicted future position of the predator. Predators far away are ignored, EVADE_PANIC_SQ is used to control the panic distance passed to FLEE.

Parameters:
  • owner (SimpleVehicle2d) – The vehicle computing this force.
  • predator (BasePointMass2d) – The vehicle that owner will pursue.
steering.activate_evade(steering, predator)

Activate EVADE behaviour.

steering.force_wander(owner, steering)

Steering force for WANDER behavior.

Parameters:
  • owner (SimpleVehicle2d) – The vehicle computing this force
  • steering (SteeringBehavior) – An instance of SteeringBehavior (not a vehicle, see note below)

Note

WANDER requires persistant data (specifically, the target of the wander circle), so we need access to the SteeringBehavior itself instead of the vehicle that owns it.

steering.activate_wander(steering, target)

Activate WANDER behaviour.

steering.force_avoid(owner, obs_list)

Steering force for AVOID stationary obstacles behaviour.

This projects a box in front of the owner and tries to find an obstacle for which collision is imminent (not always the closest obstacle). The owner will attempt to steer around that obstacle.

Parameters:
  • owner (SimpleVehicle2d) – The vehicle computing this force.
  • obs_list (list of BasePointMass2d) – List of obstacles to check for avoidance.
steering.activate_avoid(steering, target)

Activate AVOID behaviour.

steering.force_takecover(owner, target, obs_list, max_range, stalk=False)

Steering force for TAKECOVER behind obstacle.

Owner attempts to move to the nearest position that will put an obstacle between itself and the target. If no such points are within max_range, EVADE the predator instead.

By setting stalk to True, we’ll only hide when in front of the target. Stalking allows this vehicle to (somewhat clumsily) sneak up from behind.

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 to check for avoidance.
  • max_range (float) – Obstacles further than this value are ignored.
  • stalk (boolean) – If True, only hide when we are in front of the target.
steering.activate_takecover(steering, target)

Activate TAKECOVER behaviour.

steering.force_wallavoid(owner, whisk_units, whisk_lens, wall_list)

Steering force for WALLAVOID behaviour with aribtrary whiskers.

For each whisker, we find the wall with 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. Total force is the resultant vector sum.

Parameters:
  • owner (SimpleVehicle2d) – The vehicle computing this force.
  • whisk_units (list of Point2d or 2-tuple) – Whisker UNIT vectors in owner’s local coordinates (forward is x+).
  • whisk_lens (list of positive int or float) – Lengths of whiskers, in same order as whisk_units above.
  • wall_list (list of BaseWall2d) – Walls to test for avoidance.
steering.activate_wallavoid(steering, info)

Activate WALLAVOID behaviour.

Note

Whisker angles are assumed at 45 degrees; scale is set by steering constants.

steering.force_guard(owner, guard_this, guard_from, aggro)

Steering force for GUARD behavior.

Parameters:
  • owner (SimpleVehicle2d) – The vehicle computing this force.
  • guard_this (BasePointMass2d) – The target that owner is guarding.
  • guard_from (BasePointMass2d) – The target that owner is guarding against.
  • aggro (float) – Value from 0 to 1; controls aggressiveness (see notes below)

Notes

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

The formula is the standard parameterization of a line segment, so we can actually set aggro outside of the unit interval.

steering.activate_guard(steering, target)

Activate GUARD behaviour.

steering.force_follow(owner, leader, offset)

Steering force for FOLLOW the leader at some offset.

Parameters:
  • owner (SimpleVehicle2d) – The vehicle computing this force.
  • leader (BasePointMass2d) – The lead vehicle that the owner is following.
  • offset (Point2d) – Offset from leader (in leader’s local coordinates, front = +x)
steering.activate_follow(steering, target)

Activate FOLLOW behaviour.

steering.force_brake(owner, decay=0.5)

Steering force oppoisite of current forward velocity.

Parameters:
  • owner (SimpleVehicle2d) – The vehicle computing this force.
  • decay (float) – Discrete exponential decay constant for speed; 0 < decay < 1.
steering.activate_brake(steering, target)

Activate BRAKE behaviour.

class steering.WaypointPath(waypoints, is_cyclic=False)

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

Parameters:
  • waypoints (list of Point2d) – Non-empty list of waypoints on this path.
  • is_cyclic (boolean) – If set to True, path will automatically cycle. See notes below.

Notes

Instances of WaypointPath should be owned by a SteeringBehaviour, but all path-management code is controlled from within this class.

When using this for vehicle steering, the first waypoint is intended as the starting point of some owner vehicle. The vehicle will not automatically return to this point even if is_cyclic is set to True, so add it manually to the end of waypoints if a return trip is needed.

TODO: It may be helpful to rewrite this class as a generator.

reset_from_position(start_pos, do_return=False)

Reset the next waypoint to the start of this path.

Parameters:
  • start_pos (Point2d) – The new starting point for the path
  • do_return (boolean) – If set to True, start_pos becomes the final waypoint. See Notes.

Notes

As with the __init__() method, start_pos is intended as the current location of some vehicle, and is not explicitly added as the first waypoint. If the path was previously cyclic, we will not return to start_pos by default (but the previous waypoints will still continue to cycle). To override this, set do_return=True. However, start_pos will not be added explicitly is it is within the threshold given by PATH_EPSILON_SQ, because it is close enough to an actual waypoint.

advance()

Update our waypoint to the next one in the path.

Notes

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.

Notes

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

steering.force_waypathtraverse(owner, waypath)

Steering force for WAYPATHTRAVERSE behaviour.

Parameters:
  • owner (SimpleVehicle2d) – The vehicle computing this force.
  • waypath (WaypointPath) – Path to be followed by the owner

Notes

This is the simple version; we merely head towards the next waypoint. If there is only one waypoint left, we ARRIVE at it. Otherwise, we SEEK.

steering.activate_waypathtraverse(steering, waypath)

Activate WAYPATHTRAVERSE behaviour.

steering.force_waypathresume(owner, waypath, invk)

Steering force for WAYPATHRESUME behaviour.

Parameters:
  • owner (SimpleVehicle2d) – The vehicle computing this force.
  • waypath (WaypointPath) – Path to be followed by the owner
  • invk (positive float) – Reciprocal of exponential decay constant. See Notes.

Notes

If the vehicle is off course, this will give a balance between returning directly to the current path edge and progressing to the next waypoint.

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 actually pass through the waypoint, it makes computations very quick, especially since we store invk. Smaller values of invk imply a larger decay rate, and give more immediate return to the path.

steering.activate_waypathresume(steering, target)

Activate WAYPATHRESUME behaviour.

steering.force_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 (Non-negative float) – Time between steering updates.
steering.activate_flowfollow(steering, target)

Activate FLOWFOLLOW behaviour.

steering.force_separate(owner)

Steering force for SEPARATE group behaviour (flocking).

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

Notes

Flocking forces use owner.neighbor_list to decide which vehicles to flock with; update this list before calling this function.

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

steering.activate_separate(steering, n_list)

Activate SEPARATE behaviour.

steering.force_align(owner)

Steering force for ALIGN group behaviour (flocking).

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

Notes

Flocking forces use owner.neighbor_list to decide which vehicles to flock with; update this list before calling this function.

Unlike(?) traditional boids, we ALIGN with the average of neighbors’ velocity vectors. Align with heading (normalize velocity) looked weird.

steering.activate_align(steering, n_list)

Activate ALIGN behaviour.

steering.force_cohesion(owner)

Steering force for COHESION group behaviour.

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

Notes

Flocking forces use owner.neighbor_list to decide which vehicles to flock with; update this list before calling this function.

steering.activate_cohesion(steering, n_list)

Activate COHESION behaviour.

steering.FLOCKING_LIST = [u'SEPARATE', u'ALIGN', u'COHESION']

Flocking behaviours need additional set/pause/resume/stop checking.

steering.force_fnc(owner, guard_this, guard_from, aggro)

Steering force for GUARD behavior.

Parameters:
  • owner (SimpleVehicle2d) – The vehicle computing this force.
  • guard_this (BasePointMass2d) – The target that owner is guarding.
  • guard_from (BasePointMass2d) – The target that owner is guarding against.
  • aggro (float) – Value from 0 to 1; controls aggressiveness (see notes below)

Notes

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

The formula is the standard parameterization of a line segment, so we can actually set aggro outside of the unit interval.

steering.activate_fnc(steering, target)

Activate GUARD behaviour.

class steering.SteeringBehavior(vehicle, use_budget=True)

Helper class for managing a vehicle’s autonomous steering.

Each vehicle should maintain a reference to an instance of this class, and call the compute_force() method when an update is needed.

Parameters:
  • vehicle (SimpleVehicle2d) – The vehicle that will be steered by this instance
  • use_budget (boolean) – Default (True) uses vehicle’s maxforce as a force budget per update. If set to False, all active behaviors are consdidered each update.
set_target(**kwargs)

Initializes one or more steering behaviours.

Parameters:
  • SEEK ((float, float), optional) – If given, the vehicle will begin SEEKing towards this point.
  • FLEE ((float, float), optional) – If given, the vehicle will begin FLEEing towards this point.
  • ARRIVE ((float, float), optional) – If given, the vehicle will begin ARRIVEing towards this point.
  • PURSUE (BasePointMass2d, optional) – If given, the vehicle will begin PURSUEing the prey.
  • EVADE (BasePointMass2d, optional) – If given, the vehicle will begin EVADEing the predator
  • TAKECOVER (BasePointMass2d, optional) – If given, the vehicle will try to TAKECOVER from the predator.
  • WANDER (tuple of int or float, optional) – (Distance, Radius, Jitter) for WANDER behaviour
  • AVOID (tuple of BasePointMass2d, optional) – Tuple (iterable ok?) of obstacles to be avoided.
  • WALLAVOID (tuple of BaseWall2d, optional) – List of walls to be avoided
  • GUARD ((BasePointMass2d, BasePointMass2d, float), optional) – (GuardTarget, GuardFrom, AggressivePercent)
  • WAYPATHTRAVERSE ((WaypointPath), optional) – List of waypoints for WAYPATHTRAVERSE behaviour.
  • WAYPATHRESUME ((WaypointPath, invk), optional) – List of waypoints and inverse of decay constant for PATHRESUME.
  • FLOWFOLLOW ((vel_field, dt), optional) – Callable vel_field function and time increment
  • FOLLOW ((BasePointMass2d, Point2d), optional) – (Leader, OffsetFromLeader)
  • SEPARATE (List of BasePointMass2d, optional) – List of targets to flock with
  • ALIGN (List of BasePointMass2d, optional) – List of targets to flock with
  • COHESION (List of BasePointMass2d, optional) – List of targets to flock with
  • BRAKE (float, optional) – Speed decay factor (0 < decay < 1)
Returns:

Each item True/False according to whether initialization succeeded.

Return type:

list of boolean (or single boolean)

Notes

Flocking behaviours (SEPARATE, ALIGN, COHESION) automatically set self.flocking to True; this is used by force_foo functions so that neighbors need only be tagged once per cycle (for efficiency).

pause(steering_type)

Temporarilily turns off a steering behaviour, storing targets for later.

Parameters:steering_type (string) – Name of the behaviour to be paused.
Returns:True if the pause was successful, False otherwise.
Return type:boolean
resume(steering_type)

Turns on a previously paused behaviour, using old targets.

Parameters:steering_type (string) – Name of the behaviour to be resumed.
Returns:True if the resume was successful, False otherwise.
Return type:boolean
stop(steering_type)

Permanently turns off a steering behaviour until re-initialized.

Parameters:steering_type (string) – Name of the behaviour to be stopped.
Returns:True if the stop was successful, False otherwise.
Return type:boolean
update_flocking_status()

Sets or clears flocking status based on currently-active behaviours.

flag_neighbor_vehicles(vehlist=[])

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

Parameters:vehlist (List of BasePointMass2d) – List of vehicles to be checked against. See Notes below.

Notes

This function checks other vehicles based on their distance to owner and includes only vehicles in front of the owner. Maximum distance is the owner’s radius times FLOCKING_RADIUS_MULTIPLIER. We may consider more sophisticated sensing of neighbors in the future.

Any pre-processing (such as 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 of flagging are stored as owner.neighbor_list to be read later by force_foo functions (mostly flocking) that require neighbor information. Run this function before any such force_foo functions).

compute_force_simple()

Compute steering force using all currently-active behaviors.

Returns:Point2d
Return type:Steering force.

Note

This considers all active behaviours, but will still limit the final force vector’s magnitude to the owner’s maxforce.

set_priorities()

Create a prioritized list of steering behaviours for later use.

compute_force_budgeted()

Find prioritized steering force within the vehicle’s budget.

Returns:Point2d
Return type:Steering force.

steering_constants.py

Default values of constants for vehicles and steering behaviours.

steering_constants.POINTMASS2D_MASS = 1.0

Vehicle mass for rectilinear motion.

steering_constants.POINTMASS2D_MAXSPEED = 5.0

Maximum vehicle speed per rectilinear motion update.

steering_constants.POINTMASS2D_MAXFORCE = 3.5

Maximum force/budget per rectilinear motion update.

steering_constants.RIGIDBODY2D_INERTIA = 1.0

Rotational Inertia for rigid-body physics

steering_constants.RIGIDBODY2D_MAXOMEGA = 90.0

Maximum angular velocity per rigid-body update.

steering_constants.RIGIDBODY2D_MAXTORQUE = 75.0

Maximum torque per rigid-body update.

steering_constants.FLEE_PANIC_SQ = inf

Used by FLEE; ignore the point if it’s too far away

steering_constants.ARRIVE_DECEL_TWEAK = 10.0

This contols the gradual deceleration for ARRIVE behavior. Larger values will cause more gradual deceleration.

steering_constants.EVADE_PANIC_SQ = 25600

Used by EVADE; we ignore the predator if it is too far away.

steering_constants.AVOID_MIN_LENGTH = 25

This controls the size of an object detection box for AVOID obstacles. Length in front of vehicle is 100%-200% of this.

steering_constants.AVOID_BRAKE_WEIGHT = 2.0

Tweaking constant for braking force of AVOID obstacles.

steering_constants.WALLAVOID_WHISKER_SCALE = 0.8

WALLAVOID – Proportional length of side whiskers relative to front whisker.

steering_constants.TAKECOVER_STALK_T = 0.1

TAKECOVER – For stalking, set this to cos^2(theta), where theta is the max angle from target’s front vector. The stalker will not hide unless within this angle of view.

steering_constants.FOLLOW_ARRIVE_HESITANCE = 1.5

FOLLOW the leader uses ARRIVE with this hesitance, for smooth formations.

steering_constants.PATH_EPSILON_SQ = 100.0

SteeringPath will treat consecutive waypoints that are closer than this as duplicates, and remove them from the path.

steering_constants.WAYPOINT_TOLERANCE_SQ = 100.0

Used by PATHFOLLOW/RESUME to determine when we’re close enough to a waypoint.

steering_constants.PATHRESUME_DECAY = 0.075

Exponential decay constant for PATHRESUME.

steering_constants.FLOCKING_RADIUS_MULTIPLIER = 2.0

For simplicity, we multiply the vehicle’s bounding radius by this constant to determine the local neighborhood radius for group behaviours.

steering_constants.FLOCKING_SEPARATE_SCALE = 1.2

Scaling factor for SEPERATE group behaviour. Larger values give greater seperation force.

steering_constants.FLOCKING_COHESHION_HESITANCE = 3.5

Cohesion uses ARRIVE with this hesitance, for smooth flocking.

steering_constants.BASEPOINTMASS2D_DEFAULTS = {'MAXSPEED': 5.0, 'MASS': 1.0, 'MAXFORCE': 3.5}

Point-Mass physics defaults.

steering_constants.SIMPLERIGIDBODY2D_DEFAULTS = {'MAXTORQUE': 75.0, 'INERTIA': 1.0, 'MAXOMEGA': 90.0}

Additional Rigid Body physics defaults.

steering_constants.STEERING_DEFAULTS = {'TAKECOVER_STALK_T': 0.1, 'WAYPOINT_TOLERANCE_SQ': 100.0, 'PATH_EPSILON_SQ': 100.0, 'FLOCKING_COHESHION_HESITANCE': 3.5, 'FLOCKING_RADIUS_MULTIPLIER': 2.0, 'EVADE_PANIC_SQ': 25600, 'AVOID_MIN_LENGTH': 25, 'FLOCKING_SEPARATE_SCALE': 1.2, 'PATHRESUME_DECAY': 0.075, 'ARRIVE_DECEL_TWEAK': 10.0, 'FLEE_PANIC_SQ': inf, 'FOLLOW_ARRIVE_HESITANCE': 1.5, 'WALLAVOID_WHISKER_SCALE': 0.8, 'AVOID_BRAKE_WEIGHT': 2.0}

Defaults for Steering Behaviours.