splitting animation during import

This commit is contained in:
Mikulas Florek 2017-10-29 22:23:51 +01:00
parent 4a4c78206c
commit a99baced31
2 changed files with 151 additions and 109 deletions

View file

@ -220,8 +220,20 @@ struct FBXImporter
struct ImportAnimation
{
struct Split
{
int from_frame = 0;
int to_frame = 0;
StaticString<32> name;
};
ImportAnimation(IAllocator& allocator)
: splits(allocator)
{}
const ofbx::AnimationStack* fbx = nullptr;
const ofbx::IScene* scene = nullptr;
Array<Split> splits;
StaticString<MAX_PATH_LENGTH> output_filename;
bool import = true;
int root_motion_bone_idx = -1;
@ -446,7 +458,8 @@ struct FBXImporter
int anim_count = scene.getAnimationStackCount();
for (int i = 0; i < anim_count; ++i)
{
ImportAnimation& anim = animations.emplace();
IAllocator& allocator = app.getWorldEditor().getAllocator();
ImportAnimation& anim = animations.emplace(allocator);
anim.scene = &scene;
anim.fbx = (const ofbx::AnimationStack*)scene.getAnimationStack(i);
anim.import = true;
@ -926,7 +939,8 @@ struct FBXImporter
// arg parent_scale - animated scale is not supported, but we can get rid of static scale if we ignore
// it in writeSkeleton() and use parent_scale in this function
static void compressPositions(Array<TranslationKey>& out,
int frames,
int from_frame,
int to_frame,
float sample_period,
const ofbx::AnimationCurveNode* curve_node,
const ofbx::Object& bone,
@ -935,24 +949,24 @@ struct FBXImporter
{
out.clear();
if (!curve_node) return;
if (frames == 0) return;
if (to_frame == from_frame) return;
ofbx::Vec3 lcl_rotation = bone.getLocalRotation();
Vec3 pos = getTranslation(bone.evalLocal(curve_node->getNodeLocalTransform(0), lcl_rotation)) * parent_scale;
Vec3 pos = getTranslation(bone.evalLocal(curve_node->getNodeLocalTransform(from_frame * sample_period), lcl_rotation)) * parent_scale;
TranslationKey last_written = {pos, 0, 0};
out.push(last_written);
if (frames == 1) return;
if (to_frame == from_frame + 1) return;
float dt = sample_period;
pos = getTranslation(bone.evalLocal(curve_node->getNodeLocalTransform(sample_period), lcl_rotation)) *
pos = getTranslation(bone.evalLocal(curve_node->getNodeLocalTransform((from_frame + 1) * sample_period), lcl_rotation)) *
parent_scale;
Vec3 dif = (pos - last_written.pos) / sample_period;
TranslationKey prev = {pos, sample_period, 1};
for (u16 i = 2; i < (u16)frames; ++i)
float dt = sample_period;
for (u16 i = 2; i < u16(to_frame - from_frame); ++i)
{
float t = i * sample_period;
Vec3 cur =
getTranslation(bone.evalLocal(curve_node->getNodeLocalTransform(t), lcl_rotation)) * parent_scale;
getTranslation(bone.evalLocal(curve_node->getNodeLocalTransform((from_frame + i) * sample_period), lcl_rotation)) * parent_scale;
dt = t - last_written.time;
Vec3 estimate = last_written.pos + dif * dt;
if (fabs(estimate.x - cur.x) > error || fabs(estimate.y - cur.y) > error ||
@ -967,17 +981,18 @@ struct FBXImporter
prev = {cur, t, i};
}
float t = frames * sample_period;
float t = (to_frame - from_frame) * sample_period;
last_written = {
getTranslation(bone.evalLocal(curve_node->getNodeLocalTransform(t), lcl_rotation)) * parent_scale,
getTranslation(bone.evalLocal(curve_node->getNodeLocalTransform(to_frame * sample_period), lcl_rotation)) * parent_scale,
t,
(u16)frames};
u16(to_frame - from_frame)};
out.push(last_written);
}
static void compressRotations(Array<RotationKey>& out,
int frames,
int from_frame,
int to_frame,
float sample_period,
const ofbx::AnimationCurveNode* curve_node,
const ofbx::Object& bone,
@ -985,22 +1000,22 @@ struct FBXImporter
{
out.clear();
if (!curve_node) return;
if (frames == 0) return;
ofbx::Vec3 lcl_translation = bone.getLocalTranslation();
Quat rot = getRotation(bone.evalLocal(lcl_translation, curve_node->getNodeLocalTransform(0)));
RotationKey last_written = {rot, 0, 0};
out.push(last_written);
if (frames == 1) return;
if (to_frame == from_frame) return;
float dt = sample_period;
rot = getRotation(bone.evalLocal(lcl_translation, curve_node->getNodeLocalTransform(sample_period)));
ofbx::Vec3 lcl_translation = bone.getLocalTranslation();
Quat rot = getRotation(bone.evalLocal(lcl_translation, curve_node->getNodeLocalTransform(from_frame * sample_period)));
RotationKey last_written = {rot, 0, 0};
out.push(last_written);
if (to_frame == from_frame + 1) return;
rot = getRotation(bone.evalLocal(lcl_translation, curve_node->getNodeLocalTransform((from_frame + 1) * sample_period)));
RotationKey after_last = {rot, sample_period, 1};
RotationKey prev = after_last;
for (u16 i = 2; i < (u16)frames; ++i)
for (u16 i = 2; i < u16(to_frame - from_frame); ++i)
{
float t = i * sample_period;
Quat cur = getRotation(bone.evalLocal(lcl_translation, curve_node->getNodeLocalTransform(t)));
Quat cur = getRotation(bone.evalLocal(lcl_translation, curve_node->getNodeLocalTransform((from_frame + i) * sample_period)));
Quat estimate;
nlerp(cur, last_written.rot, &estimate, sample_period / (t - last_written.time));
if (fabs(estimate.x - after_last.rot.x) > error || fabs(estimate.y - after_last.rot.y) > error ||
@ -1014,9 +1029,11 @@ struct FBXImporter
prev = {cur, t, i};
}
float t = frames * sample_period;
float t = (to_frame - from_frame) * sample_period;
last_written = {
getRotation(bone.evalLocal(lcl_translation, curve_node->getNodeLocalTransform(t))), t, (u16)frames};
getRotation(bone.evalLocal(lcl_translation, curve_node->getNodeLocalTransform(to_frame * sample_period))),
t,
u16(to_frame - from_frame)};
out.push(last_written);
}
@ -1048,18 +1065,17 @@ struct FBXImporter
ImportAnimation& anim = animations[anim_idx];
dialog.setImportMessage("Writing animation...", 0.6f + 0.2f * (anim_idx / (float)animations.size()));
if (!anim.import) continue;
const ofbx::AnimationStack* stack = anim.fbx;
const char* anim_name = stack->name;
const ofbx::IScene& scene = *anim.scene;
const ofbx::TakeInfo* take_info = scene.getTakeInfo(stack->name);
float begin = 0;
float end = 0;
float scene_frame_rate = 24.0f / time_scale;
float sampling_period = 1.0f / scene_frame_rate;
int all_frames_count = 0;
if (take_info)
{
begin = (float)take_info->local_time_from;
end = (float)take_info->local_time_to;
all_frames_count = int((take_info->local_time_to - take_info->local_time_from) / sampling_period + 0.5f);
}
else
{
@ -1074,91 +1090,97 @@ struct FBXImporter
(float)((mode == FbxTime::eCustom) ? scene->GetGlobalSettings().GetCustomFrameRate()
: FbxTime::GetFrameRate(mode));
*/
float scene_frame_rate = 24.0f / time_scale;
float sampling_period = 1.0f / scene_frame_rate;
float duration = (end > begin ? end - begin : 1.0f) * time_scale;
StaticString<MAX_PATH_LENGTH> tmp(output_dir, anim.output_filename, ".ani");
IAllocator& allocator = app.getWorldEditor().getAllocator();
if (!out_file.open(tmp, FS::Mode::CREATE_AND_WRITE, allocator))
for (int i = 0; i < Math::maximum(1, anim.splits.size()); ++i)
{
g_log_error.log("FBX") << "Failed to create " << tmp;
continue;
}
Animation::Header header;
header.magic = Animation::HEADER_MAGIC;
header.version = 3;
header.fps = (u32)(scene_frame_rate + 0.5f);
write(header);
FBXImporter::ImportAnimation::Split whole_anim_split;
whole_anim_split.to_frame = all_frames_count;
auto* split = anim.splits.empty() ? &whole_anim_split : &anim.splits[i];
write(anim.root_motion_bone_idx);
write(int(duration / sampling_period));
int used_bone_count = 0;
float begin = sampling_period * split->from_frame;
float end = sampling_period * split->to_frame;
for (const ofbx::Object* bone : bones)
{
if (&bone->getScene() != &scene) continue;
float duration = (end > begin ? end - begin : 1.0f) * time_scale;
int frame_count = split->to_frame - split->from_frame;
const ofbx::AnimationLayer* layer = stack->getLayer(0);
const ofbx::AnimationCurveNode* translation_curve_node = layer->getCurveNode(*bone, "Lcl Translation");
const ofbx::AnimationCurveNode* rotation_curve_node = layer->getCurveNode(*bone, "Lcl Rotation");
if (translation_curve_node || rotation_curve_node) ++used_bone_count;
}
write(used_bone_count);
Array<TranslationKey> positions(allocator);
Array<RotationKey> rotations(allocator);
for (const ofbx::Object* bone : bones)
{
if (&bone->getScene() != &scene) continue;
const ofbx::Object* root_bone = anim.root_motion_bone_idx >= 0 ? bones[anim.root_motion_bone_idx] : nullptr;
const ofbx::AnimationLayer* layer = stack->getLayer(0);
const ofbx::AnimationCurveNode* translation_node = layer->getCurveNode(*bone, "Lcl Translation");
const ofbx::AnimationCurveNode* rotation_node = layer->getCurveNode(*bone, "Lcl Rotation");
if (!translation_node && !rotation_node) continue;
u32 name_hash = crc32(bone->name);
write(name_hash);
int frames = int((duration / sampling_period) + 0.5f);
int depth = getDepth(bone);
float parent_scale = bone->getParent() ? (float)getScaleX(bone->getParent()->getGlobalTransform()) : 1;
compressPositions(positions, frames, sampling_period, translation_node, *bone, position_error / depth, parent_scale);
write(positions.size());
for (TranslationKey& key : positions) write(key.frame);
for (TranslationKey& key : positions)
StaticString<MAX_PATH_LENGTH> tmp(output_dir, anim.output_filename, split->name, ".ani");
IAllocator& allocator = app.getWorldEditor().getAllocator();
if (!out_file.open(tmp, FS::Mode::CREATE_AND_WRITE, allocator))
{
if (bone == root_bone)
{
write(fixRootOrientation(key.pos * mesh_scale));
}
else
{
write(fixOrientation(key.pos * mesh_scale));
}
g_log_error.log("FBX") << "Failed to create " << tmp;
continue;
}
Animation::Header header;
header.magic = Animation::HEADER_MAGIC;
header.version = 3;
header.fps = (u32)(scene_frame_rate + 0.5f);
write(header);
write(anim.root_motion_bone_idx);
write(frame_count);
int used_bone_count = 0;
for (const ofbx::Object* bone : bones)
{
if (&bone->getScene() != &scene) continue;
const ofbx::AnimationLayer* layer = stack->getLayer(0);
const ofbx::AnimationCurveNode* translation_curve_node = layer->getCurveNode(*bone, "Lcl Translation");
const ofbx::AnimationCurveNode* rotation_curve_node = layer->getCurveNode(*bone, "Lcl Rotation");
if (translation_curve_node || rotation_curve_node) ++used_bone_count;
}
compressRotations(rotations, frames, sampling_period, rotation_node, *bone, rotation_error / depth);
write(rotations.size());
for (RotationKey& key : rotations) write(key.frame);
for (RotationKey& key : rotations)
write(used_bone_count);
Array<TranslationKey> positions(allocator);
Array<RotationKey> rotations(allocator);
for (const ofbx::Object* bone : bones)
{
if (bone == root_bone)
if (&bone->getScene() != &scene) continue;
const ofbx::Object* root_bone = anim.root_motion_bone_idx >= 0 ? bones[anim.root_motion_bone_idx] : nullptr;
const ofbx::AnimationLayer* layer = stack->getLayer(0);
const ofbx::AnimationCurveNode* translation_node = layer->getCurveNode(*bone, "Lcl Translation");
const ofbx::AnimationCurveNode* rotation_node = layer->getCurveNode(*bone, "Lcl Rotation");
if (!translation_node && !rotation_node) continue;
u32 name_hash = crc32(bone->name);
write(name_hash);
int depth = getDepth(bone);
float parent_scale = bone->getParent() ? (float)getScaleX(bone->getParent()->getGlobalTransform()) : 1;
compressPositions(positions, split->from_frame, split->to_frame, sampling_period, translation_node, *bone, position_error / depth, parent_scale);
write(positions.size());
for (TranslationKey& key : positions) write(key.frame);
for (TranslationKey& key : positions)
{
write(fixRootOrientation(key.rot));
if (bone == root_bone)
{
write(fixRootOrientation(key.pos * mesh_scale));
}
else
{
write(fixOrientation(key.pos * mesh_scale));
}
}
else
compressRotations(rotations, split->from_frame, split->to_frame, sampling_period, rotation_node, *bone, rotation_error / depth);
write(rotations.size());
for (RotationKey& key : rotations) write(key.frame);
for (RotationKey& key : rotations)
{
write(fixOrientation(key.rot));
if (bone == root_bone)
{
write(fixRootOrientation(key.rot));
}
else
{
write(fixOrientation(key.rot));
}
}
}
out_file.close();
}
out_file.close();
}
}
@ -2784,7 +2806,7 @@ void ImportAssetDialog::onAnimationsGUI()
ImGui::DragFloat("Max rotation error", &m_fbx_importer->rotation_error, 0, FLT_MAX);
ImGui::Indent();
ImGui::Columns(3);
ImGui::Columns(4);
ImGui::Text("Name");
ImGui::NextColumn();
@ -2792,12 +2814,14 @@ void ImportAssetDialog::onAnimationsGUI()
ImGui::NextColumn();
ImGui::Text("Root motion bone");
ImGui::NextColumn();
ImGui::Text("Splits");
ImGui::NextColumn();
ImGui::Separator();
ImGui::PushID("anims");
for (int i = 0; i < m_fbx_importer->animations.size(); ++i)
{
auto& animation = m_fbx_importer->animations[i];
FBXImporter::ImportAnimation& animation = m_fbx_importer->animations[i];
ImGui::PushID(i);
ImGui::InputText("###name", animation.output_filename.data, lengthOf(animation.output_filename.data));
ImGui::NextColumn();
@ -2810,6 +2834,24 @@ void ImportAssetDialog::onAnimationsGUI()
};
ImGui::Combo("##rb", &animation.root_motion_bone_idx, getter, this, m_fbx_importer->bones.size());
ImGui::NextColumn();
if (ImGui::Button("Add split")) animation.splits.emplace();
for (int i = 0; i < animation.splits.size(); ++i)
{
auto& split = animation.splits[i];
if (ImGui::TreeNodeEx(StaticString<64>("", i)))
{
ImGui::InputText("Name", split.name.data, sizeof(split.name.data));
ImGui::InputInt("From", &split.from_frame);
ImGui::InputInt("To", &split.to_frame);
if (ImGui::Button("Remove"))
{
animation.splits.erase(i);
--i;
}
ImGui::TreePop();
}
}
ImGui::NextColumn();
ImGui::PopID();
}

View file

@ -304,8 +304,8 @@ bool DataView::operator==(const char* rhs) const
struct Property;
template <typename T> bool parseArrayRaw(const Property& property, T* out, int max_size);
template <typename T> bool parseBinaryArray(const Property& property, std::vector<T>* out);
template <typename T> static bool parseArrayRaw(const Property& property, T* out, int max_size);
template <typename T> static bool parseBinaryArray(const Property& property, std::vector<T>* out);
struct Property : IElementProperty
@ -1173,8 +1173,8 @@ struct ClusterImpl : Cluster
int getIndicesCount() const override { return (int)indices.size(); }
const double* getWeights() const override { return &weights[0]; }
int getWeightsCount() const override { return (int)weights.size(); }
Matrix getTransformMatrix() const { return transform_matrix; }
Matrix getTransformLinkMatrix() const { return transform_link_matrix; }
Matrix getTransformMatrix() const override { return transform_matrix; }
Matrix getTransformLinkMatrix() const override { return transform_link_matrix; }
Object* getLink() const override { return link; }
@ -1265,7 +1265,7 @@ struct AnimationStackImpl : AnimationStack
}
const AnimationLayer* getLayer(int index) const
const AnimationLayer* getLayer(int index) const override
{
assert(index == 0);
return resolveObjectLink<AnimationLayer>(index);
@ -1372,7 +1372,7 @@ struct Scene : IScene
};
int getAnimationStackCount() const { return (int)m_animation_stacks.size(); }
int getAnimationStackCount() const override { return (int)m_animation_stacks.size(); }
int getMeshCount() const override { return (int)m_meshes.size(); }