Merge pull request #1857 from mesilliac/fbx_export_animation

Basic animation support in FBX export
pull/1866/head^2
Kim Kulling 2018-03-29 14:45:10 +02:00 committed by GitHub
commit 5ad2582317
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 329 additions and 7 deletions

View File

@ -116,6 +116,20 @@ FBX::Property::Property(const std::vector<int32_t>& va)
for (size_t i = 0; i < va.size(); ++i) { d[i] = va[i]; }
}
FBX::Property::Property(const std::vector<int64_t>& va)
: type('l'), data(8*va.size())
{
int64_t* d = reinterpret_cast<int64_t*>(data.data());
for (size_t i = 0; i < va.size(); ++i) { d[i] = va[i]; }
}
FBX::Property::Property(const std::vector<float>& va)
: type('f'), data(4*va.size())
{
float* d = reinterpret_cast<float*>(data.data());
for (size_t i = 0; i < va.size(); ++i) { d[i] = va[i]; }
}
FBX::Property::Property(const std::vector<double>& va)
: type('d'), data(8*va.size())
{
@ -178,6 +192,28 @@ void FBX::Property::Dump(Assimp::StreamWriterLE &s)
s.PutI4((reinterpret_cast<int32_t*>(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<int64_t*>(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<float*>(d))[i]);
}
return;
case 'd':
N = data.size() / 8;
s.PutU4(uint32_t(N)); // number of elements

View File

@ -96,7 +96,9 @@ public:
explicit Property(const std::string& s, bool raw=false);
explicit Property(const std::vector<uint8_t>& r);
explicit Property(const std::vector<int32_t>& va);
explicit Property(const std::vector<int64_t>& va);
explicit Property(const std::vector<double>& va);
explicit Property(const std::vector<float>& va);
explicit Property(const aiMatrix4x4& vm);
// this will catch any type not defined above,

View File

@ -63,6 +63,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <ctime> // localtime, tm_*
#include <map>
#include <set>
#include <vector>
#include <array>
#include <unordered_set>
#include <iostream> // 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<int32_t> vertex_indices;
// map of vertex value to its index in the data vector
std::map<aiVector3D,size_t> 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<double> uv_data;
std::vector<int32_t> uv_indices;
std::map<aiVector3D,int32_t> 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<int64_t> 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<int64_t> 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<std::vector<std::array<int64_t,3>>> 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<std::array<int64_t,3>> 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<int64_t,3> 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<int64_t,3>& ids = curve_node_uids[ai][nai];
std::vector<int64_t> times;
std::vector<float> 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<int64_t>& times,
const std::vector<float>& 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<int32_t>{0});
n.AddChild("KeyAttrDataFloat", std::vector<float>{0,0,0,0});
ai_assert(times.size() <= std::numeric_limits<int32_t>::max());
n.AddChild(
"KeyAttrRefCount",
std::vector<int32_t>{static_cast<int32_t>(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,

View File

@ -139,6 +139,23 @@ namespace Assimp
const std::unordered_set<const aiNode*>& limbnodes,
std::vector<std::pair<std::string,aiVector3D>>& 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<int64_t>& times,
const std::vector<float>& values,
int64_t curvenode_id,
const std::string& property_link // "d|X", "d|Y", etc
);
};
}