In this section we
are going to create a new behaviour module. This behaviour module is a simple follow target module.
Writing the behaviour module
First thing to do is create a Create a new class and hinerit inherit from the base Atoms::BehaviourModule class. With Define a a constructor, a desctructor destructor and a static creator function used (needed by atoms Atoms to create an isntance instance of this module).
Code Block | ||
---|---|---|
| ||
class FollowTargetModule: public Atoms::BehaviourModule
{
public:
FollowTargetModule()
{}
virtual ~FollowTargetModule()
{}
static Atoms::BehaviourModule* creator(const std::string& parameter);
};
Atoms::BehaviourModule* FollowTargetModule::creator(const std::string& parameter)
{
return new FollowTargetModule();
} |
Lets add some attributes to our node. In this case we For this module we'll need only a target position attribute. The attributes of the modules module are stored inside a MapMetadata object, and you can add new attribute attributes using the addAttribute function. So lets add this attribute Attributes should always be added inside the constructor.
The third attribute of the addAttribute method defines if the attribute is overridable per agent or not. In this example we'll make it overridable.
Code Block | ||
---|---|---|
| ||
FollowTargetModule(): Atoms::BehaviourModule() { AtomsCore::Vector3Metadata targetPosition(AtomsCore::Vector3(0.0,0.0,0.0)); // The last argumet specify if this attribute can be override per agent addAttribute("targetPosition", &targerPositiontargetPosition, true); } |
Now we need to compute the new agent direction at each frame before the agent pose is computed, so we need to override . This will be done inside the initFrame functionmethod.You can
override other function linke the endFrame or initSimulation or agentsCreated but in this case we need only the initFrameFor this example we won't need to override any other method.
Code Block | ||
---|---|---|
| ||
void initFrame(const std::vector<Atoms::Agent*>& agents, Atoms::AgentGroup* agentGroup = nullptr) { } |
In the init frame we need to get our initFrame function get the targetPosition value and , iterate over each agent to get read the position and set the their new direction.
Code Block | ||
---|---|---|
| ||
void initFrame(const std::vector<Atoms::Agent*>& agents, Atoms::AgentGroup* agentGroup = nullptr) { // Get the target position from the moduel attribute AtomsCore::MapMetadata& attributeMap = attributes(); AtomsCore::Vector3& targetPosition = attributeMap.getTypedEntry<AtomsCore::Vector3Metadata>("targetPosition")->get(); // iterate over each agent for(Atoms::Agent* agent: agents) { if (!agent) continue; //get the agent position AtomsPtr<AtomsCore::Vector3Metadata> positionPtr = agent->metadata().getTypedEntry<AtomsCore::Vector3Metadata>("position")->get(); AtomsPtr<AtomsCore::Vector3Metadata> directionPtr = agent->metadata().getTypedEntry<AtomsCore::Vector3Metadata>("direction")->get(); if(!positionPtr || !directionPtr ) continue; // compute the new direction directionPtr->set((targetPosition - positionPtr->get()).normalized()); } } |
The target position attribute
as overraidableis overridable per agent, but
we didn't putthe code to handle it
. So let's add nowisn't there yet. When you override an attribute,
Atoms creates an entry using the agent group id inside the mapmetadata saved as AttributeName_overridea new map metadata named "AttributeName_override" is created. The keys of this map metadata are the agent group ids, but they will be present only when the user specifies an override from inside his 3D application.
In order to know if an agent has an override, please check the metadata map. If it's not there, please use the global attribute.
Code Block | ||
---|---|---|
| ||
void initFrame(const std::vector<Atoms::Agent*>& agents, Atoms::AgentGroup* agentGroup = nullptr) { // Get the target position from the moduel attribute AtomsCore::MapMetadata& attributeMap = attributes(); AtomsCore::Vector3& targetPosition = attributeMap.getTypedEntry<AtomsCore::Vector3Metadata>("targetPosition")->get(); //get the override map metadata AtomsPtr<AtomsCore::MapMetadata> targetPositiontargetPositionOverride = attributeMap.getTypedEntry<AtomsCore::MapMetadata>("targetPosition_override"); // iterate over each agent for(Atoms::Agent* agent: agents) { if (!agent) continue; //get the agent position AtomsPtr<AtomsCore::Vector3Metadata> positionPtr = agent->metadata().getTypedEntry<AtomsCore::Vector3Metadata>("position"); AtomsPtr<AtomsCore::Vector3Metadata> directionPtr = agent->metadata().getTypedEntry<AtomsCore::Vector3Metadata>("direction"); if(!positionPtr || !directionPtr ) continue; const std::string& groupIdStr = agent->groupIdStr()->get(); //check if the agent is inside the override map AtomsPtr<AtomsCore::Vector3Metadata> overrideTargetPos = targetPositionOverride->getTypedEntry<AtomsCore::Vector3Metadata>(groupIdStr); if (overrideTargetPos) { // compute the new direction directionPtr->set((overrideTargetPos->get() - positionPtr->get()).normalized()); } else { // compute the new direction directionPtr->set((targetPosition - positionPtr->get()).normalized()); } } } |
If you want improve the performance of the module you can add tbb to parallelize the main for loop.
Code Block | ||
---|---|---|
| ||
void initFrame(const std::vector<Atoms::Agent*>& agents, Atoms::AgentGroup* agentGroup = nullptr) { // Get the target position from the moduel attribute AtomsCore::MapMetadata& attributeMap = attributes(); AtomsCore::Vector3& targetPosition = attributeMap.getTypedEntry<AtomsCore::Vector3Metadata>("targetPosition")->get(); //get the override map metadata AtomsPtr<AtomsCore::MapMetadata> targetPositionOverride = attributeMap.getTypedEntry<AtomsCore::MapMetadata>("targetPosition_override"); // iterate over each agent AtomsUtils::parallel_for(AtomsUtils::ParallelForRange(0, agents.size()), [&](const AtomsUtils::ParallelForRange& r TASK_PARTITION_EXTRA_ARGS) { for (size_t i = r.begin(); i < r.end(); i++) { Atoms::Agent* agent = agents[i]; if (!agent) continue; //get the agent position /* You can also access the direction ad position metadata direcly AtomsPtr<AtomsCore::Vector3Metadata>& positionPtr = agent->position(); AtomsPtr<AtomsCore::Vector3Metadata>& directionPtr = agent->direction(); */ AtomsPtr<AtomsCore::Vector3Metadata> positionPtr = agent->metadata().getTypedEntry<AtomsCore::Vector3Metadata>("directionposition")->get(; AtomsPtr<AtomsCore::Vector3Metadata> directionPtr = agent->metadata().getTypedEntry<AtomsCore::Vector3Metadata>("direction"); if(!positionPtr || !directionPtr ) continue; const std::string& groupIdStr = agent->groupIdStr()->get(); //check if the agent is inside the override map AtomsPtr<AtomsCore::Vector3Metadata> overrideTargetPos = targetPositionOverride->getTypedEntry<AtomsCore::Vector3Metadata>(groupIdStr); if (overrideTargetPos) { // compute the new direction directionPtr->set((overrideTargetPos->get() - positionPtr->get()).normalized()); } else { // compute the new direction directionPtr->set((targetPosition - positionPtr->get()).normalized()); } } }); } |
Add visual debugging options
Lets implement a the draw function to visually debug the direction vector. Add a std::vector<AtomsCore::Vector3> m_drawLines to the class to store the lines data.
Inside the preDraw function you can collect the data form the agentse because it is called only if the time changes.
Then inside the draw function you can finally call the draw lines function to draw some lines.
Code Block | ||
---|---|---|
| ||
void preDraw(Atoms::DrawContext* context, const std::vector<Atoms::Agent*>& agents, Atoms::AgentGroup* agentGroup) { // The pre draw function is called only on time or attribute change, // so it's safe to collect the data from the agents here m_drawLines.resize(agents.size()*2); for (size_t i = 0; i < agents.size(); i++) { Atoms::Agent* agent = agents[i]; //get the agent position AtomsPtr<AtomsCore::Vector3Metadata> positionPtr = agent->metadata().getTypedEntry<AtomsCore::Vector3Metadata>("position"); AtomsPtr<AtomsCore::Vector3Metadata> directionPtr = agent->metadata().getTypedEntry<AtomsCore::Vector3Metadata>("direction"); if(!positionPtr || !directionPtr ) continue; m_drawLines[i*2] = positionPtr->get(); m_drawLines[i*2 + 1] = positionPtr->get() + directionPtr0>get(); } } void draw(Atoms::DrawContext* context, const std::vector<Atoms::Agent*>& agents, Atoms::AgentGroup* agentGroup) { // the draw function is called when the dcc update the viewports context->lines(m_drawLines); } |
Register the behaviour module
Now the behaviour module must be added to the behaviour module factory.
Code Block | ||
---|---|---|
| ||
extern "C"
{
ATOMSPLUGIN_EXPORT bool initializePlugin()
{
AtomsUtils::Logger::info() << "Loading atoms test plugin";
return true;
}
ATOMSPLUGIN_EXPORT bool uninitializePlugin()
{
AtomsUtils::Logger::info() << "Unloading atoms test plugin";
return true;
}
ATOMSPLUGIN_EXPORT bool initializeScene()
{
AtomsUtils::Logger::info() << "Loading atoms test scene plugin";
Atoms::BehaviourModules& moduleManager = Atoms::BehaviourModules::instance();
moduleManager.registerBehaviourModule("SimpleFollowTarget", &FollowTargetModule::creator, Atoms::BehaviourModules::kNative,true, "MyCategory");
return true;
}
ATOMSPLUGIN_EXPORT bool uninitializeScene()
{
Atoms::BehaviourModules& moduleManager = Atoms::BehaviourModules::instance();
moduleManager.deregisterBehaviourModule("SimpleFollowTarget");
return true;
}
}
|
Compile the behaviour module
Use this cmake file as template, change the project name and the source files and set the ATOMS_MAYA_FOLDER or ATOMS_HOUDINI_FOLDER env.
Code Block |
---|
cmake_minimum_required (VERSION 3.17)
project ("MyAtomsPlugin")
if (MSVC)
cmake_policy(SET CMP0091 NEW)
endif()
enable_language(CXX)
set(CMAKE_CXX_STANDARD 14)
add_definitions(-DBUILD_ATOMSPLUGIN)
add_definitions(-DTBB_USE_DEBUG=0)
add_definitions(-D__TBB_LIB_NAME=tbb)
add_definitions(-DNOMINMAX)
add_definitions(-DBOOST_ALL_NO_LIB)
if (UNIX)
add_definitions(-DLINUX)
add_definitions(-DPIC)
add_definitions(-D_FILE_OFFSET_BITS=64)
add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0)
add_compile_options(-std=c++14 -ffloat-store -fvisibility=hidden -fPIC)
endif()
if(DEFINED ATOMS_MAYA_FOLDER)
include_directories(${ATOMS_MAYA_FOLDER}/include)
link_directories(${ATOMS_MAYA_FOLDER}/lib/${MAYA_MAJOR_VERSION})
set(ATOMS_CUSTOM_PLUGINS_FOLDER ${ATOMS_MAYA_FOLDER}/plugins/${MAYA_MAJOR_VERSION})
endif()
if(DEFINED ATOMS_HOUDINI_FOLDER)
include_directories(${ATOMS_HOUDINI_FOLDER}/include)
link_directories(${ATOMS_HOUDINI_FOLDER}/lib)
set(ATOMS_CUSTOM_PLUGINS_FOLDER ${ATOMS_HOUDINI_FOLDER}/plugins)
endif()
include_directories(include)
set(SOURCES
"MyAtomsPlugin.cpp")
add_library(${PROJECT_NAME} SHARED ${SOURCES})
if (MSVC)
set_property(TARGET ${PROJECT_NAME} PROPERTY
MSVC_RUNTIME_LIBRARY MultiThreadedDLL)
endif()
set_target_properties(${PROJECT_NAME}
PROPERTIES
INSTALL_RPATH_USE_LINK_PATH True)
target_link_libraries(${PROJECT_NAME}
PUBLIC Atoms AtomsCore AtomsGraph AtomsUtils AtomsMath
)
install(TARGETS ${PROJECT_NAME} DESTINATION ${ATOMS_CUSTOM_PLUGINS_FOLDER}) |
Use the behaviour module
Add to the ATOMS_PLUGINS_PATH environment variable, the folder where the plugin is located. Then the module is automatically loaded when you load atoms.
Final code
Code Block | ||
---|---|---|
| ||
#include <Atoms/BehaviourModule.h>
#include <Atoms/Agent.h>
#include <Atoms/AgentGroup.h>
#include <AtomsUtils/Logger.h>
#include <Atoms/GlobalNames.h>
#include <AtomsCore/Metadata/IntMetadata.h>
#include <AtomsCore/Metadata/Vector3Metadata.h>
#include <Atoms/BehaviourModules.h>
#include <Atoms/DrawContext.h>
#include <AtomsUtils/TaskScheduler.h>
#include <algorithm>
class FollowTargetModule : public Atoms::BehaviourModule
{
public:
FollowTargetModule(): Atoms::BehaviourModule()
{
AtomsCore::Vector3Metadata targetPosition(AtomsCore::Vector3(0.0,0.0,0.0));
// The last argumet specify if this attribute can be override per agent
addAttribute("targetPosition", &targetPosition, true);
}
~FollowTargetModule()
{
}
void initFrame(const std::vector<Atoms::Agent*>& agents, Atoms::AgentGroup* agentGroup = nullptr)
{
// Get the target position from the moduel attribute
AtomsCore::MapMetadata& attributeMap = attributes();
AtomsCore::Vector3& targetPosition = attributeMap.getTypedEntry<AtomsCore::Vector3Metadata>("targetPosition")->get();
//get the override map metadata
AtomsPtr<AtomsCore::MapMetadata> targetPositionOverride = attributeMap.getTypedEntry<AtomsCore::MapMetadata>("targetPosition_override");
// iterate over each agent
AtomsUtils::parallel_for(AtomsUtils::ParallelForRange(0, agents.size()),
[&](const AtomsUtils::ParallelForRange& r TASK_PARTITION_EXTRA_ARGS)
{
for (size_t i = r.begin(); i < r.end(); i++)
{
Atoms::Agent* agent = agents[i];
if (!agent)
continue;
//get the agent position
/* You can also access the direction ad position metadata direcly with
AtomsPtr<AtomsCore::Vector3Metadata>& positionPtr = agent->position();
AtomsPtr<AtomsCore::Vector3Metadata>& directionPtr = agent->direction();
*/
AtomsPtr<AtomsCore::Vector3Metadata> positionPtr = agent->metadata().getTypedEntry<AtomsCore::Vector3Metadata>("position");
AtomsPtr<AtomsCore::Vector3Metadata> directionPtr = agent->metadata().getTypedEntry<AtomsCore::Vector3Metadata>("direction");
if(!positionPtr || !directionPtr )
continue;
//convert the groupId to a string since the map metadata use only strings as key
const std::string& groupIdStr = agent->groupIdStr()->get();
//check if the agent is inside the override map
AtomsPtr<AtomsCore::Vector3Metadata> overrideTargetPos = targetPositionOverride->getTypedEntry<AtomsCore::Vector3Metadata>(groupIdStr);
if (overrideTargetPos)
{
// compute the new direction
directionPtr->set((overrideTargetPos->get() - positionPtr->get()).normalized());
}
else
{
// compute the new direction
directionPtr->set((targetPosition - positionPtr->get()).normalized());
}
}
});
}
void preDraw(Atoms::DrawContext* context, const std::vector<Atoms::Agent*>& agents, Atoms::AgentGroup* agentGroup)
{
// The pre draw function is called only when the time is changed or some attribute is changed so
// we can collect the data from the agents here
m_drawLines.resize(agents.size()*2);
for (size_t i = 0; i < agents.size(); i++)
{
Atoms::Agent* agent = agents[i];
//get the agent position
AtomsPtr<AtomsCore::Vector3Metadata> positionPtr = agent->metadata().getTypedEntry<AtomsCore::Vector3Metadata>("position");
AtomsPtr<AtomsCore::Vector3Metadata> directionPtr = agent->metadata().getTypedEntry<AtomsCore::Vector3Metadata>("direction");
if(!positionPtr || !directionPtr ) continue;
m_drawLines[i*2] = positionPtr->get();
m_drawLines[i*2 + 1] = positionPtr->get() + directionPtr->get();
}
}
void draw(Atoms::DrawContext* context, const std::vector<Atoms::Agent*>& agents, Atoms::AgentGroup* agentGroup)
{
// the draw function is called when the dcc update the viewports
context->lines(m_drawLines);
}
static Atoms::BehaviourModule* creator(const std::string& parameter);
private:
std::vector<AtomsCore::Vector3> m_drawLines;
};
Atoms::BehaviourModule* FollowTargetModule::creator(const std::string& parameter)
{
return new FollowTargetModule();
}
extern "C"
{
ATOMSPLUGIN_EXPORT bool initializePlugin()
{
AtomsUtils::Logger::info() << "Loading atoms plugin";
return true;
}
ATOMSPLUGIN_EXPORT bool uninitializePlugin()
{
AtomsUtils::Logger::info() << "Unloading atoms plugin";
return true;
}
ATOMSPLUGIN_EXPORT bool initializeScene()
{
AtomsUtils::Logger::info() << "Loading atoms scene plugin";
Atoms::BehaviourModules& moduleManager = Atoms::BehaviourModules::instance();
moduleManager.registerBehaviourModule("SimpleFollowTarget", &FollowTargetModule::creator, Atoms::BehaviourModules::kNative,true, "MyCategory");
return true;
}
ATOMSPLUGIN_EXPORT bool uninitializeScene()
{
Atoms::BehaviourModules& moduleManager = Atoms::BehaviourModules::instance();
moduleManager.deregisterBehaviourModule("SimpleFollowTarget");
return true;
}
}
|