MengeCrowdSim / Menge

The source code for the Menge crowd simulation framework
Apache License 2.0
139 stars 64 forks source link

Menge DLL expose ability to add and remove agent #53

Open JimSEOW opened 7 years ago

JimSEOW commented 7 years ago

Use Case:

to spawn new agents and to remove agents during simulation.

In XMLSimulatorBase.h

/*!
 *  @brief      Add an agent with specified position to the simulator whose properties
 *              are defined by the given agent initializer.
 *
 *  It uses the agent initializer to define the values of the remaining agent
 *  parameters.
 *
 *  @param      pos         The 2d vector representing the agent's position
 *  @param      agentInit   The AgentInitializer necessary to parse AgentSet properties
 *  @returns    A pointer to the agent (if initialization was succesful) or NULL if
 *              failed.
 */
virtual BaseAgent * addAgent( const Math::Vector2 & pos,  AgentInitializer * agentInit ) = 0;

The only way to add new agent is in SimXMLLoader.cpp

// Second pass, parse Generators
for( child = node->FirstChildElement(); child; child = child->NextSiblingElement()) {
    if ( child->ValueStr() == "Generator" ) {
        AgentGenerator * generator = AgentGeneratorDB::getInstance( child,
                                                                    _sceneFldr );

        if ( generator == 0x0 ) {
            logger << Logger::ERR_MSG << "Unable to instantiate agent generator "
                "specifcation on line " << child->Row() << ".";
            return false;
        }
        // Now instantiate the agents
        const size_t AGT_COUNT = generator->agentCount();
        Vector2 zero;
        for (size_t i = 0; i < AGT_COUNT; ++i) {
            BaseAgent * agent = _sim->addAgent(zero, profileSel->getProfile());
            generator->setAgentPosition(i, agent);
            _sim->getInitialState()->setAgentState(agent->_id, stateSel->getState());
        }
        _agtCount += (unsigned int)AGT_COUNT;

        generator->destroy();
    }
}

if we could expose SimulatorInterface in menge_c_api.h

class MENGE_API SimulatorInterface: public XMLSimulatorBase

we can get into the getInitialState() of XMLSimulatorBase to:

/*!
 *  @brief      Sets the state for the given agent.
 *
 *  @param      id          The identifier of the agent.
 *  @param      stateName   The name of the state to which the agent belongs.
 */
_sim->getInitialState()->setAgentState(agent->_id, stateSel->getState());

OR

/*!
 *  @brief      Reports the state name for the given agent.
 *
 *  @param      id          The identifier of the agent.
 *  @returns    The name of this agent's start state.
 */
const std::string getAgentState( size_t id ) const;
MengeCrowdSim commented 7 years ago

Menge's architecture was changed with the idea of eventually being able to dynamically change the population (i.e., most of the structures use map in place of vectors so that it can adapt to whatever data is available). Part of that design will be the eventual inclusion of agent Sources and Sinks as proper elements. This work has not been done yet.

I fully encourage you to try what you're suggesting and let us know how it goes. My fear is that there is some finalization work and dependencies that might make this a bit murky.

One of the things I've done in the past to simulation dynamic populations is to have a "sink" state that teleports the agents far away from the main simulation domain, giving them a zero-velocity velocity component. You would then need to have an appropriate transition out of that state that would cause them to teleport back into the meaningful domain. The nature of the transition depends on what effect you're trying to achieve. Let us know if you want to dabble with this hacky approach and we can provide some tips.

JimSEOW commented 7 years ago

@MengeCrowdSim part of the challenges is lack of sufficient documentation.

For the example 4Squares, which part of the XML files 4SquareB.xml or 4SquareS.xml define the beheviour of agent moving from ONE square to the opposite Square?

For the example Soccer: what is soccer.nav ( it is a nav_mesh; can I assume it is a path which the agent must follow?)

For the example Botteneck: same with botteneckMap.txt

Some explanation of these files will help

What is VelComponent? How many types are VelComponent? e.g. road_map; nav_mesh; More?

MengeCrowdSim commented 7 years ago

You are right. The documentation always looms over my head.

The first thing I can say is that you should build the doxygen locally. The doxygen on the website (http://gamma.cs.unc.edu/Menge/docs/code/menge/html/) is not up to date. The local doxygen is more up to date. (Looking through it, I see that it's, at best, partially documented. With the focus on the ___S.xml files. I obviously need to reserve more cycles for documenting...)

Another source for enlightenment is to read this paper: https://collective-dynamics.eu/index.php/cod/article/view/A1 It discusses the architectural overview explaining things like "what is a VelComponent"? Take a look at Section 3. The appendix creates a simple example and dissects the S.xml and B.xml file.

Some of the detail questions: 1) The navigation files are undocumented. The roadmap is simple, the nav mesh is complex. I've got cobbled tools that I use to manufacture those data structures. I've got a background project, a gui-based authoring/execution tool. Editing XML is a pain. If you're interested, I can send you my python-based authoring tools (limited though they may be) to create roadmaps and nav meshes. 1) The road map tool is a gui. You load your obstacles, and then click and drag to create a roadmap by hand. 2) The nav mesh generation takes an obj file and converts it to my proprietary nav mesh file format. It assumes that you can generate the obj file (which was the quickest, dirtiest way for me to go from nothing to nav mesh.) 2) In 4squareB.xml, each agent is assigned a goal position that is its initial position mirrored over the origin (https://github.com/MengeCrowdSim/Menge/blob/master/examples/core/4square/4squareB.xml#L5). It then attempts to move there by walking directly towards that goal point (https://github.com/MengeCrowdSim/Menge/blob/master/examples/core/4square/4squareB.xml#L6)

VelComponent: This is the module that is responsible for defining the instantaneous preferred velocity for an agent (given the agent's state -- including goal, position, etc.) The simplest is the zero velocity component (which is, your preferred velocity is to always go nowhere). Then it gets more complex: take a constant velocity, a constant direction at preferred speed, a velocity directly towards a point at preferred speed, and, for handling complex environements: follow a vector field, road map, or a navigation mesh versions you've alluded to.

You can see the various declarations here: https://github.com/MengeCrowdSim/Menge/tree/master/src/Menge/MengeCore/BFSM/VelocityComponents

JimSEOW commented 7 years ago

@MengeCrowdSim Thanks for patiently providing this explanation. VERY USEFUL. It makes more sense NOW.

I will take your offer to look into the GUI later once I understand further the data structure of the road map and Nav mesh.

Q1: One of the goals using menge is to be able to dynamically for each agent, control the goal position during simulation. e.g. Agent x should move from pos A to pos B along a rough calculation of road_Map (perhaps by A-start etc. ). When the Agent x reach pos B. This trigger a callback. This callback request the next goal position e.g. pos C. So another A-start path is calculated to populate the road_map for Agent x to move from pos B to pos C.

I think many of these components are inside menge.

However, I do not have the overview know where these information. Can this be done in plugin? perhaps there is already example using this? I read there are trigger, event, associated with agent, as well as transition (Finite State way)..

=> In short, with the C# binding, I think Menge is now an important Crowd .NET library. Keep it up. WE NEED THIS!!!!!!!

MengeCrowdSim commented 7 years ago

The big question is: How do you pick B initially? And then, upon reaching B, how do you pick C? If you're doing something really esoteric, then a plug-in may be necessary. But, generally, the behavior you've described is very much in line with the behavioral finite state machine that is the heart of Menge.

The agent starts in state 1. State 1 has a goal associated with it and a means for planning for that goal (e.g., a road map). While the agent is in State 1, it will attempt to move toward the goal.

We create a transition from State 1 to state 2. The transition is "goal reached". State 2 then selects a goal and a means to move towards it, and you repeat.

For example, if I scattered a bunch of random goals in a domain, and I wanted the agent to simply randomly move from point to point, I could do that with a single state and a transition pointing back to itself (the tradeshow demo does this -- a set of goal points from which a goal is selected every time an agent enters the state).

If, however, you need to pick a goal on some more complex state, or based on history, or prediction, then you'd need to implement a new GoalSelector that implements that logic.

(Thanks for the encouragement. Always nice to get.)

JimSEOW commented 7 years ago

Thanks for keep answering my questions 👍
powerpnt_2017-04-11_17-45-33

Q: Which examples demonstrate how to use Event triggers, Event effects, Event Targets?? Thanks

=> Yes: GoalSelectoris definitely I need to look into.

MengeCrowdSim commented 7 years ago

A: https://github.com/MengeCrowdSim/Menge/blob/master/examples/core/event.xml with the details in https://github.com/MengeCrowdSim/Menge/blob/master/examples/core/event/eventB.xml In this scenario, every time an agent reaches a goal, it makes all of the other agents (who have not yet reached the goal) get fatter and faster.

Elaboration: the event system was the newest feature to Menge's architecture. The underlying mechanism works, but there aren't many implementation of Event components. I had the idea of using events to create a Pac-man game with Menge under the hood; I thought it would be a cool demo. Short version: there is little out-of-the-box event infrastructure ready to go.

A thought to keep in the back of your mind, what an agent is up to changes over time. If the change is a function of the agent state, then it belongs in the FSM, if it is due to something that has nothing to do with the agent, it belongs in an event.

JimSEOW commented 7 years ago

@MengeCrowdSim Great job! Now VS2015 works for both win32, x64 and velocities are extracted.

Q1: How could we expose MORE for .NET binding e.g.

    MENGE_API std::string GetAgentStringID(size_t i);
´
    MENGE_API const Menge::Agents::BaseAgent* GetAgentID(size_t i);

    MENGE_API const Menge::Agents::BaseAgent* GetAgentNeighbor(size_t i);

    MENGE_API const Menge::Agents::Obstacle* GetAgentObstacle(size_t i);

=> Possibilities (a) Follow the #define MENGE_API __declspec(dllimport) Implement the corresponding .NET classes for Menge::Agents::BaseAgent and Menge::Agents::Obstacle

=> Most likely ended up with a new project that add additional (interfacing) Manage C++/CLR files to existing C++ MengeCore files [that are compatible with MengeVis and Menge.exe] (b) Alternative1, create a Manage .NET C++ DLL (as you have discussed, it is not ideal for Unity).

For user who only interested in e.g. WinForm I have tried the followings: => Compile the existing MengeCore with Common Language Runtime Support (/clr) support => The resulting MengeCore.DLL can NOW be referenced using WinForm program

(c) Alternative2, create a new Managed C++/CLR class library e.g. MengeNET.dll Include all header and source files from the existing MengeCore.

Please feedback which approach has the team tried? Pros and Cons? Prefer (c) approach, less challenging to expose classes e.g. GoalSelector etc.

MengeCrowdSim commented 7 years ago

Before getting into the discussion, I'll admit to some ignorance. I'm not a long-time C# user and really only investigated combining C++ and C# this year. So, there are going to be avenues for solution with which I'm not familiar (and I may not even be able to comment intelligently). I have to admit, in the options you've enumerated, I don't fully appreciate the distinction between all of the possibilities.

With that disclaimer aside, this is a great discussion to have. You may consider my state as having "strong opinions, weakly held". I'm open to be persuaded. As I consider our options, I feel I have two guiding principles (that are often in conflict):

1) Maximize compatibility. Make sure as long as we're exposing C++, dll functionality, that we're exposing it in a way that can be consumed as broadly as possible. 2) Minimize effort to create/maintain said interface. 3) Given conflict between 1 & 2, favor 1 over 2.

Given those priorities, it should be obvious to infer the logic that went into the current C-api. Many of the options you listed seem to favor 2 over 1 in the name of C# support (and C# win a WinForm context at that). So, it's important to decide that introducing such very specific support provides value to justify the maintenance cost.

In the absence of other arguments, I'd default to extending the menge_c_api.

In the past, I've created the equivalent C++/CLR dll. I ditched it because a) in terms of work, it closely mirrored the current C-api approach, and b) it wasn't compatible with Unity in the current state of affairs.

I'm unfamiliar with the option of just enabling CLR support and making it accessible in a WnForm program. That's nice. If you've got all the functionality you need by doing that, I'd advocate creating new build configurations (Debug CLR and Release CLR) with that flag turned on. And then you can use them directly in C# (this definitely seems to maximize priority 2 without incurring any real support cost).

As for your preference on c) (the C++/CLR approach), I'd like to discuss that some more. Again, in my mind, it entails wrapping all of the C++ details in corresponding C# classes (in order to allow users to do what they need to without using the dreaded "^". Would you concur. Or did you have something else in mind?

JimSEOW commented 7 years ago

@MengeCrowdSim The (c) approach works - although there are issues which I have not completely figure out .

Create a new CLR class library with Common Language Runtime Support (/clr) (default) Change Character Set to : Use Multi-Byte Character Set Copy call header and source files (except menge_c_api.h and menge_c_api.cpp).

Modified the CoreConfig.h slightly

#if defined(_MSC_VER)
  #if defined( MENGE_STATICLIB )
    #define MENGE_API
    #define MATHEXTERN 
  #else
    #if defined( MENGECORE_EXPORTS )
      // We are building the DLL, export the symbols tagged like this
      #define MENGE_API __declspec(dllexport)
      #define MATHEXTERN 
    #elif defined( MENGECORE_NET )
      #define MENGE_API
      #define MATHEXTERN 
    #else
      // If we are consuming the DLL, import the symbols tagged like this
      #define MENGE_API __declspec(dllimport)
      #define MATHEXTERN extern
    #endif
  #endif

To create CLR e.g. MengeNET.dll (turn on MENGECORE_NET ),

#pragma unmanaged
#include <string.h>
#include "MengeCore/CoreConfig.h"

#include "MengeCore/Agents/BaseAgent.h"
#include "MengeCore/Agents/SimulatorInterface.h"
#include "MengeCore/BFSM/FSM.h"
#include "MengeCore/PluginEngine/CorePluginEngine.h"
#include "MengeCore/Runtime/SimulatorDB.h"

#pragma managed
using namespace System;
using namespace System::Runtime::InteropServices;

namespace MengeNET {

        public ref class MengeWrapper
    {
       private:

        Menge::Agents::SimulatorInterface * _simulator = 0x0;

        const char* StringToChar(String^ managedString)
        {
            char* str2 = (char*)(void*)Marshal::StringToHGlobalAnsi(managedString);
            return str2;
        }
      public:
        MengeWrapper()
        {
        }
        ~MengeWrapper()
        {
            delete _simulator;
            _simulator = 0;
        }
        bool InitStimulator(String ^ behaveFileName, String ^ sceneFileName, String ^ modelName, 
                String ^ pluginPathName)
        {
                const char * behaveFile = StringToChar(behaveFileName);
                const char * sceneFile = StringToChar(sceneFileName);
                const char * model = StringToChar(modelName);
                const char * pluginPath = StringToChar(pluginPathName);

                                 const bool VERBOSE = false;
                if (_simulator != 0x0) delete _simulator;
                Menge::SimulatorDB simDB;

                // TODO: Plugin engine is *not* public.  I can't get plugins.
                Menge::PluginEngine::CorePluginEngine engine(&simDB);
                if (pluginPath != 0x0) {
                    //engine.loadPlugins(pluginPath); // <== Can not be compiled [not sure why?)
                }

                Menge::SimulatorDBEntry * simDBEntry = simDB.getDBEntry(std::string(model));
                if (simDBEntry == 0x0) return false;

                size_t agentCount;
                float timeStep = 0.1f;          // Default to 10Hz
                int subSteps = 0;               // take no sub steps
                float duration = 1e6;           // effectively no simulation duration.
                std::string outFile = "";       // Don't write an scb file.
                std::string scbVersion = "";    // No scb version
                bool verbose = false;
                _simulator = simDBEntry->getSimulator(agentCount, timeStep, subSteps, duration,
                    behaveFile, sceneFile, outFile, scbVersion,
                    verbose);

                return _simulator != 0x0;
        }
                 int AgentCount()
        {
            assert(_simulator != 0x0);
            return (int)_simulator->getNumAgents();
        }

        void SetTimeStep(float timeStep)
        {
            assert(_simulator != 0x0);
            _simulator->setTimeStep(timeStep);
        }

        bool DoStep()
        {
            assert(_simulator != 0x0);
            return _simulator->step();
        }

        bool GetAgentPosition(int i, float %x, float %y, float %z)
        {
            assert(_simulator != 0x0);
            Menge::Agents::BaseAgent * agt = _simulator->getAgent(i);
            if (agt != 0x0) {
                x = agt->_pos._x;
                y = _simulator->getElevation(agt);
                z = agt->_pos._y;
                return true;
            }
            return false;
        }

        bool GetAgentVelocity(int i, float %x, float %y, float %z)
        {
            assert(_simulator != 0x0);
            Menge::Agents::BaseAgent * agt = _simulator->getAgent(i);
            if (agt != 0x0) {
                x = agt->_vel._x;
                y = 0; // get elevation
                z = agt->_vel._y;
                return true;
            }
            return false;
        }

        bool GetAgentOrient(int i, float  %x, float  %y)
        {
            assert(_simulator != 0x0);
            Menge::Agents::BaseAgent * agt = _simulator->getAgent(i);
            if (agt != 0x0) {
                x = agt->_orient._x;
                y = agt->_orient._y;
                return true;
            }
            return false;
        }

        int GetAgentClass(int i)
        {
            assert(_simulator != 0x0);
            Menge::Agents::BaseAgent * agt = _simulator->getAgent(i);
            if (agt != 0x0) {
                return static_cast<int>(agt->_class);
            }
            return -1;
        }

        float GetAgentRadius(int i)
        {
            assert(_simulator != 0x0);
            Menge::Agents::BaseAgent * agt = _simulator->getAgent(i);
            if (agt != 0x0) {
                return agt->_radius;
            }
            return -1;
        }
    };
}

=> There is no more need for MengeCS, Only MengeCSExe to call the MengeWrapper from MengeNET.dll (Manage C++/CLI)

           MengeWrapper menge = new MengeWrapper();
            String scene = "4square";
            String mengePath = @"Directory to Menge\";
            String behaveXml = String.Format(@"{0}examples\core\{1}\{1}B.xml", mengePath, scene);
            String sceneXml = String.Format(@"{0}examples\core\{1}\{1}S.xml", mengePath, scene);
            if (menge.InitStimulator(behaveXml, sceneXml, "orca", ""))
            {
                this.listBox1.Items.Add(String.Format("New simulator created."));
                this.listBox1.Items.Add(String.Format("\t{0} agents", menge.AgentCount()));
                for (int i = 0; i < 20; ++i)
                {
                    this.listBox1.Items.Add(String.Format("Step {0}", i + 1));
                    if (!menge.DoStep())
                    {
                        this.listBox1.Items.Add(String.Format("Simulation done...quitting"));
                        break;
                    }
                    for (int a = 0; a < menge.AgentCount(); ++a)
                    {
                        float x = 0;
                        float y = 0;
                        float z = 0;

                        float x1 = 0;
                        float y1 = 0;

                        float x2 = 0;
                        float y2 = 0;
                        float z2 = 0;
                        menge.GetAgentPosition(a, ref x, ref y, ref z);
                        Vector3 p = new Vector3(x, y, z);

                        menge.GetAgentOrient(a, ref x1, ref y1);
                        Vector2 d = new Vector2(x1, y1);

                        menge.GetAgentVelocity(a, ref x2, ref y2, ref z2);
                        Vector3 v = new Vector3(x2, y2, z2);

                        this.listBox1.Items.Add(String.Format("\tAgent {0} at ({1}, {2})  Moving Direction: ({3}, {4} with Velocity {5} m/s Speed: (Vx:{6}, V:y{7}, V:z{8} ))", a, p.X, p.Z, d.X, d.Y, v.Length(), v.X, v.Y, v.Z));
                    }
                }

            }
            else
            {
                System.Console.WriteLine("Error initializing simulation");
            }
MengeCrowdSim commented 7 years ago

If you check out this branch (https://github.com/curds01/MengeCS/tree/advanced_wrapper_cppclr) you can see my initial CLR pass on this from back in...January or so.)

I can see that what you have is sufficient, but in my solution, I'd opted for a more class-centric approach.

MengeCrowdSim commented 7 years ago

@JimSEOW I'd recommend that you fork Menge and MengeCS and create a branch with this stuff. It'll greatly facilitate sharing code and being able to contribute your efforts back.

JimSEOW commented 7 years ago

@MengeCrowdSim thx to the ground work you have done, this makes it possible for me to proceed with the approach (c).

Now, thanks the support, I am able to work with VS2015 x64, to gradually learn the deeper parts of the codes. I am trying to figure out how to "CLR Re-construct" the different classes e.g. FSM, SimulatorInterface, Goal, State, etc. created after passing the two XML files (Scene and Behaviour)

I need advice, how to (1) extract the individual agent's goal; how to define new goal; new guide path from present destination to the next destination, how to raise event, how to convert this event to CLR call back etc. This is a slow process as I learning the code little by little. (2) This is like creating custom Behaviour FSM XML file on the fly and individualized to each agent. Can this be done? (3) How to create the BFSM attributes in C#, through the CLR wrapper to create the BSFM without the XML file.

Need to slowly figure out this.

=> Thanks. Great work! This may take a few months, at least now I see it is more feasible than before.

MengeCrowdSim commented 7 years ago

Before you start down this path, I think it's worth level setting.

Everything in Menge's architecture and design is biased towards consuming specifications, instantiating simulators, and running them. It has not been streamlined to programmatically construct simulators. And, more particularly, hasn't been designed to reconfigure the simulator during simulation.

So, before you go too far, I wonder if you're re-inventing the wheel. What do you hope to do that can't be done by constructing the appropriate XML files? (I can even imagine constructing such an XML file dynamically/programmatically in memory and instantiating a simulator on that.)

So, perhaps we want to take a step back and discuss what you want to do and how best to go about achieving that. It may well be that what you want to do is 100% captured in XML and you don't know it yet.

I'd suggest we put this github issue on hold and have this discussion via email until we have a clear objective. Email at menge@cs.unc.edu.

JimSEOW commented 7 years ago

@MengeCrowdSim Definitely not to re-invent the wheel. My impression is that the current design involve input that is ONE direction. XML files => Simulation for t time => Display.

What I hope to achieve, while Simulation for t time, click randomly on one of the agents, the agent re-evaluate the new information provided through the clicking, based on simple rules, decide one out of a list of available goals, define the (re-calculated) new guide path, move to the new destination NOT PRE-DEFINED by the XML files. Can this be done through the XML files?

For new user who is interested to explore Menge, trial and error is inevitable to gradually learn the architecture, codes, and rationales.

JimSEOW commented 7 years ago

@MengeCrowdSim there are still issues of not being able to compile:

     if (pluginPath != 0x0) {
    //engine.loadPlugins(pluginPath); // <== Can not be compiled [not sure why?)
 }
JimSEOW commented 7 years ago

To AddAgent, one needs AgentInitializer????

newly created AgentInitializer or AgentInitializer that has been populated with information parsed from xml?

MengeCrowdSim commented 7 years ago

1) In the code snippet, I need you to give me a more specific reference of where the bad code snippet is. If I have to go searching, it'll take me a lot longer to provide insight.

2) re: AgentInitializer. The AgentInitializer is responsible for generating agents from XML. It's not strictly required so long as the work done by the AgentInitializer is performed otherwise.

3) Use case: I picked up the following things from what you've written: a. You want user interaction (a user clicks on some visualization of an agent to initiate an event). b. The result of the clicking event causes the agent to select a new goal from an already known set of goals -- this is the "list of available goals" -- and then the agent proceeds toward the goal.

So, this is very doable in the current architecture.

We are definitively missing the event trigger you would need to invoke from your UI program. The only thing we're missing is an event trigger. (It's what your UI program would invoke when you've clicked on an agent.) We might also have to implement some event "targets" and "effects". But that's not a problem.

The goal changing is easy. Depending on whether the candidate goals are known before the simulation starts or not (it's not clear to me from your description), and what the "simple rules" are for picking a new goal, you can use out-of-the box functionality. Worst case, we embed your "simple rules" in a custom GoalSelector implementation in a plug-in.

So, based on what you've described, I believe the correct way to solve this is via XML (with the possibility of some plug-ins).

MengeCrowdSim commented 7 years ago

As per my previous comment, I've just pushed some additions to Menge (#57). This handles the event side of what I discussed above.

alafi commented 6 years ago

@MengeCrowdSim Would you please share the GUI tools so we can take a look at them, specially the one that converts obj file to nav mesh. Thank you.

curds01 commented 6 years ago

The GUI tools aren't official. As such, rather than living under @MengeCrowdSim they live in my personal github.

https://github.com/curds01/MengeUtils

The tool you want, specifically, is objToNavMesh.py. It'll produce a .nav file you can use in Menge. It's slightly brittle; if your obj has non-manifold surfaces (e.g., more than two faces sharing an edge), or other strange artifacts, it may choke and the error messages are lacking. If you encounter these issues, let me know and I can fix it. It's really only documented on the command-line (but it's straightforward: obj mesh in, nav mesh out.) :)