From 57d3d71b6ec68f41a10c1da2f01d096f1308e88c Mon Sep 17 00:00:00 2001 From: Haroon Qureshi Date: Fri, 26 Oct 2018 15:36:34 -0700 Subject: [PATCH 1/9] 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. --- code/glTF2Asset.h | 71 +++++----- code/glTF2Asset.inl | 94 ++++++++++++- code/glTF2AssetWriter.inl | 43 ++++-- code/glTF2Exporter.cpp | 191 +++++++++++--------------- code/glTF2Importer.cpp | 281 +++++++++++++++++++++++++++++++++----- code/glTF2Importer.h | 2 +- 6 files changed, 485 insertions(+), 197 deletions(-) diff --git a/code/glTF2Asset.h b/code/glTF2Asset.h index 0def5b74d..92be82f3b 100644 --- a/code/glTF2Asset.h +++ b/code/glTF2Asset.h @@ -303,6 +303,20 @@ namespace glTF2 TextureType_UNSIGNED_SHORT_5_5_5_1 = 32820 }; + //! Values for the Animation::Target::path field + enum AnimationPath { + AnimationPath_TRANSLATION, + AnimationPath_ROTATION, + AnimationPath_SCALE, + AnimationPath_WEIGHTS, + }; + + //! Values for the Animation::Sampler::interpolation field + enum Interpolation { + Interpolation_LINEAR, + Interpolation_STEP, + Interpolation_CUBICSPLINE, + }; //! Values for the Accessor::type field (helper class) class AttribType @@ -742,7 +756,7 @@ namespace glTF2 //extension: KHR_materials_pbrSpecularGlossiness Nullable pbrSpecularGlossiness; - //extension: KHR_materials_unlit + //extension: KHR_materials_unlit bool unlit; Material() { SetDefaults(); } @@ -870,56 +884,35 @@ namespace glTF2 struct Animation : public Object { - struct AnimSampler { - std::string id; //!< The ID of this sampler. - std::string input; //!< The ID of a parameter in this animation to use as key-frame input. - std::string interpolation; //!< Type of interpolation algorithm to use between key-frames. - std::string output; //!< The ID of a parameter in this animation to use as key-frame output. + struct Sampler { + Sampler() : interpolation(Interpolation_LINEAR) {} + + Ref input; //!< Accessor reference to the buffer storing the key-frame times. + Ref output; //!< Accessor reference to the buffer storing the key-frame values. + Interpolation interpolation; //!< Type of interpolation algorithm to use between key-frames. }; - struct AnimChannel { - int sampler; //!< The index of a sampler in the containing animation's samplers property. + struct Target { + Target() : path(AnimationPath_TRANSLATION) {} - struct AnimTarget { - Ref node; //!< The node to animate. - std::string path; //!< The name of property of the node to animate ("translation", "rotation", or "scale"). - } target; + Ref node; //!< The node to animate. + AnimationPath path; //!< The property of the node to animate. }; - struct AnimParameters { - Ref TIME; //!< Accessor reference to a buffer storing a array of floating point scalar values. - Ref rotation; //!< Accessor reference to a buffer storing a array of four-component floating-point vectors. - Ref scale; //!< Accessor reference to a buffer storing a array of three-component floating-point vectors. - Ref translation; //!< Accessor reference to a buffer storing a array of three-component floating-point vectors. + struct Channel { + Channel() : sampler(-1) {} + + int sampler; //!< The sampler index containing the animation data. + Target target; //!< The node and property to animate. }; - // AnimChannel Channels[3]; //!< Connect the output values of the key-frame animation to a specific node in the hierarchy. - // AnimParameters Parameters; //!< The samplers that interpolate between the key-frames. - // AnimSampler Samplers[3]; //!< The parameterized inputs representing the key-frame data. - - std::vector Channels; //!< Connect the output values of the key-frame animation to a specific node in the hierarchy. - AnimParameters Parameters; //!< The samplers that interpolate between the key-frames. - std::vector Samplers; //!< The parameterized inputs representing the key-frame data. + std::vector samplers; //!< All the key-frame data for this animation. + std::vector channels; //!< Data to connect nodes to key-frames. Animation() {} void Read(Value& obj, Asset& r); - - //! Get accessor given an animation parameter name. - Ref GetAccessor(std::string name) { - if (name == "TIME") { - return Parameters.TIME; - } else if (name == "rotation") { - return Parameters.rotation; - } else if (name == "scale") { - return Parameters.scale; - } else if (name == "translation") { - return Parameters.translation; - } - return Ref(); - } }; - //! Base class for LazyDict that acts as an interface class LazyDictBase { diff --git a/code/glTF2Asset.inl b/code/glTF2Asset.inl index 11e3965e5..687e16ce1 100755 --- a/code/glTF2Asset.inl +++ b/code/glTF2Asset.inl @@ -461,7 +461,7 @@ inline void Buffer::EncodedRegion_SetCurrent(const std::string& pID) throw DeadlyImportError("GLTF: EncodedRegion with ID: \"" + pID + "\" not found."); } -inline +inline bool Buffer::ReplaceData(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t* pReplace_Data, const size_t pReplace_Count) { @@ -483,8 +483,8 @@ bool Buffer::ReplaceData(const size_t pBufferData_Offset, const size_t pBufferDa return true; } - -inline + +inline bool Buffer::ReplaceData_joint(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t* pReplace_Data, const size_t pReplace_Count) { if((pBufferData_Count == 0) || (pReplace_Count == 0) || (pReplace_Data == nullptr)) { @@ -718,7 +718,7 @@ inline void Image::Read(Value& obj, Asset& r) this->mDataLength = this->bufferView->byteLength; // maybe this memcpy could be avoided if aiTexture does not delete[] pcData at destruction. - + this->mData.reset(new uint8_t[this->mDataLength]); memcpy(this->mData.get(), buffer->GetPointer() + this->bufferView->byteOffset, this->mDataLength); @@ -1083,6 +1083,10 @@ inline void Node::Read(Value& obj, Asset& r) if (meshRef) this->meshes.push_back(meshRef); } + if (Value* skin = FindUInt(obj, "skin")) { + this->skin = r.skins.Retrieve(skin->GetUint()); + } + if (Value* camera = FindUInt(obj, "camera")) { this->camera = r.cameras.Retrieve(camera->GetUint()); if (this->camera) @@ -1102,6 +1106,82 @@ inline void Scene::Read(Value& obj, Asset& r) } } +inline void Skin::Read(Value& obj, Asset& r) +{ + if (Value* matrices = FindUInt(obj, "inverseBindMatrices")) { + inverseBindMatrices = r.accessors.Retrieve(matrices->GetUint()); + } + + if (Value* joints = FindArray(obj, "joints")) { + for (unsigned i = 0; i < joints->Size(); ++i) { + if (!(*joints)[i].IsUint()) continue; + Ref node = r.nodes.Retrieve((*joints)[i].GetUint()); + if (node) { + this->jointNames.push_back(node); + } + } + } +} + +inline void Animation::Read(Value& obj, Asset& r) +{ + if (Value* samplers = FindArray(obj, "samplers")) { + for (unsigned i = 0; i < samplers->Size(); ++i) { + Value& sampler = (*samplers)[i]; + + Sampler s; + if (Value* input = FindUInt(sampler, "input")) { + s.input = r.accessors.Retrieve(input->GetUint()); + } + if (Value* output = FindUInt(sampler, "output")) { + s.output = r.accessors.Retrieve(output->GetUint()); + } + s.interpolation = Interpolation_LINEAR; + if (Value* interpolation = FindString(sampler, "interpolation")) { + const std::string interp = interpolation->GetString(); + if (interp == "LINEAR") { + s.interpolation = Interpolation_LINEAR; + } else if (interp == "STEP") { + s.interpolation = Interpolation_STEP; + } else if (interp == "CUBICSPLINE") { + s.interpolation = Interpolation_CUBICSPLINE; + } + } + this->samplers.push_back(s); + } + } + + if (Value* channels = FindArray(obj, "channels")) { + for (unsigned i = 0; i < channels->Size(); ++i) { + Value& channel = (*channels)[i]; + + Channel c; + if (Value* sampler = FindUInt(channel, "sampler")) { + c.sampler = sampler->GetUint(); + } + + if (Value* target = FindObject(channel, "target")) { + if (Value* node = FindUInt(*target, "node")) { + c.target.node = r.nodes.Retrieve(node->GetUint()); + } + if (Value* path = FindString(*target, "path")) { + const std::string p = path->GetString(); + if (p == "translation") { + c.target.path = AnimationPath_TRANSLATION; + } else if (p == "rotation") { + c.target.path = AnimationPath_ROTATION; + } else if (p == "scale") { + c.target.path = AnimationPath_SCALE; + } else if (p == "weights") { + c.target.path = AnimationPath_WEIGHTS; + } + } + } + this->channels.push_back(c); + } + } +} + inline void AssetMetadata::Read(Document& doc) { if (Value* obj = FindObject(doc, "asset")) { @@ -1277,6 +1357,12 @@ inline void Asset::Load(const std::string& pFile, bool isBinary) } } + if (Value* animsArray = FindArray(doc, "animations")) { + for (unsigned int i = 0; i < animsArray->Size(); ++i) { + animations.Retrieve(i); + } + } + // Clean up for (size_t i = 0; i < mDicts.size(); ++i) { mDicts[i]->DetachFromDocument(); diff --git a/code/glTF2AssetWriter.inl b/code/glTF2AssetWriter.inl index 0579dfdac..50d855aaa 100644 --- a/code/glTF2AssetWriter.inl +++ b/code/glTF2AssetWriter.inl @@ -113,10 +113,10 @@ namespace glTF2 { /****************** Channels *******************/ Value channels; channels.SetArray(); - channels.Reserve(unsigned(a.Channels.size()), w.mAl); + channels.Reserve(unsigned(a.channels.size()), w.mAl); - for (size_t i = 0; i < unsigned(a.Channels.size()); ++i) { - Animation::AnimChannel& c = a.Channels[i]; + for (size_t i = 0; i < unsigned(a.channels.size()); ++i) { + Animation::Channel& c = a.channels[i]; Value valChannel; valChannel.SetObject(); { @@ -126,7 +126,20 @@ namespace glTF2 { valTarget.SetObject(); { valTarget.AddMember("node", c.target.node->index, w.mAl); - valTarget.AddMember("path", c.target.path, w.mAl); + switch (c.target.path) { + case AnimationPath_TRANSLATION: + valTarget.AddMember("path", "translation", w.mAl); + break; + case AnimationPath_ROTATION: + valTarget.AddMember("path", "rotation", w.mAl); + break; + case AnimationPath_SCALE: + valTarget.AddMember("path", "scale", w.mAl); + break; + case AnimationPath_WEIGHTS: + valTarget.AddMember("path", "weights", w.mAl); + break; + } } valChannel.AddMember("target", valTarget, w.mAl); } @@ -138,16 +151,24 @@ namespace glTF2 { Value valSamplers; valSamplers.SetArray(); - for (size_t i = 0; i < unsigned(a.Samplers.size()); ++i) { - Animation::AnimSampler& s = a.Samplers[i]; + for (size_t i = 0; i < unsigned(a.samplers.size()); ++i) { + Animation::Sampler& s = a.samplers[i]; Value valSampler; valSampler.SetObject(); { - Ref inputAccessor = a.GetAccessor(s.input); - Ref outputAccessor = a.GetAccessor(s.output); - valSampler.AddMember("input", inputAccessor->index, w.mAl); - valSampler.AddMember("interpolation", s.interpolation, w.mAl); - valSampler.AddMember("output", outputAccessor->index, w.mAl); + valSampler.AddMember("input", s.input->index, w.mAl); + switch (s.interpolation) { + case Interpolation_LINEAR: + valSampler.AddMember("path", "LINEAR", w.mAl); + break; + case Interpolation_STEP: + valSampler.AddMember("path", "STEP", w.mAl); + break; + case Interpolation_CUBICSPLINE: + valSampler.AddMember("path", "CUBICSPLINE", w.mAl); + break; + } + valSampler.AddMember("output", s.output->index, w.mAl); } valSamplers.PushBack(valSampler, w.mAl); } diff --git a/code/glTF2Exporter.cpp b/code/glTF2Exporter.cpp index 564533de4..6ade100f3 100644 --- a/code/glTF2Exporter.cpp +++ b/code/glTF2Exporter.cpp @@ -961,92 +961,89 @@ void glTF2Exporter::ExportMetadata() asset.generator = buffer; } -inline void ExtractAnimationData(Asset& mAsset, std::string& animId, Ref& animRef, Ref& buffer, const aiNodeAnim* nodeChannel, float ticksPerSecond) +inline Ref GetSamplerInputRef(Asset& asset, std::string& animId, Ref& buffer, std::vector& 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, 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 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(nodeChannel->mPositionKeys[frameIndex].mTime / ticksPerSecond); - } - - Ref timeAccessor = ExportData(mAsset, animId, buffer, static_cast(numKeyframes), &timeData[0], AttribType::SCALAR, AttribType::SCALAR, ComponentType_FLOAT); - if (timeAccessor) animRef->Parameters.TIME = timeAccessor; + std::vector times(numKeyframes); + std::vector values(numKeyframes); + 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(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 tranAccessor = ExportData(mAsset, animId, buffer, static_cast(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, 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 scaleAccessor = ExportData(mAsset, animId, buffer, static_cast(numKeyframes), scaleData, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT); - if ( scaleAccessor ) { - animRef->Parameters.scale = scaleAccessor; - } - delete[] scaleData; + std::vector times(numKeyframes); + std::vector values(numKeyframes); + 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(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 rotAccessor = ExportData(mAsset, animId, buffer, static_cast(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, const aiNodeAnim* nodeChannel, float ticksPerSecond, Animation::Sampler& sampler) +{ + const unsigned int numKeyframes = nodeChannel->mNumRotationKeys; + if (numKeyframes == 0) { + return; } + + std::vector times(numKeyframes); + std::vector values(numKeyframes); + 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(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& animRef, Ref& nodeRef, Animation::Sampler& sampler, AnimationPath path) +{ + Animation::Channel channel; + channel.sampler = static_cast(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 +1052,7 @@ void glTF2Exporter::ExportAnimations() for (unsigned int i = 0; i < mScene->mNumAnimations; ++i) { const aiAnimation* anim = mScene->mAnimations[i]; + const float ticksPerSecond = static_cast(anim->mTicksPerSecond); std::string nameAnim = "anim"; if (anim->mName.length > 0) { @@ -1070,46 +1068,19 @@ void glTF2Exporter::ExportAnimations() name = mAsset->FindUniqueID(name, "animation"); Ref animRef = mAsset->animations.Create(name); - // Parameters - ExtractAnimationData(*mAsset, name, animRef, bufferRef, nodeChannel, static_cast(anim->mTicksPerSecond)); + Ref 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(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) diff --git a/code/glTF2Importer.cpp b/code/glTF2Importer.cpp index 4b99fc8da..0d0c5c5a9 100755 --- a/code/glTF2Importer.cpp +++ b/code/glTF2Importer.cpp @@ -54,6 +54,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include "MakeVerboseFormat.h" @@ -580,7 +581,7 @@ void glTF2Importer::ImportMeshes(glTF2::Asset& r) } else { // no indices provided so directly generate from counts - // use the already determined count as it includes checks + // use the already determined count as it includes checks unsigned int count = aim->mNumVertices; switch (prim.mode) { @@ -702,26 +703,7 @@ void glTF2Importer::ImportCameras(glTF2::Asset& r) } } -aiNode* ImportNode(aiScene* pScene, glTF2::Asset& r, std::vector& meshOffsets, glTF2::Ref& ptr) -{ - Node& node = *ptr; - - std::string nameOrId = node.name.empty() ? node.id : node.name; - - aiNode* ainode = new aiNode(nameOrId); - - if (!node.children.empty()) { - ainode->mNumChildren = unsigned(node.children.size()); - ainode->mChildren = new aiNode*[ainode->mNumChildren]; - - for (unsigned int i = 0; i < ainode->mNumChildren; ++i) { - aiNode* child = ImportNode(pScene, r, meshOffsets, node.children[i]); - child->mParent = ainode; - ainode->mChildren[i] = child; - } - } - - aiMatrix4x4& matrix = ainode->mTransformation; +static void GetNodeTransform(aiMatrix4x4& matrix, const glTF2::Node& node) { if (node.matrix.isPresent) { CopyValue(node.matrix.value, matrix); } @@ -748,24 +730,112 @@ aiNode* ImportNode(aiScene* pScene, glTF2::Asset& r, std::vector& matrix = matrix * s; } } +} + +static void BuildVertexWeightMapping(Ref& mesh, std::vector>& map) +{ + Mesh::Primitive::Attributes& attr = mesh->primitives[0].attributes; + if (attr.weight.empty() || attr.joint.empty()) { + return; + } + if (attr.weight[0]->count != attr.joint[0]->count) { + return; + } + + const int num_vertices = attr.weight[0]->count; + + struct Weights { float values[4]; }; + struct Indices { uint8_t values[4]; }; + Weights* weights = nullptr; + Indices* indices = nullptr; + attr.weight[0]->ExtractData(weights); + attr.joint[0]->ExtractData(indices); + + for (unsigned int i = 0; i < num_vertices; ++i) { + for (unsigned int j = 0; j < 4; ++j) { + const int bone = indices[i].values[j]; + const float weight = weights[i].values[j]; + if (weight > 0 && bone >= 0 && bone < map.size()) { + map[bone].reserve(8); + map[bone].emplace_back(i, weight); + } + } + } + + delete[] weights; + delete[] indices; +} + +aiNode* ImportNode(aiScene* pScene, glTF2::Asset& r, std::vector& meshOffsets, glTF2::Ref& ptr) +{ + Node& node = *ptr; + + std::string nameOrId = node.name.empty() ? node.id : node.name; + + aiNode* ainode = new aiNode(nameOrId); + + if (!node.children.empty()) { + ainode->mNumChildren = unsigned(node.children.size()); + ainode->mChildren = new aiNode*[ainode->mNumChildren]; + + for (unsigned int i = 0; i < ainode->mNumChildren; ++i) { + aiNode* child = ImportNode(pScene, r, meshOffsets, node.children[i]); + child->mParent = ainode; + ainode->mChildren[i] = child; + } + } + + GetNodeTransform(ainode->mTransformation, node); if (!node.meshes.empty()) { - int count = 0; - for (size_t i = 0; i < node.meshes.size(); ++i) { - int idx = node.meshes[i].GetIndex(); - count += meshOffsets[idx + 1] - meshOffsets[idx]; - } - ainode->mNumMeshes = count; + int mesh_idx = node.meshes[0].GetIndex(); + int count = meshOffsets[mesh_idx + 1] - meshOffsets[mesh_idx]; + // GLTF files contain at most 1 mesh per node. + assert(node.meshes.size() == 1); + assert(count == 1); + ainode->mNumMeshes = count; ainode->mMeshes = new unsigned int[count]; - int k = 0; - for (size_t i = 0; i < node.meshes.size(); ++i) { - int idx = node.meshes[i].GetIndex(); - for (unsigned int j = meshOffsets[idx]; j < meshOffsets[idx + 1]; ++j, ++k) { - ainode->mMeshes[k] = j; + if (node.skin) { + aiMesh* mesh = pScene->mMeshes[meshOffsets[mesh_idx]]; + mesh->mNumBones = node.skin->jointNames.size(); + mesh->mBones = new aiBone*[mesh->mNumBones]; + + // GLTF and Assimp choose to store bone weights differently. + // GLTF has each vertex specify which bones influence the vertex. + // Assimp has each bone specify which vertices it has influence over. + // To convert this data, we first read over the vertex data and pull + // out the bone-to-vertex mapping. Then, when creating the aiBones, + // we copy the bone-to-vertex mapping into the bone. This is unfortunate + // both because it's somewhat slow and because, for many applications, + // we then need to reconvert the data back into the vertex-to-bone + // mapping which makes things doubly-slow. + std::vector> weighting(mesh->mNumBones); + BuildVertexWeightMapping(node.meshes[0], weighting); + + for (size_t i = 0; i < mesh->mNumBones; ++i) { + aiBone* bone = new aiBone(); + + Ref joint = node.skin->jointNames[i]; + bone->mName = joint->name; + GetNodeTransform(bone->mOffsetMatrix, *joint); + + std::vector& weights = weighting[i]; + + bone->mNumWeights = weights.size(); + if (bone->mNumWeights > 0) { + bone->mWeights = new aiVertexWeight[bone->mNumWeights]; + memcpy(bone->mWeights, weights.data(), bone->mNumWeights * sizeof(aiVertexWeight)); + } + mesh->mBones[i] = bone; } } + + int k = 0; + for (unsigned int j = meshOffsets[mesh_idx]; j < meshOffsets[mesh_idx + 1]; ++j, ++k) { + ainode->mMeshes[k] = j; + } } if (node.camera) { @@ -802,6 +872,151 @@ void glTF2Importer::ImportNodes(glTF2::Asset& r) //} } +struct AnimationSamplers { + AnimationSamplers() : translation(nullptr), rotation(nullptr), scale(nullptr) {} + + Animation::Sampler* translation; + Animation::Sampler* rotation; + Animation::Sampler* scale; +}; + +aiNodeAnim* CreateNodeAnim(glTF2::Asset& r, Node& node, AnimationSamplers& samplers) +{ + aiNodeAnim* anim = new aiNodeAnim(); + anim->mNodeName = node.name; + + static const float kMillisecondsFromSeconds = 1000.f; + + if (samplers.translation) { + float* times = nullptr; + samplers.translation->input->ExtractData(times); + aiVector3D* values = nullptr; + samplers.translation->output->ExtractData(values); + anim->mNumPositionKeys = samplers.translation->input->count; + anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; + for (int i = 0; i < anim->mNumPositionKeys; ++i) { + anim->mPositionKeys[i].mTime = times[i] * kMillisecondsFromSeconds; + anim->mPositionKeys[i].mValue = values[i]; + } + delete[] times; + delete[] values; + } else if (node.translation.isPresent) { + anim->mNumPositionKeys = 1; + anim->mPositionKeys = new aiVectorKey(); + anim->mPositionKeys->mTime = 0.f; + anim->mPositionKeys->mValue.x = node.translation.value[0]; + anim->mPositionKeys->mValue.y = node.translation.value[1]; + anim->mPositionKeys->mValue.z = node.translation.value[2]; + } + + if (samplers.rotation) { + float* times = nullptr; + samplers.rotation->input->ExtractData(times); + aiQuaternion* values = nullptr; + samplers.rotation->output->ExtractData(values); + anim->mNumRotationKeys = samplers.rotation->input->count; + anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys]; + for (int i = 0; i < anim->mNumRotationKeys; ++i) { + anim->mRotationKeys[i].mTime = times[i] * kMillisecondsFromSeconds; + anim->mRotationKeys[i].mValue.x = values[i].w; + anim->mRotationKeys[i].mValue.y = values[i].x; + anim->mRotationKeys[i].mValue.z = values[i].y; + anim->mRotationKeys[i].mValue.w = values[i].z; + } + delete[] times; + delete[] values; + } else if (node.rotation.isPresent) { + anim->mNumRotationKeys = 1; + anim->mRotationKeys = new aiQuatKey(); + anim->mRotationKeys->mTime = 0.f; + anim->mRotationKeys->mValue.x = node.rotation.value[0]; + anim->mRotationKeys->mValue.y = node.rotation.value[1]; + anim->mRotationKeys->mValue.z = node.rotation.value[2]; + anim->mRotationKeys->mValue.w = node.rotation.value[3]; + } + + if (samplers.scale) { + float* times = nullptr; + samplers.scale->input->ExtractData(times); + aiVector3D* values = nullptr; + samplers.scale->output->ExtractData(values); + anim->mNumScalingKeys = samplers.scale->input->count; + anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys]; + for (int i = 0; i < anim->mNumScalingKeys; ++i) { + anim->mScalingKeys[i].mTime = times[i] * kMillisecondsFromSeconds; + anim->mScalingKeys[i].mValue = values[i]; + } + delete[] times; + delete[] values; + } else if (node.scale.isPresent) { + anim->mNumScalingKeys = 1; + anim->mScalingKeys = new aiVectorKey(); + anim->mScalingKeys->mTime = 0.f; + anim->mScalingKeys->mValue.x = node.scale.value[0]; + anim->mScalingKeys->mValue.y = node.scale.value[1]; + anim->mScalingKeys->mValue.z = node.scale.value[2]; + } + + return anim; +} + +std::unordered_map GatherSamplers(Animation& anim) +{ + std::unordered_map samplers; + for (unsigned int c = 0; c < anim.channels.size(); ++c) { + Animation::Channel& channel = anim.channels[c]; + if (channel.sampler >= anim.samplers.size()) { + continue; + } + + const unsigned int node_index = channel.target.node.GetIndex(); + + AnimationSamplers& sampler = samplers[node_index]; + if (channel.target.path == AnimationPath_TRANSLATION) { + sampler.translation = &anim.samplers[channel.sampler]; + } else if (channel.target.path == AnimationPath_ROTATION) { + sampler.rotation = &anim.samplers[channel.sampler]; + } else if (channel.target.path == AnimationPath_SCALE) { + sampler.scale = &anim.samplers[channel.sampler]; + } + } + + return samplers; +} + +void glTF2Importer::ImportAnimations(glTF2::Asset& r) +{ + if (!r.scene) return; + + mScene->mNumAnimations = r.animations.Size(); + if (mScene->mNumAnimations == 0) { + return; + } + + mScene->mAnimations = new aiAnimation*[mScene->mNumAnimations]; + for (unsigned int i = 0; i < r.animations.Size(); ++i) { + Animation& anim = r.animations[i]; + + aiAnimation* ai_anim = new aiAnimation(); + ai_anim->mName = anim.name; + ai_anim->mDuration = 0; + ai_anim->mTicksPerSecond = 0; + + std::unordered_map samplers = GatherSamplers(anim); + + ai_anim->mNumChannels = r.skins[0].jointNames.size(); + if (ai_anim->mNumChannels > 0) { + ai_anim->mChannels = new aiNodeAnim*[ai_anim->mNumChannels]; + int j = 0; + for (auto& iter : r.skins[0].jointNames) { + ai_anim->mChannels[j] = CreateNodeAnim(r, *iter, samplers[iter.GetIndex()]); + ++j; + } + } + mScene->mAnimations[i] = ai_anim; + } +} + void glTF2Importer::ImportEmbeddedTextures(glTF2::Asset& r) { embeddedTexIdxs.resize(r.images.Size(), -1); @@ -869,6 +1084,8 @@ void glTF2Importer::InternReadFile(const std::string& pFile, aiScene* pScene, IO ImportNodes(asset); + ImportAnimations(asset); + if (pScene->mNumMeshes == 0) { pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; } diff --git a/code/glTF2Importer.h b/code/glTF2Importer.h index 31d935da4..7414e2f95 100644 --- a/code/glTF2Importer.h +++ b/code/glTF2Importer.h @@ -83,7 +83,7 @@ private: void ImportCameras(glTF2::Asset& a); void ImportLights(glTF2::Asset& a); void ImportNodes(glTF2::Asset& a); - + void ImportAnimations(glTF2::Asset& a); }; } // Namespace assimp From acad9a06e9e7f6fe58199357ef7951470f39126c Mon Sep 17 00:00:00 2001 From: Haroon Qureshi Date: Fri, 26 Oct 2018 15:36:34 -0700 Subject: [PATCH 2/9] 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. --- code/glTF2Asset.h | 71 +++++----- code/glTF2Asset.inl | 94 ++++++++++++- code/glTF2AssetWriter.inl | 43 ++++-- code/glTF2Exporter.cpp | 194 ++++++++++++-------------- code/glTF2Importer.cpp | 281 +++++++++++++++++++++++++++++++++----- code/glTF2Importer.h | 2 +- 6 files changed, 488 insertions(+), 197 deletions(-) diff --git a/code/glTF2Asset.h b/code/glTF2Asset.h index 0def5b74d..92be82f3b 100644 --- a/code/glTF2Asset.h +++ b/code/glTF2Asset.h @@ -303,6 +303,20 @@ namespace glTF2 TextureType_UNSIGNED_SHORT_5_5_5_1 = 32820 }; + //! Values for the Animation::Target::path field + enum AnimationPath { + AnimationPath_TRANSLATION, + AnimationPath_ROTATION, + AnimationPath_SCALE, + AnimationPath_WEIGHTS, + }; + + //! Values for the Animation::Sampler::interpolation field + enum Interpolation { + Interpolation_LINEAR, + Interpolation_STEP, + Interpolation_CUBICSPLINE, + }; //! Values for the Accessor::type field (helper class) class AttribType @@ -742,7 +756,7 @@ namespace glTF2 //extension: KHR_materials_pbrSpecularGlossiness Nullable pbrSpecularGlossiness; - //extension: KHR_materials_unlit + //extension: KHR_materials_unlit bool unlit; Material() { SetDefaults(); } @@ -870,56 +884,35 @@ namespace glTF2 struct Animation : public Object { - struct AnimSampler { - std::string id; //!< The ID of this sampler. - std::string input; //!< The ID of a parameter in this animation to use as key-frame input. - std::string interpolation; //!< Type of interpolation algorithm to use between key-frames. - std::string output; //!< The ID of a parameter in this animation to use as key-frame output. + struct Sampler { + Sampler() : interpolation(Interpolation_LINEAR) {} + + Ref input; //!< Accessor reference to the buffer storing the key-frame times. + Ref output; //!< Accessor reference to the buffer storing the key-frame values. + Interpolation interpolation; //!< Type of interpolation algorithm to use between key-frames. }; - struct AnimChannel { - int sampler; //!< The index of a sampler in the containing animation's samplers property. + struct Target { + Target() : path(AnimationPath_TRANSLATION) {} - struct AnimTarget { - Ref node; //!< The node to animate. - std::string path; //!< The name of property of the node to animate ("translation", "rotation", or "scale"). - } target; + Ref node; //!< The node to animate. + AnimationPath path; //!< The property of the node to animate. }; - struct AnimParameters { - Ref TIME; //!< Accessor reference to a buffer storing a array of floating point scalar values. - Ref rotation; //!< Accessor reference to a buffer storing a array of four-component floating-point vectors. - Ref scale; //!< Accessor reference to a buffer storing a array of three-component floating-point vectors. - Ref translation; //!< Accessor reference to a buffer storing a array of three-component floating-point vectors. + struct Channel { + Channel() : sampler(-1) {} + + int sampler; //!< The sampler index containing the animation data. + Target target; //!< The node and property to animate. }; - // AnimChannel Channels[3]; //!< Connect the output values of the key-frame animation to a specific node in the hierarchy. - // AnimParameters Parameters; //!< The samplers that interpolate between the key-frames. - // AnimSampler Samplers[3]; //!< The parameterized inputs representing the key-frame data. - - std::vector Channels; //!< Connect the output values of the key-frame animation to a specific node in the hierarchy. - AnimParameters Parameters; //!< The samplers that interpolate between the key-frames. - std::vector Samplers; //!< The parameterized inputs representing the key-frame data. + std::vector samplers; //!< All the key-frame data for this animation. + std::vector channels; //!< Data to connect nodes to key-frames. Animation() {} void Read(Value& obj, Asset& r); - - //! Get accessor given an animation parameter name. - Ref GetAccessor(std::string name) { - if (name == "TIME") { - return Parameters.TIME; - } else if (name == "rotation") { - return Parameters.rotation; - } else if (name == "scale") { - return Parameters.scale; - } else if (name == "translation") { - return Parameters.translation; - } - return Ref(); - } }; - //! Base class for LazyDict that acts as an interface class LazyDictBase { diff --git a/code/glTF2Asset.inl b/code/glTF2Asset.inl index 11e3965e5..687e16ce1 100755 --- a/code/glTF2Asset.inl +++ b/code/glTF2Asset.inl @@ -461,7 +461,7 @@ inline void Buffer::EncodedRegion_SetCurrent(const std::string& pID) throw DeadlyImportError("GLTF: EncodedRegion with ID: \"" + pID + "\" not found."); } -inline +inline bool Buffer::ReplaceData(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t* pReplace_Data, const size_t pReplace_Count) { @@ -483,8 +483,8 @@ bool Buffer::ReplaceData(const size_t pBufferData_Offset, const size_t pBufferDa return true; } - -inline + +inline bool Buffer::ReplaceData_joint(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t* pReplace_Data, const size_t pReplace_Count) { if((pBufferData_Count == 0) || (pReplace_Count == 0) || (pReplace_Data == nullptr)) { @@ -718,7 +718,7 @@ inline void Image::Read(Value& obj, Asset& r) this->mDataLength = this->bufferView->byteLength; // maybe this memcpy could be avoided if aiTexture does not delete[] pcData at destruction. - + this->mData.reset(new uint8_t[this->mDataLength]); memcpy(this->mData.get(), buffer->GetPointer() + this->bufferView->byteOffset, this->mDataLength); @@ -1083,6 +1083,10 @@ inline void Node::Read(Value& obj, Asset& r) if (meshRef) this->meshes.push_back(meshRef); } + if (Value* skin = FindUInt(obj, "skin")) { + this->skin = r.skins.Retrieve(skin->GetUint()); + } + if (Value* camera = FindUInt(obj, "camera")) { this->camera = r.cameras.Retrieve(camera->GetUint()); if (this->camera) @@ -1102,6 +1106,82 @@ inline void Scene::Read(Value& obj, Asset& r) } } +inline void Skin::Read(Value& obj, Asset& r) +{ + if (Value* matrices = FindUInt(obj, "inverseBindMatrices")) { + inverseBindMatrices = r.accessors.Retrieve(matrices->GetUint()); + } + + if (Value* joints = FindArray(obj, "joints")) { + for (unsigned i = 0; i < joints->Size(); ++i) { + if (!(*joints)[i].IsUint()) continue; + Ref node = r.nodes.Retrieve((*joints)[i].GetUint()); + if (node) { + this->jointNames.push_back(node); + } + } + } +} + +inline void Animation::Read(Value& obj, Asset& r) +{ + if (Value* samplers = FindArray(obj, "samplers")) { + for (unsigned i = 0; i < samplers->Size(); ++i) { + Value& sampler = (*samplers)[i]; + + Sampler s; + if (Value* input = FindUInt(sampler, "input")) { + s.input = r.accessors.Retrieve(input->GetUint()); + } + if (Value* output = FindUInt(sampler, "output")) { + s.output = r.accessors.Retrieve(output->GetUint()); + } + s.interpolation = Interpolation_LINEAR; + if (Value* interpolation = FindString(sampler, "interpolation")) { + const std::string interp = interpolation->GetString(); + if (interp == "LINEAR") { + s.interpolation = Interpolation_LINEAR; + } else if (interp == "STEP") { + s.interpolation = Interpolation_STEP; + } else if (interp == "CUBICSPLINE") { + s.interpolation = Interpolation_CUBICSPLINE; + } + } + this->samplers.push_back(s); + } + } + + if (Value* channels = FindArray(obj, "channels")) { + for (unsigned i = 0; i < channels->Size(); ++i) { + Value& channel = (*channels)[i]; + + Channel c; + if (Value* sampler = FindUInt(channel, "sampler")) { + c.sampler = sampler->GetUint(); + } + + if (Value* target = FindObject(channel, "target")) { + if (Value* node = FindUInt(*target, "node")) { + c.target.node = r.nodes.Retrieve(node->GetUint()); + } + if (Value* path = FindString(*target, "path")) { + const std::string p = path->GetString(); + if (p == "translation") { + c.target.path = AnimationPath_TRANSLATION; + } else if (p == "rotation") { + c.target.path = AnimationPath_ROTATION; + } else if (p == "scale") { + c.target.path = AnimationPath_SCALE; + } else if (p == "weights") { + c.target.path = AnimationPath_WEIGHTS; + } + } + } + this->channels.push_back(c); + } + } +} + inline void AssetMetadata::Read(Document& doc) { if (Value* obj = FindObject(doc, "asset")) { @@ -1277,6 +1357,12 @@ inline void Asset::Load(const std::string& pFile, bool isBinary) } } + if (Value* animsArray = FindArray(doc, "animations")) { + for (unsigned int i = 0; i < animsArray->Size(); ++i) { + animations.Retrieve(i); + } + } + // Clean up for (size_t i = 0; i < mDicts.size(); ++i) { mDicts[i]->DetachFromDocument(); diff --git a/code/glTF2AssetWriter.inl b/code/glTF2AssetWriter.inl index 0579dfdac..50d855aaa 100644 --- a/code/glTF2AssetWriter.inl +++ b/code/glTF2AssetWriter.inl @@ -113,10 +113,10 @@ namespace glTF2 { /****************** Channels *******************/ Value channels; channels.SetArray(); - channels.Reserve(unsigned(a.Channels.size()), w.mAl); + channels.Reserve(unsigned(a.channels.size()), w.mAl); - for (size_t i = 0; i < unsigned(a.Channels.size()); ++i) { - Animation::AnimChannel& c = a.Channels[i]; + for (size_t i = 0; i < unsigned(a.channels.size()); ++i) { + Animation::Channel& c = a.channels[i]; Value valChannel; valChannel.SetObject(); { @@ -126,7 +126,20 @@ namespace glTF2 { valTarget.SetObject(); { valTarget.AddMember("node", c.target.node->index, w.mAl); - valTarget.AddMember("path", c.target.path, w.mAl); + switch (c.target.path) { + case AnimationPath_TRANSLATION: + valTarget.AddMember("path", "translation", w.mAl); + break; + case AnimationPath_ROTATION: + valTarget.AddMember("path", "rotation", w.mAl); + break; + case AnimationPath_SCALE: + valTarget.AddMember("path", "scale", w.mAl); + break; + case AnimationPath_WEIGHTS: + valTarget.AddMember("path", "weights", w.mAl); + break; + } } valChannel.AddMember("target", valTarget, w.mAl); } @@ -138,16 +151,24 @@ namespace glTF2 { Value valSamplers; valSamplers.SetArray(); - for (size_t i = 0; i < unsigned(a.Samplers.size()); ++i) { - Animation::AnimSampler& s = a.Samplers[i]; + for (size_t i = 0; i < unsigned(a.samplers.size()); ++i) { + Animation::Sampler& s = a.samplers[i]; Value valSampler; valSampler.SetObject(); { - Ref inputAccessor = a.GetAccessor(s.input); - Ref outputAccessor = a.GetAccessor(s.output); - valSampler.AddMember("input", inputAccessor->index, w.mAl); - valSampler.AddMember("interpolation", s.interpolation, w.mAl); - valSampler.AddMember("output", outputAccessor->index, w.mAl); + valSampler.AddMember("input", s.input->index, w.mAl); + switch (s.interpolation) { + case Interpolation_LINEAR: + valSampler.AddMember("path", "LINEAR", w.mAl); + break; + case Interpolation_STEP: + valSampler.AddMember("path", "STEP", w.mAl); + break; + case Interpolation_CUBICSPLINE: + valSampler.AddMember("path", "CUBICSPLINE", w.mAl); + break; + } + valSampler.AddMember("output", s.output->index, w.mAl); } valSamplers.PushBack(valSampler, w.mAl); } diff --git a/code/glTF2Exporter.cpp b/code/glTF2Exporter.cpp index 564533de4..83aae5136 100644 --- a/code/glTF2Exporter.cpp +++ b/code/glTF2Exporter.cpp @@ -961,92 +961,92 @@ void glTF2Exporter::ExportMetadata() asset.generator = buffer; } -inline void ExtractAnimationData(Asset& mAsset, std::string& animId, Ref& animRef, Ref& buffer, const aiNodeAnim* nodeChannel, float ticksPerSecond) +inline Ref GetSamplerInputRef(Asset& asset, std::string& animId, Ref& buffer, std::vector& 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, 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 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(nodeChannel->mPositionKeys[frameIndex].mTime / ticksPerSecond); - } - - Ref timeAccessor = ExportData(mAsset, animId, buffer, static_cast(numKeyframes), &timeData[0], AttribType::SCALAR, AttribType::SCALAR, ComponentType_FLOAT); - if (timeAccessor) animRef->Parameters.TIME = timeAccessor; + const vec3 kZeros = {0, 0, 0}; + std::vector times(numKeyframes); + std::vector 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(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 tranAccessor = ExportData(mAsset, animId, buffer, static_cast(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, 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 scaleAccessor = ExportData(mAsset, animId, buffer, static_cast(numKeyframes), scaleData, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT); - if ( scaleAccessor ) { - animRef->Parameters.scale = scaleAccessor; - } - delete[] scaleData; + const vec3 kZeros = {0, 0, 0}; + std::vector times(numKeyframes); + std::vector 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(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 rotAccessor = ExportData(mAsset, animId, buffer, static_cast(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, 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 times(numKeyframes); + std::vector 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(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& animRef, Ref& nodeRef, Animation::Sampler& sampler, AnimationPath path) +{ + Animation::Channel channel; + channel.sampler = static_cast(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(anim->mTicksPerSecond); std::string nameAnim = "anim"; if (anim->mName.length > 0) { @@ -1070,46 +1071,19 @@ void glTF2Exporter::ExportAnimations() name = mAsset->FindUniqueID(name, "animation"); Ref animRef = mAsset->animations.Create(name); - // Parameters - ExtractAnimationData(*mAsset, name, animRef, bufferRef, nodeChannel, static_cast(anim->mTicksPerSecond)); + Ref 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(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) diff --git a/code/glTF2Importer.cpp b/code/glTF2Importer.cpp index 4b99fc8da..ed7c55792 100755 --- a/code/glTF2Importer.cpp +++ b/code/glTF2Importer.cpp @@ -54,6 +54,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include "MakeVerboseFormat.h" @@ -580,7 +581,7 @@ void glTF2Importer::ImportMeshes(glTF2::Asset& r) } else { // no indices provided so directly generate from counts - // use the already determined count as it includes checks + // use the already determined count as it includes checks unsigned int count = aim->mNumVertices; switch (prim.mode) { @@ -702,26 +703,7 @@ void glTF2Importer::ImportCameras(glTF2::Asset& r) } } -aiNode* ImportNode(aiScene* pScene, glTF2::Asset& r, std::vector& meshOffsets, glTF2::Ref& ptr) -{ - Node& node = *ptr; - - std::string nameOrId = node.name.empty() ? node.id : node.name; - - aiNode* ainode = new aiNode(nameOrId); - - if (!node.children.empty()) { - ainode->mNumChildren = unsigned(node.children.size()); - ainode->mChildren = new aiNode*[ainode->mNumChildren]; - - for (unsigned int i = 0; i < ainode->mNumChildren; ++i) { - aiNode* child = ImportNode(pScene, r, meshOffsets, node.children[i]); - child->mParent = ainode; - ainode->mChildren[i] = child; - } - } - - aiMatrix4x4& matrix = ainode->mTransformation; +static void GetNodeTransform(aiMatrix4x4& matrix, const glTF2::Node& node) { if (node.matrix.isPresent) { CopyValue(node.matrix.value, matrix); } @@ -748,24 +730,112 @@ aiNode* ImportNode(aiScene* pScene, glTF2::Asset& r, std::vector& matrix = matrix * s; } } +} + +static void BuildVertexWeightMapping(Ref& mesh, std::vector>& map) +{ + Mesh::Primitive::Attributes& attr = mesh->primitives[0].attributes; + if (attr.weight.empty() || attr.joint.empty()) { + return; + } + if (attr.weight[0]->count != attr.joint[0]->count) { + return; + } + + const int num_vertices = attr.weight[0]->count; + + struct Weights { float values[4]; }; + struct Indices { uint8_t values[4]; }; + Weights* weights = nullptr; + Indices* indices = nullptr; + attr.weight[0]->ExtractData(weights); + attr.joint[0]->ExtractData(indices); + + for (int i = 0; i < num_vertices; ++i) { + for (int j = 0; j < 4; ++j) { + const unsigned int bone = indices[i].values[j]; + const float weight = weights[i].values[j]; + if (weight > 0 && bone >= 0 && bone < map.size()) { + map[bone].reserve(8); + map[bone].emplace_back(i, weight); + } + } + } + + delete[] weights; + delete[] indices; +} + +aiNode* ImportNode(aiScene* pScene, glTF2::Asset& r, std::vector& meshOffsets, glTF2::Ref& ptr) +{ + Node& node = *ptr; + + std::string nameOrId = node.name.empty() ? node.id : node.name; + + aiNode* ainode = new aiNode(nameOrId); + + if (!node.children.empty()) { + ainode->mNumChildren = unsigned(node.children.size()); + ainode->mChildren = new aiNode*[ainode->mNumChildren]; + + for (unsigned int i = 0; i < ainode->mNumChildren; ++i) { + aiNode* child = ImportNode(pScene, r, meshOffsets, node.children[i]); + child->mParent = ainode; + ainode->mChildren[i] = child; + } + } + + GetNodeTransform(ainode->mTransformation, node); if (!node.meshes.empty()) { - int count = 0; - for (size_t i = 0; i < node.meshes.size(); ++i) { - int idx = node.meshes[i].GetIndex(); - count += meshOffsets[idx + 1] - meshOffsets[idx]; - } - ainode->mNumMeshes = count; + int mesh_idx = node.meshes[0].GetIndex(); + int count = meshOffsets[mesh_idx + 1] - meshOffsets[mesh_idx]; + // GLTF files contain at most 1 mesh per node. + assert(node.meshes.size() == 1); + assert(count == 1); + ainode->mNumMeshes = count; ainode->mMeshes = new unsigned int[count]; - int k = 0; - for (size_t i = 0; i < node.meshes.size(); ++i) { - int idx = node.meshes[i].GetIndex(); - for (unsigned int j = meshOffsets[idx]; j < meshOffsets[idx + 1]; ++j, ++k) { - ainode->mMeshes[k] = j; + if (node.skin) { + aiMesh* mesh = pScene->mMeshes[meshOffsets[mesh_idx]]; + mesh->mNumBones = node.skin->jointNames.size(); + mesh->mBones = new aiBone*[mesh->mNumBones]; + + // GLTF and Assimp choose to store bone weights differently. + // GLTF has each vertex specify which bones influence the vertex. + // Assimp has each bone specify which vertices it has influence over. + // To convert this data, we first read over the vertex data and pull + // out the bone-to-vertex mapping. Then, when creating the aiBones, + // we copy the bone-to-vertex mapping into the bone. This is unfortunate + // both because it's somewhat slow and because, for many applications, + // we then need to reconvert the data back into the vertex-to-bone + // mapping which makes things doubly-slow. + std::vector> weighting(mesh->mNumBones); + BuildVertexWeightMapping(node.meshes[0], weighting); + + for (size_t i = 0; i < mesh->mNumBones; ++i) { + aiBone* bone = new aiBone(); + + Ref joint = node.skin->jointNames[i]; + bone->mName = joint->name; + GetNodeTransform(bone->mOffsetMatrix, *joint); + + std::vector& weights = weighting[i]; + + bone->mNumWeights = weights.size(); + if (bone->mNumWeights > 0) { + bone->mWeights = new aiVertexWeight[bone->mNumWeights]; + memcpy(bone->mWeights, weights.data(), bone->mNumWeights * sizeof(aiVertexWeight)); + } + mesh->mBones[i] = bone; } } + + int k = 0; + for (unsigned int j = meshOffsets[mesh_idx]; j < meshOffsets[mesh_idx + 1]; ++j, ++k) { + ainode->mMeshes[k] = j; + } } if (node.camera) { @@ -802,6 +872,151 @@ void glTF2Importer::ImportNodes(glTF2::Asset& r) //} } +struct AnimationSamplers { + AnimationSamplers() : translation(nullptr), rotation(nullptr), scale(nullptr) {} + + Animation::Sampler* translation; + Animation::Sampler* rotation; + Animation::Sampler* scale; +}; + +aiNodeAnim* CreateNodeAnim(glTF2::Asset& r, Node& node, AnimationSamplers& samplers) +{ + aiNodeAnim* anim = new aiNodeAnim(); + anim->mNodeName = node.name; + + static const float kMillisecondsFromSeconds = 1000.f; + + if (samplers.translation) { + float* times = nullptr; + samplers.translation->input->ExtractData(times); + aiVector3D* values = nullptr; + samplers.translation->output->ExtractData(values); + anim->mNumPositionKeys = samplers.translation->input->count; + anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; + for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) { + anim->mPositionKeys[i].mTime = times[i] * kMillisecondsFromSeconds; + anim->mPositionKeys[i].mValue = values[i]; + } + delete[] times; + delete[] values; + } else if (node.translation.isPresent) { + anim->mNumPositionKeys = 1; + anim->mPositionKeys = new aiVectorKey(); + anim->mPositionKeys->mTime = 0.f; + anim->mPositionKeys->mValue.x = node.translation.value[0]; + anim->mPositionKeys->mValue.y = node.translation.value[1]; + anim->mPositionKeys->mValue.z = node.translation.value[2]; + } + + if (samplers.rotation) { + float* times = nullptr; + samplers.rotation->input->ExtractData(times); + aiQuaternion* values = nullptr; + samplers.rotation->output->ExtractData(values); + anim->mNumRotationKeys = samplers.rotation->input->count; + anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys]; + for (unsigned int i = 0; i < anim->mNumRotationKeys; ++i) { + anim->mRotationKeys[i].mTime = times[i] * kMillisecondsFromSeconds; + anim->mRotationKeys[i].mValue.x = values[i].w; + anim->mRotationKeys[i].mValue.y = values[i].x; + anim->mRotationKeys[i].mValue.z = values[i].y; + anim->mRotationKeys[i].mValue.w = values[i].z; + } + delete[] times; + delete[] values; + } else if (node.rotation.isPresent) { + anim->mNumRotationKeys = 1; + anim->mRotationKeys = new aiQuatKey(); + anim->mRotationKeys->mTime = 0.f; + anim->mRotationKeys->mValue.x = node.rotation.value[0]; + anim->mRotationKeys->mValue.y = node.rotation.value[1]; + anim->mRotationKeys->mValue.z = node.rotation.value[2]; + anim->mRotationKeys->mValue.w = node.rotation.value[3]; + } + + if (samplers.scale) { + float* times = nullptr; + samplers.scale->input->ExtractData(times); + aiVector3D* values = nullptr; + samplers.scale->output->ExtractData(values); + anim->mNumScalingKeys = samplers.scale->input->count; + anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys]; + for (unsigned int i = 0; i < anim->mNumScalingKeys; ++i) { + anim->mScalingKeys[i].mTime = times[i] * kMillisecondsFromSeconds; + anim->mScalingKeys[i].mValue = values[i]; + } + delete[] times; + delete[] values; + } else if (node.scale.isPresent) { + anim->mNumScalingKeys = 1; + anim->mScalingKeys = new aiVectorKey(); + anim->mScalingKeys->mTime = 0.f; + anim->mScalingKeys->mValue.x = node.scale.value[0]; + anim->mScalingKeys->mValue.y = node.scale.value[1]; + anim->mScalingKeys->mValue.z = node.scale.value[2]; + } + + return anim; +} + +std::unordered_map GatherSamplers(Animation& anim) +{ + std::unordered_map samplers; + for (unsigned int c = 0; c < anim.channels.size(); ++c) { + Animation::Channel& channel = anim.channels[c]; + if (channel.sampler >= static_cast(anim.samplers.size())) { + continue; + } + + const unsigned int node_index = channel.target.node.GetIndex(); + + AnimationSamplers& sampler = samplers[node_index]; + if (channel.target.path == AnimationPath_TRANSLATION) { + sampler.translation = &anim.samplers[channel.sampler]; + } else if (channel.target.path == AnimationPath_ROTATION) { + sampler.rotation = &anim.samplers[channel.sampler]; + } else if (channel.target.path == AnimationPath_SCALE) { + sampler.scale = &anim.samplers[channel.sampler]; + } + } + + return samplers; +} + +void glTF2Importer::ImportAnimations(glTF2::Asset& r) +{ + if (!r.scene) return; + + mScene->mNumAnimations = r.animations.Size(); + if (mScene->mNumAnimations == 0) { + return; + } + + mScene->mAnimations = new aiAnimation*[mScene->mNumAnimations]; + for (unsigned int i = 0; i < r.animations.Size(); ++i) { + Animation& anim = r.animations[i]; + + aiAnimation* ai_anim = new aiAnimation(); + ai_anim->mName = anim.name; + ai_anim->mDuration = 0; + ai_anim->mTicksPerSecond = 0; + + std::unordered_map samplers = GatherSamplers(anim); + + ai_anim->mNumChannels = r.skins[0].jointNames.size(); + if (ai_anim->mNumChannels > 0) { + ai_anim->mChannels = new aiNodeAnim*[ai_anim->mNumChannels]; + int j = 0; + for (auto& iter : r.skins[0].jointNames) { + ai_anim->mChannels[j] = CreateNodeAnim(r, *iter, samplers[iter.GetIndex()]); + ++j; + } + } + mScene->mAnimations[i] = ai_anim; + } +} + void glTF2Importer::ImportEmbeddedTextures(glTF2::Asset& r) { embeddedTexIdxs.resize(r.images.Size(), -1); @@ -869,6 +1084,8 @@ void glTF2Importer::InternReadFile(const std::string& pFile, aiScene* pScene, IO ImportNodes(asset); + ImportAnimations(asset); + if (pScene->mNumMeshes == 0) { pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; } diff --git a/code/glTF2Importer.h b/code/glTF2Importer.h index 31d935da4..7414e2f95 100644 --- a/code/glTF2Importer.h +++ b/code/glTF2Importer.h @@ -83,7 +83,7 @@ private: void ImportCameras(glTF2::Asset& a); void ImportLights(glTF2::Asset& a); void ImportNodes(glTF2::Asset& a); - + void ImportAnimations(glTF2::Asset& a); }; } // Namespace assimp From 7b9f4a66de85c55e6457630ea96f4612319c7015 Mon Sep 17 00:00:00 2001 From: haroonq <29288912+haroonq@users.noreply.github.com> Date: Mon, 29 Oct 2018 14:52:41 -0700 Subject: [PATCH 3/9] Update glTF2Exporter.cpp --- code/glTF2Exporter.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/code/glTF2Exporter.cpp b/code/glTF2Exporter.cpp index b9d145743..83aae5136 100644 --- a/code/glTF2Exporter.cpp +++ b/code/glTF2Exporter.cpp @@ -973,14 +973,9 @@ inline void ExtractTranslationSampler(Asset& asset, std::string& animId, Ref times(numKeyframes); std::vector values(numKeyframes, kZeros); -======= - std::vector times(numKeyframes); - std::vector values(numKeyframes); ->>>>>>> 57d3d71b6ec68f41a10c1da2f01d096f1308e88c 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. @@ -1002,14 +997,9 @@ inline void ExtractScaleSampler(Asset& asset, std::string& animId, Ref& return; } -<<<<<<< HEAD const vec3 kZeros = {0, 0, 0}; std::vector times(numKeyframes); std::vector values(numKeyframes, kZeros); -======= - std::vector times(numKeyframes); - std::vector values(numKeyframes); ->>>>>>> 57d3d71b6ec68f41a10c1da2f01d096f1308e88c 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. @@ -1031,14 +1021,9 @@ inline void ExtractRotationSampler(Asset& asset, std::string& animId, Ref times(numKeyframes); std::vector values(numKeyframes, kZeros); -======= - std::vector times(numKeyframes); - std::vector values(numKeyframes); ->>>>>>> 57d3d71b6ec68f41a10c1da2f01d096f1308e88c 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. From e5eb00d8d366c63e08ee6fcdd0691ba214764ab2 Mon Sep 17 00:00:00 2001 From: haroonq <29288912+haroonq@users.noreply.github.com> Date: Mon, 29 Oct 2018 14:53:33 -0700 Subject: [PATCH 4/9] Update glTF2Importer.cpp --- code/glTF2Importer.cpp | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/code/glTF2Importer.cpp b/code/glTF2Importer.cpp index ec19c46bf..ed7c55792 100755 --- a/code/glTF2Importer.cpp +++ b/code/glTF2Importer.cpp @@ -751,15 +751,9 @@ static void BuildVertexWeightMapping(Ref& mesh, std::vectorExtractData(weights); attr.joint[0]->ExtractData(indices); -<<<<<<< HEAD for (int i = 0; i < num_vertices; ++i) { for (int j = 0; j < 4; ++j) { const unsigned int bone = indices[i].values[j]; -======= - for (unsigned int i = 0; i < num_vertices; ++i) { - for (unsigned int j = 0; j < 4; ++j) { - const int bone = indices[i].values[j]; ->>>>>>> 57d3d71b6ec68f41a10c1da2f01d096f1308e88c const float weight = weights[i].values[j]; if (weight > 0 && bone >= 0 && bone < map.size()) { map[bone].reserve(8); @@ -900,11 +894,7 @@ aiNodeAnim* CreateNodeAnim(glTF2::Asset& r, Node& node, AnimationSamplers& sampl samplers.translation->output->ExtractData(values); anim->mNumPositionKeys = samplers.translation->input->count; anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; -<<<<<<< HEAD for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) { -======= - for (int i = 0; i < anim->mNumPositionKeys; ++i) { ->>>>>>> 57d3d71b6ec68f41a10c1da2f01d096f1308e88c anim->mPositionKeys[i].mTime = times[i] * kMillisecondsFromSeconds; anim->mPositionKeys[i].mValue = values[i]; } @@ -926,11 +916,7 @@ aiNodeAnim* CreateNodeAnim(glTF2::Asset& r, Node& node, AnimationSamplers& sampl samplers.rotation->output->ExtractData(values); anim->mNumRotationKeys = samplers.rotation->input->count; anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys]; -<<<<<<< HEAD for (unsigned int i = 0; i < anim->mNumRotationKeys; ++i) { -======= - for (int i = 0; i < anim->mNumRotationKeys; ++i) { ->>>>>>> 57d3d71b6ec68f41a10c1da2f01d096f1308e88c anim->mRotationKeys[i].mTime = times[i] * kMillisecondsFromSeconds; anim->mRotationKeys[i].mValue.x = values[i].w; anim->mRotationKeys[i].mValue.y = values[i].x; @@ -956,11 +942,7 @@ aiNodeAnim* CreateNodeAnim(glTF2::Asset& r, Node& node, AnimationSamplers& sampl samplers.scale->output->ExtractData(values); anim->mNumScalingKeys = samplers.scale->input->count; anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys]; -<<<<<<< HEAD for (unsigned int i = 0; i < anim->mNumScalingKeys; ++i) { -======= - for (int i = 0; i < anim->mNumScalingKeys; ++i) { ->>>>>>> 57d3d71b6ec68f41a10c1da2f01d096f1308e88c anim->mScalingKeys[i].mTime = times[i] * kMillisecondsFromSeconds; anim->mScalingKeys[i].mValue = values[i]; } @@ -983,11 +965,7 @@ std::unordered_map GatherSamplers(Animation& an std::unordered_map samplers; for (unsigned int c = 0; c < anim.channels.size(); ++c) { Animation::Channel& channel = anim.channels[c]; -<<<<<<< HEAD if (channel.sampler >= static_cast(anim.samplers.size())) { -======= - if (channel.sampler >= anim.samplers.size()) { ->>>>>>> 57d3d71b6ec68f41a10c1da2f01d096f1308e88c continue; } From bc80652ae90787cbb87d772ca346362664a930a7 Mon Sep 17 00:00:00 2001 From: haroonq <29288912+haroonq@users.noreply.github.com> Date: Mon, 29 Oct 2018 15:04:48 -0700 Subject: [PATCH 5/9] Update glTF2Exporter.cpp --- code/glTF2Exporter.cpp | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/code/glTF2Exporter.cpp b/code/glTF2Exporter.cpp index 83aae5136..f6134492c 100644 --- a/code/glTF2Exporter.cpp +++ b/code/glTF2Exporter.cpp @@ -973,16 +973,15 @@ inline void ExtractTranslationSampler(Asset& asset, std::string& animId, Ref times(numKeyframes); - std::vector values(numKeyframes, kZeros); + std::vector values(numKeyframes * 3); 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(key.mTime / ticksPerSecond); - values[i][0] = key.mValue.x; - values[i][1] = key.mValue.y; - values[i][2] = key.mValue.z; + values[i * 3][0] = key.mValue.x; + values[i * 3][1] = key.mValue.y; + values[i * 3][2] = key.mValue.z; } sampler.input = GetSamplerInputRef(asset, animId, buffer, times); @@ -997,16 +996,15 @@ inline void ExtractScaleSampler(Asset& asset, std::string& animId, Ref& return; } - const vec3 kZeros = {0, 0, 0}; std::vector times(numKeyframes); - std::vector values(numKeyframes, kZeros); + std::vector values(numKeyframes * 3); 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(key.mTime / ticksPerSecond); - values[i][0] = key.mValue.x; - values[i][1] = key.mValue.y; - values[i][2] = key.mValue.z; + values[i * 3][0] = key.mValue.x; + values[i * 3][1] = key.mValue.y; + values[i * 3][2] = key.mValue.z; } sampler.input = GetSamplerInputRef(asset, animId, buffer, times); @@ -1021,17 +1019,16 @@ inline void ExtractRotationSampler(Asset& asset, std::string& animId, Ref times(numKeyframes); - std::vector values(numKeyframes, kZeros); + std::vector values(numKeyframes * 4); 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(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; + values[i * 4][0] = key.mValue.x; + values[i * 4][1] = key.mValue.y; + values[i * 4][2] = key.mValue.z; + values[i * 4][3] = key.mValue.w; } sampler.input = GetSamplerInputRef(asset, animId, buffer, times); From 04c6d8347b67cf0c930ab34e54e48e419cbc9bdc Mon Sep 17 00:00:00 2001 From: haroonq <29288912+haroonq@users.noreply.github.com> Date: Mon, 29 Oct 2018 15:23:26 -0700 Subject: [PATCH 6/9] Update glTF2Exporter.cpp --- code/glTF2Exporter.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/glTF2Exporter.cpp b/code/glTF2Exporter.cpp index f6134492c..ee71188d5 100644 --- a/code/glTF2Exporter.cpp +++ b/code/glTF2Exporter.cpp @@ -974,7 +974,7 @@ inline void ExtractTranslationSampler(Asset& asset, std::string& animId, Ref times(numKeyframes); - std::vector values(numKeyframes * 3); + std::vector values(numKeyframes * 3); 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. @@ -997,7 +997,7 @@ inline void ExtractScaleSampler(Asset& asset, std::string& animId, Ref& } std::vector times(numKeyframes); - std::vector values(numKeyframes * 3); + std::vector values(numKeyframes * 3); 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. @@ -1020,7 +1020,7 @@ inline void ExtractRotationSampler(Asset& asset, std::string& animId, Ref times(numKeyframes); - std::vector values(numKeyframes * 4); + std::vector values(numKeyframes * 4); 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. From 69abac59d213cf19a778d43d046fedbfb0e659b2 Mon Sep 17 00:00:00 2001 From: haroonq <29288912+haroonq@users.noreply.github.com> Date: Thu, 1 Nov 2018 08:49:06 -0700 Subject: [PATCH 7/9] Update glTF2Exporter.cpp --- code/glTF2Exporter.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/code/glTF2Exporter.cpp b/code/glTF2Exporter.cpp index ee71188d5..40db27264 100644 --- a/code/glTF2Exporter.cpp +++ b/code/glTF2Exporter.cpp @@ -979,9 +979,9 @@ inline void ExtractTranslationSampler(Asset& asset, std::string& animId, RefmPositionKeys[i]; // mTime is measured in ticks, but GLTF time is measured in seconds, so convert. times[i] = static_cast(key.mTime / ticksPerSecond); - values[i * 3][0] = key.mValue.x; - values[i * 3][1] = key.mValue.y; - values[i * 3][2] = key.mValue.z; + values[(i * 3) + 0] = key.mValue.x; + values[(i * 3) + 1] = key.mValue.y; + values[(i * 3) + 2] = key.mValue.z; } sampler.input = GetSamplerInputRef(asset, animId, buffer, times); @@ -1002,9 +1002,9 @@ inline void ExtractScaleSampler(Asset& asset, std::string& animId, Ref& const aiVectorKey& key = nodeChannel->mScalingKeys[i]; // mTime is measured in ticks, but GLTF time is measured in seconds, so convert. times[i] = static_cast(key.mTime / ticksPerSecond); - values[i * 3][0] = key.mValue.x; - values[i * 3][1] = key.mValue.y; - values[i * 3][2] = key.mValue.z; + values[(i * 3) + 0] = key.mValue.x; + values[(i * 3) + 1] = key.mValue.y; + values[(i * 3) + 2] = key.mValue.z; } sampler.input = GetSamplerInputRef(asset, animId, buffer, times); @@ -1025,10 +1025,10 @@ inline void ExtractRotationSampler(Asset& asset, std::string& animId, RefmRotationKeys[i]; // mTime is measured in ticks, but GLTF time is measured in seconds, so convert. times[i] = static_cast(key.mTime / ticksPerSecond); - values[i * 4][0] = key.mValue.x; - values[i * 4][1] = key.mValue.y; - values[i * 4][2] = key.mValue.z; - values[i * 4][3] = key.mValue.w; + values[(i * 4) + 0] = key.mValue.x; + values[(i * 4) + 1] = key.mValue.y; + values[(i * 4) + 2] = key.mValue.z; + values[(i * 4) + 3] = key.mValue.w; } sampler.input = GetSamplerInputRef(asset, animId, buffer, times); From e205399052a86b8e6db6fb8cdefd942dfd934d01 Mon Sep 17 00:00:00 2001 From: haroonq <29288912+haroonq@users.noreply.github.com> Date: Thu, 1 Nov 2018 09:01:19 -0700 Subject: [PATCH 8/9] Update glTF2Importer.cpp --- code/glTF2Importer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/glTF2Importer.cpp b/code/glTF2Importer.cpp index ed7c55792..f474a90b5 100755 --- a/code/glTF2Importer.cpp +++ b/code/glTF2Importer.cpp @@ -755,7 +755,7 @@ static void BuildVertexWeightMapping(Ref& mesh, std::vector 0 && bone >= 0 && bone < map.size()) { + if (weight > 0 && bone < map.size()) { map[bone].reserve(8); map[bone].emplace_back(i, weight); } From d1af9526b1531b2acb4e4fef320f146530e1138f Mon Sep 17 00:00:00 2001 From: haroonq <29288912+haroonq@users.noreply.github.com> Date: Thu, 1 Nov 2018 10:52:39 -0700 Subject: [PATCH 9/9] Update glTF2Importer.cpp --- code/glTF2Importer.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/code/glTF2Importer.cpp b/code/glTF2Importer.cpp index f474a90b5..277eddcfd 100755 --- a/code/glTF2Importer.cpp +++ b/code/glTF2Importer.cpp @@ -788,11 +788,10 @@ aiNode* ImportNode(aiScene* pScene, glTF2::Asset& r, std::vector& GetNodeTransform(ainode->mTransformation, node); if (!node.meshes.empty()) { - int mesh_idx = node.meshes[0].GetIndex(); - int count = meshOffsets[mesh_idx + 1] - meshOffsets[mesh_idx]; // GLTF files contain at most 1 mesh per node. assert(node.meshes.size() == 1); - assert(count == 1); + int mesh_idx = node.meshes[0].GetIndex(); + int count = meshOffsets[mesh_idx + 1] - meshOffsets[mesh_idx]; ainode->mNumMeshes = count; ainode->mMeshes = new unsigned int[count];