ofxDabFlock is an openFrameworks addon for creating flocking simulations. The addon is available here. This addon also includes an example for creating a flocking simulation.
Simulation Overview
ofxDabFlock models the behaviour of agents that organise in groups. In the terminology of ofxDabFlock, a group is a “Swarm”. The agents possess properties which are named “Parameters”. The world within which one or several swarms exist can either be an empty space or it can contain “Environments”. Environments represent spatially distributed data that may or may not change over time.
ofxDabFlock is highly generic in that it doesn’t impose any restrictions on the number, type and dimensionality of parameters that agents can possess. The parameters are implemented as n-dimensional vectors using the Eigen library.
In ofxDabFlock, neighborhood relationships don’t exist between agents but between agent parameters. This offers the benefit that agents can potentially perceive and respond to each other not only based on how close they are to each other with regards to their spatial position but also based on other similarity criteria such as for example a similar mass or a similar velocity. ofxDabFlock provides functions to calculate spatial relationships among parameters as well as between parameters and other spatial structures such as splines or vector-fields.
Agent behaviours are instances of classes that establish functional relationships between parameters.
ofxDabFlock uses events for modifying the simulation while the simulation is running. Events can be executed either instantaneously or at a specified time in the future. Some events can also cause simulation properties to change over a duration.
ofxDabFlock can communicate via OSC messages with other applications. OSC messages can be used by other applications to receive the values of simulation parameters. In addition, OSC messages can also be employed as commands to remotely configure and control the simulation.
Finally, ofxDabFlock provides a simple rendering functionality to visualise the simulation.
Entities
- Agent: an agent is a single autonomous entity that is typically part of a larger group. An agent possesses properties and behaviours.
- Swarm: a swarm is a collection of agents that typically possess the same properties and behaviours. Agents can be removed from or added to a swarm. A swarm can also possess its own properties and behaviours that are independent of those of the agents.
- Env: an environment is discretised as a grid where each grid cell has a position in space. An environment can possess its own properties and behaviours. Its properties can have different values at each grid location.
Parameters
- Parameter: represents a named property of an agent or swarm and stores an array of floating point values. Neighbourhood relationships are based on the Euclidian distance between parameters.
- EnvParameter: represents a named property of an environment. Stores the values of the parameter at each cell position of a spatial grid.
- ParameterList: a collection of all parameters of an agent, swarm, or environment.
Behaviour Types
- Behavior: represents a named behaviour of an agent or swarm. Behaviours operate on input and output parameters and overwrite the values of the latter based on the values of the former. Many behaviours generate a force vector that is added to an overall force acting on an agent. Accordingly, the effect of these behaviours is cumulative. Depending on the behaviour, neighbourhood relationships between parameters are taken into account or not. Most behaviours depend on additional parameters than only those provided as input and output. These additional parameters are called internal parameters. They are created automatically when the behaviour is created and then added to the list of parameters of an agent, swarm, or environment.
- EnvBehavior: represents a named behaviour of an environment.
- BehaviorList: a collection of all behaviours of an agent, swarm, or environment.
Agent Behaviours
ofxDabFlock provides a large number of different behaviours all of which are derived from a basic Behavior class. All behaviours create at least one additional parameter when instantiated. This parameter determines if the behaviour is active or not. In the following description of each behaviour, the “active” parameter is not explicitly mentioned.
- AccelerationBehavior: Calculates the acceleration of an agent from the forces that act on it. The linear and angular components of the resulting acceleration can be limited.
- Input parameters: mass, velocity, and force.
- Output parameter: acceleration.
- Internal parameters: maxLinearAcceleration, maxAngularAcceleration.
- EulerIntegration: Calculates new values for position and velocity parameters based on the current values of the velocity and acceleration parameters. The numerical integration is based on the Euler method.
- Input parameters: position, velocity, force.
- Output parameters: velocity, acceleration.
- Internal parameters: timestep.
- RandomizeBehavior: Randomises the value of a parameter.
- Input parameter: none.
- Output parameter: the parameter to be randomised.
- Internal parameters: range.
- DampingBehavior: Adapts the velocity of an agent towards a target speed.
- Input parameter: velocity.
- Output parameter: force.
- Internal parameters: prefVelocity, amount.
- CircularBehavior: Causes agents to move within a zone between an inner and outer surface of a sphere.
- Input parameters: position.
- Output parameters: force.
- Internal parameters: innerRadius, outerRadius, radialAmount.
- ConeVisionBehavior: Implements cone vision for agents. It does so by copying those neighbours from an input neighbour group into an output neighbour group that are within an agent’s vision cone. The tip of vision cone is placed at the agent’s position and oriented in the direction of the agent’s velocity.
- Input parameters: position (with neighbors), velocity.
- Output parameters: position (with neighbors).
- Internal parameters: visionAngle.
- CohesionBehavior: Causes agents to move towards centre position of their neighbours.
- Input parameters: position (with neighbours).
- Output parameters: force.
- Internal parameters: minDist, maxDist, amount.
- EvasionBehavior: Causes agents to move away from their neighbours.
- Input parameters: position (with neighbors).
- Output parameters: force.
- Internal parameters: maxDist, amount.
- AlignmentBehavior: Causes agents to adapt their velocity to the velocities of their neighbours.
- Input parameters: position (with neighbours), velocity.
- Output parameters: force.
- Internal parameters: minDist, maxDist, amount.
- OrbitBehavior: Causes agents to orbit around the positions of neighbouring agents. A cosine function that extends from the position of the neighbouring agent establishes rings of concentric peaks and valleys to which the orbiting agent respond to. Peaks in the cosine function are avoided by the orbiting agent. Valleys in the cosine function attract the orbiting agent.
- Input parameters: position (with neighbours), velocity.
- Output parameters: force.
- Internal parameters: frequency, phase, minDist, maxDist, radAmount, tanAmount, amount.
- SpiralBehavior: Causes agents to move in a spiral around centre position of their neighbours.
- Input parameter: positions (with neighbours).
- Output parameter: velocity.
- Internal parameters: minDist, maxDist, amount.
- BoundaryMirrorBehavior: Restricts an agent ‘s position to a cubic region by reflecting the agent’s velocity and acceleration at the cube’s boundaries.
- Input parameters: position, velocity, acceleration.
- Output parameters: velocity, acceleration.
- Internal parameters: lowerBoundary, upperBoundary.
- BoundaryRepulsionBehavior: Restricts an agent ‘s position to a cubic region by generating a force that pushes the agent away from the cube’s boundaries.
- Input parameters: position.
- Output parameters: force.
- Internal parameters: lowerBoundary, upperBoundary, amount.
- BoundaryWrapBehaviour: Restricts an agent ‘s position to a cubic region by wrapping the position to the opposite side of the cube when it exceeds the cube’s boundaries.
- Input parameters: position.
- Output parameters: position.
- Internal parameters: lowerBoundary, upperBoundary.
- GridAvgBehavior: Adds the grid values of an environment at an agent’s position to an agent’s output parameter. The grid value is linearly interpolated.
- Input parameters: position (with neighbours).
- Output parameters: any parameter.
- Internal parameter: minDist, maxDist, amount.
- DistanceFieldFollowBehavior: Causes an agent to move along the surface represented by a distance field. The force created by the behaviour acts both along the vectors of the distance field as well as tangential to them. The balance between the aligned and tangential force components depends on the length of the distance vector.
- Input parameters: position (with neighbours), velocity.
- Output parameters: force.
- Internal parameters: minDist, maxDist, amount.
- EnvAgentInteractBehavior: Causes an agent to change the grid values of an environment at the agent’s position.
- Input parameters: position (with neighbours).
- Output parameters: none.
- Internal parameters: amount.
- LineFollowBehavior: Causes an agent to follow a polyline. The polyline needs to consist of at least three line segments and should form a closed shape.
- Input parameters: position (with neighbours).
- Output parameters: force.
- Internal parameters: minDist, maxDist, contourMaintainDist, ortAmount, tanAmount, amount.
- ResetBehavior: Sets the value of a parameter to a fixed value.
- Input parameters: none.
- Output parameters: any parameter.
- Internal parameters: resetValue, resetAmount.
- TargetParameterBehavior: Shift the values of a parameter towards those of a target parameter. This behaviour is similar to the ResetBehaviour but differs from it in that it also has input parameter.
- Input parameters: any parameter.
- Output parameters: any parameter.
- Internal parameters: target, adapt, amount, absolute.
- ParameterCombineBehavior: Combines several input parameters into single output parameter.
- Input parameters: any two parameters.
- Output parameters: any parameter.
- Internal parameters: none.
- ParameterMagBehavior: Stores the magnitude of an input parameter in an output parameter.
- Input parameters: any parameter.
- Output parameter: any parameter.
- Internal parameters: none.
- ParameterMapBehavior: Maps an input parameter into an output parameter. Any combination of input parameter components can be selected, scaled and copied into any combination of output parameter components. The mapping relies on the values stored in the internal map parameter.
- Input parameters: any parameter.
- Output parameter: any parameter.
- Internal parameters: map.
- ParameterPrintBehavior: Prints the value of a parameter on the console.
- Input parameters: any parameter.
- Output parameters: none.
- Internal parameters: none.
- ParameterScaleBehavior: Scales the values of an input parameter and copies the result into an output parameter.
- Input parameters: any parameter.
- Output parameter: any parameter.
- Internal parameters: minInput, maxInput, minOutput, maxOutput.
- ParameterThresholdToEventBehavior: Triggers events depending on whether the values an observed input parameter enter or leave a value range. The event is sent to another input parameter.
- Input parameters: observed parameter, send parameter.
- Output parameters: target range.
- Internal parameters: lowerThreshold, upperThreshold, eventTriggerCriteria, thresholdComparisonCriteria, eventTriggerFrequency.
- AgentIdBehavior: Stores the numerical id of an agent in an output parameter.
- Input parameters: none.
- Output parameters: any parameter.
- Internal parameters: none.
- CopyBehavior: Copies the values of an input parameter into an output parameter.
- Input parameters: any parameter.
- Output parameters: any parameter.
- Internal parameters: none.
- NeighborDirectionStoreBehavior: Stores the directions of neighbours of an input parameter in an output parameter.
- Input parameter: any parameter (with neighbours).
- Output parameter: any parameter.
- Internal parameter: none.
- NeighborDistanceStoreBehavior: Stores the distances of neighbours of an input parameter in an output parameter.
- Input parameter: any parameter (with neighbours).
- Output parameter: any parameter.
- Internal parameter: none.
- NeighborIndexStoreBehavior: Stores the indices of neighbours of an input parameter in an output parameter.
- Input parameter: any parameter (with neighbours).
- Output parameter: any parameter.
- Internal parameter: none.
- NeighborParameterStoreBehavior: Stores the parameter values of neighbours of an input parameter in an output parameter.
- Input parameter: any parameter (with neighbours).
- Output parameter: any parameter.
- Internal parameter: none.
- NeighborStoreBehavior: Stores the indices and distances of neighbours of an input parameter in an output parameter.
- Input parameter: any parameter (with neighbours).
- Output parameter: any parameter.
- Internal parameter: none.
Environment Behaviours
- EnvClampBehavior: Clamps the grid values of a parameter to a range of values.
- Input parameters: any parameter.
- Output parameters: any parameter.
- Internal parameters: clampMin, clampMax.
- EnvDecayBehavior: Multiplies the grid values of a parameter with a factor to decay them.
- Input parameters: any parameter.
- Output parameters: any parameter.
- Internal parameters: decay.
- EnvDiffusionBehavior: Diffuses the grid values of a parameter to neighbouring grid positions.
- Input parameters. any parameter.
- Output parameters: any parameter.
- Internal parameters: diffusion.
- EnvGiererMeinhardtBehavior: Treats the grid values of two different parameters as concentrations of two different chemicals and calculates the concentration changes due to chemical reactions based on the Gierer Meinhardt Reaction Diffusion equations.
- Input parameters: chemical1, chemical2.
- Output parameters: chemical1, chemical2.
- Internal parameters: chem1Production, chem2Production, chem1Decay, chem2Decay, reactionRate.
- EnvGrayScottBehavior: Treats the grid values of two different parameters as concentrations of two different chemicals and calculates the concentration changes due to chemical reactions based on the Gray Scott Reaction Diffusion equations.
- Input parameters: chemical1, chemical2.
- Output parameters: chemical1, chemical2.
- Internal parameters: F, k.
Simulation Events
- ClearSimulationEvent: Removes all agents, swarms, environments, OSC senders, OSC receivers, and spaces from a simulation. The result is an empty simulation.
- SaveSimulationEvent: Saves the state of the simulation into JSON files (see Description of Serialisation).
- RestoreSimulationEvent: Restores the state of the simulation from JSON files (see Description of Serialisation).
- SetSimulationRateEvent: Changes the update interval of the simulation.
- AddAgentEvent: Adds a number of agents to a swarm in the simulation.
- RemoveAgentsEvent: Removes a number of agents from a swarm in the simulation.
- AddSpaceEvent: Adds a space for calculation neighbourhoods between parameters to a simulation.
- SetParameterEvent: Sets the value of parameters to target values.
Visualisation Event
- ShowSwarmEvent: Activates the rendering of a swarm.
- HideSwarmEvent: Deactivates the rendering of a swarm.
- ShowSpaceEvent: Activates the rendering of a neighbourhood space.
- HideSpaceEvent: Deactivates the rendering of a neighbourhood space.
Communication
- FlockCom: manages the communication with a flocking simulation. Allows to register parameters whose values are then sent via OSC to a listening port.
- OscControl: handles the remote control of a flocking simulation via OSC messages
Serialisation
- SerializeTools: The configuration and state of a flocking simulation can be saved as JSON files and later on restored from these files. Serialisation can take one of two forms: either the configuration of the simulation is saved/restored, or the values of all parameters for a given configuration are saved/restored. In the former case, separate files are used for: general simulation settings, swarms, neighbourhood spaces, communication settings, visualisation settings. In the latter case, only one file is used for all parameter values.
Visualisation
- FlockVisuals: provides simple functionality for visualisation the flocking simulation.
- VisAgentShape: draws agents as geometrical object
- VisAgentTrail: draws the trajectory of an agent parameter as polyline
- VisNeighborSpace: draws neigbourhood relationships between parameters as lines
- VisGridSpace: draws the values at the grid positions of an environment grid as lines
Programming Tutorial
The addon provides a large number of classes for a range of different purposes. To provide a better overview, the classes are grouped into the following categories: Simulation, Behaviours, Events, Serialisation, Communication, Visualisation.
The core classes for the simulation are:
- dab::flock::Simulation
- dab::flock::Agent
- dab::flock::Parameter
- dab::flock::ParameterList
- dab::flock::Swarm
- dab::flock::SwarmBehavior
- dab::flock::Env
- dab::flock::EnvParameter
The classes for implementing agent behaviours are:
- dab::flock::Behavior
- dab::flock::BehaviorList
- dab::flock::AccelerationBehavior
- dab::flock::AgentIdBehavior
- dab::flock::AlignmentBehavior
- dab::flock::BoundaryMirrorBehavior
- dab::flock::BoundaryRepulsionBehavior
- dab::flock::BoundaryWrapBehavior
- dab::flock::CircularBehavior
- dab::flock::CohesionBehavior
- dab::flock::ConeVisionBehavior
- dab::flock::CopyBehavior
- dab::flock::DampingBehavior
- dab::flock::DistanceFieldFollowBehavior
- dab::flock::EulerIntegration
- dab::flock::EvasionBehavior
- dab::flock::GridAvgBehavior
- dab::flock::LineFollowBehavior
- dab::flock::NeighborDirectionStoreBehavior
- dab::flock::NeighborDistanceStoreBehavior
- dab::flock::NeighborIndexStoreBehavior
- dab::flock::NeighborParameterStoreBehavior
- dab::flock::NeighborStoreBehavior
- dab::flock::OrbitBehavior
- dab::flock::ParameterCombineBehavior
- dab::flock::ParameterMagBehavior
- dab::flock::ParameterMapBehavior
- dab::flock::ParameterPrintBehavior
- dab::flock::ParameterScaleBehavior
- dab::flock::ParameterThresholdToEventBehavior
- dab::flock::RandomizeBehavior
- dab::flock::ResetBehavior
- dab::flock::SpiralBehavior
- dab::flock::TargetParameterBehavior
The classes for implementing environment behaviours are:
- dab::flock::EnvBehavior
- dab::flock::EnvAgentInteractBehavior
- dab::flock::EnvClampBehavior
- dab::flock::EnvDecayBehavior
- dab::flock::EnvDiffusionBehavior
- dab::flock::EnvGiererMeinhardtBehavior
- dab::flock::EnvGrayScottBehavior
The classes for visualising the simulation are:
- dab::flock::FlockVisuals
- dab::flock::VisAgentShape
- dab::flock::VisAgentTrail
- dab::flock::VisGridSpace
- dab::flock::VisNeighborSpace
- dab::flock::VisSwarm
The classes for communicating with the simulation are:
- dab::flock::FlockCom
- dab::flock::OscControl
The classes for serialising the simulation are:
- dab::flock::SerializeTools
The classes for implementing simulation events are:
- dab::flock::AddAgentsEvent
- dab::flock::AddSpaceEvent
- dab::flock::ClearSimulationEvent
- dab::flock::RemoveAgentsEvent
- dab::flock::RestoreSimulationEvent
- dab::flock::SaveSimulationEvent
- dab::flock::SetParameterEvent
- dab::flock::SetSimulationRateEvent
The classes for implementing visualisation events are:
- dab::flock::HideSpaceEvent
- dab::flock::HideSwarmEvent
- dab::flock::ShowSpaceEvent
- dab::flock::ShowSwarmEvent
In the following, those classes that are frequently used and directly exposed to the user are explained in some detail.
dab::flock::Simulation
The Simulation is a Singleton class from which a single instance can be obtained using the “get” method. Once this instance is available, one of the first things to do is to set the update step size in milliseconds using the “setUpdateInterval” function.
#include "dab_flock_simulation.h"
Simulation& simulation = Simulation::get();
simulation.setUpdateInterval(1.0);
Once a simulation has been fully configured (i.e. the communication and visualisation have been setup and the swarms have been created), it can be started using the “start” function.
simulation.start();
dab::flock::FlockCom
After the Simulation class has been instantiated, it contains an instance of the FlockCom class which can be obtained using the “com” function. The three most frequently used functions of the FlockCom class are “createOscControl”, “createSender”, “registerParameter”. The “createOscControl” function creates an OSC message receiver and OSC message sender. The receiver is used to remote control the simulation. The sender sends error messages and warnings about problems that occur when running the simulation. The arguments for the “createOscControl” are: port number to receive simulation control messages, ip address and port number to send error messages. The “createSender” function creates additional OSC message senders which are typically used to send the values of parameters to a destination address. The arguments for the “createSender” function are: name of the sender, ip address and port number of the destination, and a flag indicating which OSC message format to use. If the flag is false, then conventional OSC messages are sent. Of the flag is true, than extended OSC messages are sent. More information about the extended format is available here. Once such a sender has been created, simulation parameters can be assigned to it. More about this later on.
simulation.com().createOscControl(7400, "127.0.0.1", 7800);
simulation.com().createSender("FlockSender", "127.0.0.1", 7500, false);
dab::space::SpaceManager
The SpaceManager class is provided by the ofxDabSpace addon. Information about this addon is available here. The Simulation class once instantiated also contains an instance of the SpaceManager class which can be obtained using the “space” function. The SpaceManager class handles neighbourhood relationships among parameters. Before creating one or several swarms, the spaces which will be used for neighbourhood calculations should be prepared. Spaces are associated with spatial partitioning algorithms that accelerate neighbourhood calculations.
For agent parameters, useful algorithms include: KDTree, NTree, and ANN. For environment parameters, the Grid algorithm is used. For spatial objects that are not mere points but possess a shape (such as polylines), the RTree algorithm is used.
To create such a space, the function “addSpace” is called on an instance of the SpaceManager class. This function expects a pointer to an instance of the Space class. This class in turn expects as parameters for its constructor a name of the space and a pointer to a space partitioning algorithm
Creating a Space for an Agent Parameter
A space for an agent parameter of dimension 3 can for example be created as follows:
simulation.space().addSpace(std::shared_ptr<space::Space>(new space::Space("agent_position", new space::KDTreeAlg(3))));
Creating a Space for a Spatial Grid of Values
A space for a spatial grid that could for example contain externally created force vectors can be created as follows:
space::SpaceGrid* forceGrid = new space::SpaceGrid(3, Array<unsigned int>{ 20, 20, 5 }, Eigen::Vector3f(-5.0, -5.0, -5.0), Eigen::Vector3f(5.0, 5.0, 5.0));
simulation.space().addSpace(std::shared_ptr<space::Space>(new space::Space("forcegrid", new space::GridAlg(forceGrid, space::GridAlg::AvgLocationMode, space::GridAlg::NoUpdateMode))));
In the above code example, an instance of the SpaceGrid class is created by passing to its constructor the dimension of the space, the number of grid subdivisions of the space, and the spatial extension of the grid as minimum and maximum positions. Alternatively, such a grid space can be obtained from an environment parameter. To add the grid to a new space, the spatial partitioning algorithm that is passed as argument to the Space constructor is an instance of the GridAlg class. The constructor for this class requires three arguments. An instance of the space grid, a method for sampling grid values at arbitrary positions such as by averaging them, and a flag indicating how the grid values should be changed when they are overwritten.
A space grid contains a vector field. This field contains all the vector values of the grid. One method to populate a space grid with new vector values is to replace the vector field that it contains. A new vector field can for example be obtained from one of the field algorithms that are provided by the ofxDabMath addon. More information about this addon is available here.
math::VectorField<float>& vectorField = forceGrid->vectorField();
math::RoesselerFieldAlgorithm fieldAlg;
fieldAlg.setVectorField(&vectorField);
fieldAlg.update();
Creating a Space for Spatial Shapes
A space for spatial objects that possess a shape can be created as follows:
space::RTreeAlg* shapeSpaceAlg = new space::RTreeAlg(Eigen::Vector3f(-5.0, -5.0, -5.0), Eigen::Vector3f(5.0, 5.0, 5.0));
shapeSpaceAlg->setClosestPointType(space::ClosestPointShape);
simulation.space().addSpace(std::shared_ptr<space::Space>(new space::Space("shapespace", shapeSpaceAlg)));
In the above code example, an RTree is used as space partitioning algorithm. This algorithm takes as arguments for its constructor a spatial extension as minimum and maximum positions. Once created, the RTree can be configured to either return the closest point of a shape or to return the closest point to the axis aligned bounding box of a shape. Here, it is configured to do the former. Finally, this algorithm is added to the constructor for a new space in the usual manner.
A shape to populate a shape space can either be created manually or obtained by creating contours for text characters.
A manually created simple shape can be added to the previously created shape space by using the GeometryGroup class. This class is provided by the ofxDabGeom addon for which more information is available here.
std::shared_ptr<geom::GeometryGroup> lineGeomGroup(new geom::GeometryGroup());
lineGeomGroup->addGeometry(new geom::Line(glm::vec3(-1.0, -1.0, 0.0), glm::vec3(1.0, -1.0, 0.0)));
lineGeomGroup->addGeometry(new geom::Line(glm::vec3(1.0, -1.0, 0.0), glm::vec3(0.0, 1.0, 0.0)));
lineGeomGroup->addGeometry(new geom::Line(glm::vec3(0.0, 1.0, 0.0), glm::vec3(-1.0, -1.0, 0.0)));
space::SpaceShape* lineShape = new space::SpaceShape(lineGeomGroup);
simulation.space().addObject("shapespace", lineShape, true);
The following code example illustrates how to add shapes to a shape space that are created as outlines for text characters.
space::RTreeAlg* textSpaceAlg = new space::RTreeAlg(Eigen::Vector3f(-5.0, -5.0, -5.0), Eigen::Vector3f(5.0, 5.0, 5.0));
simulation.space().addSpace(std::shared_ptr<space::Space>(new space::Space("textspace", textSpaceAlg)));
TextTools& textTools = TextTools::get();
textTools.createFont("defaultfont", "C:/Windows/Fonts/Ebrima.ttf");
textTools.createText("char0", "A", "defaultfont");
space::SpaceShape* textShape = textTools.text("char0");
textShape->setScale(Eigen::Vector3f(5.0, 5.0, 5.0));
textShape->setPosition(Eigen::Vector3f(0.0, 2.0, 0.0));
simulation.space().addObject("textspace", textShape, true);
dab::flock::Swarm
A new swarm can be created by instantiating the Swarm class. The constructor takes a single argument which specifies the name of the swarm.
Swarm* swarm = new Swarm("swarm");
The parameters and behaviours that the agents in the swarm possess are declared through functions provided by the Swarm class. These parameters and behaviours are then replicated for each agent that is added to a swarm.
Adding Agent Parameters to a Swarm
To add an agent parameters to a swarm, the function “addParameter” can be used. One version of this function takes two arguments: the name of the parameter and the initial values of the parameter. A three dimensional parameter named position can be created like this:
swarm->addParameter("position", { 0.0, 0.0, 0.0 });
Other typically added agent parameters are “velocity”, “acceleration”, “force”, and “mass”.
swarm->addParameter("velocity", { 0.0, 0.0, 0.0 });
swarm->addParameter("acceleration", { 0.0, 0.0, 0.0 });
swarm->addParameter("force", { 0.0, 0.0, 0.0 });
swarm->addParameter("mass", { 0.1f });
Assigning Agent Parameters to a Space for Neighbourhood Calculation
Once a agent parameter has been added to a swarm, the spaces with which the parameter is associated with can be assigned. The function “assignNeighbors” of the Swarm class can be used for this. This function takes as arguments: the name of the agent parameter, the name of the space, a flag indicating if the parameters is visible in the space or not, and a pointer to an instance of an algorithm for handling neighbouring parameters. In the following code example, the position parameters is assigned to all the previously created spaces.
swarm->assignNeighbors("position", "agent_position", true, new space::NeighborGroupAlg(3.0, 8, true));
swarm->assignNeighbors("position", "forcegrid", false, new space::NeighborGroupAlg(2.5, 1, true));
swarm->assignNeighbors("position", "shapespace", false, new space::NeighborGroupAlg(60.0, 1, true));
swarm->assignNeighbors("position", "textspace", false, new space::NeighborGroupAlg(60.0, 1, true));
A few comments concerning the visibility flag and the neighbour group algorithm are in place.
The visibility flag controls if the parameters that are assigned to a space are visible for other parameters in that space. Normally, this flag is set to true. But in some cases, for example when working with multiple swarms in which agents in one swarm should perceive parameters of agents in another swarm but not vice versa, then this flag can be set to zero.
The NeighborGroupAlg class controls how neighbouring parameters are added to a neighbour group of a parameter. The constructor of the NeighborGroupAlg class takes three parameters: the maximum distance between the current parameter and a second parameter below which the second parameter will be added as neighbour to the current parameter. the maximum number of neighbouring parameters that can be added to a neighbour group, and a flag indicating if the neighbouring parameters in the neighbour group should be sorted by distance with the first neighbouring parameter being closest and the last furthest.
Instead of passing a pointer to an instance of a NeighborGroupAlg class to the “assignNeighbors” function, this pointer can also be set to NULL. In that case, the corresponding parameters are not able to store any neighbouring parameters. Metaphorically, these parameters are “blind”. Such a setting can be useful again in the case of working with multiple swarms.
Adding Agent Behaviours to a Swarm
Agent behaviours can be added using the “addBehavior” function of the swarm. This function takes two arguments: the name of the behaviour, and an instance of the appropriate behaviour class.
The constructor of a behaviour class takes two arguments: a string specifying all the names of the input parameters, and a second string specifying all the names of the output parameters. An example of this is the “DampingBehavior”.
swarm->addBehavior("damping", DampingBehavior("velocity", "force"));
For those behaviours that require that a parameter possesses neighbours, the name of this parameter is combined with the name of the space to which the parameter is associated with. The parameter name and the space name are separated by a “@” character. An example of this is the “CohesionBehavior”.
swarm->addBehavior("cohesion", CohesionBehavior("position@agent_position", "force"));
For those behaviours that don’t only require that a parameter possesses neighbours but that also need to know which additional parameters to read from once the agents associated with the neighbouring parameter have been identified, the name of the additional parameter needs to be separated with a “:” from the preceding names. An example of this is the “AlignmentBehavior”.
swarm->addBehavior("alignment", AlignmentBehavior("position@agent_position:velocity", "force"));
While a swarm is extremely generic and flexible with regards to the behaviours that are added to it, a few behaviours are mandatory. These are:
- ResetBehaviour: for setting the forces acting on agents back to zero at the beginning of a simulation step.
- RandomizeBehavior: for slightly randomising the forces to prevent them from having a value of zero. Several other behaviours won’t operate properly otherwise.
- AccelerationBehavior: for calculating the acceleration of agents based on their mass, velocity, and force.
- EulerIntegration: for updating the positions and velocities of agents based on their acceleration.
A further requirement concerns the sequence in which these behaviours are added to a swarm. The ResetBehaviour and RandomizeBehavior should always be added first, and the AccelerationBehavior and EulerIntegration should always be added last, with all the other behaviours added in between.
swarm->addBehavior("resetForce", ResetBehavior("", "force"));
swarm->addBehavior("randomize", RandomizeBehavior("", "force"));
/*
Add other behaviours
*/
swarm->addBehavior("acceleration", AccelerationBehavior("mass velocity force", "acceleration"));
swarm->addBehavior("integration", EulerIntegration("position velocity acceleration", "position velocity"));
When a behaviour has been added to a Swarm, the behaviour’s internal parameters become accessible externally through a name that consists of the behaviour name, an underscore, and the internal name of the parameter. For example, for an instance of the RandomizeBehavior behaviour with the name “randomize”, its internal parameter “range” becomes accessible externally with the name “randomize_range”.
Setting Parameter Values
Parameters that have been added to a swarm can be modified using the “set” function of the Swarm class. If the swarm already contains agents, then the set function sets the corresponding parameter of all the agents to the same value.
An example for setting the value of the parameter “velocity”:
swarm->set("velocity", { 1.0, 0.0, 0.0 });
An example for setting the value of the parameter with the name “range” that is internal to the behaviour with the name “randomize”:
swarm->set("randomize_range", { 0.0001f, 0.0001f, 0.0001f });
Add Agents to a Swarm
Agents can be added or removed from a swarm by using the functions “addAgents” and “removeAgents”. These function take as argument the number of agents to be added or removed.
swarm->addAgents(100);
swarm->removeAgents(20);
Agents that are added to a swarm copy all the parameters and behaviours that have previously been added to the swarm. Agents can be added to and removed from a swarm before, while, or after parameters and behaviours have been added to the swarm. When agents are added to a swarm before the swarm possesses any parameters or behaviours, then the added agents won’t posses any parameters or behaviours either. But when parameters and behaviours are added later on to the swarm, these parameters and behaviours are automatically copied by all the agents that are already part of the swarm. So in the end it doesn’t matter a which point agents are added to a swarm. But the most common practice is to add agents to a swarm after the swarm has been fully configured with parameters and behaviours.
Randomize Parameter Values
Normally, when adding agents to the swarm, each parameter that is copied into an agent possesses the value that it had when adding it to the swarm. A simple method to vary the values for the parameters of the agents is to randomise them. The Swarm class offers the function “randomize” for this purpose. This function takes three arguments: the name of the parameter, the lower bound of the range of the parameter values, and the upper bound of the range.
One parameter that should typically be randomised before starting the simulation is the agent position. Otherwise some of the behaviours that require neighbourhood calculations between this parameters will produce strange results.
swarm->randomize("position", { -1.0, -1.0, -1.0 }, { 1.0, 1.0, 1.0 });
Registering Parameters for Communication
In order to send the values of parameters via OSC messages to a destination address, these parameters have to be registered with an OSC sender. The FlockCom class provides the function “registerParameter” for this. This function exists in multiple versions that take a different number of arguments. The version with the smallest number of arguments takes three arguments: the name of the OSC sender, the name of the swarm, the name of the parameter. The version with largest number of arguments takes six arguments: the name of the OSC Sender, the name of the swarm, the name of the parameter, the number of agents whose parameter should be grouped into one OSC message, the minimum value of the parameter used for normalisation, the maximum value of the parameter used for normalisation.
The shortest version of the “registerParameter” function can be called for example like this:
simulation.com().registerParameter("FlockSender", "swarm", "position" );
The OSC messages that are sent when a parameter is registered like in the example above have the following form:
/swarm_name/agent_nr/parameter_name parameter_values[0] parameter_values[1] … parameter_values[n]
Here, the arguments of the message contain the values of a parameter of a single agent. These values are not normalised.
The longest version of the “registerParameter” function can be called for example like this:
simulation.com().registerParameter("FlockSender", "swarm", "position", swarm->agentCount(), Eigen::Vector3f(-5.0, -5.0, -5.0), Eigen::Vector3f(5.0, 5.0, 5.0));
The OSC messages that are sent when a parameter is registered like in the example above have the following form:
/swarm_name/start_agent_nr/end_agent_nr/parameter_name parameter_values[0] parameter_values[1] … parameter_values[n]
Here, the arguments of the message contain the values of all the parameter of a group of agents. The parameter values are non-interleaved and normalised. When using this method, care must be taken to not exceed the maximum size of an OSC message. If the swarm contains more agents than an OSC message can handle, then a smaller number of agents than what the swarm contains can be passed to the “registerParameter” function. In this case, multiple messages with the parameter values of consecutive agent groups are sent via OSC.
dab::flock::FlockVisuals
The FlockVisuals class offers the possibility to render a simple visualisation of the simulation. The FlockVisuals is a Singleton class from which a single instance can be obtained using the “get” method.
FlockVisuals& visuals = FlockVisuals::get();
Using this visualisation, agents are drawn as geometrical primitives, the most recent trajectories are drawn as polylines that become more transparent further in the past, neighbourhood relationships are drawn as lines connection points, and grid spaces are drawn as vector fields.
To prepare a swarm for rendering, the FlockVisuals class provides the function “showSwarm”. This function exists in multiple version. The most commonly used version takes four arguments: the name of the swarm, the name of the position parameter, the name of the velocity parameter, and the maximum length of the trail.
visuals.showSwarm("swarm", "position", "velocity", 10);
Calling the function as above displays agents as pyramids that point into the direction of the agent’s velocity. The name of the velocity parameter can be left empty when calling this function. In that case, the agents are not displayed but their trails are visible.
To prepare a space for rendering, the FlockVisuals class provides the function “showSpace”. This function takes as only argument the name of the space.
visuals.showSpace("agent_position");
When adding a space for rendering, the FlockVisuals class automatically chooses the appropriate form of rendering, i.e. lines connecting dots for a regular parameter neighbourhood space, a field of vectors for a grid space.
For the visualisation of the simulation to be updated and displayed repeatedly, the “update” and “display” functions of the FlockVisuals class have to be called. These function calls are typically made inside the ofApp::update and ofApp::draw function.
void ofApp::update()
{
FlockVisuals::get().update();
}
void ofApp::draw()
{
FlockVisuals::get().display();
}
OSC Control
Communication – Sending OSC Messages to the Simulation and Visualisation
OSC messages that are sent to the simulation can be used to modify a variety of simulation and behaviour parameters. The parsing of the OSC messages is performed by the OscControl class, which is automatically instantiated when the function “createOscControl” of the FlockCom class is called.
OSC Messages for Controlling the Simulation
// Clear Simulation
/ClearSimulation
// Restore Simulation from Configuration Files
// fileType can be either Config or Values
/RestoreSimulation <String: fileName>
/RestoreSimulation <String: fileName> <String: fileType>
// Save Simulation as Configuration Files
// fileType can be either Config or Values
/SaveSimulation <String: value> <String: fileName>
/SaveSimulation <String: value> <String: fileName> <String: fileType>
// Set Simulation Update Interval
/SetSimulationRate <Float: updateInterval>
/SetSimulationRate <Int: updateInterval>
// Set Simulation Update Interval
// with frozen == 0 equals to false, and anything else to true
/FreezeSimulation <Float: frozen>
/FreezeSimulation <Int: frozen>
// Add Space
/AddSpace <String: spaceName> <String: spaceAlgName> <Int: spaceDim>
/AddSpace <String: spaceName> <String: spaceAlgName> <Int: spaceDim> FloatArray: spaceMinPosValues> <FloatArray: spaceMaxPosValues>
/AddSpace <String: spaceName> <String: spaceAlgName> <Int: gridValueDim> <IntArray: gridSubdivisionValues> FloatArray: spaceMinPosValues> <FloatArray: spaceMaxPosValues> <String: gridNeighborModeName> <String: gridUpdateModeName>
// Add Agents
/AddAgents <String: swarmName> <Int: agentCount>
// Remove Agents
/RemoveAgents <String: swarmName>
/RemoveAgents <String: swarmName> <Int: agentCount>
/RemoveAgents <String: swarmName> <Int: parameterDim> <IntArray: agentIndices> <Int: agentStartIndex> <Int: agentEndIndex>
// Set Parameter Values
/SetParameter <String: swarmName> <String: parameterName> <Float: parameterValue>
/SetParameter <String: swarmName> <String: parameterName> <Int: parameterDim> <FloatArray: parameterValues>
/SetParameter <String: swarmName> <String: parameterName> <Float: parameterValue> <Float: interpolationDuration>
/SetParameter <String: swarmName> <String: parameterName> <Int: parameterDim> <FloatArray: interpolationDuration>
/SetParameter <String: swarmName> <String: parameterName> <Int: agentIndex> <Float: parameterValue>
/SetParameter <String: swarmName> <String: parameterName> <Int: agentIndex> <Int: parameterDim> <FloatArray: parameterValues>
/SetParameter <String: swarmName> <String: parameterName> <Int: agentIndex> <Int: parameterDim> <Float: parameterValue> <Float: interpolationDuration>
/SetParameter <String: swarmName> <String: parameterName> <Int: agentIndex> <Int: parameterDim> <FloatArray: parameterValues> <Float: interpolationDuration>
// Assign Neighbours
/AssignNeighbors <String: swarmName> <Int: agentIndex> <String: parameterName> <String: spaceName> <Int: visible>
OSC Messages for Controlling the Visualisation
// Show Swarm
/ShowSwarm <String: swarmName> <String: posParName>
/ShowSwarm <String: swarmName> <String: posParName> <String: velParName>
/ShowSwarm <String: swarmName> <String: posParName> <String: velParName> <Int: trailLength>
// Hide Swarm
/HideSwarm <String: swarmName>
// Show Space
/ShowSpace <String: spaceName>
/ShowSpace <String: spaceName> <Int: colorDim> <FloatArray: colorValues>
/ShowSpace <String: spaceName> <Int: colorDim> <FloatArray: colorValues> <Float: valueScale>
// Hide Space
/HideSpace <String: spaceName>
// Set Display Position
/DisplayPosition <Int: posDim> <FloatArray: posValues>
// Set Display Orientation
/DisplayOrientation <Int: orientDim> <FloatArray: orientValues>
// Change Display Orientation
/DisplayOrientationChange <Int: orientDim> <FloatArray: orientValues>
// Set Display Zoom
/DisplayZoom <Float: zoom>
// Set Agent Colour
/AgentColor <String: swarmName> <Int: colorDim> <FloatArray: colorValues>
// Set Agent Scale
/AgentScale <String: swarmName> <Float: agentScale>
/AgentScale <String: swarmName> <int agentScaleDim> <FloatArray: agentScaleValues>
// Set Agent Line Width
/AgentLineWidth <String: swarmName> <Float: agentLineWidth>
// Set Trail Colour
/TrailColor <String: swarmName> <Int: trailColorDim> <FloatArray: trailColorValues>
// Set Trail Length
/TrailLength <String: swarmName> <Int: trailLength>
// Set Trail Decay
/TrailDecay <String: swarmName> <Float: trailDecay>
// Set Trail Line Width
/TrailLineWidth <String: swarmName> <Float: trailWidth>