diff --git a/code/FBXExportProperty.cpp b/code/FBXExportProperty.cpp index 975e9a09a..e139bb95a 100644 --- a/code/FBXExportProperty.cpp +++ b/code/FBXExportProperty.cpp @@ -116,6 +116,20 @@ FBX::Property::Property(const std::vector& va) for (size_t i = 0; i < va.size(); ++i) { d[i] = va[i]; } } +FBX::Property::Property(const std::vector& va) + : type('l'), data(8*va.size()) +{ + int64_t* d = reinterpret_cast(data.data()); + for (size_t i = 0; i < va.size(); ++i) { d[i] = va[i]; } +} + +FBX::Property::Property(const std::vector& va) + : type('f'), data(4*va.size()) +{ + float* d = reinterpret_cast(data.data()); + for (size_t i = 0; i < va.size(); ++i) { d[i] = va[i]; } +} + FBX::Property::Property(const std::vector& va) : type('d'), data(8*va.size()) { @@ -178,6 +192,28 @@ void FBX::Property::Dump(Assimp::StreamWriterLE &s) s.PutI4((reinterpret_cast(d))[i]); } return; + case 'l': + N = data.size() / 8; + s.PutU4(uint32_t(N)); // number of elements + s.PutU4(0); // no encoding (1 would be zip-compressed) + // TODO: compress if large? + s.PutU4(uint32_t(data.size())); // data size + d = data.data(); + for (size_t i = 0; i < N; ++i) { + s.PutI8((reinterpret_cast(d))[i]); + } + return; + case 'f': + N = data.size() / 4; + s.PutU4(uint32_t(N)); // number of elements + s.PutU4(0); // no encoding (1 would be zip-compressed) + // TODO: compress if large? + s.PutU4(uint32_t(data.size())); // data size + d = data.data(); + for (size_t i = 0; i < N; ++i) { + s.PutF4((reinterpret_cast(d))[i]); + } + return; case 'd': N = data.size() / 8; s.PutU4(uint32_t(N)); // number of elements diff --git a/code/FBXExportProperty.h b/code/FBXExportProperty.h index 8920346a3..40a020688 100644 --- a/code/FBXExportProperty.h +++ b/code/FBXExportProperty.h @@ -96,7 +96,9 @@ public: explicit Property(const std::string& s, bool raw=false); explicit Property(const std::vector& r); explicit Property(const std::vector& va); + explicit Property(const std::vector& va); explicit Property(const std::vector& va); + explicit Property(const std::vector& va); explicit Property(const aiMatrix4x4& vm); // this will catch any type not defined above, diff --git a/code/FBXExporter.cpp b/code/FBXExporter.cpp index 9ba5c0333..7dcf752da 100644 --- a/code/FBXExporter.cpp +++ b/code/FBXExporter.cpp @@ -63,6 +63,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include // localtime, tm_* #include #include +#include +#include #include #include // endl @@ -525,8 +527,9 @@ void FBXExporter::WriteDefinitions () total_count += count; // AnimationStack / FbxAnimStack - // this seems to always be here in Maya exports - count = 0; + // this seems to always be here in Maya exports, + // but no harm seems to come of leaving it out. + count = mScene->mNumAnimations; if (count) { n = FBX::Node("ObjectType", Property("AnimationStack")); n.AddChild("Count", count); @@ -544,8 +547,11 @@ void FBXExporter::WriteDefinitions () } // AnimationLayer / FbxAnimLayer - // this seems to always be here in Maya exports - count = 0; + // this seems to always be here in Maya exports, + // but no harm seems to come of leaving it out. + // Assimp doesn't support animation layers, + // so there will be one per aiAnimation + count = mScene->mNumAnimations; if (count) { n = FBX::Node("ObjectType", Property("AnimationLayer")); n.AddChild("Count", count); @@ -821,7 +827,7 @@ void FBXExporter::WriteDefinitions () } // AnimationCurveNode / FbxAnimCurveNode - count = 0; + count = mScene->mNumAnimations * 3; if (count) { n = FBX::Node("ObjectType", Property("AnimationCurveNode")); n.AddChild("Count", count); @@ -834,6 +840,15 @@ void FBXExporter::WriteDefinitions () total_count += count; } + // AnimationCurve / FbxAnimCurve + count = mScene->mNumAnimations * 9; + if (count) { + n = FBX::Node("ObjectType", Property("AnimationCurve")); + n.AddChild("Count", count); + object_nodes.push_back(n); + total_count += count; + } + // Pose count = 0; for (size_t i = 0; i < mScene->mNumMeshes; ++i) { @@ -911,6 +926,12 @@ aiMatrix4x4 get_world_transform(const aiNode* node, const aiScene* scene) return transform; } +int64_t to_ktime(double ticks, const aiAnimation* anim) { + if (anim->mTicksPerSecond <= 0) { + return ticks * FBX::SECOND; + } + return (ticks / anim->mTicksPerSecond) * FBX::SECOND; +} void FBXExporter::WriteObjects () { @@ -943,7 +964,7 @@ void FBXExporter::WriteObjects () std::vector vertex_indices; // map of vertex value to its index in the data vector std::map index_by_vertex_value; - int32_t index = 0; + int32_t index = 0; for (size_t vi = 0; vi < m->mNumVertices; ++vi) { aiVector3D vtx = m->mVertices[vi]; auto elem = index_by_vertex_value.find(vtx); @@ -1052,7 +1073,7 @@ void FBXExporter::WriteObjects () std::vector uv_data; std::vector uv_indices; std::map index_by_uv; - int32_t index = 0; + int32_t index = 0; for (size_t fi = 0; fi < m->mNumFaces; ++fi) { const aiFace &f = m->mFaces[fi]; for (size_t pvi = 0; pvi < f.mNumIndices; ++pvi) { @@ -1849,6 +1870,193 @@ void FBXExporter::WriteObjects () outstream, mScene->mRootNode, 0, limbnodes ); + // animations + // + // in FBX there are: + // * AnimationStack - corresponds to an aiAnimation + // * AnimationLayer - a combinable animation component + // * AnimationCurveNode - links the property to be animated + // * AnimationCurve - defines animation data for a single property value + // + // the CurveNode also provides the default value for a property, + // such as the X, Y, Z coordinates for animatable translation. + // + // the Curve only specifies values for one component of the property, + // so there will be a separate AnimationCurve for X, Y, and Z. + // + // Assimp has: + // * aiAnimation - basically corresponds to an AnimationStack + // * aiNodeAnim - defines all animation for one aiNode + // * aiVectorKey/aiQuatKey - define the keyframe data for T/R/S + // + // assimp has no equivalent for AnimationLayer, + // and these are flattened on FBX import. + // we can assume there will be one per AnimationStack. + // + // the aiNodeAnim contains all animation data for a single aiNode, + // which will correspond to three AnimationCurveNode's: + // one each for translation, rotation and scale. + // The data for each of these will be put in 9 AnimationCurve's, + // T.X, T.Y, T.Z, R.X, R.Y, R.Z, etc. + + // AnimationStack / aiAnimation + std::vector animation_stack_uids(mScene->mNumAnimations); + for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) { + int64_t animstack_uid = generate_uid(); + animation_stack_uids[ai] = animstack_uid; + const aiAnimation* anim = mScene->mAnimations[ai]; + + FBX::Node asnode("AnimationStack"); + std::string name = anim->mName.C_Str() + FBX::SEPARATOR + "AnimStack"; + asnode.AddProperties(animstack_uid, name, ""); + FBX::Node p("Properties70"); + p.AddP70time("LocalStart", 0); // assimp doesn't store this + p.AddP70time("LocalStop", to_ktime(anim->mDuration, anim)); + p.AddP70time("ReferenceStart", 0); + p.AddP70time("ReferenceStop", to_ktime(anim->mDuration, anim)); + asnode.AddChild(p); + + // this node absurdly always pretends it has children + // (in this case it does, but just in case...) + asnode.Begin(outstream); + asnode.DumpProperties(outstream); + asnode.EndProperties(outstream); + asnode.DumpChildren(outstream); + asnode.End(outstream, true); + + // note: animation stacks are not connected to anything + } + + // AnimationLayer - one per aiAnimation + std::vector animation_layer_uids(mScene->mNumAnimations); + for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) { + int64_t animlayer_uid = generate_uid(); + animation_layer_uids[ai] = animlayer_uid; + FBX::Node alnode("AnimationLayer"); + alnode.AddProperties(animlayer_uid, FBX::SEPARATOR + "AnimLayer", ""); + + // this node absurdly always pretends it has children + alnode.Begin(outstream); + alnode.DumpProperties(outstream); + alnode.EndProperties(outstream); + alnode.DumpChildren(outstream); + alnode.End(outstream, true); + + // connect to the relevant animstack + FBX::Node c("C"); + c.AddProperties("OO", animlayer_uid, animation_stack_uids[ai]); + connections.push_back(c); // TODO: emplace_back + } + + // AnimCurveNode - three per aiNodeAnim + std::vector>> curve_node_uids; + for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) { + const aiAnimation* anim = mScene->mAnimations[ai]; + const int64_t layer_uid = animation_layer_uids[ai]; + std::vector> nodeanim_uids; + for (size_t nai = 0; nai < anim->mNumChannels; ++nai) { + const aiNodeAnim* na = anim->mChannels[nai]; + // get the corresponding aiNode + const aiNode* node = mScene->mRootNode->FindNode(na->mNodeName); + // and its transform + const aiMatrix4x4 node_xfm = get_world_transform(node, mScene); + aiVector3D T, R, S; + node_xfm.Decompose(S, R, T); + + // AnimationCurveNode uids + std::array ids; + ids[0] = generate_uid(); // T + ids[1] = generate_uid(); // R + ids[2] = generate_uid(); // S + + // translation + WriteAnimationCurveNode(outstream, + ids[0], "T", T, "Lcl Translation", + layer_uid, node_uids[node] + ); + + // rotation + WriteAnimationCurveNode(outstream, + ids[1], "R", R, "Lcl Rotation", + layer_uid, node_uids[node] + ); + + // scale + WriteAnimationCurveNode(outstream, + ids[2], "S", S, "Lcl Scale", + layer_uid, node_uids[node] + ); + + // store the uids for later use + nodeanim_uids.push_back(ids); + } + curve_node_uids.push_back(nodeanim_uids); + } + + // AnimCurve - defines actual keyframe data. + // there's a separate curve for every component of every vector, + // for example a transform curvenode will have separate X/Y/Z AnimCurve's + for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) { + const aiAnimation* anim = mScene->mAnimations[ai]; + for (size_t nai = 0; nai < anim->mNumChannels; ++nai) { + const aiNodeAnim* na = anim->mChannels[nai]; + // get the corresponding aiNode + const aiNode* node = mScene->mRootNode->FindNode(na->mNodeName); + // and its transform + const aiMatrix4x4 node_xfm = get_world_transform(node, mScene); + aiVector3D T, R, S; + node_xfm.Decompose(S, R, T); + const std::array& ids = curve_node_uids[ai][nai]; + + std::vector times; + std::vector xval, yval, zval; + + // position/translation + for (size_t ki = 0; ki < na->mNumPositionKeys; ++ki) { + const aiVectorKey& k = na->mPositionKeys[ki]; + times.push_back(to_ktime(k.mTime, anim)); + xval.push_back(k.mValue.x); + yval.push_back(k.mValue.y); + zval.push_back(k.mValue.z); + } + // one curve each for X, Y, Z + WriteAnimationCurve(outstream, T.x, times, xval, ids[0], "d|X"); + WriteAnimationCurve(outstream, T.y, times, yval, ids[0], "d|Y"); + WriteAnimationCurve(outstream, T.z, times, zval, ids[0], "d|Z"); + + // rotation + times.clear(); xval.clear(); yval.clear(); zval.clear(); + for (size_t ki = 0; ki < na->mNumRotationKeys; ++ki) { + const aiQuatKey& k = na->mRotationKeys[ki]; + times.push_back(to_ktime(k.mTime, anim)); + // TODO: aiQuaternion method to convert to Euler... + aiMatrix4x4 m(k.mValue.GetMatrix()); + aiVector3D qs, qr, qt; + m.Decompose(qs, qr, qt); + qr *= DEG; + xval.push_back(qr.x); + yval.push_back(qr.y); + zval.push_back(qr.z); + } + WriteAnimationCurve(outstream, R.x, times, xval, ids[1], "d|X"); + WriteAnimationCurve(outstream, R.y, times, yval, ids[1], "d|Y"); + WriteAnimationCurve(outstream, R.z, times, zval, ids[1], "d|Z"); + + // scaling/scale + times.clear(); xval.clear(); yval.clear(); zval.clear(); + for (size_t ki = 0; ki < na->mNumScalingKeys; ++ki) { + const aiVectorKey& k = na->mScalingKeys[ki]; + times.push_back(to_ktime(k.mTime, anim)); + xval.push_back(k.mValue.x); + yval.push_back(k.mValue.y); + zval.push_back(k.mValue.z); + } + WriteAnimationCurve(outstream, S.x, times, xval, ids[2], "d|X"); + WriteAnimationCurve(outstream, S.y, times, yval, ids[2], "d|Y"); + WriteAnimationCurve(outstream, S.z, times, zval, ids[2], "d|Z"); + } + } + object_node.End(outstream, true); } @@ -2119,6 +2327,65 @@ void FBXExporter::WriteModelNodes( } } + +void FBXExporter::WriteAnimationCurveNode( + StreamWriterLE& outstream, + int64_t uid, + std::string name, // "T", "R", or "S" + aiVector3D default_value, + std::string property_name, // "Lcl Translation" etc + int64_t layer_uid, + int64_t node_uid +) { + FBX::Node n("AnimationCurveNode"); + n.AddProperties(uid, name + FBX::SEPARATOR + "AnimCurveNode", ""); + FBX::Node p("Properties70"); + p.AddP70numberA("d|X", default_value.x); + p.AddP70numberA("d|Y", default_value.y); + p.AddP70numberA("d|Z", default_value.z); + n.AddChild(p); + n.Dump(outstream); + // connect to layer + FBX::Node cl("C"); + cl.AddProperties("OO", uid, layer_uid); + this->connections.push_back(cl); // TODO: emplace_back + // connect to bone + FBX::Node cb("C"); + cb.AddProperties("OP", uid, node_uid, property_name); + this->connections.push_back(cb); // TODO: emplace_back +} + + +void FBXExporter::WriteAnimationCurve( + StreamWriterLE& outstream, + double default_value, + const std::vector& times, + const std::vector& values, + int64_t curvenode_uid, + const std::string& property_link // "d|X", "d|Y", etc +) { + FBX::Node n("AnimationCurve"); + int64_t curve_uid = generate_uid(); + n.AddProperties(curve_uid, FBX::SEPARATOR + "AnimCurve", ""); + n.AddChild("Default", default_value); + n.AddChild("KeyVer", int32_t(4009)); + n.AddChild("KeyTime", times); + n.AddChild("KeyValueFloat", values); + // TODO: keyattr flags and data (STUB for now) + n.AddChild("KeyAttrFlags", std::vector{0}); + n.AddChild("KeyAttrDataFloat", std::vector{0,0,0,0}); + ai_assert(times.size() <= std::numeric_limits::max()); + n.AddChild( + "KeyAttrRefCount", + std::vector{static_cast(times.size())} + ); + n.Dump(outstream); + FBX::Node c("C"); + c.AddProperties("OP", curve_uid, curvenode_uid, property_link); + this->connections.push_back(c); // TODO: emplace_back +} + + void FBXExporter::WriteConnections () { // we should have completed the connection graph already, diff --git a/code/FBXExporter.h b/code/FBXExporter.h index ce2f67e24..553cf60fe 100644 --- a/code/FBXExporter.h +++ b/code/FBXExporter.h @@ -139,6 +139,23 @@ namespace Assimp const std::unordered_set& limbnodes, std::vector>& transform_chain ); + void WriteAnimationCurveNode( + StreamWriterLE& outstream, + int64_t uid, + std::string name, // "T", "R", or "S" + aiVector3D default_value, + std::string property_name, // "Lcl Translation" etc + int64_t animation_layer_uid, + int64_t node_uid + ); + void WriteAnimationCurve( + StreamWriterLE& outstream, + double default_value, + const std::vector& times, + const std::vector& values, + int64_t curvenode_id, + const std::string& property_link // "d|X", "d|Y", etc + ); }; }