Versions Compared

Key

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

...

This example operator adds an offset to a joint transform.

Code Block
languagecpp
#pragma once
#include <AtomsGraph/Ports.h>
#include <Atoms/Globals.h>
#include <Atoms/Graph/Operator.h>


class JointOffsetOperatorMyJointTransformOperator : public Atoms::Operator
{
public:

	NODE_STANDARD_MEMBERS

	JointOffsetOperatorMyJointTransformOperator();

	virtual ~JointOffsetOperator~MyJointTransformOperator();

	virtual bool	compute() compute(const AtomsGraph::ComputeData* computeData) override;

	virtual void reset() override;

private:

	AtomsGraph::PosePort* m_inPose;
	AtomsGraph::StringPort *m_jointNameinJointName;
	AtomsGraph::MatrixPortLongPort *m_offsetMatrixinRotationOrder;
	AtomsGraph::BooleanPort *m_worldSpaceinWorldSpace;
		AtomsGraph::BooleanPort *m_inOffset;
	intAtomsGraph::VectorPort *m_jointIdinTranslate;
	boolAtomsGraph::VectorPort *m_firstinRotate;
};

// this must be unique
#define JOINT_OFFSET_OPERATOR_ID 9999991 

NODE_STANDARD_MEMBERS_IMPL(JointOffsetOperator)
unsigned int JointOffsetOperator::staticTypeId() { return JOINT_OFFSET_OPERATOR_ID; }
std::string JointOffsetOperator::staticTypeStr() { return std::string("JointOffsetOperator"); }

JointOffsetOperator::JointOffsetOperator() : Operator()
{
	m_jointName= new AtomsGraph::StringPort("jointName");
	m_jointName->set("");
	addInputPort(m_jointName);

	m_offsetMatrix = new AtomsGraph::MatrixPort("offsetMatrix");
	addInputPort(m_offsetMatrix);

	m_inPose = new AtomsGraph::PosePort("inPose");
	addInputPort(m_inPose);

	m_worldSpace = new AtomsGraph::BooleanPort("worldSpace");
	m_worldSpace->set(false);
	addInputPort(m_worldSpace );

	m_jointId= -1;
	m_first = true;
}

JointConstraintOperator::~JointConstraintOperator()
{
}

bool JointConstraintOperator::compute()
{
	AtomsCore::Pose &inPose = m_inPose->getRef();
	if (inPose.numJoints() == 0)
	{
		AtomsUtils::Logger::warning() << "Empty input pose";
		return false;
	}


	AtomsCore::Pose &outPose = m_outPose->getRef();
	// Copy the input pose to the out pose port
	outPose = inPose;

	// Check if the agent and the agent type are valid
	if (!m_agent) 
	{
		AtomsUtils::Logger::error() << "Invalid agent type";
		return false;
	}

	if(!m_agent->agentType())
	{
		AtomsUtils::Logger::error() << "Invalid agent type";
		return false;
	}

	// Get the skeleton from the agent type
	const AtomsCore::Skeleton& skeleton = m_agent->agentType()->skeleton();
	
	if (m_first)
	{
		m_jointId = skeleton.jointId(m_targetJoint->getRef());
	}

	if (m_jointId == -1)
	{
		Logger::error() << "Could not find " << m_targetJoint->getRef() << "joint.";
		return false;
	}


	if (worldSpace->get())
	{
		// Compute the offset in world space
		AtomsCore::Poser poser(&skeleton);
		AtomsCore::Matrix currentMtx = poser.getWorldMatrix(pose, jointId);
		poser.setWorldMatrix(pose, m_offsetMatrix->getRef()* currentMtx, jointIdTmp);
	}
	else
	{
		// Compute the offset in local space		
		AtomsCore::JointPose& jp = outPose.jointPose(m_jointId);
		jp.setMatrix(m_offsetMatrix->getRef() * jp.matrix());
	}
	return true;
}

The operator can be added by this custom behaviour module.

Code Block
languagecpp
#include <Atoms/BehaviourModule.h>


class JointOffsetModule : public Atoms::BehaviourModule
{
public:

	JointOffsetModule ();

	virtual ~JointOffsetModule ();

	void agentsCreated(const std::vector<Atoms::Agent*>& agents, Atoms::AgentGroup* agentGroup = nullptr);

	static Atoms::BehaviourModule* creator(const std::string& parameter);
};

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

JointOffsetModule::JointOffsetModule() :
	Atoms::BehaviourModule()
{
	AtomsCore::StringMetadata jointName("");
	addAttribute("jointName", &jointName, false);

	AtomsCore::MatrixMetadata offsetMatrix;
	addAttribute("offsetMatrix", &offsetMatrix, true);

	AtomsCore::BoolMetadata worldSpace(false);
	addAttribute("worldSpace", &worldSpace);
}

JointOffsetModule::~JointOffsetModule()
{
}

void JointOffsetModule::agentsCreated(const std::vector<Atoms::Agent*>& agents, Atoms::AgentGroup* agentGroup)
{
	AtomsCore::MapMetadata& metadata = attributes();
	const std::string& jointName= metadata.getTypedEntry<AtomsCore::StringMetadata>("jointName")->get();
	bool worldSpace = metadata.getTypedEntry<AtomsCore::BoolMetadata>("worldSpace")->get();
	AtomsCore::Matrix& offsetMatrix = metadata.getTypedEntry<AtomsCore::MatrixMetadata>("offsetMatrix")->get();

	AtomsPtr<AtomsCore::MapMetadata> offsetMatrixOverrideMap = metadata.getTypedEntry<AtomsCore::MapMetadata>("offsetMatrix_override");

		tbb::parallel_for(tbb::blocked_range<unsigned int>(0, agents.size()),
		[&](const tbb::blocked_range<unsigned int>& r)
	{
		char groupIdChar[12];
		std::string groupIdStr;
		groupIdStr.reserve(12);

		for (unsigned int i = r.begin(); i < r.end(); i++)
		{
			Atoms::Agent* agent = agents[i];
			// Get the agent dg network
			Atoms::AgentBehaviourNetwork& network = agent->network();

			// Get the last operator fron the dg
			Atoms::Operator* endOperator = network.buildPoseNode();

			// Create the joint offset operator
			Atoms::JointOffsetOperator* jointOffset = static_cast<Atoms::JointOffsetOperator*>(network.manager().createNode(Atoms::JointOffsetOperator::staticTypeStr(), "jointOffset"));
			jointOffset->setAgent(agent);

			// Connect the last operator to the joint offset operator
			network.manager().connectAttr(endOperator->name(), "outPose", jointOffset ->name(), "inPose");

			// Set the operator parameters
			jointOffset->getInputPort<AtomsGraph::StringPort>("jointName")->set(jointName);
			jointOffset->getInputPort<AtomsGraph::BooleanPort>("worldSpace")->set(worldSpace );			

			AtomsCore::Matrix agentOffsetMatrix = offsetMatrix;
			
			// Check if the offset matrix is overridden
			AtomsPtr<AtomsCore::IntMetadata> groupIdPtr = agent->metadata().getTypedEntry<AtomsCore::IntMetadata>(ATOMS_AGENT_GROUPID);
			if (groupIdPtr)
			{
				sprintf(groupIdChar, "%d", groupIdPtr->value());
				groupIdStr = groupIdChar;

				if (offsetMatrixOverrideMap)
				{
					AtomsPtr<AtomsCore::MatrixMetadata> offsetMatrixAgentMeta = offsetMatrixOverrideMap->getTypedEntry<AtomsCore::MatrixMetadata>(groupIdStr);
					if (offsetMatrixAgentMeta)
						offsetMatrix = offsetMatrixAgentMeta->get();
				}
						
			}
				
			jointOffset->getInputPort<AtomsGraph::MatrixPort>("offsetMatrix")->set(offsetMatrix);
			
			// Set the joint offset operator as the last operator to compute
			network.setBuildPoseNode(jointOffset);
		}
	});
}

void FAtomsDemoGameModule::StartupModule()
{
      AtomsUtils::Logger::info() << "Loading Joint offset plugin";
      // Register the node to the factory
      	AtomsGraph::VectorPort *m_inScale;
	AtomsGraph::DoublePort *m_inWeight;

	int m_jointId;
	bool m_first;
};

Code Block
languagecpp
#include "MyJointTransformOperator.h"
#include <Atoms/Graph/Operators/OperatorIds.h>
#include <Atoms/AgentTypes.h>
#include <Atoms/Agent.h>
#include <AtomsCore/Metadata/StringMetadata.h>
#include <AtomsCore/Poser.h>

using namespace Atoms;
using namespace AtomsGraph;

#define MY_JOINT_TRANSFORM_OPERATOR_ID 9999991 

NODE_STANDARD_MEMBERS_IMPL(MyJointTransformOperator)
unsigned int MyJointTransformOperator::staticTypeId() { return JOINT_TRANSFORM_OPERATOR_ID; }
std::string MyJointTransformOperator::staticTypeStr() { return std::string("MyJointTransformOperator"); }

AtomsCore::Euler::Order jtOpConvertRotateOrderToEulerRot(int value)
{
	AtomsCore::Euler::Order returnOrder;

	switch (value)
	{
	case 0:
		returnOrder = AtomsCore::Euler::XYZ;
		break;
	case 1:
		returnOrder = AtomsCore::Euler::YZX;
		break;
	case 2:
		returnOrder = AtomsCore::Euler::ZXY;
		break;
	case 3:
		returnOrder = AtomsCore::Euler::XZY;
		break;
	case 4:
		returnOrder = AtomsCore::Euler::YXZ;
		break;
	case 5:
		returnOrder = AtomsCore::Euler::ZYX;
		break;
	default:
		returnOrder = AtomsCore::Euler::XYZ;
		break;
	}

	return returnOrder;
}

MyJointTransformOperator::MyJointTransformOperator() : Operator()
{
	m_inJointName = new AtomsGraph::StringPort("jointName");
	m_inJointName->set("");
	addInputPort(m_inJointName);

	m_inOffset = new AtomsGraph::BooleanPort("offset");
	addInputPort(m_inOffset);

	m_inRotationOrder = new AtomsGraph::LongPort("rotationOrder");
	addInputPort(m_inRotationOrder);

	m_inWorldSpace = new AtomsGraph::BooleanPort("worldSpace");
	addInputPort(m_inWorldSpace);

	m_inTranslate = new AtomsGraph::VectorPort("translate");
	m_inTranslate->set(AtomsCore::Vector3(0, 0, 0));
	addInputPort(m_inTranslate);

	m_inRotate = new AtomsGraph::VectorPort("rotate");
	m_inRotate->set(AtomsCore::Vector3(0, 0, 0));
	addInputPort(m_inRotate);

	m_inScale = new AtomsGraph::VectorPort("scale");
	m_inScale->set(AtomsCore::Vector3(1, 1, 1));
	addInputPort(m_inScale);

	m_inPose = new AtomsGraph::PosePort("inPose");
	addInputPort(m_inPose);

	m_inWeight = new AtomsGraph::DoublePort("weight");
	addInputPort(m_inWeight);

	m_jointId = -1;

	m_first = true;
}

MyJointTransformOperator::~MyJointTransformOperator()
{
}

bool MyJointTransformOperator::compute(const AtomsGraph::ComputeData* computeData)
{
	AtomsCore::Pose &inPose = m_inPose->getRef();
	if (inPose.numJoints() == 0)
	{
		Logger::warning() << "Empty input pose";
		return false;
	}
	
	AtomsCore::Pose &outPose = m_outPose->getRef();
	outPose = inPose;

	if (!m_agent || !m_agent->agentType())
	{
		AtomsUtils::Logger::error() << "Invalid agent type";
		return false;
	}

	const AtomsCore::Skeleton& skeleton = m_agent->agentType()->skeleton();

	if (m_first)
	{
		m_jointId = skeleton.jointId(m_inJointName->getRef());
	}

	if (m_jointId == -1)
	{
		Logger::error() << "Could not find joint.";
		return false;
	}

	double weight = m_inWeight->get();

	if (weight < 0.0001)
		return true;

	bool offset = m_inOffset->get();
	bool worldSpace = m_inWorldSpace->get();

	AtomsCore::Vector3 translate = m_inTranslate->get();
	AtomsCore::Vector3 rotate = m_inRotate->get();
	AtomsCore::Vector3 scale = m_inScale->get();

	int rotationOrder = m_inRotationOrder->get();

	AtomsCore::Poser poser(&skeleton);

	if (worldSpace)
	{
		AtomsCore::Matrix jointMtx;
		if (!offset)
		{
			AtomsCore::Matrix currentMtx = poser.getWorldMatrix(outPose, m_jointId);
			AtomsCore::Euler currEulerRotation;
			AtomsCore::Vector3 currScale(1.0, 1.0, 1.0);
			AtomsCore::Vector3 shear(0.0, 0.0, 0.0);
			AtomsCore::Vector3 currTranslation(0.0, 0.0, 0.0);
			AtomsMath::extractSHRT(currentMtx, currScale, shear, currEulerRotation, currTranslation);
			AtomsCore::Quaternion currRotation = currEulerRotation.toQuat();

			scale = currScale * (1.0 - weight) + scale * weight;
			translate = currTranslation * (1.0 - weight) + translate * weight;

			AtomsCore::Euler rotation(rotate.x * M_PI / 180.0, rotate.y * M_PI / 180.0, rotate.z * M_PI / 180.0, jtOpConvertRotateOrderToEulerRot(rotationOrder));
			AtomsCore::Quaternion quatTmp = AtomsMath::slerp(currRotation, rotation.toQuat(), weight);

			jointMtx.makeIdentity();
			jointMtx.translate(translate);
			jointMtx = quatTmp.toMatrix44() * jointMtx;
			jointMtx.scale(scale);
			poser.setWorldMatrix(outPose, jointMtx, m_jointId);
		}
		else
		{
			AtomsCore::Vector3 currScale(1.0, 1.0, 1.0);
			AtomsCore::Vector3 currTranslation(0.0, 0.0, 0.0);
			AtomsCore::Quaternion currRotation;

			scale = currScale * (1.0 - weight) + scale * weight;
			translate = currTranslation * (1.0 - weight) + translate * weight;

			AtomsCore::Euler rotation(rotate.x * M_PI / 180.0, rotate.y * M_PI / 180.0, rotate.z * M_PI / 180.0, jtOpConvertRotateOrderToEulerRot(rotationOrder));
			AtomsCore::Quaternion quatTmp = AtomsMath::slerp(currRotation, rotation.toQuat(), weight);

			jointMtx.makeIdentity();
			jointMtx.translate(translate);
			jointMtx = quatTmp.toMatrix44() * jointMtx;
			jointMtx.scale(scale);

			AtomsCore::Matrix currentMtx = poser.getWorldMatrix(outPose, m_jointId);
			poser.setWorldMatrix(outPose, jointMtx * currentMtx, m_jointId);
		}
	}
	else
	{
		if (!offset)
		{
			auto& jPose = outPose.jointPose(m_jointId);
			AtomsCore::Vector3& currScale = jPose.scale;
			AtomsCore::Vector3& currTranslation = jPose.translation;
			AtomsCore::Quaternion& currRotation = jPose.rotation;

			AtomsCore::Euler rotation(rotate.x * M_PI / 180.0, rotate.y * M_PI / 180.0, rotate.z * M_PI / 180.0, jtOpConvertRotateOrderToEulerRot(rotationOrder));

			jPose.rotation = AtomsMath::slerp(currRotation, rotation.toQuat(), weight);
			jPose.scale = currScale * (1.0 - weight) + scale * weight;
			jPose.translation = currTranslation * (1.0 - weight) + translate * weight;

		}
		else
		{
			auto& jPose = outPose.jointPose(m_jointId);
			AtomsCore::Vector3 currScale(1.0, 1.0, 1.0);
			AtomsCore::Vector3 currTranslation(0.0, 0.0, 0.0);
			AtomsCore::Quaternion currRotation;

			AtomsCore::Euler rotation(rotate.x * M_PI / 180.0, rotate.y * M_PI / 180.0, rotate.z * M_PI / 180.0, jtOpConvertRotateOrderToEulerRot(rotationOrder));

			AtomsCore::Quaternion quatTmp = AtomsMath::slerp(currRotation, rotation.toQuat(), weight);

			jPose.scale.x *= currScale.x * (1.0 - weight) + scale.x * weight;
			jPose.scale.y *= currScale.y * (1.0 - weight) + scale.y * weight;
			jPose.scale.z *= currScale.z * (1.0 - weight) + scale.z * weight;

			jPose.translation += currTranslation * (1.0 - weight) + translate * weight;

			jPose.rotation = jPose.rotation * quatTmp;
		}
	}

	return true;
}

void MyJointTransformOperator::reset()
{
	Operator::reset();
	m_first = true;
	m_jointId = -1;
	m_inScale->set(AtomsCore::Vector3(1, 1, 1));
	m_inRotate->set(AtomsCore::Vector3(0, 0, 0));
	m_inJointName->set("");
}

The operator can be added by this custom behaviour module.

Code Block
languagecpp
#pragma once
#include <Atoms/BehaviourModule.h>

class MyJointTransformModule : public Atoms::BehaviourModule
{
public:

	MyJointTransformModule();

	virtual ~MyJointTransformModule();

	void agentsCreated(const std::vector<Atoms::Agent*>& agents, Atoms::AgentGroup* agentGroup);

	void initFrame(const std::vector<Atoms::Agent*>& agents, Atoms::AgentGroup* agentGroup);

	static Atoms::BehaviourModule* creator(const std::string& parameter);

};
Code Block
#include "MyJointTransformModule.h"
#include "MyJointTransformOperator.h"
#include <AtomsCore/Metadata/IntMetadata.h>
#include <AtomsCore/Metadata/BoolMetadata.h>
#include <AtomsCore/Metadata/Vector3Metadata.h>
#include <AtomsCore/Metadata/DoubleMetadata.h>
#include <AtomsCore/Metadata/StringMetadata.h>
#include <AtomsCore/Metadata/MatrixMetadata.h>
#include <AtomsCore/Metadata/StringArrayMetadata.h>
#include <Atoms/GlobalNames.h>
#include <Atoms/Agent.h>
#include <AtomsCore/Poser.h>
#include <AtomsUtils/TaskScheduler.h>


AtomsCore::Euler::Order jtmConvertRotateOrderToEulerRot(int value)
{
	AtomsCore::Euler::Order returnOrder;

	switch (value)
	{
	case 0:
		returnOrder = AtomsCore::Euler::XYZ;
		break;
	case 1:
		returnOrder = AtomsCore::Euler::YZX;
		break;
	case 2:
		returnOrder = AtomsCore::Euler::ZXY;
		break;
	case 3:
		returnOrder = AtomsCore::Euler::XZY;
		break;
	case 4:
		returnOrder = AtomsCore::Euler::YXZ;
		break;
	case 5:
		returnOrder = AtomsCore::Euler::ZYX;
		break;
	default:
		returnOrder = AtomsCore::Euler::XYZ;
		break;
	}

	return returnOrder;
}

MyJointTransformModule::MyJointTransformModule() : Atoms::BehaviourModule()
{
	AtomsCore::StringMetadata jointName;
	addAttribute("jointName", &jointName, true);
	AtomsCore::StringMetadata tooltipJointName("The joint name");
	addAttributeProperty("jointName", ATOMS_BEHAVIOUR_TOOLTIP, &tooltipJointName);
	AtomsCore::StringMetadata jointNameConstructor(Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_HARD_CONSTRUCTOR_JOINT_NAME_KEY);
	addAttributeProperty("jointName", Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_HARD_CONSTRUCTOR_KEY, &jointNameConstructor);

	AtomsCore::IntMetadata order(0);
	addAttribute("rotationOrder", &order, true);
	AtomsCore::StringMetadata tooltipOrder("The rotation order");
	addAttributeProperty("rotationOrder", ATOMS_BEHAVIOUR_TOOLTIP, &tooltipOrder);

	AtomsCore::BoolMetadata worldSpace(false);
	addAttribute("worldSpace", &worldSpace, true);
	AtomsCore::StringMetadata tooltipWorldSpace("Apply the transformation in world space");
	addAttributeProperty("worldSpace", ATOMS_BEHAVIOUR_TOOLTIP, &tooltipWorldSpace);

	AtomsCore::BoolMetadata add(true);
	addAttribute("offset", &add, true);
	AtomsCore::StringMetadata tooltipOffset("Apply the transformation as an offset to the current one");
	addAttributeProperty("offset", ATOMS_BEHAVIOUR_TOOLTIP, &tooltipOffset);

	AtomsCore::Vector3Metadata rotate(AtomsCore::Vector3(0.0, 0.0, 0.0));
	addAttribute("rotate", &rotate, true);
	AtomsCore::StringMetadata tooltipRotate("Rotation value in degrees");
	addAttributeProperty("rotate", ATOMS_BEHAVIOUR_TOOLTIP, &tooltipRotate);

	AtomsCore::Vector3Metadata scale(AtomsCore::Vector3(1.0, 1.0, 1.0));
	addAttribute("scale", &scale, true);
	AtomsCore::StringMetadata tooltipScale("Scale value");
	addAttributeProperty("scale", ATOMS_BEHAVIOUR_TOOLTIP, &tooltipScale);

	AtomsCore::Vector3Metadata translate(AtomsCore::Vector3(0.0, 0.0, 0.0));
	addAttribute("translate", &translate, true);
	AtomsCore::StringMetadata tooltipTranslate("Translation value");
	addAttributeProperty("translate", ATOMS_BEHAVIOUR_TOOLTIP, &tooltipTranslate);

	AtomsCore::DoubleMetadata weight(1.0);
	addAttribute("weight", &weight, true);
	AtomsCore::StringMetadata tooltipWeight("The weight");
	addAttributeProperty("weight", ATOMS_BEHAVIOUR_TOOLTIP, &tooltipWeight);
	AtomsCore::DoubleMetadata minWeight(0.0);
	addAttributeProperty("weight", ATOMS_BEHAVIOUR_MIN_VALUE, &minWeight);
	AtomsCore::DoubleMetadata maxWeight(1.0);
	addAttributeProperty("weight", ATOMS_BEHAVIOUR_MAX_VALUE, &maxWeight);

	AtomsCore::BoolMetadata useOperator(false);
	addAttribute("useOperator", &useOperator, true);
	AtomsCore::StringMetadata tooltipUseOperator(
		"If true, the transformation will be applied by an operator instead of the module itself at the endFrame stage.\n"
		"This option should be enabled when a joint transform module is used on an agent group using other modules\n"
		"actively modifying the pose with an operator and not at the endFrame stage(i.e.Sync)");
	addAttributeProperty("useOperator", ATOMS_BEHAVIOUR_TOOLTIP, &tooltipUseOperator);

	// Display order
	std::vector<std::string> displayOrderVector{
		"jointName", "offset", "translate", "rotate", "scale", "rotationOrder",
		"worldSpace", "useOperator", "weight" };
	AtomsCore::StringArrayMetadata displayOrder(displayOrderVector);
	addAttributeProperty(ATOMS_BEHAVIOUR_MODULE_PROPERTIES, ATOMS_BEHAVIOUR_MODULE_DISPLAY_ORDER, &displayOrder);
}

MyJointTransformModule::~MyJointTransformModule()
{
}

void MyJointTransformModule::agentsCreated(const std::vector<Atoms::Agent*>& agents, Atoms::AgentGroup* agentGroup)
{
	auto& attribs = attributes();


	bool add = attribs.getTypedEntry<AtomsCore::BoolMetadata>("offset")->value();
	bool worldSpace = attribs.getTypedEntry<AtomsCore::BoolMetadata>("worldSpace")->value();
	const std::string& jointName = attribs.getTypedEntry<AtomsCore::StringMetadata>("jointName")->value();
	int rotationOrder = attribs.getTypedEntry<AtomsCore::IntMetadata>("rotationOrder")->value();

	AtomsPtr<AtomsCore::MapMetadata> jointNameOverrideMap = attribs.getTypedEntry<AtomsCore::MapMetadata>("jointName_override");
	AtomsPtr<AtomsCore::MapMetadata> rotationOrderOverrideMap = attribs.getTypedEntry<AtomsCore::MapMetadata>("rotationOrder_override");
	AtomsPtr<AtomsCore::MapMetadata> addOverrideMap = attribs.getTypedEntry<AtomsCore::MapMetadata>("offset_override");
	AtomsPtr<AtomsCore::MapMetadata> worldSpaceOverrideMap = attribs.getTypedEntry<AtomsCore::MapMetadata>("worldSpace_override");

	const std::string jointNameOverrideName = name() + "@jointName";
	const std::string rotationOrderOverrideName = name() + "@rotationOrder";
	const std::string offsetOverrideName = name() + "@offset";
	const std::string worldSpaceOverrideName = name() + "@worldSpace";

	std::string operatorName = name() + "Op";

	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::Agent* agent = agents[i];

			std::string jointNameTmp = jointName;
			int rotationOrderTmp = rotationOrder;
			bool worldSpaceTmp = worldSpace;
			bool addTmp = add;

			auto& groupIdStr = agent->groupIdStr()->get();
			jointNameTmp = getAttributePerAgent(jointName, jointNameOverrideMap.get(), groupIdStr, agent->metadata(), jointNameOverrideName);
			rotationOrderTmp = getAttributePerAgent(rotationOrder, rotationOrderOverrideMap.get(), groupIdStr, agent->metadata(), rotationOrderOverrideName);
			addTmp = getAttributePerAgent(add, addOverrideMap.get(), groupIdStr, agent->metadata(), offsetOverrideName);
			worldSpaceTmp = getAttributePerAgent(worldSpace, worldSpaceOverrideMap.get(), groupIdStr, agent->metadata(), worldSpaceOverrideName);
			

			if (jointNameTmp.empty())
				continue;

			Atoms::AgentBehaviourNetwork& network = agent->network();

			Atoms::Operator* endOperator = network.buildPoseNode();

			MyJointTransformOperator* jointTransform = static_cast<MyJointTransformOperator*>(network.manager().createNode(MyJointTransformOperator::staticTypeStr(), "jointTransform"));
			jointTransform->setAgent(agent);
			network.manager().connectAttr(endOperator->name(), "outPose", jointTransform->name(), "inPose");

			AtomsCore::StringMetadata bm(jointTransform->name());
			agent->metadata().addEntry(operatorName, &bm);

			jointTransform->getInputPort<AtomsGraph::StringPort>("jointName")->set(jointNameTmp);
			jointTransform->getInputPort<AtomsGraph::LongPort>("rotationOrder")->set(rotationOrderTmp);
			jointTransform->getInputPort<AtomsGraph::BooleanPort>("offset")->set(addTmp);
			jointTransform->getInputPort<AtomsGraph::BooleanPort>("worldSpace")->set(worldSpaceTmp);

			network.setBuildPoseNode(jointTransform);
		}
	});
}

void MyJointTransformModule::initFrame(const std::vector<Atoms::Agent*>& agents, Atoms::AgentGroup* agentGroup)
{
	auto& attribs = attributes();


	double weight = attribs.getTypedEntry<AtomsCore::DoubleMetadata>("weight")->value();
	AtomsCore::Vector3 rotate = attribs.getTypedEntry<AtomsCore::Vector3Metadata>("rotate")->value();
	AtomsCore::Vector3 scale = attribs.getTypedEntry<AtomsCore::Vector3Metadata>("scale")->value();
	AtomsCore::Vector3 translate = attribs.getTypedEntry<AtomsCore::Vector3Metadata>("translate")->value();

	AtomsPtr<AtomsCore::MapMetadata> rotateOverrideMap = attribs.getTypedEntry<AtomsCore::MapMetadata>("rotate_override");
	AtomsPtr<AtomsCore::MapMetadata> scaleOverrideMap = attribs.getTypedEntry<AtomsCore::MapMetadata>("scale_override");
	AtomsPtr<AtomsCore::MapMetadata> translateOverrideMap = attribs.getTypedEntry<AtomsCore::MapMetadata>("translate_override");
	AtomsPtr<AtomsCore::MapMetadata> weightOverrideMap = attribs.getTypedEntry<AtomsCore::MapMetadata>("weight_override");

	const std::string rotateOverrideName = name() + "@rotate";
	const std::string scaleOverrideName = name() + "@scale";
	const std::string translateOverrideName = name() + "@translate";
	const std::string weightOverrideName = name() + "@weight";
	const std::string operatorName = name() + "Op";

	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::Agent* agent = agents[i];
			if (!agent)
				continue;

			AtomsPtr<AtomsCore::StringMetadata> jointTransformOperatorNamePtr = agent->metadata().getTypedEntry<AtomsCore::StringMetadata>(operatorName);
			if (!jointTransformOperatorNamePtr)
			{
				continue;
			}

			AtomsGraph::Node *node = agent->network().manager().getNode(jointTransformOperatorNamePtr->get());
			if (node == nullptr)
			{
				AtomsUtils::Logger::warning() << "Could not find joint transform operator";
				continue;
			}

			auto agent_type = agent->agentType();
			if (!agent_type)
				continue;
			auto& skeleton = agent_type->skeleton();

			double weightTmp = getAttributePerAgent(weight, weightOverrideMap.get(), agent->groupIdStr()->get(), agent->metadata(), weightOverrideName);
			AtomsCore::Vector3 rotateTmp = getAttributePerAgent(rotate, rotateOverrideMap.get(), agent->groupIdStr()->get(), agent->metadata(), rotateOverrideName);
			AtomsCore::Vector3 scaleTmp = getAttributePerAgent(scale, scaleOverrideMap.get(), agent->groupIdStr()->get(), agent->metadata(), scaleOverrideName);
			AtomsCore::Vector3 translateTmp = getAttributePerAgent(translate, translateOverrideMap.get(), agent->groupIdStr()->get(), agent->metadata(), translateOverrideName);

			node->getInputPort<AtomsGraph::VectorPort>("translate")->set(translateTmp);
			node->getInputPort<AtomsGraph::VectorPort>("rotate")->set(rotateTmp);
			node->getInputPort<AtomsGraph::VectorPort>("scale")->set(scaleTmp);
			node->getInputPort<AtomsGraph::DoublePort>("weight")->set(weightTmp);
		}

	}
	);
}

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

And the module is exposed to Unreal by a custom component

Code Block
languagecpp
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

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

/**
 * 
 */
UCLASS(ClassGroup = (AtomsBehaviours), meta = (BlueprintSpawnableComponent))
class UMyJointTransformComponent : public UAtomsBehaviourComponent
{
	GENERATED_BODY()

public:
	/** The joint name */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		FName jointName;

	/** The joint name */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		TMap<int32, FName> jointNameOverride;

	/** Apply the transformation as an offset to the current one */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		bool offset;

	/** Apply the transformation as an offset to the current one */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		TMap<int32, bool> offsetOverride;

	/** Translation value */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		FVector translate;

	/** Translation value */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		TMap<int32, FVector> translateOverride;

	/** Rotation value in degrees */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		FVector rotate;

	/** Rotation value in degrees */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		TMap<int32, FVector> rotateOverride;

	/** Scale value */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		FVector scale;

	/** Scale value */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		TMap<int32, FVector> scaleOverride;

	/** The rotation order */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		int32 rotationOrder;

	/** The rotation order */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		TMap<int32, int32> rotationOrderOverride;

	/** Apply the transformation in world space */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		bool worldSpace;

	/** Apply the transformation in world space */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		TMap<int32, bool> worldSpaceOverride;

	/** The weight */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour", meta = (UIMin = "0.00", UIMax = "1.00"))
		float weight;

	/** The weight */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "Behaviour")
		TMap<int32, float> weightOverride;


	UMyJointTransformComponent();

	~UMyJointTransformComponent();

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

	/** The joint name */
	UFUNCTION(BlueprintCallable, Category = "JointTransformBehaviourComponent", meta = (CallInEditor = "true"))
		void SetJointName(const FName& Value);

	/** The joint name */
	UFUNCTION(BlueprintCallable, Category = "JointTransformBehaviourComponent", meta = (CallInEditor = "true"))
		void SetJointNameOverride(const FName& Value, const int32 AgentId);

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

	/** Apply the transformation as an offset to the current one */
	UFUNCTION(BlueprintCallable, Category = "JointTransformBehaviourComponent", meta = (CallInEditor = "true"))
		void SetOffset(const bool Value);

	/** Apply the transformation as an offset to the current one */
	UFUNCTION(BlueprintCallable, Category = "JointTransformBehaviourComponent", meta = (CallInEditor = "true"))
		void SetOffsetOverride(const bool Value, const int32 AgentId);

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

	/** Translation value */
	UFUNCTION(BlueprintCallable, Category = "JointTransformBehaviourComponent", meta = (CallInEditor = "true"))
		void SetTranslate(const FVector& Value);

	/** Translation value */
	UFUNCTION(BlueprintCallable, Category = "JointTransformBehaviourComponent", meta = (CallInEditor = "true"))
		void SetTranslateOverride(const FVector& Value, const int32 AgentId);

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

	/** Rotation value in degrees */
	UFUNCTION(BlueprintCallable, Category = "JointTransformBehaviourComponent", meta = (CallInEditor = "true"))
		void SetRotate(const FVector& Value);

	/** Rotation value in degrees */
	UFUNCTION(BlueprintCallable, Category = "JointTransformBehaviourComponent", meta = (CallInEditor = "true"))
		void SetRotateOverride(const FVector& Value, const int32 AgentId);

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

	/** Scale value */
	UFUNCTION(BlueprintCallable, Category = "JointTransformBehaviourComponent", meta = (CallInEditor = "true"))
		void SetScale(const FVector& Value);

	/** Scale value */
	UFUNCTION(BlueprintCallable, Category = "JointTransformBehaviourComponent", meta = (CallInEditor = "true"))
		void SetScaleOverride(const FVector& Value, const int32 AgentId);

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

	/** The rotation order */
	UFUNCTION(BlueprintCallable, Category = "JointTransformBehaviourComponent", meta = (CallInEditor = "true"))
		void SetRotationOrder(const int32 Value);

	/** The rotation order */
	UFUNCTION(BlueprintCallable, Category = "JointTransformBehaviourComponent", meta = (CallInEditor = "true"))
		void SetRotationOrderOverride(const int32 Value, const int32 AgentId);

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

	/** Apply the transformation in world space */
	UFUNCTION(BlueprintCallable, Category = "JointTransformBehaviourComponent", meta = (CallInEditor = "true"))
		void SetWorldSpace(const bool Value);

	/** Apply the transformation in world space */
	UFUNCTION(BlueprintCallable, Category = "JointTransformBehaviourComponent", meta = (CallInEditor = "true"))
		void SetWorldSpaceOverride(const bool Value, const int32 AgentId);

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

	/** The weight */
	UFUNCTION(BlueprintCallable, Category = "JointTransformBehaviourComponent", meta = (CallInEditor = "true"))
		void SetWeight(const float Value);

	/** The weight */
	UFUNCTION(BlueprintCallable, Category = "JointTransformBehaviourComponent", meta = (CallInEditor = "true"))
		void SetWeightOverride(const float Value, const int32 AgentId);

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




#if WITH_EDITOR
	virtual void PreEditChange(UnrealProperty * PropertyAboutToChange) override;
	virtual void PostEditChangeProperty(struct FPropertyChangedEvent& e) override;
#endif
	
};
Code Block
languagecpp
#include "MyJointTransformComponent.h"
#include "Actors/AtomsAgentGroup.h"
#include "AtomsModuleGeneratorUtils.h"
#include "AtomsAttributeUtils.h"

UMyJointTransformComponent::UMyJointTransformComponent() : UAtomsBehaviourComponent()
{
	AtomsBehaviourModule = "MyJointTransform";

	jointName = "";
	offset = true;
	translate = FVector(0.0, 0.0, 0.0);
	rotate = FVector(0.0, 0.0, 0.0);
	scale = FVector(1.0, 1.0, 1.0);
	rotationOrder = 0;
	worldSpace = false;
	weight = 1.0;
}

UMyJointTransformComponent::~UMyJointTransformComponent()
{

}

void UMyJointTransformComponent::GetAtomsAttributes(AtomsPtr<AtomsCore::MapMetadata>& attributes)
{
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(jointName, AtomsCore::StringMetadata, UAtomsAttributeUtils::ToString);
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META_OVERRIDE(jointName_override, jointNameOverride, AtomsCore::StringMetadata, UAtomsAttributeUtils::ToString);
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(offset, AtomsCore::BoolMetadata, );
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META_OVERRIDE(offset_override, offsetOverride, AtomsCore::BoolMetadata, );
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(translate, AtomsCore::Vector3Metadata, AtomsConverter::ToVector3);
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META_OVERRIDE(translate_override, translateOverride, AtomsCore::Vector3Metadata, AtomsConverter::ToVector3);
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(rotate, AtomsCore::Vector3Metadata, AtomsConverter::ToVector3);
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META_OVERRIDE(rotate_override, rotateOverride, AtomsCore::Vector3Metadata, AtomsConverter::ToVector3);
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(scale, AtomsCore::Vector3Metadata, AtomsConverter::ToVector3);
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META_OVERRIDE(scale_override, scaleOverride, AtomsCore::Vector3Metadata, AtomsConverter::ToVector3);
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(rotationOrder, AtomsCore::IntMetadata, );
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META_OVERRIDE(rotationOrder_override, rotationOrderOverride, AtomsCore::IntMetadata, );
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(worldSpace, AtomsCore::BoolMetadata, );
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META_OVERRIDE(worldSpace_override, worldSpaceOverride, AtomsCore::BoolMetadata, );
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(weight, AtomsCore::DoubleMetadata, );
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META_OVERRIDE(weight_override, weightOverride, AtomsCore::DoubleMetadata, );

}

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

	Super::PreEditChange(PropertyAboutToChange);

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



}

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



	Super::PostEditChangeProperty(e);
}
#endif

void UMyJointTransformComponent::SetJointName(const FName& Value)
{
	jointName = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(jointName, AtomsCore::StringMetadata, UAtomsAttributeUtils::ToString);

}

void UMyJointTransformComponent::SetJointNameOverride(const FName& Value, const int32 AgentId)
{
	ATOMS_BEHAVIOUR_COMPONENT_SET_META_OVERRIDE(jointName_override, jointNameOverride, AtomsCore::StringMetadata, UAtomsAttributeUtils::ToString, AgentId);

}

void UMyJointTransformComponent::RemoveJointNameOverride(const int32 AgentId)
{
	ATOMS_BEHAVIOUR_COMPONENT_REMOVE_OVERRIDE(jointName_override, jointNameOverride, AgentId);
}

void UMyJointTransformComponent::SetOffset(const bool Value)
{
	offset = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(offset, AtomsCore::BoolMetadata, );

}

void UMyJointTransformComponent::SetOffsetOverride(const bool Value, const int32 AgentId)
{
	ATOMS_BEHAVIOUR_COMPONENT_SET_META_OVERRIDE(offset_override, offsetOverride, AtomsCore::BoolMetadata, , AgentId);

}

void UMyJointTransformComponent::RemoveOffsetOverride(const int32 AgentId)
{
	ATOMS_BEHAVIOUR_COMPONENT_REMOVE_OVERRIDE(offset_override, offsetOverride, AgentId);
}

void UMyJointTransformComponent::SetTranslate(const FVector& Value)
{
	translate = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(translate, AtomsCore::Vector3Metadata, AtomsConverter::ToVector3);

}

void UMyJointTransformComponent::SetTranslateOverride(const FVector& Value, const int32 AgentId)
{
	ATOMS_BEHAVIOUR_COMPONENT_SET_META_OVERRIDE(translate_override, translateOverride, AtomsCore::Vector3Metadata, AtomsConverter::ToVector3, AgentId);

}

void UMyJointTransformComponent::RemoveTranslateOverride(const int32 AgentId)
{
	ATOMS_BEHAVIOUR_COMPONENT_REMOVE_OVERRIDE(translate_override, translateOverride, AgentId);
}

void UMyJointTransformComponent::SetRotate(const FVector& Value)
{
	rotate = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(rotate, AtomsCore::Vector3Metadata, AtomsConverter::ToVector3);

}

void UMyJointTransformComponent::SetRotateOverride(const FVector& Value, const int32 AgentId)
{
	ATOMS_BEHAVIOUR_COMPONENT_SET_META_OVERRIDE(rotate_override, rotateOverride, AtomsCore::Vector3Metadata, AtomsConverter::ToVector3, AgentId);

}

void UMyJointTransformComponent::RemoveRotateOverride(const int32 AgentId)
{
	ATOMS_BEHAVIOUR_COMPONENT_REMOVE_OVERRIDE(rotate_override, rotateOverride, AgentId);
}

void UMyJointTransformComponent::SetScale(const FVector& Value)
{
	scale = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(scale, AtomsCore::Vector3Metadata, AtomsConverter::ToVector3);

}

void UMyJointTransformComponent::SetScaleOverride(const FVector& Value, const int32 AgentId)
{
	ATOMS_BEHAVIOUR_COMPONENT_SET_META_OVERRIDE(scale_override, scaleOverride, AtomsCore::Vector3Metadata, AtomsConverter::ToVector3, AgentId);

}

void UMyJointTransformComponent::RemoveScaleOverride(const int32 AgentId)
{
	ATOMS_BEHAVIOUR_COMPONENT_REMOVE_OVERRIDE(scale_override, scaleOverride, AgentId);
}

void UMyJointTransformComponent::SetRotationOrder(const int32 Value)
{
	rotationOrder = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(rotationOrder, AtomsCore::IntMetadata, );

}

void UMyJointTransformComponent::SetRotationOrderOverride(const int32 Value, const int32 AgentId)
{
	ATOMS_BEHAVIOUR_COMPONENT_SET_META_OVERRIDE(rotationOrder_override, rotationOrderOverride, AtomsCore::IntMetadata, , AgentId);

}

void UMyJointTransformComponent::RemoveRotationOrderOverride(const int32 AgentId)
{
	ATOMS_BEHAVIOUR_COMPONENT_REMOVE_OVERRIDE(rotationOrder_override, rotationOrderOverride, AgentId);
}

void UMyJointTransformComponent::SetWorldSpace(const bool Value)
{
	worldSpace = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(worldSpace, AtomsCore::BoolMetadata, );

}

void UMyJointTransformComponent::SetWorldSpaceOverride(const bool Value, const int32 AgentId)
{
	ATOMS_BEHAVIOUR_COMPONENT_SET_META_OVERRIDE(worldSpace_override, worldSpaceOverride, AtomsCore::BoolMetadata, , AgentId);

}

void UMyJointTransformComponent::RemoveWorldSpaceOverride(const int32 AgentId)
{
	ATOMS_BEHAVIOUR_COMPONENT_REMOVE_OVERRIDE(worldSpace_override, worldSpaceOverride, AgentId);
}

void UMyJointTransformComponent::SetWeight(const float Value)
{
	weight = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
		ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(weight, AtomsCore::DoubleMetadata, );

}

void UMyJointTransformComponent::SetWeightOverride(const float Value, const int32 AgentId)
{
	ATOMS_BEHAVIOUR_COMPONENT_SET_META_OVERRIDE(weight_override, weightOverride, AtomsCore::DoubleMetadata, , AgentId);

}

void UMyJointTransformComponent::RemoveWeightOverride(const int32 AgentId)
{
	ATOMS_BEHAVIOUR_COMPONENT_REMOVE_OVERRIDE(weight_override, weightOverride, AgentId);
}

Finally, the operator and the module is registered to Atoms

Code Block
languagecpp
#include "MyJointTransformModule.h"
#include "MyJointTransformOperator.h"
#include <AtomsGraph/NodeFactory.h>
#include <Atoms/BehaviourModules.h>

void FAtomsUnrealTestGameModule::StartupModule()
{
	AtomsGraph::NodeFactory& manager = AtomsGraph::NodeFactory::instance();
      	manager.registerNode(JointConstraintOperatorMyJointTransformOperator::staticTypeStr(), &JointConstraintOperatorMyJointTransformOperator::creator);

    	Atoms::BehaviourModules& moduleManager = Atoms::BehaviourModules::instance();
	moduleManager.registerBehaviourModule("SimpleJointOffsetMyJointTransform", &JointOffsetModuleMyJointTransformModule::creator, Atoms::JointOffsetModuleBehaviourModules::kNative);

     return true;
  }


void FAtomsDemoGameModuleFAtomsUnrealTestGameModule::ShutdownModule()
{
    AtomsUtils::Logger::info() << "Unloading Joint offset plugin";
    	// Deregister the node from the node factory
    	AtomsGraph::NodeFactory& manager = AtomsGraph::NodeFactory::instance();
    	manager.deregisterNode(JointConstraintOperatorMyJointTransformOperator::staticTypeStr());
	
   	Atoms::BehaviourModules& moduleManager = Atoms::BehaviourModules::instance();
    	moduleManager.deregisterBehaviourModule("SimpleJointOffsetMyJointTransform");
    return true;
}