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.