Writing an Atoms operator (AtomsUnreal)
An Atoms operator is a specialized Atoms graph node having a built-in out pose port and specific functions to access the owner agent.
This operator adds an offset to a joint.
#pragma once
#include <AtomsGraph/Ports.h>
#include <Atoms/Globals.h>
#include <Atoms/Graph/Operator.h>
class MyJointTransformOperator : public Atoms::Operator
{
public:
NODE_STANDARD_MEMBERS
MyJointTransformOperator();
virtual ~MyJointTransformOperator();
virtual bool compute(const AtomsGraph::ComputeData* computeData) override;
virtual void reset() override;
private:
AtomsGraph::PosePort* m_inPose;
AtomsGraph::StringPort *m_inJointName;
AtomsGraph::LongPort *m_inRotationOrder;
AtomsGraph::BooleanPort *m_inWorldSpace;
AtomsGraph::BooleanPort *m_inOffset;
AtomsGraph::VectorPort *m_inTranslate;
AtomsGraph::VectorPort *m_inRotate;
AtomsGraph::VectorPort *m_inScale;
AtomsGraph::DoublePort *m_inWeight;
int m_jointId;
bool m_first;
};
#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.
#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);
};
And the module is exposed to Unreal by a custom component
Finally, the operator and the module is registered to Atoms
Copyright © 2017, Toolchefs LTD.