Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

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

Image RemovedImage Added

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.

Code Block
languagecpp
#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;

};
Code Block
languagecpp
#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.

Code Block
languagecpp
#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);
};
Code Block
languagecpp
#include "MyCurveLayoutModule.h"
#include "MyCurveLayoutGenerator.h"
#include <Atoms/AgentTypes.h>
#include <Atoms/AgentGroup.h>
#include <Atoms/GlobalNames.h>
#include <AtomsCore/Metadata/IntMetadata.h>
#include <AtomsCore/Metadata/Vector3ArrayMetadata.h>
#include <AtomsCore/Metadata/StringMetadata.h>
#include <AtomsCore/Metadata/CurveMetadata.h>
#include <AtomsCore/Metadata/BoolMetadata.h>
#include <AtomsCore/Metadata/DoubleMetadata.h>
#include <AtomsCore/Metadata/StringArrayMetadata.h>
#include <AtomsCore/Metadata/UIntArrayMetadata.h>
#include <AtomsCore/Metadata/ArrayMetadata.h>
#include <AtomsCore/Metadata/Vector2Metadata.h>
#include <Atoms/Graph/Operators/BindPoseOperator.h>
#include <Atoms/Graph/Operators/PelvisOperator.h>
#include <AtomsUtils/TaskScheduler.h>

MyCurveLayoutModule::MyCurveLayoutModule() :
	Atoms::BehaviourModule()
{
	AtomsCore::MapMetadata& metadata = attributes();
	AtomsCore::StringMetadata atype("man");
	addAttribute("agentType", &atype, true);
	AtomsCore::StringMetadata tooltipAType("The agent type name");
	addAttributeProperty("agentType", Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TOOLTIP_KEY, &tooltipAType);

	AtomsCore::CurveMetadata curve;
	metadata.addEntry("curve", &curve);
	AtomsCore::StringMetadata tooltipCurve("The reference curve");
	addAttributeProperty("curve", Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TOOLTIP_KEY, &tooltipCurve);

	AtomsCore::IntMetadata numAgents(10);
	metadata.addEntry("numAgents", &numAgents);
	AtomsCore::StringMetadata tooltipNumAgents("Number of agents");
	addAttributeProperty("numAgents", Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TOOLTIP_KEY, &tooltipNumAgents);

	AtomsCore::IntMetadata depth(1);
	metadata.addEntry("depth", &depth);
	AtomsCore::StringMetadata tooltipDepth("Number of rows");
	addAttributeProperty("depth", Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TOOLTIP_KEY, &tooltipDepth);

	AtomsCore::DoubleMetadata depthSpace(50.0);
	metadata.addEntry("depthSpace", &depthSpace);
	AtomsCore::StringMetadata tooltipDepthSpace("Space between each row");
	addAttributeProperty("depthSpace", Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TOOLTIP_KEY, &tooltipDepthSpace);

	AtomsCore::BoolMetadata useCvs(false);
	metadata.addEntry("useCVs", &useCvs);
	AtomsCore::StringMetadata tooltipUseCvs("Creates the agents on the curve cvs instead of using the numAgents attribute");
	addAttributeProperty("useCVs", Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TOOLTIP_KEY, &tooltipUseCvs);

	AtomsCore::IntMetadata dirType(0);
	metadata.addEntry("directionType", &dirType);
	AtomsCore::StringMetadata tooltipDirType("0-tangent, 1-normal, 2-custom direction");
	addAttributeProperty("directionType", Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TOOLTIP_KEY, &tooltipDirType);

	AtomsCore::BoolMetadata invDir(false);
	metadata.addEntry("invertDirection", &invDir);
	AtomsCore::StringMetadata tooltipInvDir("Invert the agent direction");
	addAttributeProperty("invertDirection", Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TOOLTIP_KEY, &tooltipInvDir);

	AtomsCore::Vector3 dirVec(1.0, 0.0, 0.0);
	AtomsCore::Vector3Metadata dir(dirVec);
	addAttribute("direction", &dir, true);
	AtomsCore::StringMetadata tooltipDir("The custom direction");
	addAttributeProperty("direction", Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TOOLTIP_KEY, &tooltipDir);

	AtomsCore::Vector3 upVec(0.0, 1.0, 0.0);
	AtomsCore::Vector3Metadata up(upVec);
	//metadata.addEntry("direction", &dir);
	addAttribute("upVector", &up, false);
	AtomsCore::StringMetadata tooltipUp("Agent up vector");
	addAttributeProperty("upVector", Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TOOLTIP_KEY, &tooltipUp);

	AtomsCore::IntMetadata seed(0);
	metadata.addEntry("seed", &seed);
	AtomsCore::StringMetadata tooltipSeed("Random seed");
	addAttributeProperty("seed", Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TOOLTIP_KEY, &tooltipSeed);

	AtomsCore::Vector3 randomSpaceVec(0.0, 0.0, 0.0);
	AtomsCore::Vector3Metadata randomSpace(randomSpaceVec);
	//metadata.addEntry("direction", &dir);
	addAttribute("randomSpace", &randomSpace, false);
	AtomsCore::StringMetadata tooltipRandomSpace("Max spacing variation from point on curve");
	addAttributeProperty("randomSpace", Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TOOLTIP_KEY, &tooltipRandomSpace);

	AtomsCore::IntMetadata groupIdOffset(0);
	addAttribute("groupIdOffset", &groupIdOffset);
	AtomsCore::StringMetadata tooltipGroupIdOffset("Applies the offset to the agent ids if there are multiple layout modules");
	addAttributeProperty("groupIdOffset", Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TOOLTIP_KEY, &tooltipGroupIdOffset);

	AtomsCore::BoolMetadata isDynamic(false);
	addAttribute("isDynamic", &isDynamic);
	AtomsCore::StringMetadata tooltipDynamic("If true, generate agents dynamically");
	addAttributeProperty("isDynamic", Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TOOLTIP_KEY, &tooltipDynamic);

	AtomsCore::IntMetadata dynamicMethod(0);
	addAttribute("dynamicMethod", &dynamicMethod);
	AtomsCore::StringMetadata tooltipMethod("The method to be used for dynamic agent generation: Timer (0), Gnerator Trigger (1)");
	addAttributeProperty("dynamicMethod", Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TOOLTIP_KEY, &tooltipMethod);

	AtomsCore::Vector2Metadata timer(AtomsCore::Vector2(1.0, 100.0));
	addAttribute("timer", &timer);
	AtomsCore::StringMetadata tooltipTimer("Range to pick a random value of frames to trigger a new generation of agents (if the module is dynamic and dynamic method is by timer)");
	addAttributeProperty("timer", Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TOOLTIP_KEY, &tooltipTimer);

	AtomsCore::BoolMetadata generatorTrigger(false);
	addAttribute("generatorTrigger", &generatorTrigger);
	AtomsCore::StringMetadata tooltipGenerateFlag("Flag to trigger a new generation of agents (if the module is dynamic and dynamic method is by Generator Trigger)");
	addAttributeProperty("generatorTrigger", Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TOOLTIP_KEY, &tooltipGenerateFlag);

	AtomsCore::BoolMetadata resetSeed(false);
	addAttribute("resetSeed", &resetSeed);
	AtomsCore::StringMetadata tooltipResetSeed("If true, reset the seed at each generation (only used if the module is dynamic)");
	addAttributeProperty("resetSeed", Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TOOLTIP_KEY, &tooltipResetSeed);

	AtomsCore::BoolMetadata generateOnInitSimulation(true);
	addAttribute("generateOnInitSimulation", &generateOnInitSimulation);
	AtomsCore::StringMetadata tooltipForceGenerationOnInitSimulation("If true, generate agents at first frame (only used if the module is dynamic)");
	addAttributeProperty("generateOnInitSimulation", Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TOOLTIP_KEY, &tooltipForceGenerationOnInitSimulation);

	AtomsCore::BoolMetadata randomizeAgentTypes(false);
	addAttribute("randomizeAgentTypes", &randomizeAgentTypes);
	AtomsCore::StringMetadata tooltipRandomizeAgentTypes("If true, pick random agent type on dynamic generations (only if the module is dynamic)");
	addAttributeProperty("randomizeAgentTypes", Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TOOLTIP_KEY, &tooltipRandomizeAgentTypes);


	std::vector<std::string> rndomAgentTypesVector;
	AtomsCore::StringArrayMetadata randomAgentTypes(rndomAgentTypesVector);
	addAttribute("randomAgentTypes", &randomAgentTypes);
	AtomsCore::StringMetadata tooltipAgentTypes("List of agent agent types to be used on dynamic generations (only if the module is dynamic)");
	addAttributeProperty("randomAgentTypes", Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TOOLTIP_KEY, &tooltipAgentTypes);
}

MyCurveLayoutModule::~MyCurveLayoutModule()
{}

void MyCurveLayoutModule::agentsCreated(const std::vector<Atoms::Agent*>& agents, Atoms::AgentGroup* agentGroup)
{
	AtomsUtils::parallel_for(AtomsUtils::ParallelForRange(0, agents.size()),
		[&](const AtomsUtils::ParallelForRange& r TASK_PARTITION_EXTRA_ARGS)
	{
		for (unsigned int i = r.begin(); i < r.end(); i++)
		{
			Atoms::AgentBehaviourNetwork& network = agents[i]->network();
			Atoms::PelvisOperator* pelvisOperator = static_cast<Atoms::PelvisOperator*>(network.manager().getNode("pelvisOperator"));
			Atoms::BindPoseOperator* bindPoseOperator = static_cast<Atoms::BindPoseOperator*>(network.manager().getNode("bindPoseOperator"));
			if (pelvisOperator || bindPoseOperator)
				continue;

			bindPoseOperator = static_cast<Atoms::BindPoseOperator*>(network.manager().createNode(Atoms::BindPoseOperator::staticTypeStr(), "bindPoseOperator"));
			if (!bindPoseOperator)
				continue;
			bindPoseOperator->setAgent(agents[i]);
			network.setBuildPoseNode(bindPoseOperator);
		}
	});
}

void MyCurveLayoutModule::preFrame(Atoms::AgentGroup* agentGroup)
{
	AtomsCore::MapMetadata& metadata = attributes();
	AtomsPtr<AtomsCore::BoolMetadata> isDynamicPtr = metadata.getTypedEntry<AtomsCore::BoolMetadata>("isDynamic");

	if (isDynamicPtr->value())
	{
		auto layoutGenerator = agentGroup->getLayoutGenerator(name());
		if (layoutGenerator)
		{
			AtomsPtr<MyCurveLayoutGenerator> curveLayoutPtr = std::static_pointer_cast<MyCurveLayoutGenerator>(layoutGenerator);
			if (curveLayoutPtr)
			{
				AtomsPtr<AtomsCore::IntMetadata> dynamicMethodPtr = metadata.getTypedEntry<AtomsCore::IntMetadata>("dynamicMethod");
				AtomsPtr<AtomsCore::Vector2Metadata> timerPtr = metadata.getTypedEntry<AtomsCore::Vector2Metadata>("timer");
				AtomsPtr<AtomsCore::BoolMetadata> generatorTriggerPtr = metadata.getTypedEntry<AtomsCore::BoolMetadata>("generatorTrigger");
				curveLayoutPtr->setRandomTimer(timerPtr->value());
				curveLayoutPtr->setGeneratorTrigger(generatorTriggerPtr->value());

				if (dynamicMethodPtr->value() == Atoms::DynamicLayoutGenerator::kTimer)
					curveLayoutPtr->decreaseTimeToGenerate();
			}
		}
	}
}

void MyCurveLayoutModule::initSimulation(Atoms::AgentGroup* agentGroup)
{

	AtomsCore::MapMetadata& metadata = attributes();

	AtomsPtr<AtomsCore::CurveMetadata> curvePtr = metadata.getTypedEntry<AtomsCore::CurveMetadata>("curve");
	AtomsPtr<AtomsCore::IntMetadata> numAgentsPtr = metadata.getTypedEntry<AtomsCore::IntMetadata>("numAgents");
	AtomsPtr<AtomsCore::IntMetadata> depthPtr = metadata.getTypedEntry<AtomsCore::IntMetadata>("depth");
	auto depthSpacePtr = metadata.getTypedEntry<AtomsCore::DoubleMetadata>("depthSpace");
	AtomsPtr<AtomsCore::BoolMetadata> useCVsPtr = metadata.getTypedEntry<AtomsCore::BoolMetadata>("useCVs");
	AtomsPtr<AtomsCore::IntMetadata> directionTypePtr = metadata.getTypedEntry<AtomsCore::IntMetadata>("directionType");
	AtomsPtr<AtomsCore::BoolMetadata> invertDirectionPtr = metadata.getTypedEntry<AtomsCore::BoolMetadata>("invertDirection");
	AtomsPtr<AtomsCore::Vector3Metadata> directionPtr = metadata.getTypedEntry<AtomsCore::Vector3Metadata>("direction");
	AtomsPtr<AtomsCore::Vector3Metadata> upPtr = metadata.getTypedEntry<AtomsCore::Vector3Metadata>("upVector");
	AtomsPtr<AtomsCore::StringMetadata> agentTypePtr = metadata.getTypedEntry<AtomsCore::StringMetadata>("agentType");
	AtomsPtr<AtomsCore::Vector3Metadata> randomSpacePtr = metadata.getTypedEntry<AtomsCore::Vector3Metadata>("randomSpace");
	AtomsPtr<AtomsCore::IntMetadata> seedPtr = metadata.getTypedEntry<AtomsCore::IntMetadata>("seed");
	AtomsPtr<AtomsCore::IntMetadata> groupIdOffsetPtr = metadata.getTypedEntry<AtomsCore::IntMetadata>("groupIdOffset");
	AtomsPtr<AtomsCore::BoolMetadata> isDynamicPtr = metadata.getTypedEntry<AtomsCore::BoolMetadata>("isDynamic");
	AtomsPtr<AtomsCore::IntMetadata> dynamicMethodPtr = metadata.getTypedEntry<AtomsCore::IntMetadata>("dynamicMethod");
	AtomsPtr<AtomsCore::Vector2Metadata> timerPtr = metadata.getTypedEntry<AtomsCore::Vector2Metadata>("timer");
	AtomsPtr<AtomsCore::BoolMetadata> generatorTriggerPtr = metadata.getTypedEntry<AtomsCore::BoolMetadata>("generatorTrigger");
	AtomsPtr<AtomsCore::BoolMetadata> generateOnInitSimulationPtr = metadata.getTypedEntry<AtomsCore::BoolMetadata>("generateOnInitSimulation");
	AtomsPtr<AtomsCore::BoolMetadata> randomizeAgentTypesPtr = metadata.getTypedEntry<AtomsCore::BoolMetadata>("randomizeAgentTypes");
	AtomsPtr<AtomsCore::StringArrayMetadata> randomAgentTypesPtr = metadata.getTypedEntry<AtomsCore::StringArrayMetadata>("randomAgentTypes");
	AtomsPtr<AtomsCore::BoolMetadata> resetSeedPtr = metadata.getTypedEntry<AtomsCore::BoolMetadata>("resetSeed");

	Atoms::AgentTypePtr aType = Atoms::AgentTypes::instance().agentType(agentTypePtr->value());
	if (!aType)
	{
		if (!(isDynamicPtr->value() && randomizeAgentTypesPtr->value() && randomAgentTypesPtr->get().size() > 0))
		{
			AtomsUtils::Logger::warning() << "CurveLayout::initSimulation no agent type found!";
			return;
		}
	}

	AtomsPtr<AtomsCore::MapMetadata> atypeOverride = metadata.getTypedEntry<AtomsCore::MapMetadata>("agentType_override");

	std::map<int, std::string> aTypesOverride;
	for (auto it = atypeOverride->begin(); it != atypeOverride->end(); it++)
	{
		if (!it->second)
			continue;

		if (it->second->typeId() != AtomsCore::StringMetadata::staticTypeId())
			continue;

		auto value = std::static_pointer_cast<AtomsCore::StringMetadata>(it->second);
		if (!value)
			continue;

		Atoms::AgentTypePtr thisAType = Atoms::AgentTypes::instance().agentType(value->get());
		if (!thisAType)
		{
			continue;
		}

		aTypesOverride[std::stoi(it->first)] = value->get();
	}

	AtomsPtr<AtomsCore::MapMetadata> directionOverride = metadata.getTypedEntry<AtomsCore::MapMetadata>("direction_override");

	std::map<int, AtomsCore::Vector3> dirOverride;
	for (auto it = directionOverride->begin(); it != directionOverride->end(); it++)
	{
		if (!it->second)
			continue;

		if (it->second->typeId() != AtomsCore::Vector3Metadata::staticTypeId())
			continue;

		auto value = std::static_pointer_cast<AtomsCore::Vector3Metadata>(it->second);
		if (value)
			dirOverride[std::stoi(it->first)] = value->get();
	}

	std::vector<std::string> filteredAgentTypes;
	if (isDynamicPtr->value() && randomizeAgentTypesPtr->value())
	{
		for (const auto& aTypeName : randomAgentTypesPtr->value())
		{
			Atoms::AgentTypePtr aTypePtr = Atoms::AgentTypes::instance().agentType(aTypeName);
			if (!aTypePtr)
			{
				AtomsUtils::Logger::warning() << "CurveGeneratorModule::initSimulation unknown agent type" << aTypeName << ": the randomizer will use the default" << aType->name() << "instead";
				continue;
			}
			filteredAgentTypes.push_back(aTypeName);
		}
	}

	MyCurveLayoutGenerator* curveLayout = new MyCurveLayoutGenerator();
	curveLayout->setCurve(curvePtr->get());
	curveLayout->setNumberOfAgents(numAgentsPtr->value());
	curveLayout->setDepth(std::max(1, depthPtr->value()));
	curveLayout->setDepthSpace(depthSpacePtr->value());
	curveLayout->setUseCVs(useCVsPtr->value());
	curveLayout->setDirectionType(directionTypePtr->value());
	curveLayout->setInvertDirection(invertDirectionPtr->value());
	curveLayout->setDirection(directionPtr->get());
	curveLayout->setUpVector(upPtr->get());
	curveLayout->setAgentTypeOverride(aTypesOverride);
	curveLayout->setDirectionOverride(dirOverride);
	curveLayout->setRandomSpace(randomSpacePtr->get());
	curveLayout->setSeed(seedPtr->value());
	curveLayout->setName(name());
	curveLayout->setGroupIdOffset(groupIdOffsetPtr->value());
	curveLayout->setAsStaticGenerator(!isDynamicPtr->value());
	curveLayout->setDynamicMethod(dynamicMethodPtr->value());
	curveLayout->setRandomTimer(timerPtr->value());
	curveLayout->setGeneratorTrigger(generatorTriggerPtr->value());
	curveLayout->setGenerateOnInitSimulation(generateOnInitSimulationPtr->value());
	curveLayout->setRandomizeAgentTypes(randomizeAgentTypesPtr->value());
	curveLayout->setRandomAgentTypes(filteredAgentTypes);
	curveLayout->setResetSeed(resetSeedPtr->value());

	Atoms::LayoutGeneratorPtr generator(curveLayout);

	generator->setAgentType(aType);
	agentGroup->addLayoutGenerator(generator);
}

Atoms::BehaviourModule* MyCurveLayoutModule::creator(const std::string& parameter)
{
	return new MyCurveLayoutModule();
}

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

Code Block
languagecpp
#pragma once

#include "CoreMinimal.h"
#include "Components/AtomsBehaviourComponent.h"
#include "AtomsUnrealUtils.h"
#include "MyCurveLayoutBehaviourComponent.generated.h"


UCLASS(ClassGroup = (AtomsBehaviours), meta = (BlueprintSpawnableComponent))
class UMyCurveLayoutBehaviourComponent : public UAtomsBehaviourComponent
{
	GENERATED_BODY()

public:

	UMyCurveLayoutBehaviourComponent();

	~UMyCurveLayoutBehaviourComponent();

	/** The agent type name */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		UAtomsAgentTypeAsset* agentType;

	/** The agent type name */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		TMap<int32, UAtomsAgentTypeAsset*> agentTypeOverride;

	/** The reference curve */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		AAtomsSplineActor* curve;

	/** 0-tangent, 1-normal, 2-custom direction */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		int32 directionType;

	/** The custom direction */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		FVector direction;

	/** The custom direction */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		TMap<int32, FVector> directionOverride;

	/** Invert the agent direction */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		bool invertDirection;

	/** Agent up vector */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		FVector upVector;

	/** Creates the agents on the curve cvs instead of using the numAgents attribute */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		bool useCVs;

	/** Number of agents */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		int32 numAgents;

	/** Number of rows */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		int32 depth;

	/** Space between each row */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		float depthSpace;

	/** Random seed */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		int32 seed;

	/** Max spacing variation from point on curve */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		FVector randomSpace;

	/** Applies the offset to the agent ids if there are multiple layout modules */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		int32 groupIdOffset;

	/** If true, generate agents dynamically */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		bool isDynamic;

	/** The method to be used for dynamic agent generation: Timer (0), Gnerator Trigger (1) */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		int32 dynamicMethod;

	/** Range to pick a random value of frames to trigger a new generation of agents (if the module is dynamic and dynamic method is by timer) */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		FVector2D timer;

	/** Flag to trigger a new generation of agents (if the module is dynamic and dynamic method is by Generator Trigger) */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		bool generatorTrigger;

	/** If true, reset the seed at each generation (only used if the module is dynamic) */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		bool resetSeed;

	/** If true, generate agents at first frame (only used if the module is dynamic) */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		bool generateOnInitSimulation;

	/** If true, pick random agent type on dynamic generations (only if the module is dynamic) */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		bool randomizeAgentTypes;

	/** List of agent agent types to be used on dynamic generations (only if the module is dynamic) */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		TArray<UAtomsAgentTypeAsset*> randomAgentTypes;

	virtual void GetAtomsAttributes(AtomsPtr<AtomsCore::MapMetadata>& attributes) override;

	/** The agent type name */
	UFUNCTION(BlueprintCallable, Category = "CurveLayoutBehaviourComponent", meta = (CallInEditor = "true"))
		void SetAgentType(UPARAM(ref) UAtomsAgentTypeAsset* Value);

	/** The agent type name */
	UFUNCTION(BlueprintCallable, Category = "CurveLayoutBehaviourComponent", meta = (CallInEditor = "true"))
		void SetAgentTypeOverride(UPARAM(ref) UAtomsAgentTypeAsset* Value, const int32 AgentId);

	/** Remove agent override */
	UFUNCTION(BlueprintCallable, Category = "CurveLayoutBehaviourComponent", meta = (CallInEditor = "true"))
		void RemoveAgentTypeOverride(const int32 AgentId);

	/** The reference curve */
	UFUNCTION(BlueprintCallable, Category = "CurveLayoutBehaviourComponent", meta = (CallInEditor = "true"))
		void SetCurve(UPARAM(ref) AAtomsSplineActor* Value);

	/** 0-tangent, 1-normal, 2-custom direction */
	UFUNCTION(BlueprintCallable, Category = "CurveLayoutBehaviourComponent", meta = (CallInEditor = "true"))
		void SetDirectionType(const int32 Value);

	/** The custom direction */
	UFUNCTION(BlueprintCallable, Category = "CurveLayoutBehaviourComponent", meta = (CallInEditor = "true"))
		void SetDirection(const FVector& Value);

	/** The custom direction */
	UFUNCTION(BlueprintCallable, Category = "CurveLayoutBehaviourComponent", meta = (CallInEditor = "true"))
		void SetDirectionOverride(const FVector& Value, const int32 AgentId);

	/** Remove agent override */
	UFUNCTION(BlueprintCallable, Category = "CurveLayoutBehaviourComponent", meta = (CallInEditor = "true"))
		void RemoveDirectionOverride(const int32 AgentId);

	/** Invert the agent direction */
	UFUNCTION(BlueprintCallable, Category = "CurveLayoutBehaviourComponent", meta = (CallInEditor = "true"))
		void SetInvertDirection(const bool Value);

	/** Agent up vector */
	UFUNCTION(BlueprintCallable, Category = "CurveLayoutBehaviourComponent", meta = (CallInEditor = "true"))
		void SetUpVector(const FVector& Value);

	/** Creates the agents on the curve cvs instead of using the numAgents attribute */
	UFUNCTION(BlueprintCallable, Category = "CurveLayoutBehaviourComponent", meta = (CallInEditor = "true"))
		void SetUseCVs(const bool Value);

	/** Number of agents */
	UFUNCTION(BlueprintCallable, Category = "CurveLayoutBehaviourComponent", meta = (CallInEditor = "true"))
		void SetNumAgents(const int32 Value);

	/** Number of rows */
	UFUNCTION(BlueprintCallable, Category = "CurveLayoutBehaviourComponent", meta = (CallInEditor = "true"))
		void SetDepth(const int32 Value);

	/** Space between each row */
	UFUNCTION(BlueprintCallable, Category = "CurveLayoutBehaviourComponent", meta = (CallInEditor = "true"))
		void SetDepthSpace(const float Value);

	/** Random seed */
	UFUNCTION(BlueprintCallable, Category = "CurveLayoutBehaviourComponent", meta = (CallInEditor = "true"))
		void SetSeed(const int32 Value);

	/** Max spacing variation from point on curve */
	UFUNCTION(BlueprintCallable, Category = "CurveLayoutBehaviourComponent", meta = (CallInEditor = "true"))
		void SetRandomSpace(const FVector& Value);

	/** Applies the offset to the agent ids if there are multiple layout modules */
	UFUNCTION(BlueprintCallable, Category = "CurveLayoutBehaviourComponent", meta = (CallInEditor = "true"))
		void SetGroupIdOffset(const int32 Value);

	/** If true, generate agents dynamically */
	UFUNCTION(BlueprintCallable, Category = "CurveLayoutBehaviourComponent", meta = (CallInEditor = "true"))
		void SetIsDynamic(const bool Value);

	/** The method to be used for dynamic agent generation: Timer (0), Gnerator Trigger (1) */
	UFUNCTION(BlueprintCallable, Category = "CurveLayoutBehaviourComponent", meta = (CallInEditor = "true"))
		void SetDynamicMethod(const int32 Value);

	/** Range to pick a random value of frames to trigger a new generation of agents (if the module is dynamic and dynamic method is by timer) */
	UFUNCTION(BlueprintCallable, Category = "CurveLayoutBehaviourComponent", meta = (CallInEditor = "true"))
		void SetTimer(const FVector2D& Value);

	/** Flag to trigger a new generation of agents (if the module is dynamic and dynamic method is by Generator Trigger) */
	UFUNCTION(BlueprintCallable, Category = "CurveLayoutBehaviourComponent", meta = (CallInEditor = "true"))
		void SetGeneratorTrigger(const bool Value);

	/** If true, reset the seed at each generation (only used if the module is dynamic) */
	UFUNCTION(BlueprintCallable, Category = "CurveLayoutBehaviourComponent", meta = (CallInEditor = "true"))
		void SetResetSeed(const bool Value);

	/** If true, generate agents at first frame (only used if the module is dynamic) */
	UFUNCTION(BlueprintCallable, Category = "CurveLayoutBehaviourComponent", meta = (CallInEditor = "true"))
		void SetGenerateOnInitSimulation(const bool Value);

	/** If true, pick random agent type on dynamic generations (only if the module is dynamic) */
	UFUNCTION(BlueprintCallable, Category = "CurveLayoutBehaviourComponent", meta = (CallInEditor = "true"))
		void SetRandomizeAgentTypes(const bool Value);

	/** List of agent agent types to be used on dynamic generations (only if the module is dynamic) */
	UFUNCTION(BlueprintCallable, Category = "CurveLayoutBehaviourComponent", meta = (CallInEditor = "true"))
		void SetRandomAgentTypes(const TArray<UAtomsAgentTypeAsset*>& Value);

#if WITH_EDITOR
	virtual void PreEditChange(UnrealProperty * PropertyAboutToChange) override;
	virtual void PostEditChangeProperty(struct FPropertyChangedEvent& e) override;
#endif

};

Code Block
languagecpp
#include "MyCurveLayoutBehaviourComponent.h"
#include "Actors/AtomsAgentGroup.h"
#include "AtomsModuleGeneratorUtils.h"
#include "AtomsAttributeUtils.h"

UMyCurveLayoutBehaviourComponent::UMyCurveLayoutBehaviourComponent() : UAtomsBehaviourComponent()
{
	AtomsBehaviourModule = "MyCurveLayout";

	agentType = nullptr;	
	curve = nullptr;
	directionType = 0;
	direction = FVector(1.0, 0.0, 0.0);
	invertDirection = false;
	upVector = FVector(0.0, 0.0, 1.0);
	useCVs = false;
	numAgents = 10;
	depth = 1;
	depthSpace = 50.0;
	seed = 0;
	randomSpace = FVector(0.0, 0.0, 0.0);
	groupIdOffset = 0;
	isDynamic = false;
	dynamicMethod = 0;
	timer = FVector2D(1.0, 100.0);
	generatorTrigger = false;
	resetSeed = false;
	generateOnInitSimulation = true;
	randomizeAgentTypes = false;
}

UMyCurveLayoutBehaviourComponent::~UMyCurveLayoutBehaviourComponent()
{

}

void UMyCurveLayoutBehaviourComponent::GetAtomsAttributes(AtomsPtr<AtomsCore::MapMetadata>& attributes)
{
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_AGENTTYPE_META(agentType, AtomsCore::StringMetadata, UAtomsAttributeUtils::ToAgentTypeName);
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_AGENTTYPE_META_OVERRIDE(agentType_override, agentTypeOverride, AtomsCore::StringMetadata, UAtomsAttributeUtils::ToAgentTypeName);
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(curve, AtomsCore::CurveMetadata, UAtomsAttributeUtils::ToCurve);
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(directionType, AtomsCore::IntMetadata, );
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(direction, AtomsCore::Vector3Metadata, AtomsConverter::ToVector3);
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META_OVERRIDE(direction_override, directionOverride, AtomsCore::Vector3Metadata, AtomsConverter::ToVector3);
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(invertDirection, AtomsCore::BoolMetadata, );
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(upVector, AtomsCore::Vector3Metadata, AtomsConverter::ToVector3);
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(useCVs, AtomsCore::BoolMetadata, );
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(numAgents, AtomsCore::IntMetadata, );
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(depth, AtomsCore::IntMetadata, );
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(depthSpace, AtomsCore::DoubleMetadata, );
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(seed, AtomsCore::IntMetadata, );
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(randomSpace, AtomsCore::Vector3Metadata, AtomsConverter::ToVector3);
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(groupIdOffset, AtomsCore::IntMetadata, );
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(isDynamic, AtomsCore::BoolMetadata, );
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(dynamicMethod, AtomsCore::IntMetadata, );
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(timer, AtomsCore::Vector2Metadata, AtomsConverter::ToVector2);
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(generatorTrigger, AtomsCore::BoolMetadata, );
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(resetSeed, AtomsCore::BoolMetadata, );
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(generateOnInitSimulation, AtomsCore::BoolMetadata, );
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(randomizeAgentTypes, AtomsCore::BoolMetadata, );
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_AGENTTYPE_ARRAYMETA(randomAgentTypes, AtomsCore::StringArrayMetadata, UAtomsAttributeUtils::ToAgentTypeName);

}

#if WITH_EDITOR
void UMyCurveLayoutBehaviourComponent::PreEditChange(UnrealProperty * PropertyAboutToChange)
{
	if (!PropertyAboutToChange)
		return;

	Super::PreEditChange(PropertyAboutToChange);

	auto PropertyName = (PropertyAboutToChange != NULL) ? PropertyAboutToChange->GetFName() : NAME_None;



}

void UMyCurveLayoutBehaviourComponent::PostEditChangeProperty(FPropertyChangedEvent & e)
{
	auto PropertyName = (e.Property != NULL) ? e.Property->GetFName() : NAME_None;

	if (PropertyName == GET_MEMBER_NAME_CHECKED(UMyCurveLayoutBehaviourComponent, curve))
	{
		if (curve) curve->CheckForMinimumPoints();
	}

	Super::PostEditChangeProperty(e);
}
#endif

void UMyCurveLayoutBehaviourComponent::SetAgentType(UPARAM(ref) UAtomsAgentTypeAsset* Value)
{
	agentType = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_AGENTTYPE_META(agentType, AtomsCore::StringMetadata, UAtomsAttributeUtils::ToAgentTypeName);

}

void UMyCurveLayoutBehaviourComponent::SetAgentTypeOverride(UPARAM(ref) UAtomsAgentTypeAsset* Value, const int32 AgentId)
{
	ATOMS_BEHAVIOUR_COMPONENT_SET_AGENTTYPE_META_OVERRIDE(agentType_override, agentTypeOverride, AtomsCore::StringMetadata, UAtomsAttributeUtils::ToAgentTypeName, AgentId);

}

void UMyCurveLayoutBehaviourComponent::RemoveAgentTypeOverride(const int32 AgentId)
{
	ATOMS_BEHAVIOUR_COMPONENT_REMOVE_OVERRIDE(agentType_override, agentTypeOverride, AgentId);
}

void UMyCurveLayoutBehaviourComponent::SetCurve(UPARAM(ref) AAtomsSplineActor* Value)
{
	curve = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(curve, AtomsCore::CurveMetadata, UAtomsAttributeUtils::ToCurve);

}

void UMyCurveLayoutBehaviourComponent::SetDirectionType(const int32 Value)
{
	directionType = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(directionType, AtomsCore::IntMetadata, );

}

void UMyCurveLayoutBehaviourComponent::SetDirection(const FVector& Value)
{
	direction = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(direction, AtomsCore::Vector3Metadata, AtomsConverter::ToVector3);

}

void UMyCurveLayoutBehaviourComponent::SetDirectionOverride(const FVector& Value, const int32 AgentId)
{
	ATOMS_BEHAVIOUR_COMPONENT_SET_META_OVERRIDE(direction_override, directionOverride, AtomsCore::Vector3Metadata, AtomsConverter::ToVector3, AgentId);

}

void UMyCurveLayoutBehaviourComponent::RemoveDirectionOverride(const int32 AgentId)
{
	ATOMS_BEHAVIOUR_COMPONENT_REMOVE_OVERRIDE(direction_override, directionOverride, AgentId);
}

void UMyCurveLayoutBehaviourComponent::SetInvertDirection(const bool Value)
{
	invertDirection = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(invertDirection, AtomsCore::BoolMetadata, );

}

void UMyCurveLayoutBehaviourComponent::SetUpVector(const FVector& Value)
{
	upVector = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(upVector, AtomsCore::Vector3Metadata, AtomsConverter::ToVector3);

}

void UMyCurveLayoutBehaviourComponent::SetUseCVs(const bool Value)
{
	useCVs = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(useCVs, AtomsCore::BoolMetadata, );

}

void UMyCurveLayoutBehaviourComponent::SetNumAgents(const int32 Value)
{
	numAgents = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(numAgents, AtomsCore::IntMetadata, );

}

void UMyCurveLayoutBehaviourComponent::SetDepth(const int32 Value)
{
	depth = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(depth, AtomsCore::IntMetadata, );

}

void UMyCurveLayoutBehaviourComponent::SetDepthSpace(const float Value)
{
	depthSpace = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(depthSpace, AtomsCore::DoubleMetadata, );

}

void UMyCurveLayoutBehaviourComponent::SetSeed(const int32 Value)
{
	seed = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(seed, AtomsCore::IntMetadata, );

}

void UMyCurveLayoutBehaviourComponent::SetRandomSpace(const FVector& Value)
{
	randomSpace = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(randomSpace, AtomsCore::Vector3Metadata, AtomsConverter::ToVector3);

}

void UMyCurveLayoutBehaviourComponent::SetGroupIdOffset(const int32 Value)
{
	groupIdOffset = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(groupIdOffset, AtomsCore::IntMetadata, );

}

void UMyCurveLayoutBehaviourComponent::SetIsDynamic(const bool Value)
{
	isDynamic = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(isDynamic, AtomsCore::BoolMetadata, );

}

void UMyCurveLayoutBehaviourComponent::SetDynamicMethod(const int32 Value)
{
	dynamicMethod = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(dynamicMethod, AtomsCore::IntMetadata, );

}

void UMyCurveLayoutBehaviourComponent::SetTimer(const FVector2D& Value)
{
	timer = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(timer, AtomsCore::Vector2Metadata, AtomsConverter::ToVector2);

}

void UMyCurveLayoutBehaviourComponent::SetGeneratorTrigger(const bool Value)
{
	generatorTrigger = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(generatorTrigger, AtomsCore::BoolMetadata, );

}

void UMyCurveLayoutBehaviourComponent::SetResetSeed(const bool Value)
{
	resetSeed = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(resetSeed, AtomsCore::BoolMetadata, );

}

void UMyCurveLayoutBehaviourComponent::SetGenerateOnInitSimulation(const bool Value)
{
	generateOnInitSimulation = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(generateOnInitSimulation, AtomsCore::BoolMetadata, );

}

void UMyCurveLayoutBehaviourComponent::SetRandomizeAgentTypes(const bool Value)
{
	randomizeAgentTypes = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(randomizeAgentTypes, AtomsCore::BoolMetadata, );

}

void UMyCurveLayoutBehaviourComponent::SetRandomAgentTypes(const TArray<UAtomsAgentTypeAsset*>& Value)
{
	randomAgentTypes = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_AGENTTYPE_ARRAYMETA(randomAgentTypes, AtomsCore::StringArrayMetadata, UAtomsAttributeUtils::ToAgentTypeName);

}

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

Code Block
languagecpp
#include "MyCurveLayoutModule.h"

void FAtomsUnrealTestGameModule::StartupModule()
{
	moduleManager.registerBehaviourModule("MyCurveLayout", &MyCurveLayoutModule::creator, Atoms::BehaviourModules::kNative);
}

void FAtomsUnrealTestGameModule::ShutdownModule()
{
	Atoms::BehaviourModules& moduleManager = Atoms::BehaviourModules::instance();
	moduleManager.deregisterBehaviourModule("MyCurveLayout");
}