ANTARES Visualization

ANTARES Visualization#

Note

This is current NOT supported. There are plans to resurrect ANTARES but no time frame as yet (2022/11)

We will start with the code we used in the previous tutorial: Building Your First Agent

#include "Core\Core.h"

using namespace polaris;

template<typename MasterType,typename InheritanceList = NULLTYPELIST>
struct Agent : public Polaris_Component<MasterType,INHERIT(Agent),Execution_Object>
{
	static void Talk(Agent* _this,Event_Response& response)
	{
		cout << "My name is: " << _this->_name << "; the current iteration is: " << iteration() << endl;

		response.next._iteration = iteration() + 1;
		response.next._sub_iteration = sub_iteration();

		if(_this->uuid() == 1)
		{
			Agent* sally = Object_Lookup<Agent>(2);
			cout << "Found: " << sally->_name << endl;
		}
	}

	void Setup(string& name)
	{
		_name = name;

		Load_Event(&Talk,0,0);
	}

	string _name;
};

struct MasterType
{
	typedef Agent<MasterType> agent_type;
};

int main()
{
	Simulation_Configuration cfg;
	cfg.Single_Threaded_Setup(10);
	INITIALIZE_SIMULATION(cfg);

	MT::agent_type* bob = Allocate< MT::agent_type >(1);

	bob->Setup(string("Bob"));

	MT::agent_type* sally = Allocate< MT::agent_type >(2);
	
	sally->Setup(string("Sally"));

	START();

	TERMINATE_SIMULATION();
}

Antares uses a layer-based visualization system which groups similar shapes into a single layer. We will start out by making a layer for agents and add the requisite boiler plate code to MasterType to define Antares classes. It should be pointed out that there is usually one layer per type rather than one layer per object which is why Antares_Layer was defined as a static member as opposed to a regular one.

#include "Antares\Antares.h"

using namespace polaris;

template<typename MasterType,typename InheritanceList = NULLTYPELIST>
struct Agent : public Polaris_Component<MasterType,INHERIT(Agent),Execution_Object>
{
    static void Talk(Agent* _this,Event_Response& response)
    {
        cout << "My name is: " << _this->_name << "; the current iteration is: " << iteration() << endl;

        response.next._iteration = iteration() + 1;
        response.next._sub_iteration = sub_iteration();

        if(_this->uuid() == 1)
        {
            Agent* sally = Object_Lookup<Agent>(2);
            cout << "Found: " << sally->_name << endl;
        }
    }

    void Setup(string& name)
    {
        _name = name;

        Load_Event(&Talk,0,0);
    }

	static void Setup()
	{

	}

    string _name;

	static Antares_Layer<typename MasterType::antares_layer_type>* _agent_layer;
};

template<typename MasterType,typename InheritanceList>
Antares_Layer<typename MasterType::antares_layer_type>* Agent<MasterType,InheritanceList>::_agent_layer;


struct MasterType
{
	// Antares boiler plate code
	typedef Conductor_Implementation<MasterType> conductor_type;
	typedef Control_Panel_Implementation<MasterType> control_panel_type;
	typedef Time_Panel_Implementation<MasterType> time_panel_type;
	typedef Information_Panel_Implementation<MasterType> information_panel_type;
	typedef Canvas_Implementation<MasterType> canvas_type;
	typedef Antares_Layer_Implementation<MasterType> antares_layer_type;
	typedef Layer_Options_Implementation<MasterType> layer_options_type;
	typedef Attributes_Panel_Implementation<MasterType> attributes_panel_type;
	typedef Control_Dialog_Implementation<MasterType> control_dialog_type;
	typedef Information_Page_Implementation<MasterType> information_page_type;
	typedef Splash_Panel_Implementation<MasterType> splash_panel_type;

    typedef Agent<MasterType> agent_type;
};

In order to instantiate the layer we need to use a special allocate statement “Allocate_New_Layer”. The next step is to let Antares know what sort of layer this is by passing it an Antares_Layer_Configuration object. There are a number of default setups, the simplest to work with is probably: Configure_Dynamic_Points - which will allow the layer to draw points of a single color and size (configurable using the member cfg.head_color and cfg.head_size_value respectively).

	static void Setup()
	{
		_agent_layer = Allocate_New_Layer<MT>(string("Agents"));

		Antares_Layer_Configuration cfg;
		cfg.Configure_Dynamic_Points();
		cfg.draw = true;

		_agent_layer->Initialize<NT>(cfg);
	}

Antares expects the developer to push new geometric information at a regular iteration frequency (defined by storage_period and storage_offset). By default, it is expected every iteration so let’s update our agent’s behavior to tell the layer where to draw them. We will use the pre-defined Point_Element structure from Primitives.h to define the agent’s position. Next, all points should be run through the Scale_Coordinates function so that Antares can place them on the canvas correctly. Finally, the element is pushed to the layer using Push_Element.

    static void Talk(Agent* _this,Event_Response& response)
    {
		Point_Element my_position;

		my_position._point._x = 5;
		my_position._point._y = 5;

		Scale_Coordinates<MT>(my_position._point);

		_agent_layer->Push_Element<Regular_Element>(&my_position);

        cout << "My name is: " << _this->_name << "; the current iteration is: " << iteration() << endl;

        response.next._iteration = iteration() + 1;
        response.next._sub_iteration = sub_iteration();

        if(_this->uuid() == 1)
        {
            Agent* sally = Object_Lookup<Agent>(2);
            cout << "Found: " << sally->_name << endl;
        }
    }

Ok, last step before we get to see something! The UI needs to be initialized. It is important to remember that you must initialize the UI after the simulation and to initialize any layers after you initialize the UI (unless you like wonky error messages). The START_UI command doesn’t need much information, simply the MasterType and the extent of the xy plane of the canvas to draw. We will draw a 10x10 canvas because both of our agents will be at (5,5). In addition to setting up the UI, we will also extend our simulation a bit (to 1000 iterations) and call the static Setup function we made in the first step.

int main()
{
    Simulation_Configuration cfg;
    cfg.Single_Threaded_Setup(1000);
    INITIALIZE_SIMULATION(cfg);

	START_UI(MasterType,0,0,10,10);
	
	MT::agent_type::Setup();


    MT::agent_type* bob = Allocate< MT::agent_type >(1);

    bob->Setup(string("Bob"));

    MT::agent_type* sally = Allocate< MT::agent_type >(2);

    sally->Setup(string("Sally"));

    START();

    TERMINATE_SIMULATION();
}

After compiling and running you should see the main UI window, nothing is shown because we don’t have any visual data yet (the visualizer always runs one iteration behind the simulation running in the background). Once you press the play button, you will see a very underwhelming blue dot. Note, when you run this, you will probably want to do it at a rate of 5 or 10 (controlled by the time control next to the play button) otherwise the simulation will end pretty quickly.

To make things a bit more interesting, let’s spice up our agent’s behaviors so that Sally will do a random walk and Bob will follow her. Once again, when you run this, you will probably want to do it at a rate of 5 or 10.

template<typename MasterType,typename InheritanceList = NULLTYPELIST>
struct Agent : public Polaris_Component<MasterType,INHERIT(Agent),Execution_Object>
{
    static void Talk(Agent* _this,Event_Response& response)
    {
		Point_Element my_position;

        response.next._iteration = iteration() + 1;
        response.next._sub_iteration = sub_iteration();

		if(_this->uuid() == 1)
        {
            Agent* sally = Object_Lookup<Agent>(2);

			_this->x_pos = (9.5f/10.0f)*_this->x_pos + (0.5f/10.0f)*sally->x_pos;
			_this->y_pos = (9.5f/10.0f)*_this->y_pos + (0.5f/10.0f)*sally->y_pos;
		}
		else
		{
			if(iteration()%10 == 0)
			{
				_this->x_dir = 1 - rand()%3;
				_this->y_dir = 1 - rand()%3;
			}

			_this->x_pos += _this->x_dir;
			_this->y_pos += _this->y_dir;
		}

		my_position._point._x = _this->x_pos;
		my_position._point._y = _this->y_pos;

		Scale_Coordinates<MT>(my_position._point);

		_agent_layer->Push_Element<Regular_Element>(&my_position);
    }

    void Setup(string& name)
    {
        _name = name;

        Load_Event(&Talk,0,0);

		x_pos = (float)(rand()%100);
		y_pos = (float)(rand()%100);

		x_dir = 1;
		y_dir = 1;
	}

	static void Setup()
	{
		_agent_layer = Allocate_New_Layer<MT>(string("Agents"));

		Antares_Layer_Configuration cfg;
		cfg.Configure_Dynamic_Points();
		cfg.draw = true;

		_agent_layer->Initialize<NT>(cfg);
	}

    string _name;

	float x_pos;
	float y_pos;
	int x_dir;
	int y_dir;

	static Antares_Layer<typename MasterType::antares_layer_type>* _agent_layer;
};

Now let’s send this into overdrive with 1000 agents all following each other, run this one at a speed of 100.

#include "Antares\Antares.h"

using namespace polaris;

template<typename MasterType,typename InheritanceList = NULLTYPELIST>
struct Agent : public Polaris_Component<MasterType,INHERIT(Agent),Execution_Object>
{
    static void Talk(Agent* _this,Event_Response& response)
    {
		Point_Element my_position;

        response.next._iteration = iteration() + 1;
        response.next._sub_iteration = sub_iteration();

		if(_this->uuid() != 0)
        {
            Agent* other = Object_Lookup<Agent>(_this->uuid()-1);

			_this->x_pos = (9.5f/10.0f)*_this->x_pos + (0.5f/10.0f)*other->x_pos;
			_this->y_pos = (9.5f/10.0f)*_this->y_pos + (0.5f/10.0f)*other->y_pos;
			_this->z_pos = (9.5f/10.0f)*_this->z_pos + (0.5f/10.0f)*other->z_pos;
		}
		else
		{
			if(iteration()%10 == 0)
			{
				_this->x_dir = 1 - rand()%3;
				_this->y_dir = 1 - rand()%3;
				_this->z_dir = 1 - rand()%3;
			}

			_this->x_pos += _this->x_dir;
			_this->y_pos += _this->y_dir;
			_this->z_pos += _this->z_dir;
		}

		my_position._point._x = _this->x_pos;
		my_position._point._y = _this->y_pos;
		my_position._point._z = _this->z_pos;

		Scale_Coordinates<MT>(my_position._point);

		_agent_layer->Push_Element<Regular_Element>(&my_position);
    }

    void Setup()
    {
        Load_Event(&Talk,0,0);

		x_pos = (float)(rand()%100);
		y_pos = (float)(rand()%100);
		z_pos = (float)(rand()%100);
	}

	static void Setup_Type()
	{
		_agent_layer = Allocate_New_Layer<MT>(string("Agents"));

		Antares_Layer_Configuration cfg;
		cfg.Configure_Dynamic_Points();
		cfg.draw = true;

		_agent_layer->Initialize<NT>(cfg);
	}

	string _name;

	float x_pos;
	float y_pos;
	float z_pos;

	int x_dir;
	int y_dir;
	int z_dir;

	static Antares_Layer<typename MasterType::antares_layer_type>* _agent_layer;
};

template<typename MasterType,typename InheritanceList>
Antares_Layer<typename MasterType::antares_layer_type>* Agent<MasterType,InheritanceList>::_agent_layer;


struct MasterType
{
	typedef Conductor_Implementation<MasterType> conductor_type;
	typedef Control_Panel_Implementation<MasterType> control_panel_type;
	typedef Time_Panel_Implementation<MasterType> time_panel_type;
	typedef Information_Panel_Implementation<MasterType> information_panel_type;
	typedef Canvas_Implementation<MasterType> canvas_type;
	typedef Antares_Layer_Implementation<MasterType> antares_layer_type;
	typedef Layer_Options_Implementation<MasterType> layer_options_type;
	typedef Attributes_Panel_Implementation<MasterType> attributes_panel_type;
	typedef Control_Dialog_Implementation<MasterType> control_dialog_type;
	typedef Information_Page_Implementation<MasterType> information_page_type;
	typedef Splash_Panel_Implementation<MasterType> splash_panel_type;

    typedef Agent<MasterType> agent_type;
};

int main()
{
	Simulation_Configuration cfg;
	cfg.Single_Threaded_Setup(100000000);
	INITIALIZE_SIMULATION(cfg);

	START_UI(MasterType,0,0,500,500);
	
	MT::agent_type::Setup_Type();

	for(int i=0;i<1000;i++)
	{
		MT::agent_type* person = Allocate< MT::agent_type >(i);
		person->Setup();
	}

	START();

	TERMINATE_SIMULATION();
}

Pretty cool for 120 lines of code, huh?

####Next Adding Projects Using CMake