Skip to content

Commit

Permalink
Improvements in the Assimp importer (#41)
Browse files Browse the repository at this point in the history
* remove hardcoded root node name

* change importer to process bones apart from mesh

* improvements in the assimp loader

* fix code style

* remove unused code

* remove unused param

* cleanup unecessary code

* removed boneNodes

* improve readability in bone hierarchy processing

* extract material processor

* remove empty line

* Update deploy.yml

* AnimationProcessor

* fix test

* fix tests

* fix test

* fix test

* fix test

* fix test

* fix test

* fix test

* fix test

* fix test

* fix test

* fix test

* fix test

* fix test

* fix test

* fix test

* fix tests

* Update deploy.yml

* Simplified importer code,
and allow loading textures from embedded files

* improve material processor code

* extracts the bone processor

* extract meshprocessor

* add unit test to bone processor

* Include the mesh processor test

* fix after cpp check insight

* improve based on cpp check
  • Loading branch information
fernandotonon committed Aug 25, 2023
1 parent 2b4b91e commit 2e7de27
Show file tree
Hide file tree
Showing 16 changed files with 1,027 additions and 600 deletions.
89 changes: 89 additions & 0 deletions src/Assimp/AnimationProcessor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#include "AnimationProcessor.h"

AnimationProcessor::AnimationProcessor(Ogre::SkeletonPtr skeleton): skeleton(skeleton) {}

void AnimationProcessor::processAnimations(const aiScene* scene) {
for(auto i = 0u; i < scene->mNumAnimations; i++) {
aiAnimation* animation = scene->mAnimations[i];
processAnimation(animation, scene);
}
}

void AnimationProcessor::processAnimation(aiAnimation* animation, const aiScene* scene) {
// Create the animation
Ogre::Animation* ogreAnimation = skeleton->createAnimation(animation->mName.C_Str(), animation->mDuration/10.0f);
// Process the animation channels
for(auto i = 0u; i < animation->mNumChannels; i++) {
aiNodeAnim* nodeAnim = animation->mChannels[i];
processAnimationChannel(nodeAnim, ogreAnimation, scene, i);
}
}


void AnimationProcessor::processAnimationChannel(aiNodeAnim* nodeAnim, Ogre::Animation* animation, const aiScene* scene, unsigned int channelIndex) {
// Create the animation track
Ogre::Bone* bone = skeleton->getBone(nodeAnim->mNodeName.C_Str());
Ogre::NodeAnimationTrack* track = animation->createNodeTrack(bone->getHandle(), bone);

// Create a map to store keyframes by time
std::map<double, std::tuple<Ogre::Vector3, Ogre::Quaternion, Ogre::Vector3>> keyframes;

// Process the position keys
for(auto i = 0u; i < nodeAnim->mNumPositionKeys; i++) {
aiVectorKey positionKey = nodeAnim->mPositionKeys[i];
Ogre::Vector3 position(positionKey.mValue.x, positionKey.mValue.y, positionKey.mValue.z);

// Get the bone's T-pose position
auto boneTPosePosition = bone->getPosition();

// Convert the position from local space to model space
position = position - boneTPosePosition;

keyframes[positionKey.mTime] = std::make_tuple(
position,
Ogre::Quaternion::IDENTITY,
Ogre::Vector3::UNIT_SCALE
);
}

// Process the rotation keys
for(auto i = 0u; i < nodeAnim->mNumRotationKeys; i++) {
aiQuatKey rotationKey = nodeAnim->mRotationKeys[i];
Ogre::Quaternion boneTPoseRotation = bone->getOrientation();
Ogre::Quaternion rot(rotationKey.mValue.w, rotationKey.mValue.x, rotationKey.mValue.y, rotationKey.mValue.z);
rot = boneTPoseRotation.Inverse() * rot; // Convert from local space to model space
rot.normalise(); // Normalize the quaternion
if (keyframes.find(rotationKey.mTime) == keyframes.end()) {
keyframes[rotationKey.mTime] = std::make_tuple(
Ogre::Vector3::ZERO,
rot,
Ogre::Vector3::UNIT_SCALE
);
} else {
std::get<1>(keyframes[rotationKey.mTime]) = rot;
}
}

// Process the scaling keys
for(auto i = 0u; i < nodeAnim->mNumScalingKeys; i++) {
aiVectorKey scalingKey = nodeAnim->mScalingKeys[i];
Ogre::Vector3 scale(scalingKey.mValue.x, scalingKey.mValue.y, scalingKey.mValue.z);
if (keyframes.find(scalingKey.mTime) == keyframes.end()) {
keyframes[scalingKey.mTime] = std::make_tuple(
Ogre::Vector3::ZERO,
Ogre::Quaternion::IDENTITY,
scale
);
} else {
std::get<2>(keyframes[scalingKey.mTime]) = scale;
}
}

// Now create the keyframes in the track
for(auto& [time, transform] : keyframes) {
Ogre::TransformKeyFrame* keyFrame = track->createNodeKeyFrame(time/10.0f);
keyFrame->setTranslate(std::get<0>(transform));
keyFrame->setRotation(std::get<1>(transform));
keyFrame->setScale(std::get<2>(transform));
}
}
17 changes: 17 additions & 0 deletions src/Assimp/AnimationProcessor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma once

#include <Ogre.h>
#include <assimp/scene.h>

class AnimationProcessor
{
public:
AnimationProcessor(Ogre::SkeletonPtr skeleton);
void processAnimations(const aiScene* scene);

private:
void processAnimation(aiAnimation* animation, const aiScene* scene);
void processAnimationChannel(aiNodeAnim* nodeAnim, Ogre::Animation* animation, const aiScene* scene, unsigned int channelIndex);
Ogre::SkeletonPtr skeleton;
};

26 changes: 26 additions & 0 deletions src/Assimp/AnimationProcessor_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "AnimationProcessor.h"

// Test if processAnimations processes all animations
TEST(AnimationProcessorTest, ProcessAllAnimations) {
auto ogreRoot = std::make_unique<Ogre::Root>();
auto mockSkeleton= Ogre::SkeletonManager::getSingleton().create("MockSkeleton",Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, true);
AnimationProcessor processor(mockSkeleton);

aiScene scene;
scene.mNumAnimations = 2;
scene.mAnimations = new aiAnimation*[2];
scene.mAnimations[0] = new aiAnimation;
scene.mAnimations[1] = new aiAnimation;
scene.mAnimations[0]->mName = aiString( std::string( "Animation1"));
scene.mAnimations[1]->mName = aiString( std::string( "Animation2"));
scene.mAnimations[0]->mNumChannels=0;
scene.mAnimations[1]->mNumChannels=0;

processor.processAnimations(&scene);

EXPECT_EQ(mockSkeleton->getNumAnimations(), 2);
EXPECT_EQ(mockSkeleton->getAnimation(0)->getName(), "Animation1");
EXPECT_EQ(mockSkeleton->getAnimation(1)->getName(), "Animation2");
}
110 changes: 110 additions & 0 deletions src/Assimp/BoneProcessor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#include "BoneProcessor.h"

void BoneProcessor::processBoneHierarchy(aiBone* bone) {
// Process the bone node
processBoneNode(bone);

// Recursively process children bones
for(auto i = 0u; i < bone->mNode->mNumChildren; i++) {
aiNode* childNode = bone->mNode->mChildren[i];
if(aiBonesMap.find(childNode->mName.C_Str()) != aiBonesMap.end()) {
processBoneHierarchy(aiBonesMap[childNode->mName.C_Str()]);
}
}
}

void BoneProcessor::processBones(Ogre::SkeletonPtr skeleton, const aiScene *scene) {
this->skeleton = skeleton;
for(auto i = 0u; i < scene->mNumMeshes; i++) {
aiMesh* mesh = scene->mMeshes[i];
for(auto j = 0u; j < mesh->mNumBones; j++) {
aiBone* bone = mesh->mBones[j];
// Check if the bone already exists
if(!skeleton->hasBone(bone->mName.C_Str())) {
aiBonesMap[bone->mName.C_Str()] = bone;
createBone(bone->mName.C_Str());
}
}
}

// Some models have bone animations but not all the bones are related to the meshes
for(auto i = 0u; i < scene->mNumAnimations; i++) {
aiAnimation* anim = scene->mAnimations[i];
for(auto j = 0u; j < anim->mNumChannels; j++) {
aiNodeAnim* nodeAnim = anim->mChannels[j];
createBone(nodeAnim->mNodeName.C_Str());
}
}

// Process the root bones first
for(auto i = 0u; i < scene->mNumMeshes; i++) {
aiMesh* mesh = scene->mMeshes[i];
for(auto j = 0u; j < mesh->mNumBones; j++) {
aiBone* bone = mesh->mBones[j];
if(bone->mNode && bone->mNode->mParent && !skeleton->hasBone(bone->mNode->mParent->mName.C_Str())) {
createBone(bone->mNode->mParent->mName.C_Str());
processBoneHierarchy(bone);
}
}
}
}

void BoneProcessor::createBone(const std::string& boneName) {
// Check if the bone already exists
if(!skeleton->hasBone(boneName)) {
// If the bone does not exist, create it
skeleton->createBone(boneName);
}
}

void BoneProcessor::processBoneNode(aiBone* bone) {
// Convert the aiBone's offset matrix to an Ogre::Matrix4
Ogre::Matrix4 offsetMatrix(
bone->mOffsetMatrix.a1, bone->mOffsetMatrix.a2, bone->mOffsetMatrix.a3, bone->mOffsetMatrix.a4,
bone->mOffsetMatrix.b1, bone->mOffsetMatrix.b2, bone->mOffsetMatrix.b3, bone->mOffsetMatrix.b4,
bone->mOffsetMatrix.c1, bone->mOffsetMatrix.c2, bone->mOffsetMatrix.c3, bone->mOffsetMatrix.c4,
bone->mOffsetMatrix.d1, bone->mOffsetMatrix.d2, bone->mOffsetMatrix.d3, bone->mOffsetMatrix.d4
);

// Invert the offset matrix to get the global transformation of the bone
Ogre::Matrix4 globalTransform = offsetMatrix.inverse();

// If the bone has a parent, multiply the global transformation of the bone with the inverse of the global transformation of the parent to get the local transformation
if(bone->mNode->mParent && bone->mNode->mParent->mName.length) {
Ogre::Bone* parentBone = skeleton->getBone(bone->mNode->mParent->mName.C_Str());
if(parentBone) {
Ogre::Matrix4 parentGlobalTransform = parentBone->_getFullTransform().inverse();
globalTransform = parentGlobalTransform * globalTransform;
}
}

// Convert the Ogre::Matrix4 to an Ogre::Affine3
Ogre::Affine3 affine(globalTransform);

// Decompose the offset matrix into position, scale, and orientation
Ogre::Vector3 position, scale;
Ogre::Quaternion orientation;
affine.decomposition(position, scale, orientation);

// Retrieve the bone (it should already exist)
Ogre::Bone* ogreBone = skeleton->getBone(bone->mName.C_Str());

// Set the bone's position, orientation, and scale
ogreBone->setPosition(position);
ogreBone->setOrientation(orientation);
ogreBone->setScale(scale);

// Add the bone to the parent bone, if it exists
if(bone->mNode->mParent && bone->mNode->mParent->mName.length) {
Ogre::Bone* parentBone = skeleton->getBone(bone->mNode->mParent->mName.C_Str());
if(parentBone) {
// Check if ogreBone is already a child of parentBone
if (!std::any_of(parentBone->getChildren().begin(), parentBone->getChildren().end(),
[&ogreBone](const auto& childNode) {
return childNode->getName() == ogreBone->getName();
})) {
parentBone->addChild(ogreBone);
}
}
}
}
17 changes: 17 additions & 0 deletions src/Assimp/BoneProcessor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma once

#include <Ogre.h>
#include <assimp/scene.h>

class BoneProcessor {
public:
void processBones(Ogre::SkeletonPtr skeleton, const aiScene* scene);

private:
void createBone(const std::string& boneName);
void processBoneHierarchy(aiBone* bone);
void processBoneNode(aiBone *bone);

Ogre::SkeletonPtr skeleton;
std::map<std::string, aiBone*> aiBonesMap;
};
Loading

0 comments on commit 2e7de27

Please sign in to comment.