- fbx: animation conversion from fbx's representation to assimp's. This involves evaluating animation layers etc.
parent
711878567a
commit
382f4619ad
|
@ -96,6 +96,7 @@ AnimationCurve::~AnimationCurve()
|
|||
// ------------------------------------------------------------------------------------------------
|
||||
AnimationCurveNode::AnimationCurveNode(uint64_t id, const Element& element, const std::string& name, const Document& doc)
|
||||
: Object(id, element, name)
|
||||
, target()
|
||||
{
|
||||
// resolve attached animation curves
|
||||
const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID());
|
||||
|
@ -118,7 +119,9 @@ AnimationCurveNode::AnimationCurveNode(uint64_t id, const Element& element, cons
|
|||
DOMWarning("source object for ->AnimationCurveNode link is not an AnimationCurve",&element);
|
||||
continue;
|
||||
}
|
||||
curves[con->PropertyName()] = anim;
|
||||
|
||||
prop = con->PropertyName();
|
||||
curves[prop] = anim;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER
|
||||
|
||||
#include <boost/tuple/tuple.hpp>
|
||||
|
||||
#include "FBXParser.h"
|
||||
#include "FBXConverter.h"
|
||||
#include "FBXDocument.h"
|
||||
|
@ -71,6 +73,7 @@ public:
|
|||
, doc(doc)
|
||||
{
|
||||
ConvertRootNode();
|
||||
//ConvertAnimations();
|
||||
|
||||
if(doc.Settings().readAllMaterials) {
|
||||
// unfortunately this means we have to evaluate all objects
|
||||
|
@ -865,6 +868,346 @@ private:
|
|||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// convert animation data to aiAnimation et al
|
||||
void ConvertAnimations()
|
||||
{
|
||||
const std::vector<const AnimationStack*>& animations = doc.AnimationStacks();
|
||||
BOOST_FOREACH(const AnimationStack* stack, animations) {
|
||||
ConvertAnimationStack(*stack);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
std::string FixNodeName(const std::string& name)
|
||||
{
|
||||
// XXX handle prefix
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
typedef std::map<const AnimationCurveNode*, const AnimationLayer*> LayerMap;
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ConvertAnimationStack(const AnimationStack& st)
|
||||
{
|
||||
aiAnimation* const anim = new aiAnimation();
|
||||
animations.push_back(anim);
|
||||
|
||||
// strip AnimationStack:: prefix
|
||||
std::string name = st.Name();
|
||||
if(name.substr(0,16) == "AnimationStack::") {
|
||||
name = name.substr(16);
|
||||
}
|
||||
|
||||
anim->mName.Set(name);
|
||||
const AnimationLayerList& layers = st.Layers();
|
||||
|
||||
// need to find all nodes for which we need to generate node animations -
|
||||
// it may happen that we need to merge multiple layers, though.
|
||||
// XXX: better use multi_map ..
|
||||
typedef std::map<std::string, std::vector<const AnimationCurveNode*> > NodeMap;
|
||||
NodeMap node_map;
|
||||
|
||||
// reverse mapping from curves to layers, much faster than querying
|
||||
// the FBX DOM for it.
|
||||
LayerMap layer_map;
|
||||
|
||||
BOOST_FOREACH(const AnimationLayer* layer, layers) {
|
||||
ai_assert(layer);
|
||||
|
||||
const AnimationCurveNodeList& nodes = layer->Nodes();
|
||||
BOOST_FOREACH(const AnimationCurveNode* node, nodes) {
|
||||
ai_assert(node);
|
||||
|
||||
const Model* model = node->TargetNode();
|
||||
ai_assert(model);
|
||||
|
||||
const std::string& name = FixNodeName(model->Name());
|
||||
node_map[name].push_back(node);
|
||||
|
||||
layer_map[node] = layer;
|
||||
}
|
||||
}
|
||||
|
||||
// generate node animations
|
||||
std::vector<aiNodeAnim*> node_anims;
|
||||
|
||||
try {
|
||||
|
||||
NodeMap node_property_map;
|
||||
BOOST_FOREACH(const NodeMap::value_type& kv, node_map) {
|
||||
node_property_map.clear();
|
||||
|
||||
BOOST_FOREACH(const AnimationCurveNode* node, kv.second) {
|
||||
ai_assert(node);
|
||||
|
||||
if (node->TargetProperty().empty()) {
|
||||
FBXImporter::LogWarn("target property for animation curve not set");
|
||||
continue;
|
||||
}
|
||||
|
||||
node_property_map[node->TargetProperty()].push_back(node);
|
||||
}
|
||||
|
||||
const NodeMap::const_iterator itScale = node_property_map.find("Lcl Scaling");
|
||||
const NodeMap::const_iterator itRotation = node_property_map.find("Lcl Rotation");
|
||||
const NodeMap::const_iterator itTranslation = node_property_map.find("Lcl Translation");
|
||||
|
||||
const bool hasScale = !!(*itScale).second.size();
|
||||
const bool hasRotation = !!(*itRotation).second.size();
|
||||
const bool hasTranslation = !!(*itTranslation).second.size();
|
||||
|
||||
if (!hasScale && !hasRotation && !hasTranslation) {
|
||||
FBXImporter::LogWarn("ignoring node animation, did not find transformation key frames");
|
||||
continue;
|
||||
}
|
||||
|
||||
aiNodeAnim* const na = new aiNodeAnim();
|
||||
node_anims.push_back(na);
|
||||
|
||||
if(hasScale) {
|
||||
ConvertScaleKeys(na, (*itScale).second, layer_map);
|
||||
}
|
||||
|
||||
if(hasRotation) {
|
||||
ConvertRotationKeys(na, (*itRotation).second, layer_map);
|
||||
}
|
||||
|
||||
if(hasTranslation) {
|
||||
ConvertTranslationKeys(na, (*itTranslation).second, layer_map);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(std::exception&) {
|
||||
std::for_each(node_anims.begin(), node_anims.end(), Util::delete_fun<aiNodeAnim>());
|
||||
}
|
||||
|
||||
if(node_anims.size()) {
|
||||
anim->mChannels = new aiNodeAnim*[node_anims.size()]();
|
||||
anim->mNumChannels = static_cast<unsigned int>(node_anims.size());
|
||||
|
||||
std::swap_ranges(node_anims.begin(),node_anims.end(),anim->mChannels);
|
||||
}
|
||||
}
|
||||
|
||||
// key (time), value, mapto (component index)
|
||||
typedef boost::tuple< std::vector<float>*, std::vector<float>*, unsigned int > KeyFrameList;
|
||||
typedef std::vector<KeyFrameList> KeyFrameListList;
|
||||
|
||||
typedef std::vector<float> KeyTimeList;
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
KeyFrameListList GetKeyframeList(const std::vector<const AnimationCurveNode*>& nodes)
|
||||
{
|
||||
KeyFrameListList inputs;
|
||||
inputs.reserve(nodes.size()*3);
|
||||
|
||||
BOOST_FOREACH(const AnimationCurveNode* node, nodes) {
|
||||
ai_assert(node);
|
||||
|
||||
const AnimationCurveMap& curves = node->Curves();
|
||||
BOOST_FOREACH(const AnimationCurveMap::value_type& kv, curves) {
|
||||
|
||||
unsigned int mapto;
|
||||
if (kv.first == "d|X") {
|
||||
mapto = 0;
|
||||
}
|
||||
else if (kv.first == "d|Y") {
|
||||
mapto = 1;
|
||||
}
|
||||
else if (kv.first == "d|Z") {
|
||||
mapto = 2;
|
||||
}
|
||||
else {
|
||||
FBXImporter::LogWarn("ignoring scale animation curve, did not recognize target component");
|
||||
continue;
|
||||
}
|
||||
|
||||
const AnimationCurve* const curve = kv.second;
|
||||
ai_assert(curve->GetKeys().size() == curve->GetValues().size() && curve->GetKeys().size());
|
||||
|
||||
inputs.push_back(boost::make_tuple(&curve->GetKeys(), &curve->GetValues(), mapto));
|
||||
}
|
||||
}
|
||||
return inputs; // pray for NRVO :-)
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
std::vector<float> GetKeyTimeList(const KeyFrameListList& inputs)
|
||||
{
|
||||
// XXX reserve some space upfront
|
||||
std::vector<float> keys;
|
||||
|
||||
std::vector<unsigned int> next_pos;
|
||||
next_pos.resize(inputs.size(),0);
|
||||
|
||||
const size_t count = inputs.size();
|
||||
while(true) {
|
||||
|
||||
float min_tick = 1e10f;
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
const KeyFrameList& kfl = inputs[i];
|
||||
|
||||
if (kfl.get<0>()->size() > next_pos[i] && kfl.get<0>()->at(next_pos[i]) < min_tick) {
|
||||
min_tick = kfl.get<0>()->at(next_pos[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (min_tick > 1e9f) {
|
||||
break;
|
||||
}
|
||||
keys.push_back(min_tick);
|
||||
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
const KeyFrameList& kfl = inputs[i];
|
||||
|
||||
const float time_epsilon = 1e-4f;
|
||||
while(kfl.get<0>()->size() > next_pos[i] && fabs(kfl.get<0>()->at(next_pos[i]) - min_tick) < time_epsilon) {
|
||||
++next_pos[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void InterpolateKeys(aiVectorKey* valOut,const KeyTimeList& keys, const KeyFrameListList& inputs, const bool geom = false)
|
||||
{
|
||||
ai_assert(keys.size());
|
||||
ai_assert(valOut);
|
||||
|
||||
std::vector<unsigned int> next_pos;
|
||||
const size_t count = inputs.size();
|
||||
|
||||
next_pos.resize(inputs.size(),0);
|
||||
|
||||
BOOST_FOREACH(float time, keys) {
|
||||
float result[3] = {0.0f, 0.0f, 0.0f};
|
||||
if(geom) {
|
||||
result[0] = result[1] = result[2] = 1.0f;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
const KeyFrameList& kfl = inputs[i];
|
||||
|
||||
const float time_epsilon = 1e-4f;
|
||||
if (kfl.get<0>()->size() > next_pos[i] && fabs(kfl.get<0>()->at(next_pos[i]) - time) < time_epsilon) {
|
||||
++next_pos[i];
|
||||
}
|
||||
|
||||
// use lerp for interpolation
|
||||
const float valueA = kfl.get<1>()->at(next_pos[i]>0 ? next_pos[i]-1 : 0);
|
||||
const float valueB = kfl.get<1>()->at(next_pos[i]);
|
||||
|
||||
const float timeA = kfl.get<0>()->at(next_pos[i]>0 ? next_pos[i]-1 : 0);
|
||||
const float timeB = kfl.get<0>()->at(next_pos[i]);
|
||||
|
||||
const float factor = (time - timeA) / (timeB - timeA);
|
||||
const float interpValue = valueA + (valueB - valueA) * factor;
|
||||
|
||||
if(geom) {
|
||||
result[kfl.get<2>()] *= interpValue;
|
||||
}
|
||||
else {
|
||||
result[kfl.get<2>()] += interpValue;
|
||||
}
|
||||
}
|
||||
|
||||
valOut->mTime = time;
|
||||
valOut->mValue.x = result[0];
|
||||
valOut->mValue.y = result[1];
|
||||
valOut->mValue.z = result[2];
|
||||
|
||||
++valOut;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void InterpolateKeys(aiQuatKey* valOut,const KeyTimeList& keys, const KeyFrameListList& inputs, const bool geom = false)
|
||||
{
|
||||
ai_assert(keys.size());
|
||||
ai_assert(valOut);
|
||||
|
||||
boost::scoped_array<aiVectorKey> temp(new aiVectorKey[keys.size()]);
|
||||
InterpolateKeys(temp.get(),keys,inputs,geom);
|
||||
|
||||
for (size_t i = 0, c = keys.size(); i < c; ++i) {
|
||||
|
||||
const aiVector3D rot = temp[i].mValue;
|
||||
|
||||
aiMatrix4x4 m, mtemp;
|
||||
if(fabs(rot.x) > 1e-6f) {
|
||||
m *= aiMatrix4x4::RotationX(rot.x,mtemp);
|
||||
}
|
||||
if(fabs(rot.y) > 1e-6f) {
|
||||
m *= aiMatrix4x4::RotationY(rot.y,mtemp);
|
||||
}
|
||||
if(fabs(rot.z) > 1e-6f) {
|
||||
m *= aiMatrix4x4::RotationZ(rot.z,mtemp);
|
||||
}
|
||||
|
||||
valOut[i].mTime = temp[i].mTime;
|
||||
valOut[i].mValue = aiQuaternion(aiMatrix3x3(m));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ConvertScaleKeys(aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes, const LayerMap& layers)
|
||||
{
|
||||
ai_assert(nodes.size());
|
||||
|
||||
// XXX for now, assume scale should be blended geometrically (i.e. two
|
||||
// layers should be multiplied with each other). There is a FBX
|
||||
// property in the layer to specify the behaviour, though.
|
||||
|
||||
const KeyFrameListList& inputs = GetKeyframeList(nodes);
|
||||
const KeyTimeList& keys = GetKeyTimeList(inputs);
|
||||
|
||||
na->mNumScalingKeys = static_cast<unsigned int>(keys.size());
|
||||
na->mScalingKeys = new aiVectorKey[keys.size()];
|
||||
InterpolateKeys(na->mScalingKeys, keys, inputs, true);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ConvertTranslationKeys(aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes, const LayerMap& layers)
|
||||
{
|
||||
ai_assert(nodes.size());
|
||||
|
||||
// XXX see notes in ConvertScaleKeys()
|
||||
const KeyFrameListList& inputs = GetKeyframeList(nodes);
|
||||
const KeyTimeList& keys = GetKeyTimeList(inputs);
|
||||
|
||||
na->mNumPositionKeys = static_cast<unsigned int>(keys.size());
|
||||
na->mPositionKeys = new aiVectorKey[keys.size()];
|
||||
InterpolateKeys(na->mPositionKeys, keys, inputs, false);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ConvertRotationKeys(aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes, const LayerMap& layers)
|
||||
{
|
||||
ai_assert(nodes.size());
|
||||
|
||||
// XXX see notes in ConvertScaleKeys()
|
||||
const std::vector< KeyFrameList >& inputs = GetKeyframeList(nodes);
|
||||
const std::vector<float>& keys = GetKeyTimeList(inputs);
|
||||
|
||||
na->mNumRotationKeys = static_cast<unsigned int>(keys.size());
|
||||
na->mRotationKeys = new aiQuatKey[keys.size()];
|
||||
InterpolateKeys(na->mRotationKeys, keys, inputs, false);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// copy generated meshes, animations, lights, cameras and textures to the output scene
|
||||
void TransferDataToScene()
|
||||
|
@ -886,6 +1229,13 @@ private:
|
|||
|
||||
std::swap_ranges(materials.begin(),materials.end(),out->mMaterials);
|
||||
}
|
||||
|
||||
if(animations.size()) {
|
||||
out->mAnimations = new aiAnimation*[animations.size()]();
|
||||
out->mNumAnimations = static_cast<unsigned int>(animations.size());
|
||||
|
||||
std::swap_ranges(animations.begin(),animations.end(),out->mAnimations);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -896,6 +1246,7 @@ private:
|
|||
|
||||
std::vector<aiMesh*> meshes;
|
||||
std::vector<aiMaterial*> materials;
|
||||
std::vector<aiAnimation*> animations;
|
||||
|
||||
typedef std::map<const Material*, unsigned int> MaterialMap;
|
||||
MaterialMap materials_converted;
|
||||
|
|
|
@ -700,7 +700,7 @@ void Document::ReadConnections()
|
|||
// ------------------------------------------------------------------------------------------------
|
||||
const std::vector<const AnimationStack*>& Document::AnimationStacks() const
|
||||
{
|
||||
if (animationStacksResolved.empty() && animationStacks.size()) {
|
||||
if (!animationStacksResolved.empty() || !animationStacks.size()) {
|
||||
return animationStacksResolved;
|
||||
}
|
||||
|
||||
|
|
|
@ -474,11 +474,17 @@ public:
|
|||
return target;
|
||||
}
|
||||
|
||||
const std::string& TargetProperty() const {
|
||||
return prop;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
const Model* target;
|
||||
boost::shared_ptr<const PropertyTable> props;
|
||||
AnimationCurveMap curves;
|
||||
|
||||
std::string prop;
|
||||
};
|
||||
|
||||
typedef std::vector<const AnimationCurveNode*> AnimationCurveNodeList;
|
||||
|
|
Loading…
Reference in New Issue