Add support for importing GLTF2 animations.
Refactors the glTF2 internal classes to more closely reflect the structure of the actual GLTF2 file format. Adds implementations for reading skins and animations from GLTF2 files into those structures. Also provides implementations for converting skins and animations from GLTF into assimp data structures. Special handling is required for bone weights since assimp stores vertex-weights-per-bone whereas GLTF2 stores bone-weights-per-vertex. Only supports keyframed LINEAR animation data; STEP and CUBICSPLINE is not currently supported.
This commit is contained in:
committed by
Haroon Qureshi
parent
19844d0cec
commit
acad9a06e9
+84
-110
@@ -961,92 +961,92 @@ void glTF2Exporter::ExportMetadata()
|
||||
asset.generator = buffer;
|
||||
}
|
||||
|
||||
inline void ExtractAnimationData(Asset& mAsset, std::string& animId, Ref<Animation>& animRef, Ref<Buffer>& buffer, const aiNodeAnim* nodeChannel, float ticksPerSecond)
|
||||
inline Ref<Accessor> GetSamplerInputRef(Asset& asset, std::string& animId, Ref<Buffer>& buffer, std::vector<float>& times)
|
||||
{
|
||||
// Loop over the data and check to see if it exactly matches an existing buffer.
|
||||
// If yes, then reference the existing corresponding accessor.
|
||||
// Otherwise, add to the buffer and create a new accessor.
|
||||
return ExportData(asset, animId, buffer, times.size(), ×[0], AttribType::SCALAR, AttribType::SCALAR, ComponentType_FLOAT);
|
||||
}
|
||||
|
||||
size_t counts[3] = {
|
||||
nodeChannel->mNumPositionKeys,
|
||||
nodeChannel->mNumScalingKeys,
|
||||
nodeChannel->mNumRotationKeys,
|
||||
};
|
||||
size_t numKeyframes = 1;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (counts[i] > numKeyframes) {
|
||||
numKeyframes = counts[i];
|
||||
}
|
||||
inline void ExtractTranslationSampler(Asset& asset, std::string& animId, Ref<Buffer>& buffer, const aiNodeAnim* nodeChannel, float ticksPerSecond, Animation::Sampler& sampler)
|
||||
{
|
||||
const unsigned int numKeyframes = nodeChannel->mNumPositionKeys;
|
||||
if (numKeyframes == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------
|
||||
// Extract TIME parameter data.
|
||||
// Check if the timeStamps are the same for mPositionKeys, mRotationKeys, and mScalingKeys.
|
||||
if(nodeChannel->mNumPositionKeys > 0) {
|
||||
typedef float TimeType;
|
||||
std::vector<TimeType> timeData;
|
||||
timeData.resize(numKeyframes);
|
||||
for (size_t i = 0; i < numKeyframes; ++i) {
|
||||
size_t frameIndex = i * nodeChannel->mNumPositionKeys / numKeyframes;
|
||||
// mTime is measured in ticks, but GLTF time is measured in seconds, so convert.
|
||||
// Check if we have to cast type here. e.g. uint16_t()
|
||||
timeData[i] = static_cast<float>(nodeChannel->mPositionKeys[frameIndex].mTime / ticksPerSecond);
|
||||
}
|
||||
|
||||
Ref<Accessor> timeAccessor = ExportData(mAsset, animId, buffer, static_cast<unsigned int>(numKeyframes), &timeData[0], AttribType::SCALAR, AttribType::SCALAR, ComponentType_FLOAT);
|
||||
if (timeAccessor) animRef->Parameters.TIME = timeAccessor;
|
||||
const vec3 kZeros = {0, 0, 0};
|
||||
std::vector<float> times(numKeyframes);
|
||||
std::vector<vec3> values(numKeyframes, kZeros);
|
||||
for (unsigned int i = 0; i < numKeyframes; ++i) {
|
||||
const aiVectorKey& key = nodeChannel->mPositionKeys[i];
|
||||
// mTime is measured in ticks, but GLTF time is measured in seconds, so convert.
|
||||
times[i] = static_cast<float>(key.mTime / ticksPerSecond);
|
||||
values[i][0] = key.mValue.x;
|
||||
values[i][1] = key.mValue.y;
|
||||
values[i][2] = key.mValue.z;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------
|
||||
// Extract translation parameter data
|
||||
if(nodeChannel->mNumPositionKeys > 0) {
|
||||
C_STRUCT aiVector3D* translationData = new aiVector3D[numKeyframes];
|
||||
for (size_t i = 0; i < numKeyframes; ++i) {
|
||||
size_t frameIndex = i * nodeChannel->mNumPositionKeys / numKeyframes;
|
||||
translationData[i] = nodeChannel->mPositionKeys[frameIndex].mValue;
|
||||
}
|
||||
sampler.input = GetSamplerInputRef(asset, animId, buffer, times);
|
||||
sampler.output = ExportData(asset, animId, buffer, numKeyframes, &values[0], AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT);
|
||||
sampler.interpolation = Interpolation_LINEAR;
|
||||
}
|
||||
|
||||
Ref<Accessor> tranAccessor = ExportData(mAsset, animId, buffer, static_cast<unsigned int>(numKeyframes), translationData, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT);
|
||||
if ( tranAccessor ) {
|
||||
animRef->Parameters.translation = tranAccessor;
|
||||
}
|
||||
delete[] translationData;
|
||||
inline void ExtractScaleSampler(Asset& asset, std::string& animId, Ref<Buffer>& buffer, const aiNodeAnim* nodeChannel, float ticksPerSecond, Animation::Sampler& sampler)
|
||||
{
|
||||
const unsigned int numKeyframes = nodeChannel->mNumScalingKeys;
|
||||
if (numKeyframes == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------
|
||||
// Extract scale parameter data
|
||||
if(nodeChannel->mNumScalingKeys > 0) {
|
||||
C_STRUCT aiVector3D* scaleData = new aiVector3D[numKeyframes];
|
||||
for (size_t i = 0; i < numKeyframes; ++i) {
|
||||
size_t frameIndex = i * nodeChannel->mNumScalingKeys / numKeyframes;
|
||||
scaleData[i] = nodeChannel->mScalingKeys[frameIndex].mValue;
|
||||
}
|
||||
|
||||
Ref<Accessor> scaleAccessor = ExportData(mAsset, animId, buffer, static_cast<unsigned int>(numKeyframes), scaleData, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT);
|
||||
if ( scaleAccessor ) {
|
||||
animRef->Parameters.scale = scaleAccessor;
|
||||
}
|
||||
delete[] scaleData;
|
||||
const vec3 kZeros = {0, 0, 0};
|
||||
std::vector<float> times(numKeyframes);
|
||||
std::vector<vec3> values(numKeyframes, kZeros);
|
||||
for (unsigned int i = 0; i < numKeyframes; ++i) {
|
||||
const aiVectorKey& key = nodeChannel->mScalingKeys[i];
|
||||
// mTime is measured in ticks, but GLTF time is measured in seconds, so convert.
|
||||
times[i] = static_cast<float>(key.mTime / ticksPerSecond);
|
||||
values[i][0] = key.mValue.x;
|
||||
values[i][1] = key.mValue.y;
|
||||
values[i][2] = key.mValue.z;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------
|
||||
// Extract rotation parameter data
|
||||
if(nodeChannel->mNumRotationKeys > 0) {
|
||||
vec4* rotationData = new vec4[numKeyframes];
|
||||
for (size_t i = 0; i < numKeyframes; ++i) {
|
||||
size_t frameIndex = i * nodeChannel->mNumRotationKeys / numKeyframes;
|
||||
rotationData[i][0] = nodeChannel->mRotationKeys[frameIndex].mValue.x;
|
||||
rotationData[i][1] = nodeChannel->mRotationKeys[frameIndex].mValue.y;
|
||||
rotationData[i][2] = nodeChannel->mRotationKeys[frameIndex].mValue.z;
|
||||
rotationData[i][3] = nodeChannel->mRotationKeys[frameIndex].mValue.w;
|
||||
}
|
||||
sampler.input = GetSamplerInputRef(asset, animId, buffer, times);
|
||||
sampler.output = ExportData(asset, animId, buffer, numKeyframes, &values[0], AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT);
|
||||
sampler.interpolation = Interpolation_LINEAR;
|
||||
}
|
||||
|
||||
Ref<Accessor> rotAccessor = ExportData(mAsset, animId, buffer, static_cast<unsigned int>(numKeyframes), rotationData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT);
|
||||
if ( rotAccessor ) {
|
||||
animRef->Parameters.rotation = rotAccessor;
|
||||
}
|
||||
delete[] rotationData;
|
||||
inline void ExtractRotationSampler(Asset& asset, std::string& animId, Ref<Buffer>& buffer, const aiNodeAnim* nodeChannel, float ticksPerSecond, Animation::Sampler& sampler)
|
||||
{
|
||||
const unsigned int numKeyframes = nodeChannel->mNumRotationKeys;
|
||||
if (numKeyframes == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const vec4 kZeros = {0, 0, 0, 0};
|
||||
std::vector<float> times(numKeyframes);
|
||||
std::vector<vec4> values(numKeyframes, kZeros);
|
||||
for (unsigned int i = 0; i < numKeyframes; ++i) {
|
||||
const aiQuatKey& key = nodeChannel->mRotationKeys[i];
|
||||
// mTime is measured in ticks, but GLTF time is measured in seconds, so convert.
|
||||
times[i] = static_cast<float>(key.mTime / ticksPerSecond);
|
||||
values[i][0] = key.mValue.x;
|
||||
values[i][1] = key.mValue.y;
|
||||
values[i][2] = key.mValue.z;
|
||||
values[i][3] = key.mValue.w;
|
||||
}
|
||||
|
||||
sampler.input = GetSamplerInputRef(asset, animId, buffer, times);
|
||||
sampler.output = ExportData(asset, animId, buffer, numKeyframes, &values[0], AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT);
|
||||
sampler.interpolation = Interpolation_LINEAR;
|
||||
}
|
||||
|
||||
static void AddSampler(Ref<Animation>& animRef, Ref<Node>& nodeRef, Animation::Sampler& sampler, AnimationPath path)
|
||||
{
|
||||
Animation::Channel channel;
|
||||
channel.sampler = static_cast<int>(animRef->samplers.size());
|
||||
channel.target.path = path;
|
||||
channel.target.node = nodeRef;
|
||||
animRef->channels.push_back(channel);
|
||||
animRef->samplers.push_back(sampler);
|
||||
}
|
||||
|
||||
void glTF2Exporter::ExportAnimations()
|
||||
@@ -1055,6 +1055,7 @@ void glTF2Exporter::ExportAnimations()
|
||||
|
||||
for (unsigned int i = 0; i < mScene->mNumAnimations; ++i) {
|
||||
const aiAnimation* anim = mScene->mAnimations[i];
|
||||
const float ticksPerSecond = static_cast<float>(anim->mTicksPerSecond);
|
||||
|
||||
std::string nameAnim = "anim";
|
||||
if (anim->mName.length > 0) {
|
||||
@@ -1070,46 +1071,19 @@ void glTF2Exporter::ExportAnimations()
|
||||
name = mAsset->FindUniqueID(name, "animation");
|
||||
Ref<Animation> animRef = mAsset->animations.Create(name);
|
||||
|
||||
// Parameters
|
||||
ExtractAnimationData(*mAsset, name, animRef, bufferRef, nodeChannel, static_cast<float>(anim->mTicksPerSecond));
|
||||
Ref<Node> animNode = mAsset->nodes.Get(nodeChannel->mNodeName.C_Str());
|
||||
|
||||
for (unsigned int j = 0; j < 3; ++j) {
|
||||
std::string channelType;
|
||||
int channelSize;
|
||||
switch (j) {
|
||||
case 0:
|
||||
channelType = "rotation";
|
||||
channelSize = nodeChannel->mNumRotationKeys;
|
||||
break;
|
||||
case 1:
|
||||
channelType = "scale";
|
||||
channelSize = nodeChannel->mNumScalingKeys;
|
||||
break;
|
||||
case 2:
|
||||
channelType = "translation";
|
||||
channelSize = nodeChannel->mNumPositionKeys;
|
||||
break;
|
||||
}
|
||||
Animation::Sampler translationSampler;
|
||||
ExtractTranslationSampler(*mAsset, name, bufferRef, nodeChannel, ticksPerSecond, translationSampler);
|
||||
AddSampler(animRef, animNode, translationSampler, AnimationPath_TRANSLATION);
|
||||
|
||||
if (channelSize < 1) { continue; }
|
||||
|
||||
Animation::AnimChannel tmpAnimChannel;
|
||||
Animation::AnimSampler tmpAnimSampler;
|
||||
|
||||
tmpAnimChannel.sampler = static_cast<int>(animRef->Samplers.size());
|
||||
tmpAnimChannel.target.path = channelType;
|
||||
tmpAnimSampler.output = channelType;
|
||||
tmpAnimSampler.id = name + "_" + channelType;
|
||||
|
||||
tmpAnimChannel.target.node = mAsset->nodes.Get(nodeChannel->mNodeName.C_Str());
|
||||
|
||||
tmpAnimSampler.input = "TIME";
|
||||
tmpAnimSampler.interpolation = "LINEAR";
|
||||
|
||||
animRef->Channels.push_back(tmpAnimChannel);
|
||||
animRef->Samplers.push_back(tmpAnimSampler);
|
||||
}
|
||||
Animation::Sampler rotationSampler;
|
||||
ExtractRotationSampler(*mAsset, name, bufferRef, nodeChannel, ticksPerSecond, rotationSampler);
|
||||
AddSampler(animRef, animNode, rotationSampler, AnimationPath_ROTATION);
|
||||
|
||||
Animation::Sampler scaleSampler;
|
||||
ExtractScaleSampler(*mAsset, name, bufferRef, nodeChannel, ticksPerSecond, scaleSampler);
|
||||
AddSampler(animRef, animNode, scaleSampler, AnimationPath_SCALE);
|
||||
}
|
||||
|
||||
// Assimp documentation staes this is not used (not implemented)
|
||||
|
||||
Reference in New Issue
Block a user