Writing a layout module (AtomsUnreal)

Atoms has several layout modules but you can create new modules with c++.  This example shows how to create a layout module for generating agents along an input curve.

This example is made of two parts:

  • the layout generator responsible for creating the agents

  • a behaviour module responsible for creating the layout generator and attaching it to the agent group

Writing the curve layout generator

The layout generator object generates the description of new agents created at every frame. A generator can be static or dynamic. A static generator creates the agent descriptions only at the first frame, while a dynamic one can generate new agents at different frames.

The generate function creates a vector of AgentInitData objects. This object defines the position, direction, agent type, groupId, scale, sync data of the new agents. The agent group uses this information to create the new agents.

#pragma once #include <Atoms/LayoutGenerators/DynamicLayoutGenerator.h> #include <AtomsUtils/Curve.h> #include <Atoms/AgentTypes.h> class MyCurveLayoutGenerator : public Atoms::DynamicLayoutGenerator { public: MyCurveLayoutGenerator(); virtual ~MyCurveLayoutGenerator(); // the generator function virtual std::vector<Atoms::AgentInitData> generate(double time) #ifdef __APPLE__ override #endif ; inline void setCurve(AtomsUtils::Curve& curve) { m_curve = curve; } inline void setDirection(AtomsCore::Vector3& dir) { m_direction = dir; } inline void setUpVector(AtomsCore::Vector3& dir) { m_upVector = dir; } inline void setNumberOfAgents(size_t value) { m_numberOfAgents = value; } inline void setDepth(size_t value) { m_depth = value; } inline void setDepthSpace(double value) { m_depthSpace = value; } inline void setUseCVs(bool value) { m_useCVs = value; } inline void setDirectionType(short value) { m_directionType = value; } inline void setInvertDirection(bool value) { m_invertDirection = value; } inline void setAgentTypeOverride(const std::map<int, std::string>& value) { m_agentTypeOverride = value; } inline void setDirectionOverride(const std::map<int, AtomsCore::Vector3>& value) { m_directionOverride = value; } inline void setRandomSpace(AtomsCore::Vector3& dir) { m_randomSpace = dir; } inline void setSeed(size_t value) { m_seed = value; } inline const char* typeName() override { return "curveLayout"; }; private: // Input curve AtomsUtils::Curve m_curve; // Agent direction AtomsCore::Vector3 m_direction; // Up vector AtomsCore::Vector3 m_upVector; // Random space AtomsCore::Vector3 m_randomSpace; // Agent direction override std::map<int, AtomsCore::Vector3> m_directionOverride; // Agent type override std::map<int, std::string> m_agentTypeOverride; // Number of agents along the curve size_t m_numberOfAgents; // Number of depth rows size_t m_depth; // Seed size_t m_seed; // Depth row space double m_depthSpace; // Direction type: 0 - tangent, 1 - normal, 2 - custom short m_directionType; // Invert the direction bool m_invertDirection; // Use cvs instead num agents bool m_useCVs; };
#include "MyCurveLayoutGenerator.h" MyCurveLayoutGenerator::MyCurveLayoutGenerator() : Atoms::DynamicLayoutGenerator(), m_direction(1.0, 0.0, 0.0), m_upVector(0.0, 1.0, 0.0), m_randomSpace(0.0, 0.0, 0.0), m_numberOfAgents(0), m_depth(1), m_seed(0), m_depthSpace(0.0), m_directionType(0), m_invertDirection(false), m_useCVs(false) { } MyCurveLayoutGenerator::~MyCurveLayoutGenerator() { } // the generator function std::vector<Atoms::AgentInitData> MyCurveLayoutGenerator::generate(double time) { // Check if is a valid curve if ((m_curve.cvs().size() < 2) || (!m_useCVs && m_numberOfAgents < 1)) return std::vector<Atoms::AgentInitData>(); if (isStaticGenerator()) { srand(m_seed); } else if (time == 0.0) { m_randDynamic.init(m_seed); srand(m_seed); } // Check conditions for dynamic generation if (!checkGeneration(time)) { return std::vector<Atoms::AgentInitData>(); } // Check if constant pattern if (!isStaticGenerator() && resetSeed()) { const auto& fixedData = getData(); if (!fixedData.empty()) { applyOffsetToData(); const auto& updatedData = getData(); return updatedData; } } Atoms::AgentTypes &agTypes = Atoms::AgentTypes::instance(); size_t numberOfAgents = m_useCVs ? m_curve.cvs().size() : m_numberOfAgents; std::vector<Atoms::AgentInitData> data(numberOfAgents * m_depth); float curveStep = m_curve.maxU() / static_cast<float>(std::max(static_cast<size_t>(1), m_numberOfAgents - 1)); auto currentOffset = isStaticGenerator() ? groupIdOffset() : groupIdOffset() + dynamicGroupIdOffset(); for (size_t i = 0; i < numberOfAgents; i++) { float param = static_cast<float>(i) * curveStep; AtomsCore::Vector3f pos(0.0f, 0.0f, 0.0f); AtomsCore::Vector3f direction(1.0f, 0.0f, 0.0f); if (m_useCVs) { auto res = m_curve.closestPoint(m_curve.cvs()[i]); pos = m_curve.cvs()[i]; param = res.first; } else { // set the agent position pos = m_curve.pointOnCurve(param); } switch (m_directionType) { case 0: { direction = m_curve.tangentOnCurve(param); break; } case 1: { direction = m_curve.tangentOnCurve(param); direction = direction.cross(m_upVector); break; } default: { direction = m_direction; break; } } if (m_invertDirection) { direction *= -1.0; } direction.normalize(); for (size_t dp = 0; dp < m_depth; ++dp) { AtomsCore::Rand32 rand32(rand() + i * m_depth + dp); AtomsCore::Vector3 randomOffset((rand32.nextf() * 2.0 - 1.0) * m_randomSpace.x, (rand32.nextf() * 2.0 - 1.0) * m_randomSpace.y, (rand32.nextf() * 2.0 - 1.0) * m_randomSpace.z); AtomsCore::Vector3 offsetVector = m_directionType == 0 ? direction.cross(m_upVector) : direction; offsetVector.normalize(); Atoms::AgentInitData& agentData = data[i * m_depth + dp]; agentData.position = pos - offsetVector * m_depthSpace * ((m_depth - 1) * 0.5) + offsetVector * m_depthSpace * dp; agentData.position += direction * randomOffset.x + m_upVector * randomOffset.y + offsetVector * randomOffset.z; agentData.groupId = i * m_depth + dp + currentOffset; auto dIt = m_directionOverride.find(agentData.groupId); if (m_directionType == 2 && dIt != m_directionOverride.end()) { agentData.direction = dIt->second; if (m_invertDirection) agentData.direction *= -1.0; agentData.direction.normalize(); } else agentData.direction = direction; agentData.layoutName = name(); // set the agent local id, this must be unique agentData.upDirection = m_upVector; // set the agent type checking if there is any override auto atIt = m_agentTypeOverride.find(agentData.groupId); if (atIt != m_agentTypeOverride.end()) { agentData.agentType = agTypes.agentType(atIt->second); } else { agentData.agentType = pickAgentType(); } } } if (!isStaticGenerator()) { // add the id offset if it is in dynamic mode addDynamicGroupIdOffset(data.size()); setData(data, true); } return data; }

 


Writing the curve layout module

To use the curve layout generator a new behaviour module is needed. This behaviour module exposes the generator attributes to the user and sends all the data to the generator itself.

#pragma once #include <Atoms/BehaviourModule.h> class MyCurveLayoutModule : public Atoms::BehaviourModule { public: MyCurveLayoutModule(); ~MyCurveLayoutModule(); virtual void agentsCreated(const std::vector<Atoms::Agent*>& agents, Atoms::AgentGroup* agentGroup = nullptr); virtual void preFrame(Atoms::AgentGroup* agentGroup = nullptr); virtual void initSimulation(Atoms::AgentGroup* agentGroup = nullptr); static Atoms::BehaviourModule* creator(const std::string& parameter); };

Then this si the behaviour component. It is the bridge between the unreal and Atoms world. It exposes the module attributes to Unreal.

 

Finally, the new module must be registered to atoms. This can be done inside the module startup method.

Copyright © 2017, Toolchefs LTD.