/**
* This example demonstrates how to use animation to animate
* Ramses scene content.
*/
struct SceneAndNodes
{
ramses::Scene* scene;
ramses::Node* node1;
ramses::Node* node2;
};
/**
* Helper method which creates a simple ramses scene. For more ramses
* examples, check the ramses docs at https://covesa.github.io/ramses
*/
SceneAndNodes CreateSceneWithTriangles(ramses::RamsesClient& client);
int main(int argc, char* argv[])
{
/**
* Create Ramses framework and client objects. Ramses Logic does not manage
* or encapsulate Ramses objects - it only interacts with existing Ramses objects.
* The application must take special care to not destroy Ramses objects while a
* LogicEngine instance is still referencing them!
*/
ramses::RamsesFramework ramsesFramework(argc, argv);
ramses::RamsesClient* ramsesClient = ramsesFramework.createClient("example client");
/**
* To keep this example simple, we don't include a Renderer, but only provide the scene
* over network. Start a ramses daemon and a renderer additionally to see the visual result!
* The connect() ensures the scene published in this example will be distributed over network.
*/
ramsesFramework.connect();
/**
* Create a test Ramses scene with two simple triangles to be animated separately.
*/
auto [scene, tri1, tri2] = CreateSceneWithTriangles(*ramsesClient);
rlogic::LogicEngine logicEngine;
/**
* Create a binding object which serves as a bridge between logic nodes and animations on one end
* and a Ramses scene on the other end.
*/
rlogic::RamsesNodeBinding* nodeBinding1 = logicEngine.createRamsesNodeBinding(*tri1);
rlogic::RamsesNodeBinding* nodeBinding2 = logicEngine.createRamsesNodeBinding(*tri2);
/**
* Create two simple animations (cubic and step) by providing keyframes and timestamps.
* Animations have a single key-frame channel in this example for simplicity.
*
* First, create the data arrays which contain the time stamp data, the key-frame data points, and tangent arrays for the cubic animation.
*/
rlogic::DataArray* animTimestamps = logicEngine.createDataArray(std::vector<float>{ 0.f, 0.5f, 1.f, 1.5f }); // will be interpreted as seconds
rlogic::DataArray* animKeyframes = logicEngine.createDataArray(std::vector<rlogic::vec3f>{ {0.f, 0.f, 0.f}, {0.f, 0.f, 180.f}, {0.f, 0.f, 100.f}, {0.f, 0.f, 360.f} });
rlogic::DataArray* cubicAnimTangentsIn = logicEngine.createDataArray(std::vector<rlogic::vec3f>{ {0.f, 0.f, 0.f}, { 0.f, 0.f, 0.f }, { 0.f, 0.f, 0.f }, { 0.f, 0.f, 0.f } });
rlogic::DataArray* cubicAnimTangentsOut = logicEngine.createDataArray(std::vector<rlogic::vec3f>{ {0.f, 0.f, 0.f}, { 0.f, 0.f, 0.f }, { 0.f, 0.f, 0.f }, { 0.f, 0.f, 0.f } });
/**
* Create a channel for each animation - cubic and nearest/step.
*/
const rlogic::AnimationChannel cubicAnimChannel { "rotationZcubic", animTimestamps, animKeyframes, rlogic::EInterpolationType::Cubic, cubicAnimTangentsIn, cubicAnimTangentsOut };
const rlogic::AnimationChannel stepAnimChannel { "rotationZstep", animTimestamps, animKeyframes, rlogic::EInterpolationType::Step };
/**
* Finally, create the animation nodes by passing in the channel data via config
*/
rlogic::AnimationNodeConfig animConfigCubic;
animConfigCubic.addChannel(cubicAnimChannel);
rlogic::AnimationNode* cubicAnimNode = logicEngine.createAnimationNode(animConfigCubic);
rlogic::AnimationNodeConfig animConfigStep;
animConfigStep.addChannel(stepAnimChannel);
rlogic::AnimationNode* stepAnimNode = logicEngine.createAnimationNode(animConfigStep);
/**
* Connect the animation channel 'rotationZ' output with the rotation property of the RamsesNodeBinding object.
* After this, the value computed in the animation output channel will be propagated to the ramses node's rotation property.
*/
logicEngine.link(
*cubicAnimNode->getOutputs()->getChild("rotationZcubic"),
*nodeBinding1->getInputs()->getChild("rotation"));
logicEngine.link(
*stepAnimNode->getOutputs()->getChild("rotationZstep"),
*nodeBinding2->getInputs()->getChild("rotation"));
/**
* We need to provide time information to the animation nodes, the easiest way is to create a TimerNode
* and link its 'timeDelta' output to all animation nodes' 'timeDelta' input.
* Timer node will internally get a system time ticker on every update, it will calculate a 'timeDelta' (time period since last update)
* which is exactly what an animation node needs to advance its animation. See TimerNode API for more use cases.
*/
rlogic::TimerNode* timer = logicEngine.createTimerNode();
logicEngine.link(
*timer->getOutputs()->getChild("timeDelta"),
*cubicAnimNode->getInputs()->getChild("timeDelta"));
logicEngine.link(
*timer->getOutputs()->getChild("timeDelta"),
*stepAnimNode->getInputs()->getChild("timeDelta"));
/**
* Start the cubic animation right away.
*/
cubicAnimNode->getInputs()->getChild("play")->set(true);
/**
* Simulate an application loop.
*/
for(int loop = 0; loop < 500; ++loop)
{
/**
* Query progress of cubic animation and if finished, trigger play of step animation.
* Note that this logic can also be implemented as a simple Lua script plugged
* in between 'progress' output of cubic animation and 'play' input of step animation.
*/
if (cubicAnimNode->getOutputs()->getChild("progress")->get<float>() > 0.999f)
stepAnimNode->getInputs()->getChild("play")->set(true);
/**
* Update the LogicEngine. This will apply changes to Ramses scene from any running animation.
*/
logicEngine.update();
/**
* In order to commit the changes to Ramses scene caused by animations logic we need to "flush" them.
*/
scene->flush();
/**
* Throttle the simulation loop by sleeping for a bit.
*/
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
/**
* Ramses logic objects are managed and will be automatically released with destruction of the LogicEngine instance,
* however it is good practice to destroy objects if they are not going to be needed anymore.
* When destroying manually, keep order in mind, any logic content referencing a Ramses scene should be destroyed
* before the scene. Similarly objects using DataArray instances (e.g. AnimationNodes) should be destroyed before
* the data arrays. Generally objects referencing other objects should always be destroyed first.
*/
logicEngine.destroy(*cubicAnimNode);
logicEngine.destroy(*stepAnimNode);
logicEngine.destroy(*animTimestamps);
logicEngine.destroy(*animKeyframes);
logicEngine.destroy(*cubicAnimTangentsIn);
logicEngine.destroy(*cubicAnimTangentsOut);
logicEngine.destroy(*nodeBinding1);
logicEngine.destroy(*nodeBinding2);
ramsesClient->destroy(*scene);