Animation exampleΒΆ

/**
* 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://genivi.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
     */
    rlogic::AnimationNode* cubicAnimNode = logicEngine.createAnimationNode({ cubicAnimChannel });
    rlogic::AnimationNode* stepAnimNode = logicEngine.createAnimationNode({ stepAnimChannel });

    /**
    * 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"));

    /**
     * Start the cubic animation right away.
     */
    cubicAnimNode->getInputs()->getChild("play")->set(true);

    /**
     * Store the timeDelta inputs of both animations, because we will be updating these each frame (see the loop below).
     * For a larger scene with many animations, consider looping over the LogicEngine::animationNodes() collection
     * and storing all timeDelta properties into an array.
     */
    rlogic::Property* timeDeltaInput1 = cubicAnimNode->getInputs()->getChild("timeDelta");
    rlogic::Property* timeDeltaInput2 = stepAnimNode->getInputs()->getChild("timeDelta");

    /**
     * Simulate an application loop.
     */
    auto lastFrameTime = std::chrono::steady_clock::now();
    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);

        /**
         * In every iteration where animation update is desired a 'timeDelta' must be set,
         * timeDelta represents a time change since last update.
         * In this example, the time units are seconds measured in float (the time units must match the timestamp data of the animations).
         * If animation is playing the timeDelta determines how much it will progress in this update,
         * the actual output value is then calculated by interpolating between the two keyframes closest
         * to the current (elapsed) play time.
         */
        const auto timeNow = std::chrono::steady_clock::now();
        const float timeDeltaSecs = 0.001f * static_cast<float>(std::chrono::duration_cast<std::chrono::milliseconds>(timeNow - lastFrameTime).count());
        timeDeltaInput1->set(timeDeltaSecs);
        timeDeltaInput2->set(timeDeltaSecs);

        lastFrameTime = timeNow;

        /**
        * Update the LogicEngine. This will apply changes from any running animation which had its timeDelta input set before this update call.
        */
        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);