Last active
July 4, 2025 02:52
-
-
Save luke10x/b27f4e1f2b394bcf4831d9f901db822d to your computer and use it in GitHub Desktop.
gen-anim-from-glb-using-assimp.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef STB_IMAGE_IMPLEMENTATION | |
#include <stb_image.h> | |
#endif | |
#define GLM_ENABLE_EXPERIMENTAL | |
#include <glm/fwd.hpp> | |
#include <glm/gtc/quaternion.hpp> | |
#include <glm/gtc/type_ptr.hpp> | |
#include <glm/gtx/quaternion.hpp> | |
#include <assimp/postprocess.h> | |
#include <assimp/scene.h> | |
#include <assimp/Importer.hpp> | |
#include <vector> | |
#include <map> | |
#include <iostream> | |
#include <iostream> | |
#include <fstream> | |
#include <string> | |
#include <map> | |
#include <vector> | |
#include <sstream> | |
#include <iostream> | |
#include <fstream> | |
#include <string> | |
#include <map> | |
#include <sstream> | |
#include <vector> | |
#include <cmath> // For std::ceil | |
struct AnimationMixer | |
{ | |
// TODO only use Assimp during loading, | |
// not for entire lifespan of this application | |
// Assimp::Importer importer; | |
const aiScene *scene; | |
const aiMesh *mesh; | |
glm::mat4 globalInverseTransform; | |
std::map<std::string, uint> boneNameToIndex; | |
std::vector<glm::mat4> boneOffsets; | |
void applyBoneTransformsFromNodeTree( | |
const aiAnimation &animation0, | |
float currentTick0, | |
const aiAnimation &animation1, | |
float currentTick1, | |
float blendingFactor, | |
const aiNode *pNode, | |
const glm::mat4 &parentTransform, | |
std::vector<glm::mat4> &resultsBuffer); | |
private: | |
aiVector3D calcInterpolatedPosition( | |
float currentTick, | |
const aiNodeAnim *pNodeAnim); | |
aiQuaternion calcInterpolatedRotation( | |
float currentTick, | |
const aiNodeAnim *pNodeAnim); | |
aiVector3D calcInterpolatedScaling( | |
float currentTick, | |
const aiNodeAnim *pNodeAnim); | |
const aiNodeAnim *findChannel( | |
const aiAnimation &Animation, | |
const std::string &NodeName); | |
}; | |
struct AnimationConfigItem | |
{ | |
std::string name; | |
std::string alias; | |
float frequency = -1.0f; | |
int samples = -1; | |
}; | |
#define VERBOSE 1 | |
#define MAX_BONES (200) | |
glm::mat4 assimpToGlmMatrix(aiMatrix4x4 mat); | |
// Helper function to trim leading and trailing spaces | |
std::string trim(const std::string &str) | |
{ | |
size_t first = str.find_first_not_of(' '); | |
if (std::string::npos == first) | |
{ | |
return str; | |
} | |
size_t last = str.find_last_not_of(' '); | |
return str.substr(first, (last - first + 1)); | |
} | |
void printMat4(const glm::mat4& mat) { | |
std::cout << " {"; | |
for (int i = 0; i < 4; ++i) { | |
std::cout << " "; | |
for (int j = 0; j < 4; ++j) { | |
std::cout << mat[i][j]; | |
if (j < 3) std::cout << ", "; | |
} | |
if (i < 3) std::cout << ","; | |
else std::cout << ""; | |
} | |
std::cout << "},\n"; | |
// for (int i = 0; i < 4; ++i) { | |
// std::cout << "[ "; | |
// for (int j = 0; j < 4; ++j) { | |
// std::cout << mat[i][j] << " "; | |
// } | |
// std::cout << "]\n"; | |
// } | |
} | |
int main(int argc, char *argv[]) | |
{ | |
std::string glbFilePath; | |
std::string meshName; | |
size_t numBones; | |
std::string variableName; | |
std::vector<AnimationConfigItem> animationConfigItems; | |
/* 🗂️ Parsing config file */ { | |
std::cerr << "Tool works now fuck off" << std::endl; | |
if (argc != 2) | |
{ | |
std::cerr << "Usage: " << argv[0] << " <file_path>" << std::endl; | |
return 1; | |
} | |
char *iniFilePath = argv[1]; | |
std::ifstream file(iniFilePath); | |
if (!file.is_open()) | |
{ | |
std::cerr << "Failed to open file: " << iniFilePath << std::endl; | |
return 1; | |
} | |
std::string line; | |
int ln = 0; | |
while (std::getline(file, line)) | |
{ | |
// std::cerr << "processing line " << ++ln << ": " << line << std::endl; | |
if (line.empty() || line[0] == '#' || line[0] == ';') | |
{ | |
continue; | |
} | |
// Check if the line is an animation header | |
if (line == "[animation]") | |
{ | |
animationConfigItems.push_back(AnimationConfigItem()); | |
continue; | |
} | |
std::istringstream iss(line); | |
std::string key, value; | |
std::getline(iss, key, '='); | |
std::getline(iss, value); | |
// Trim leading and trailing spaces | |
key = trim(key); | |
value = trim(value); | |
// Root attribut prooperties | |
if (key == "file") | |
{ | |
glbFilePath = std::string(value); | |
} | |
if (key == "mesh") | |
{ | |
meshName = value; | |
} | |
else if (key == "variable") | |
{ | |
variableName = std::string(value); | |
} | |
// Animation properties | |
else if (key == "name") | |
{ | |
if (value.empty()) | |
{ | |
std::cerr << "Invalid animation name" << std::endl; | |
return 1; | |
} | |
animationConfigItems.back().name = value; | |
} | |
else if (key == "alias") | |
{ | |
if (value.empty()) | |
{ | |
std::cerr << "Invalid animation alias" << std::endl; | |
return 1; | |
} | |
animationConfigItems.back().alias = value; | |
} | |
else if (key == "frequency") | |
{ | |
float freq; | |
if (sscanf(value.c_str(), "%f", &freq) == 1) | |
{ | |
animationConfigItems.back().frequency = freq; | |
} | |
else | |
{ | |
std::cerr << "Invalid frequency value: " << value << std::endl; | |
return 1; | |
} | |
} | |
else if (key == "samples") | |
{ | |
int samples; | |
if (sscanf(value.c_str(), "%d", &samples) == 1) | |
{ | |
animationConfigItems.back().samples = samples; | |
} | |
else | |
{ | |
std::cerr << "Invalid samples value: " << value << std::endl; | |
return 1; | |
} | |
} | |
} | |
std::cerr << "after loop\n" | |
<< std::endl; | |
// Check if there is at least one animation | |
if (animationConfigItems.empty()) | |
{ | |
std::cerr << "No animations found in the file" << std::endl; | |
return 1; | |
} | |
// // Print the parsed data | |
// std::cout << "Mesh: " << config["mesh"] << std::endl; | |
// std::cout << "Variable: " << config["variable"] << std::endl; | |
// for (const auto& animation : animations) { | |
// std::cout << "Animation: " << animation.name << std::endl; | |
// std::cout << " Alias: " << animation.alias << std::endl; | |
// if (animation.frequency > 0.0f) { | |
// std::cout << " Frequency: " << animation.frequency << " seconds" << std::endl; | |
// } else { | |
// std::cout << " Samples: " << animation.samples << std::endl; | |
// } | |
// } | |
} // end of parsing | |
// For every animation -> for every key at interval -> for every bone | |
std::vector<std::map<int, std::vector<glm::mat4>>> animationCaches; | |
animationCaches.resize(animationConfigItems.size()); | |
// Going to use them everywhere | |
Assimp::Importer importer; | |
const aiScene *scene; | |
const aiMesh *mesh; | |
// Also will fullfil this | |
std::map<std::string, uint> boneNameToIndex; | |
std::vector<glm::mat4> boneOffsets; | |
/* 🍑 Load some ass */ { | |
/* Load scene from filePath */ { | |
scene = importer.ReadFile( | |
glbFilePath, // CLI arg file name | |
aiProcess_Triangulate | // Convert any polygons to triangles | |
aiProcess_FlipUVs | // Flip UV coordinates to match typical texture mapping | |
aiProcess_CalcTangentSpace // Calculate tangent space for normals (Instead of loading from file) | |
); // Pointer to the parsed 3D model data | |
if ( | |
!scene || // Scene cannot be loaded | |
scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || // Or loaded incomplete | |
!scene->mRootNode // Or it has no root node | |
) | |
{ | |
std::cerr << "Error loading scene " | |
<< "from file " << glbFilePath << " :" | |
<< importer.GetErrorString() << std::endl; | |
return -1; | |
} | |
} | |
mesh = nullptr; | |
/* Try loading mesh */ { | |
// Iterate through all the meshes in the scene | |
unsigned int meshIndex = 0; | |
for (meshIndex = 0; meshIndex < scene->mNumMeshes; meshIndex++) | |
{ | |
aiMesh *currentMesh = scene->mMeshes[meshIndex]; | |
if (currentMesh->mName.length > 0) | |
{ | |
const char *currentMeshName = currentMesh->mName.C_Str(); | |
if (strcmp(currentMeshName, meshName.c_str()) == 0) | |
{ | |
std::cerr << "Found matching mesh: " << meshName | |
<< std::endl; | |
mesh = currentMesh; | |
break; // Exit the loop | |
} | |
} | |
} | |
if (mesh == nullptr) | |
{ | |
// If mesh not loaded list all possible meshes | |
std::cerr << "Error loading mesh: " << meshName | |
<< " File " << glbFilePath << " contains the following meshes:" | |
<< std::endl; // Fallback name | |
for (meshIndex = 0; meshIndex < scene->mNumMeshes; meshIndex++) | |
{ | |
aiMesh *currentMesh = scene->mMeshes[meshIndex]; | |
// Print the name of the mesh | |
if (currentMesh->mName.length > 0) | |
{ | |
std::cerr << " - " << currentMesh->mName.C_Str() << std::endl; | |
} | |
} | |
exit(1); | |
} | |
} | |
// glm::mat4 globalInverseTransform; | |
/* Init bones */ { | |
if (mesh->mNumBones > MAX_BONES) | |
{ | |
std::cerr << "This model has too many bones " | |
<< mesh->mNumBones << std::endl; | |
assert(0); | |
} | |
std::cerr << "Mesh " << meshName << " has " << mesh->mNumBones << " bones" << std::endl; | |
numBones = mesh->mNumBones; | |
// For each bone | |
for (uint boneInd = 0; boneInd < mesh->mNumBones; boneInd++) | |
{ | |
auto pBone = mesh->mBones[boneInd]; | |
std::string boneName = std::string(pBone->mName.C_Str()); | |
// Add bone to index mapping | |
boneNameToIndex[boneName] = boneInd; | |
// reads binding pose offsets | |
boneOffsets.push_back( | |
assimpToGlmMatrix(pBone->mOffsetMatrix)); | |
} | |
} | |
} | |
int buffersCopied = 0; | |
for (unsigned long i = 0; i < animationConfigItems.size(); i++) // 🌳 Generate caches using animation system | |
{ | |
const auto &animationConfigItem = animationConfigItems[i]; | |
const char *animationName = animationConfigItem.name.c_str(); | |
float frequency = animationConfigItem.frequency; | |
int frames = animationConfigItem.samples; | |
aiAnimation *animation = nullptr; | |
int tickInterval; | |
/* try loading animation */ { | |
unsigned int aniIndex = 0; | |
for (aniIndex = 0; aniIndex < scene->mNumAnimations; aniIndex++) | |
{ | |
aiAnimation *currentAnimation = scene->mAnimations[aniIndex]; | |
if (currentAnimation->mName.length > 0) | |
{ | |
const char *currentAnimationName = currentAnimation->mName.C_Str(); | |
if (strcmp(currentAnimationName, animationName) == 0) | |
{ | |
std::cerr << "Found matching animation: " << animationName | |
<< std::endl; | |
animation = currentAnimation; | |
break; // Exit the loop | |
} | |
} | |
} | |
if (animation == nullptr) | |
{ | |
// If mesh not loaded list all possible meshes | |
std::cerr << "Error loading animation: " << animationName << " of mesh " << meshName | |
<< " File " << glbFilePath << " contains the following animations:" | |
<< std::endl; | |
for (aniIndex = 0; aniIndex < scene->mNumAnimations; aniIndex++) | |
{ | |
aiAnimation *currentAnimation = scene->mAnimations[aniIndex]; | |
// Print the name of the mesh | |
if (currentAnimation->mName.length > 0) | |
{ | |
std::cerr << " - " << currentAnimation->mName.C_Str() << std::endl; | |
} | |
} | |
exit(1); | |
} | |
float safeTicksPerSecond = animation->mTicksPerSecond == 0 | |
? 30.0f | |
: (float)animation->mTicksPerSecond; | |
float durationInSeconds = (float)animation->mDuration / safeTicksPerSecond; | |
std::cerr << " " << durationInSeconds << " seconds" << std::endl; | |
tickInterval = frames > 0 | |
? (int)(animation->mDuration / (float)frames) | |
: (int)(safeTicksPerSecond * frequency); | |
if (frames < 1) { | |
// exit(1); | |
animationConfigItems[i].samples = static_cast<int>(std::ceil(durationInSeconds / frequency)); | |
std::cerr << "tetes gher " << animationConfigItems[i].samples << std::endl; | |
} | |
} | |
/* Print animation info */ { | |
std::cerr << "Animation length: " << std::endl; | |
std::cerr << " " << animation->mDuration << " ticks" << std::endl; | |
std::cerr << " " << animation->mTicksPerSecond << " ticks per second" << std::endl; | |
// Print the number of channels | |
std::cerr << "Number of channels: " << animation->mNumChannels << std::endl; | |
std::cerr << "Sampling interval: every " << tickInterval << " ticks" << std::endl; | |
std::cerr << "Animation number of samples " << animationConfigItems[i].samples << std::endl; | |
// Print the number of keys for each type (position, rotation, scale) for each channel | |
if (VERBOSE == 2) | |
{ | |
for (unsigned int i = 0; i < animation->mNumChannels; ++i) | |
{ | |
const aiNodeAnim *channel = animation->mChannels[i]; | |
uint boneIndex = boneNameToIndex[channel->mNodeName.data]; | |
std::cerr << "Channel " << i | |
<< ", " << "Bone: " << boneIndex | |
<< " " << " (" << channel->mNodeName.C_Str() << ")" | |
<< " keys: " | |
<< channel->mNumPositionKeys << " pos, " | |
<< channel->mNumRotationKeys << " rot, " | |
<< channel->mNumScalingKeys << " scale" | |
<< std::endl; | |
} | |
} | |
} | |
/* Use animation mixer to extract caches */ { | |
AnimationMixer am; | |
am.scene = scene; | |
am.mesh = mesh; | |
am.boneNameToIndex = boneNameToIndex; | |
am.boneOffsets = boneOffsets; | |
// this is important | |
am.globalInverseTransform = glm::inverse( | |
assimpToGlmMatrix(scene->mRootNode->mTransformation)); | |
std::vector<glm::mat4> resultsBuffer; | |
resultsBuffer.resize(mesh->mNumBones); | |
for (int currentTick = 0; currentTick < animation->mDuration; currentTick += tickInterval) | |
{ | |
// Recurse starts here | |
glm::mat4 rootParentTransform(1.0f); | |
am.applyBoneTransformsFromNodeTree( | |
*animation, | |
(float)currentTick, | |
*animation, // yes I know | |
(float)currentTick, // this is the same shit | |
0.0f, scene->mRootNode, rootParentTransform, | |
resultsBuffer); | |
// no need to move unless resultsBuffer won't be reused | |
animationCaches[i][currentTick] = resultsBuffer; | |
buffersCopied++; | |
} | |
// here it gives me 2Mil. should be 300+ | |
} | |
} | |
std::cerr << "Buffers copied " << buffersCopied << std::endl; | |
/* Final printout */ { | |
std::cout << R"( | |
#pragma once | |
#include <glm/glm.hpp> | |
#include <vector> | |
)" << std::endl; | |
std::cout << "enum ENUM_" << variableName << " {" << std::endl; | |
for (size_t i = 0; i < animationConfigItems.size(); i++) { | |
AnimationConfigItem item = animationConfigItems[i]; | |
std::cout << " " << item.alias << "," << std::endl; | |
} | |
int totalFrameCount = 0; | |
for (size_t i = 0; i < animationCaches.size(); ++i) { | |
AnimationConfigItem item = animationConfigItems[i]; | |
totalFrameCount += item.samples; | |
} | |
std::cout << " __LAST_ENUM" << variableName << std::endl; | |
std::cout << "};" <<std::endl; | |
std::cout << "size_t " << variableName << "FrameOffset[] = {" << std::endl; | |
int offset = 0; | |
for (size_t i = 0; i < animationCaches.size(); i++) { | |
AnimationConfigItem item = animationConfigItems[i]; | |
std::cout << " " << offset << ", //"<< item.alias << std::endl; | |
offset += item.samples; | |
} | |
std::cout << "};" << std::endl; | |
std::cout << "size_t " << variableName << "LengthInFrames[] = {" << std::endl; | |
for (size_t i = 0; i < animationCaches.size(); i++) { | |
AnimationConfigItem item = animationConfigItems[i]; | |
std::cout << " " << item.samples << ", // "<< (item.samples * (int)numBones * 64) / 1024 << "KB " << item.alias << std::endl; | |
} | |
std::cout << "};" << std::endl; | |
std::cout << "" <<std::endl; | |
std::cout << "const uint " << variableName << "TotalFrameCount = " << totalFrameCount << ";" << std::endl; | |
std::cout << "const uint " << variableName <<"BoneCount = "<< numBones << ";" <<std::endl; | |
std::cout << "glm::mat4 " << variableName << "["<< variableName << "TotalFrameCount]["<<variableName<<"BoneCount] = {" <<std::endl; | |
size_t j = 0; | |
for (size_t i = 0; i < animationCaches.size(); ++i) { | |
// std::cerr << "Animation " << i << ":\n"; | |
for (const auto& [tick, matrices] : animationCaches[i]) { | |
std::cout << " {" << std::endl; | |
for (size_t k = 0; k < matrices.size(); k++) { | |
glm::mat4 mat = matrices[k]; | |
printMat4(mat); | |
} | |
std::cout << " }, // " << j << " Tick: " << tick << " " << " \n" << std::endl; | |
j++; | |
} | |
} | |
std::cout << "};" <<std::endl; | |
} | |
return 0; | |
} | |
glm::mat4 assimpToGlmMatrix(aiMatrix4x4 mat) | |
{ | |
glm::mat4 m; | |
for (int y = 0; y < 4; y++) | |
{ | |
for (int x = 0; x < 4; x++) | |
{ | |
m[x][y] = mat[(uint)y][(uint)x]; | |
} | |
} | |
return m; | |
} | |
void AnimationMixer::applyBoneTransformsFromNodeTree( | |
const aiAnimation &animation0, | |
float currentTick0, | |
const aiAnimation &animation1, | |
float currentTick1, | |
float blendingFactor, | |
const aiNode *pNode, | |
const glm::mat4 &parentTransform, | |
std::vector<glm::mat4> &resultsBuffer) | |
{ | |
std::string nodeName(pNode->mName.data); | |
glm::mat4 nodeTransform( | |
assimpToGlmMatrix(pNode->mTransformation)); | |
const aiNodeAnim *channel0 = findChannel(animation0, nodeName); | |
const aiNodeAnim *channel1 = findChannel(animation1, nodeName); | |
if (channel0 && channel1) | |
{ | |
// Get TRS components from animation | |
aiVector3D aiPosition0 = | |
calcInterpolatedPosition(currentTick0, channel0); | |
aiVector3D aiPosition1 = | |
calcInterpolatedPosition(currentTick1, channel1); | |
aiVector3D blendedPosition = | |
(1.0f - blendingFactor) * aiPosition0 + | |
aiPosition1 * blendingFactor; | |
glm::vec3 position( | |
blendedPosition.x, blendedPosition.y, blendedPosition.z); | |
aiQuaternion aiRotation0 = | |
calcInterpolatedRotation(currentTick0, channel0); | |
aiQuaternion aiRotation1 = | |
calcInterpolatedRotation(currentTick1, channel1); | |
aiQuaternion blendedRotation; | |
aiQuaternion::Interpolate( | |
blendedRotation, aiRotation0, aiRotation1, blendingFactor); | |
glm::quat rotation( | |
blendedRotation.w, blendedRotation.x, blendedRotation.y, | |
blendedRotation.z); | |
aiVector3D aiScaling0 = | |
calcInterpolatedScaling(currentTick0, channel0); | |
aiVector3D aiScaling1 = | |
calcInterpolatedScaling(currentTick1, channel1); | |
aiVector3D blendedScaling = | |
(1.0f - blendingFactor) * aiScaling0 + | |
aiScaling1 * blendingFactor; | |
glm::vec3 scale( | |
blendedScaling.x, blendedScaling.y, blendedScaling.z); | |
// Inflate them into matrices | |
glm::mat4 positionMat = glm::mat4(1.0f); | |
positionMat = glm::translate(positionMat, position); | |
glm::mat4 rotationMat = glm::toMat4(rotation); | |
glm::mat4 scaleMat = glm::mat4(1.0f); | |
scaleMat = glm::scale(scaleMat, scale); | |
// Multiply TRS matrices | |
nodeTransform = positionMat * rotationMat * scaleMat; | |
} | |
glm::mat4 cascadeTransform = | |
parentTransform * nodeTransform; | |
auto isBoneNode = this->boneNameToIndex.find(nodeName) != | |
this->boneNameToIndex.end(); | |
if (isBoneNode) | |
{ | |
uint boneIndex = this->boneNameToIndex[nodeName]; | |
resultsBuffer[boneIndex] = glm::transpose( | |
this->globalInverseTransform * cascadeTransform * | |
boneOffsets[boneIndex]); | |
} | |
else | |
{ | |
// Because there are some nodes at the root of the mesh, | |
// that are not bones, but they have some transformations | |
// Therefore, here I apply them to the globalTransformation | |
cascadeTransform = | |
cascadeTransform * nodeTransform; | |
} | |
for (uint i = 0; i < pNode->mNumChildren; i++) | |
{ | |
// Go deeper into recursion | |
applyBoneTransformsFromNodeTree( | |
animation0, currentTick0, animation1, currentTick1, | |
blendingFactor, pNode->mChildren[i], cascadeTransform, | |
resultsBuffer); | |
} | |
} | |
const aiNodeAnim *AnimationMixer::findChannel( | |
const aiAnimation &animation, | |
const std::string &nodeName) | |
{ | |
for (uint i = 0; i < animation.mNumChannels; i++) | |
{ | |
const aiNodeAnim *channel = animation.mChannels[i]; | |
if (std::string(channel->mNodeName.data) == nodeName) | |
{ | |
return channel; | |
} | |
} | |
return nullptr; | |
} | |
aiVector3D AnimationMixer::calcInterpolatedPosition( | |
float currentTick, | |
const aiNodeAnim *pNodeAnim) | |
{ | |
if (pNodeAnim->mNumPositionKeys == 1) | |
{ | |
return pNodeAnim->mPositionKeys[0].mValue; | |
} | |
uint positionIndex = 0; | |
for (uint i = 0; i < pNodeAnim->mNumPositionKeys - 1; i++) | |
{ | |
float t = (float)pNodeAnim->mPositionKeys[i + 1].mTime; | |
if (currentTick < t) | |
{ | |
positionIndex = i; | |
break; | |
} | |
} | |
uint nextPositionIndex = positionIndex + 1; | |
assert(nextPositionIndex < pNodeAnim->mNumPositionKeys); | |
float t1 = (float)pNodeAnim->mPositionKeys[positionIndex].mTime; | |
if (t1 > currentTick) | |
{ | |
return pNodeAnim->mPositionKeys[positionIndex].mValue; | |
} | |
float t2 = | |
(float)pNodeAnim->mPositionKeys[nextPositionIndex].mTime; | |
float deltaTime = t2 - t1; | |
float factor = (currentTick - t1) / deltaTime; | |
assert(factor >= 0.0f && factor <= 1.0f); | |
const aiVector3D &start = | |
pNodeAnim->mPositionKeys[positionIndex].mValue; | |
const aiVector3D &end = | |
pNodeAnim->mPositionKeys[nextPositionIndex].mValue; | |
return start + factor * (end - start); | |
} | |
aiQuaternion AnimationMixer::calcInterpolatedRotation( | |
float currentTick, | |
const aiNodeAnim *pNodeAnim) | |
{ | |
if (pNodeAnim->mNumRotationKeys == 1) | |
{ | |
return pNodeAnim->mRotationKeys[0].mValue; | |
} | |
uint rotationIndex; | |
assert(pNodeAnim->mNumRotationKeys > 0); | |
for (uint i = 0; i < pNodeAnim->mNumRotationKeys - 1; i++) | |
{ | |
float t = (float)pNodeAnim->mRotationKeys[i + 1].mTime; | |
if (currentTick < t) | |
{ | |
rotationIndex = i; | |
break; | |
} | |
} | |
uint nextRotationIndex = rotationIndex + 1; | |
assert(nextRotationIndex < pNodeAnim->mNumRotationKeys); | |
aiQuaternion quat; | |
float t1 = (float)pNodeAnim->mRotationKeys[rotationIndex].mTime; | |
if (t1 > currentTick) | |
{ | |
quat = pNodeAnim->mRotationKeys[rotationIndex].mValue; | |
} | |
else | |
{ | |
float t2 = | |
(float)pNodeAnim->mRotationKeys[nextRotationIndex].mTime; | |
float deltaTime = t2 - t1; | |
float factor = (currentTick - t1) / deltaTime; | |
assert(factor >= 0.0f && factor <= 1.0f); | |
const aiQuaternion &startRotationQ = | |
pNodeAnim->mRotationKeys[rotationIndex].mValue; | |
const aiQuaternion &endRotationQ = | |
pNodeAnim->mRotationKeys[nextRotationIndex].mValue; | |
aiQuaternion::Interpolate( | |
quat, startRotationQ, endRotationQ, factor); | |
} | |
quat.Normalize(); | |
return quat; | |
} | |
aiVector3D AnimationMixer::calcInterpolatedScaling( | |
float currentTick, | |
const aiNodeAnim *pNodeAnim) | |
{ | |
aiVector3D Out; | |
// we need at least two values to interpolate... | |
if (pNodeAnim->mNumScalingKeys == 1) | |
{ | |
return pNodeAnim->mScalingKeys[0].mValue; | |
} | |
uint scalingIndex; | |
assert(pNodeAnim->mNumScalingKeys > 0); | |
for (uint i = 0; i < pNodeAnim->mNumScalingKeys - 1; i++) | |
{ | |
float t = (float)pNodeAnim->mScalingKeys[i + 1].mTime; | |
if (currentTick < t) | |
{ | |
scalingIndex = i; | |
break; | |
} | |
} | |
uint nextScalingIndex = scalingIndex + 1; | |
assert(nextScalingIndex < pNodeAnim->mNumScalingKeys); | |
float t1 = (float)pNodeAnim->mScalingKeys[scalingIndex].mTime; | |
if (t1 > currentTick) | |
{ | |
return pNodeAnim->mScalingKeys[scalingIndex].mValue; | |
} | |
float t2 = (float)pNodeAnim->mScalingKeys[nextScalingIndex].mTime; | |
float deltaTime = t2 - t1; | |
float factor = (currentTick - (float)t1) / deltaTime; | |
assert(factor >= 0.0f && factor <= 1.0f); | |
const aiVector3D &start = | |
pNodeAnim->mScalingKeys[scalingIndex].mValue; | |
const aiVector3D &end = | |
pNodeAnim->mScalingKeys[nextScalingIndex].mValue; | |
return start + factor * (end - start); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment