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