SkinBinding exampleΒΆ

/**
* This example demonstrates how to use SkinBinding for basic bone animation.
* This example is inspired by the GLTF skin example
* https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_019_SimpleSkin.md
* It is recommended to go through the GLTF example first (or in parallel) and then see how to implement
* the same result in Ramses Logic.
*/

struct SceneAndAppearance
{
    ramses::Scene* scene;
    ramses::Appearance* appearance;
};

/**
* Helper method which creates a simple ramses scene. For more ramses
* examples, check the ramses docs at https://bmwcarit.github.io/ramses
*/
SceneAndAppearance CreateSceneWithSkinnableMesh(ramses::RamsesClient& client);

/**
* Helper method which sets up simple animation for a skeleton joint node.
* Details on how to set up animations are covered in the animation example.
*/
void SetupJointAnimation(const rlogic::RamsesNodeBinding& node, rlogic::LogicEngine& logicEngine);

int main()
{
    /**
    * Use simple class to create ramses framework objects which are not essential for this example.
    * For more info on those, please refer to the ramses docs: https://bmwcarit.github.io/ramses
    */
    SimpleRenderer renderer;

    /**
     * Create a simple Ramses scene with a simple mesh.
     * We will then apply vertex skinning on this mesh.
     */
    auto [scene, appearance] = CreateSceneWithSkinnableMesh(*renderer.getClient());

    /**
    * Skin binding requires feature level 04 or higher
    */
    rlogic::LogicEngine logicEngine{ rlogic::EFeatureLevel_04 };

    /**
    * Show the scene on the renderer
    */
    renderer.showScene(scene->getSceneId());

    /**
    * First create skeleton joints, each skeleton joint is represented by a Ramses node and a node binding.
    * Joint 2 is slightly offset in Y axis so that both joints form a simple 2-bone skeleton.
    */
    auto skeletonJoint1 = scene->createNode();
    auto skeletonJoint2 = scene->createNode();
    skeletonJoint2->setTranslation(0.f, 1.f, 0.f);
    const auto skeletonJointBinding1 = logicEngine.createRamsesNodeBinding(*skeletonJoint1);
    const auto skeletonJointBinding2 = logicEngine.createRamsesNodeBinding(*skeletonJoint2);

    /**
    * Set up a simple rotation animation for joint 2.
    * Details on how to set up animations are covered in the animation example.
    */
    SetupJointAnimation(*skeletonJointBinding2, logicEngine);

    /**
    * Prepare inverse binding matrices, we will need an inverse matrix for each joint.
    * These are needed for skinning calculations, refer to #rlogic::SkinBinding for details.
    * Inverse binding matrices often come with asset data but here we utilize Ramses
    * to calculate them for us and then convert to a data container suited for later use.
    */
    float tempData[16]; // NOLINT(modernize-avoid-c-arrays) Ramses uses C array in matrix getters
    rlogic::matrix44f inverseBindMatrix1{};
    rlogic::matrix44f inverseBindMatrix2{};
    skeletonJoint1->getInverseModelMatrix(tempData);
    std::copy(std::begin(tempData), std::end(tempData), inverseBindMatrix1.begin());
    skeletonJoint2->getInverseModelMatrix(tempData);
    std::copy(std::begin(tempData), std::end(tempData), inverseBindMatrix2.begin());

    /**
    * Now prepare the inputs for #rlogic::SkinBinding creation, we will need:
    *  - list of joints in form of node bindings
    *  - list of inverse binding matrices
    *  - appearance binding of the appearance used to render the mesh
    *  - uniform input of the appearance where joint matrices are expected
    */
    const std::vector<const rlogic::RamsesNodeBinding*> skinBindingJoints = {
        skeletonJointBinding1,
        skeletonJointBinding2 };
    const std::vector<rlogic::matrix44f> skinBindingInverseBindMatrices = {
        inverseBindMatrix1,
        inverseBindMatrix2 };
    rlogic::RamsesAppearanceBinding* appearanceBinding = logicEngine.createRamsesAppearanceBinding(*appearance);

    ramses::UniformInput jointMatUniform;
    appearance->getEffect().findUniformInput("u_jointMat", jointMatUniform);

    /**
    * Finally create instance of skin binding using all the data.
    */
    logicEngine.createSkinBinding(
        skinBindingJoints,
        skinBindingInverseBindMatrices,
        *appearanceBinding,
        jointMatUniform);

    /**
    * Note that after this point there is no application logic needed, all the steps needed for skinning happen
    * automatically within the skin binding as central place to bind all the needed data.
    * On every update animation will rotate one of the skeleton joints, the joint's transformation affects
    * the calculation of the joint matrix which is then used in the vertex shader.
    */

    /**
     * Simulate an application loop.
     */
    while (!renderer.isWindowClosed())
    {
        /**
        * 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();

        /**
        * Process window events, check if window was closed
        */
        renderer.processEvents();

        /**
        * Throttle the simulation loop by sleeping for a bit.
        */
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }