Table of content:
Built-in Behaviors
AccelerationBehavior
AlignmentBehavior
BoundaryMirrorBehavior
BoundaryWrapBehavior
BoundaryRepulsionBehavior
CohesionBehavior
CopyBehavior
DampingBehavior
EvasionBehavior
GridAvgBehavior
RandomizeBehavior
ResetBehavior
SpiralBehavior
SplineFollowBehavior
DistanceFieldFollowBehavior
EulerIntegration
Circular Behavior
ConeVisionBehavior
NeighborStoreBehavior
NeighborIndexStoreBehavior
NeighborDistanceStoreBehavior
NeighborDirectionStoreBehavior
ParameterCombineBehavior
ParameterMapBehavior
ParameterPrintBehavior
ParameterScaleBehavior
How to create your own behavior
This page is a reference for all built-in behaviors as well as a small tutorial on how to create your own behaviors. Each behavior section will list all input and output parameters along with the number of dimensions. In all cases "n-dim" could be replaced with "3-dim", because that's the standard number of dimensions for realistic flocking behavior. However we would like to keep this open, so that even a string theorist can get his fix and create 11-dimensional swarms ;-). Some of the listed parameters have the remark "(set parameter)" next to them, this means that this parameter is in most cases just set once at initiation and is left more or less the same throughout the simulation run. This parameters are also characterized by the behavior name prepended to the parameter name (e.g. acceleration_maxAngularAcceleration for AccelerationBehavior).
At the bottom of this page, you will find a instruction for creating custom hehaviors.
calculates the agent's acceleration
swarm.addBehavior( "acceleration", AccelerationBehavior( "mass velocity force", "acceleration") ); swarm.set("acceleration_maxAngularAcceleration", 0.1); swarm.set("acceleration_maxAngularAcceleration", 1.0);
mass | 1-dim |
velocity | n-dim |
force | n-dim |
acceleration_maxAngularAcceleration (set parameter) | 1-dim |
acceleration_maxAngularAcceleration (set parameter) | 1-dim |
acceleration | n-dim |
The AccelerationBehavior has been explained as part of the Hello World 1: Moving Agents code example tutorial.
Boids behavior component. See Hello World 3 code example.
swarm.addBehavior( "alignment", AlignmentBehavior("position:agent_position velocity", "force") ); swarm.set("alignment_minDist", 0.0); swarm.set("alignment_maxDist", 1.7); swarm.set("alignment_amount", 0.5);
position:position_space * | n-dim |
velocity | n-dim |
alignment_minDist (set parameter) | 1-dim |
alignment_maxDist (set parameter) | 1-dim |
alignment_amount (set parameter) | 1-dim |
* supplied by the neighbor parameters (parameter space)
force | n-dim |
reflects agents at a specified boundary (pool table effect)
swarm.addBehavior( "mirror", BoundaryMirrorBehavior("position velocity force", "velocity force") ); swarm.set("mirror_upperBoundary", 2.0); swarm.set("mirror_lowerBoundary", -2.0);
position | n-dim |
velocity | n-dim |
force | n-dim |
mirror_upperBoundary | n-dim |
mirror_lowerBoundary | n-dim |
Notice how position is only an input parameter. This is due to the fact, that when an agent is reflected at a boundary, at the moment of the actual reflection only the motion direction (= 3-dimensional velocity parameter) changes as well as the force acting on the agent. The position itself however stays the same.
The two internal parameters upperBoundary and lowerBoundary mark the edge points of a rectangular space. This space can be cubic, but doesn't have to be.
velocity | n-dim |
force | n-dim |
Agents exiting the boundary on one side enter it from the opposite side. This behavior is only concerned with an agent's position inside a boundary. If that position would be outside of this boundary, the agent is moved to the opposite side of the boundary space.
swarm.addBehavior( "wrap", BoundaryWrapBehavior("position", "position") ); swarm.set("wrap_upperBoundary", 2.0); swarm.set("wrap_lowerBoundary", -2.0);
position | n-dim |
wrap_upperBoundary | n-dim |
wrap_lowerBoundary | n-dim |
position | n-dim |
Similar to Boundary Mirror Behavior, but the repulsion starts at a certain distance from the boundary and the repulsion force rises steadily from there up until the boundary itself. This behavior has been distribed in detail in the previous "Hello World 2: Motion Boundaries" section.
swarm.addBehavior( "repulsion", BoundaryRepulsionBehavior( "position", "force" ) ); swarm.set("repulsion_lowerBoundary", math::Vector3<float>(-5.0, -5.0, -5.0)); swarm.set("repulsion_upperBoundary", math::Vector3<float>(5.0, 5.0, 5.0)); swarm.set("repulsion_maxDist", 2.0); swarm.set("repulsion_amount", 0.03);
position | n-dim |
repulsion_lowerBoundary | n-dim |
repulsion_upperBoundary | n-dim |
repulsion_maxDist | 1-dim |
repulsion_amount | 1-dim |
force | n-dim |
Boids behavior component. See Hello World 3 code example.
swarm.addBehavior( "cohesion", CohesionBehavior( "position:agent_position", "force") ); swarm.set("cohesion_minDist", 0.0); swarm.set("cohesion_maxDist", 1.7); swarm.set("cohesion_amount", 0.1);
position:position_space * | n-dim |
cohesion_minDist | 1-dim |
cohesion_maxDist | 1-dim |
cohesion_amount | 1-dim |
* supplied by the neighbor parameters (parameter space)
force | n-dim |
Simple template behavior which copies an agents parameter without modifying it. As with most behavior parameters it is not actually defined what this parameter is (position, velocity, ...) but rather chosen by the user of this behavior class, when he initializes the behavior. In the example syntax bellow we will assume you would like to copy
Use this code when you attempt to create your own behavior class.
swarm.addBehavior( "copy", CohesionBehavior( "position", "position") );
position (e.g.) | n-dim |
(same as input parameter) | n-dim |
This behavior acts like an attractor that pulls an agent's speed towards a preferred value. We are using the term speed here on purpose for the following reason: Our usage of the term velocity defines it as a three dimensional parameter, which means it includes not only the "speed" at which it moves, but also the direction. However the motion direction is unaffected by this behavior. It simply makes sure that any acceleration forces that speed up the agent will be gradually countered by damping it back down to the norm speed.
It's important to understand that not velocity itself is changed by this behavior but an additional force parameter is created and cumulated with the force parameter outputs of previous behaviors. This process has been explained in the figure in the "Hello world 1: Moving Agents" section.
swarm.addBehavior( "damping", DampingBehavior( "velocity", "force") ); swarm.set("damping_prefVelocity", 0.30); swarm.set("damping_amount", 0.5);
velocity | n-dim |
damping_prefVelocity | 1-dim (!) |
damping_amount | 1-dim |
damping_prefVelocity: prefered speed
damping_amount: aggressiveness of the behavior. Smaller values will attract the speed more gently, meaning it will take more time until this preferred velocity has been reached.
force | n-dim |
Boids behavior component. See Hello World 3 code example.
swarm.addBehavior( "evasion", EvasionBehavior( "position:agent_position", "force") ); swarm.set("evasion_maxDist", 1.0); swarm.set("evasion_amount", 0.5);
position:position_space * | n-dim |
evasion_maxDist | 1-dim |
evasion_amount | 1-dim |
* supplied by the neighbor parameters (parameter space)
force | n-dim |
swarm->addBehavior( "trackMotion", GridAvgBehavior( "position:trackMotion", "force" ) ); swarm->set("trackMotion_amount", 0.01);
position:grid_space * | n-dim |
trackMotion_amount | 1-dim |
trackMotion_amount: scale down factor on force. 1.0 = maximal force
force |
The RandomizeBehavior will generate individual random forces for each agent. Again, keep in mind, that not actually position or velocity is randomized, but an additional force parameter that will be cumulated with all other forces generated by pre-acceleration-step behaviors. If you have difficulties grasping the concept, please read the "Hello world 1: Moving Agents" section, that explains the cumulative function of force.
swarm.addBehavior( "randomize", RandomizeBehavior( "", "force" ) ); swarm.set("randomize_range", 0.001);
randomize_range | 1-dim |
randomize_range: output force will in a range from 0 to this value.
force | n-dim |
This behavior sets the force to 0, preparing it for the cumulation of all forces generated by subsequent behaviors.
swarm.addBehavior( "resetForce", ResetBehavior( "", "force" ) );
force | n-dim |
Again, this output force value is not cumulated, but actually set to zero.
...todo...
... todo...
swarm->addBehavior( "splineFollow", SplineFollowBehavior( "position:trackContour", "force" ) ); swarm->set("splineFollow_minDist", 0.0); swarm->set("splineFollow_maxDist", 1.0); swarm->set("splineFollow_amount", 0.03);
position:shape_space * | n-dim |
splineFollow_minDist | 1-dim |
splineFollow_maxDist | 1-dim |
splineFollow_amount | 1-dim |
* shape_space: space that contains the SplineShape(s) (see Shapes chapter).
force | n-dim |
...todo...
swarm->addBehavior( "forcegrid", DistanceFieldFollowBehavior( "position:forcegrid velocity", "force" ) ); swarm->set("forcegrid_amount", 0.02);
position:grid_space * | n-dim |
forcegrid_amount | 1-dim |
* grid_space: space that contains the GridShape (see Shapes chapter)
force | n-dim |
Necessary step in calculating the final position and velocity. Like AccelerationBehavior, EulerIntergration is not a behavior in the usual sense of flocking behaviors, but rather a calculation process that integrates the position and velocity of the previous time step into the position and velocity of the next time step. There are other and more precise ways to accomplish this, but Euler integration is the most computationally inexpensive one.
This "behavior" comes immediately after the AccelerationBehavior without which you wouldn't have an acceleration parameter to work with!
swarm->addBehavior( "integration", EulerIntegration("position velocity acceleration", "position velocity") ); swarm->set("integration_timestep", 0.01);
position | n-dim |
velocity | n-dim |
acceleration | n-dim |
integration_timestep | 1-dim |
integration_timestep: time granulation, ... todo
position | n-dim |
velocity | n-dim |
Allows the agents to move in circles.
swarm->addBehavior( "circular", CircularBehavior("position", "force") ); swarm->set("circular_innerRadius", 2.0); swarm->set("circular_outerRadius", 3.0); swarm->set("circular_radialAmount", 0.1); swarm->set("circular_tangentialAmount", 0.1);
innerRadius/outer Radius = radius of the inner and outer ring, agents are forced back into the defined zone if the are propelled outside of it. Of course outerRadius is expected to be greater than innerRadius.
radialAmount/tangentialAmount: ...TODO...
position | n-dim |
force | n-dim |
...TODO
swarm->addBehavior( "conevision", ConeVisionBehavior("???", "???") ); swarm->set("conevision_???", 0.01);
??? | n-dim |
??? | n-dim |
??? | n-dim |
convevision_??? | 1-dim |
... todo
??? | n-dim |
??? | n-dim |
Saves all neighborhood information in a parameter. This parameter can be sent to an external instance (like any other parameter), for example if neighborhood information is needed in a particular sound synthesis environment. The number of dimensions has to be specified on creation. If there are less neighbors availabe than the parameter dimension allows, the remaining parameter values (positions) will be overwritte with "-1.0".
This neighbor store parameter has the following format:
agent_idx, neighbor_1_idx, neighbor_1_distance, neighbor_2_idx, neighbor_2_distance, ...
swarm->addBehavior( "nbStore", NeighborStoreBehavior("???", "???") ); swarm->set("nbStore_???", 0.01);
??? | n-dim |
... todo
??? | n-dim |
??? | n-dim |
This one's similar to NeighborStoreBehavior, however only the indices of the neighbors are stored (not the actual distance)
Format of the neighbor parameter:
agent_index, neighbor_1_index, neighbor_2_index, ...
swarm->addBehavior( "nbIndexStore", NeighborIndexStoreBehavior("???", "???") ); swarm->set("nbIndexStore_???", 0.01);
??? | n-dim |
??? | n-dim |
... todo
??? | n-dim |
??? | n-dim |
Another variation of the NeighborStoreBehavior, but this one stores only the distances (and not the indices)
The neighbor parameter has the following format:
0.0 (distance to the agent itself), neighbor_1_distance, neighbor_2_distance, ...
swarm->addBehavior( "nbDistStore", NeighborDistanceStoreBehavior("???", "???") ); swarm->set("nbDistStore_???", 0.01);
??? | n-dim |
??? | n-dim |
... todo
??? | n-dim |
??? | n-dim |
Like NeighborStoreBehavior this one stores the neighbor indices plus neighbor information. However unlike NeighborStoreBehavior that addresses the distance to each neighbor, the NeighbroDirectionStoreBehavior is stores the directions in (TODO what format, rad?, degree?, ...)
swarm->addBehavior( "nbDirStore", NeighborDirectionStoreBehavior("???", "???") ); swarm->set("nbDirStore_???", 0.01);
??? | n-dim |
??? | n-dim |
... todo
??? | n-dim |
??? | n-dim |
Allows the combination (addition?? TODO) of multiple input parameters into one output parameter
swarm->addBehavior( "combine", ParameterCombineBehavior("x1 x2 x3", "y1") ); swarm->set("combine_???", 0.01);
Input parameters are arbitrarely chosen by the instantiator of this behavior
Output parameters are also chosen on creation.
This behavior is a more flexible version of ParameterCombineBehavior. It allows it's parameters to be scaled or even map specific input value ranges to a specific output value.
swarm->addBehavior( "map", ParameterMapBehavior("x1 x2 x3", "y1") ); swarm->set("integration_timestep", 0.01);
Chosen on creation, TODO
Chosen on creation, TODO
This behavior is intended mainly for debugging. It prints out its parameter names and values to the system console.
swarm->addBehavior( "print", ParameterPrintBehavior("a b c", "") ); swarm->set("print_???", 0.01);
Input parameters are chosen on creation
Similar to ParameterMapBehavior, but only scales (multiplies) the received parameter values
swarm->addBehavior( "scale", ParameterScaleBehavior("a", "a") ); swarm->set("scale_factor???", 0.01);
Input parameters are chosen at creation and must comply with output parameters. TODO: > 1 parameter possible, is the above assertion correct? interparam = factor?
As a final section in this behavior-related page, we will show you how to implement your own behavior class. This includes as well, creating and using your own parameters (if your behavior needs additional ones).
Please remind yourself of the relation between agents, parameters and behaviors:
First of all, make sure you have the ISOFlock SVN code downloaded (details in "Installation"). Open your iso_flock project. You will notice, that all built-in behaviors in this page, can be found in a folder called behavior. However, we recommend you create your own folder (something like "my behaviors"), otherwise you will likely forget code you've written yourself and even erase it accidentally.
As you can observe in any of these behavior source code files, they all inherit from the Behavior base class:
class MyNewBehavior : public Behavior
which gives them a set of methods to use as is and another set of methods to re-implement yourself (aka programming interface):
First and foremost, of course the constructors:
/** \brief create behavior \param pInputParameterString input parameter string (parameters are space separated) \param pOutputParameterString output paramaters are space separated) */ MyNewBehavior(const base::String& pInputParameterString, const base::String& pOutputParameterString); /** \brief create behavior \param pAgent agent this behavior belongs to \param pBehaviorName name of behavior \param pInputParameterString input parameter string (parameters are space separated) \param pOutputParameterString output paramaters are space separated) \exception FlockException wrong number of type of parameters */ MyNewBehavior(Agent* pAgent, const base::String& pBehaviorName, const base::String& pInputParameterString, const base::String& pOutputParameterString) throw (FlockException);
These are of course the means by which you will later instantiate a behavior as part of your flock.
The first constructor is actually only a blueprint from which you can copy behaviors to different agents using the create() method:
virtual Behavior* create(const base::String& pBehaviorName, Agent* pAgent) const throw (FlockException);
This will be used if you create swarms of agents, so that the swarm itself can copy the behavior for all its agents.
However you can of course use the second constructor and pass a target agent to the behavior at the point of instantiation.
By far the most important method of your behavior class is the act() method:
/** \brief perform behavior action */ virtual void act();
This method will be called every simulation step. It calculates the output parameters of your behavior using the input parameters as source. Since this is the one method you will spend of your time programming, we will first explain the rest of the methods and types you need to be concerned with.
In the protected body, you will keep the pointers to the parameters you need in your behavior. Let's look for example at the parameter pointers of AccelerationBehavior, one of the built-in behaviors:
Parameter* mMassPar; /// \brief mass parameter (input) Parameter* mVelocityPar; /// \brief velocity parameter (input) Parameter* mForcePar; /// \brief force parameter (input) Parameter* mAcceleration; /// \brief acceleration parameter (output) Parameter* mMaxLinearAccelerationPar; /// \brief maximum linear acceleration parameter (internal) Parameter* mMaxAngularAccelerationPar; /// \brief maximum angular acceleration parameter (internal)
The way these parameter pointers are being set correctly goes like this: In your constructor you provided the class with a set of input parameters in the form of a string holding the parameter names (e.g. "mass velocity force"). The behavior base class will parse these strings and create a vector of mInputParameters and mOutputParameters. You then read the content of these vectors to your parameter pointers:
mMyFirstInputParam* = mInputParameters[0]; mMySecondInputParam* = mInputParameters[1]; ...
If you need additional internal parameters, you have to create them yourself in your constructor:
mMyInternalParameter = createInternalParameter("PARAMETER_NAME", math::Vector<real>(NUM_OF_DIMENSIONS, DEFAULT_VALUE));
Internal Parameters are parameters created and used only by your behavior. However, since they are parameters as well, nothing will actually keep you from using them as input for other behaviors.
Okay, time to do some acting!
For your act() method to run as fast as possible, it's a good idea, to first of all create local references of your parameters, so that you have to access the parameter object only once every act() method call:
real& param1 = mMyFirstInputParam->value(); //1-dimensional math::Vector<real>& param2 = mMySecondInputParam->values(); //n-dimensional
The above example depicts this process with a one-dimensional parameter (e.g. "mass") and a three-dimensional parameter (e.g. "position" in a 3D space). Do the same thing for all output and internal parameters.
In some cases for output parameters, you need the output of your previous act() call while at the same time already filling that specific output parameter with new values. This is why we do not only supply a values() method for parameters, but also a backupValues() method, which will not be overwritten immediately.
math::Vector<real>& param3 = mMyOutputParam->backupValues();
Now, all that's left to do is you personal monkey voodoo magic using the local references we just created. Write your output directly to these references as well.
1) Keep it fast! There is a Pitfalls section at the bottom of "Create your own units", that will give you some hints, to keep your code fast. In short: no casts, no unnecessary computation, avoid division, minimize conditional structures.
2) Test it for boundary conditions.