Initial commit

This commit is contained in:
Martin Felis
2021-11-11 21:22:24 +01:00
commit b78045ffe7
812 changed files with 421882 additions and 0 deletions
@@ -0,0 +1,37 @@
add_library(ozz_animation_offline STATIC
decimate.h
${PROJECT_SOURCE_DIR}/include/ozz/animation/offline/raw_animation.h
raw_animation.cc
raw_animation_archive.cc
${PROJECT_SOURCE_DIR}/include/ozz/animation/offline/raw_animation_utils.h
raw_animation_utils.cc
${PROJECT_SOURCE_DIR}/include/ozz/animation/offline/animation_builder.h
animation_builder.cc
${PROJECT_SOURCE_DIR}/include/ozz/animation/offline/animation_optimizer.h
animation_optimizer.cc
${PROJECT_SOURCE_DIR}/include/ozz/animation/offline/additive_animation_builder.h
additive_animation_builder.cc
${PROJECT_SOURCE_DIR}/include/ozz/animation/offline/raw_skeleton.h
raw_skeleton.cc
raw_skeleton_archive.cc
${PROJECT_SOURCE_DIR}/include/ozz/animation/offline/skeleton_builder.h
skeleton_builder.cc
${PROJECT_SOURCE_DIR}/include/ozz/animation/offline/raw_track.h
raw_track.cc
${PROJECT_SOURCE_DIR}/include/ozz/animation/offline/track_builder.h
track_builder.cc
${PROJECT_SOURCE_DIR}/include/ozz/animation/offline/track_optimizer.h
track_optimizer.cc)
target_link_libraries(ozz_animation_offline
ozz_animation)
set_target_properties(ozz_animation_offline PROPERTIES FOLDER "ozz")
install(TARGETS ozz_animation_offline DESTINATION lib)
fuse_target("ozz_animation_offline")
add_subdirectory(fbx)
add_subdirectory(gltf)
add_subdirectory(tools)
@@ -0,0 +1,164 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "ozz/animation/offline/additive_animation_builder.h"
#include <cassert>
#include <cstddef>
#include "ozz/animation/offline/raw_animation.h"
#include "ozz/base/maths/transform.h"
namespace ozz {
namespace animation {
namespace offline {
namespace {
template <typename _RawTrack, typename _RefType, typename _MakeDelta>
void MakeDelta(const _RawTrack& _src, const _RefType& reference,
const _MakeDelta& _make_delta, _RawTrack* _dest) {
_dest->reserve(_src.size());
// Early out if no key.
if (_src.empty()) {
return;
}
// Copy animation keys.
for (size_t i = 0; i < _src.size(); ++i) {
const typename _RawTrack::value_type delta = {
_src[i].time, _make_delta(reference, _src[i].value)};
_dest->push_back(delta);
}
}
math::Float3 MakeDeltaTranslation(const math::Float3& _reference,
const math::Float3& _value) {
return _value - _reference;
}
math::Quaternion MakeDeltaRotation(const math::Quaternion& _reference,
const math::Quaternion& _value) {
return _value * Conjugate(_reference);
}
math::Float3 MakeDeltaScale(const math::Float3& _reference,
const math::Float3& _value) {
return _value / _reference;
}
} // namespace
// Setup default values (favoring quality).
AdditiveAnimationBuilder::AdditiveAnimationBuilder() {}
bool AdditiveAnimationBuilder::operator()(const RawAnimation& _input,
RawAnimation* _output) const {
if (!_output) {
return false;
}
// Reset output animation to default.
*_output = RawAnimation();
// Validate animation.
if (!_input.Validate()) {
return false;
}
// Rebuilds output animation.
_output->duration = _input.duration;
_output->tracks.resize(_input.tracks.size());
for (size_t i = 0; i < _input.tracks.size(); ++i) {
const RawAnimation::JointTrack& track_in = _input.tracks[i];
RawAnimation::JointTrack& track_out = _output->tracks[i];
const RawAnimation::JointTrack::Translations& translations =
track_in.translations;
const math::Float3 ref_translation =
translations.size() > 0 ? translations[0].value : math::Float3::zero();
const RawAnimation::JointTrack::Rotations& rotations = track_in.rotations;
const math::Quaternion ref_rotation = rotations.size() > 0
? rotations[0].value
: math::Quaternion::identity();
const RawAnimation::JointTrack::Scales& scales = track_in.scales;
const math::Float3 ref_scale =
scales.size() > 0 ? scales[0].value : math::Float3::one();
MakeDelta(translations, ref_translation, MakeDeltaTranslation,
&track_out.translations);
MakeDelta(rotations, ref_rotation, MakeDeltaRotation, &track_out.rotations);
MakeDelta(scales, ref_scale, MakeDeltaScale, &track_out.scales);
}
// Output animation is always valid though.
return _output->Validate();
}
bool AdditiveAnimationBuilder::operator()(
const RawAnimation& _input,
const span<const math::Transform>& _reference_pose,
RawAnimation* _output) const {
if (!_output) {
return false;
}
// Reset output animation to default.
*_output = RawAnimation();
// Validate animation.
if (!_input.Validate()) {
return false;
}
// The reference pose must have at least the same number of
// tracks as the raw animation.
if (_input.num_tracks() > static_cast<int>(_reference_pose.size())) {
return false;
}
// Rebuilds output animation.
_output->duration = _input.duration;
_output->tracks.resize(_input.tracks.size());
for (size_t i = 0; i < _input.tracks.size(); ++i) {
MakeDelta(_input.tracks[i].translations, _reference_pose[i].translation,
MakeDeltaTranslation, &_output->tracks[i].translations);
MakeDelta(_input.tracks[i].rotations, _reference_pose[i].rotation,
MakeDeltaRotation, &_output->tracks[i].rotations);
MakeDelta(_input.tracks[i].scales, _reference_pose[i].scale, MakeDeltaScale,
&_output->tracks[i].scales);
}
// Output animation is always valid though.
return _output->Validate();
}
} // namespace offline
} // namespace animation
} // namespace ozz
@@ -0,0 +1,328 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "ozz/animation/offline/animation_builder.h"
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstring>
#include <limits>
#include "ozz/animation/offline/raw_animation.h"
#include "ozz/animation/runtime/animation.h"
#include "ozz/base/containers/vector.h"
#include "ozz/base/maths/simd_math.h"
#include "ozz/base/memory/allocator.h"
// Internal include file
#define OZZ_INCLUDE_PRIVATE_HEADER // Allows to include private headers.
#include "animation/runtime/animation_keyframe.h"
namespace ozz {
namespace animation {
namespace offline {
namespace {
struct SortingTranslationKey {
uint16_t track;
float prev_key_time;
RawAnimation::TranslationKey key;
};
struct SortingRotationKey {
uint16_t track;
float prev_key_time;
RawAnimation::RotationKey key;
};
struct SortingScaleKey {
uint16_t track;
float prev_key_time;
RawAnimation::ScaleKey key;
};
// Keyframe sorting. Stores first by time and then track number.
template <typename _Key>
bool SortingKeyLess(const _Key& _left, const _Key& _right) {
const float time_diff = _left.prev_key_time - _right.prev_key_time;
return time_diff < 0.f || (time_diff == 0.f && _left.track < _right.track);
}
template <typename _SrcKey, typename _DestTrack>
void PushBackIdentityKey(uint16_t _track, float _time, _DestTrack* _dest) {
typedef typename _DestTrack::value_type DestKey;
float prev_time = -1.f;
if (!_dest->empty() && _dest->back().track == _track) {
prev_time = _dest->back().key.time;
}
const DestKey key = {_track, prev_time, {_time, _SrcKey::identity()}};
_dest->push_back(key);
}
// Copies a track from a RawAnimation to an Animation.
// Also fixes up the front (t = 0) and back keys (t = duration).
template <typename _SrcTrack, typename _DestTrack>
void CopyRaw(const _SrcTrack& _src, uint16_t _track, float _duration,
_DestTrack* _dest) {
typedef typename _SrcTrack::value_type SrcKey;
typedef typename _DestTrack::value_type DestKey;
if (_src.size() == 0) { // Adds 2 new keys.
PushBackIdentityKey<SrcKey, _DestTrack>(_track, 0.f, _dest);
PushBackIdentityKey<SrcKey, _DestTrack>(_track, _duration, _dest);
} else if (_src.size() == 1) { // Adds 1 new key.
const SrcKey& raw_key = _src.front();
assert(raw_key.time >= 0 && raw_key.time <= _duration);
const DestKey first = {_track, -1.f, {0.f, raw_key.value}};
_dest->push_back(first);
const DestKey last = {_track, 0.f, {_duration, raw_key.value}};
_dest->push_back(last);
} else { // Copies all keys, and fixes up first and last keys.
float prev_time = -1.f;
if (_src.front().time != 0.f) { // Needs a key at t = 0.f.
const DestKey first = {_track, prev_time, {0.f, _src.front().value}};
_dest->push_back(first);
prev_time = 0.f;
}
for (size_t k = 0; k < _src.size(); ++k) { // Copies all keys.
const SrcKey& raw_key = _src[k];
assert(raw_key.time >= 0 && raw_key.time <= _duration);
const DestKey key = {_track, prev_time, {raw_key.time, raw_key.value}};
_dest->push_back(key);
prev_time = raw_key.time;
}
if (_src.back().time - _duration != 0.f) { // Needs a key at t = _duration.
const DestKey last = {_track, prev_time, {_duration, _src.back().value}};
_dest->push_back(last);
}
}
assert(_dest->front().key.time == 0.f &&
_dest->back().key.time - _duration == 0.f);
}
template <typename _SortingKey>
void CopyToAnimation(ozz::vector<_SortingKey>* _src,
ozz::span<Float3Key>* _dest, float _inv_duration) {
const size_t src_count = _src->size();
if (!src_count) {
return;
}
// Sort animation keys to favor cache coherency.
std::sort(&_src->front(), (&_src->back()) + 1, &SortingKeyLess<_SortingKey>);
// Fills output.
const _SortingKey* src = &_src->front();
for (size_t i = 0; i < src_count; ++i) {
Float3Key& key = (*_dest)[i];
key.ratio = src[i].key.time * _inv_duration;
key.track = src[i].track;
key.value[0] = ozz::math::FloatToHalf(src[i].key.value.x);
key.value[1] = ozz::math::FloatToHalf(src[i].key.value.y);
key.value[2] = ozz::math::FloatToHalf(src[i].key.value.z);
}
}
// Compares float absolute values.
bool LessAbs(float _left, float _right) {
return std::abs(_left) < std::abs(_right);
}
// Compresses quaternion to ozz::animation::RotationKey format.
// The 3 smallest components of the quaternion are quantized to 16 bits
// integers, while the largest is recomputed thanks to quaternion normalization
// property (x^2+y^2+z^2+w^2 = 1). Because the 3 components are the 3 smallest,
// their value cannot be greater than sqrt(2)/2. Thus quantization quality is
// improved by pre-multiplying each componenent by sqrt(2).
void CompressQuat(const ozz::math::Quaternion& _src,
ozz::animation::QuaternionKey* _dest) {
// Finds the largest quaternion component.
const float quat[4] = {_src.x, _src.y, _src.z, _src.w};
const size_t largest = std::max_element(quat, quat + 4, LessAbs) - quat;
assert(largest <= 3);
_dest->largest = largest & 0x3;
// Stores the sign of the largest component.
_dest->sign = quat[largest] < 0.f;
// Quantize the 3 smallest components on 16 bits signed integers.
const float kFloat2Int = 32767.f * math::kSqrt2;
const int kMapping[4][3] = {{1, 2, 3}, {0, 2, 3}, {0, 1, 3}, {0, 1, 2}};
const int* map = kMapping[largest];
const int a = static_cast<int>(floor(quat[map[0]] * kFloat2Int + .5f));
const int b = static_cast<int>(floor(quat[map[1]] * kFloat2Int + .5f));
const int c = static_cast<int>(floor(quat[map[2]] * kFloat2Int + .5f));
_dest->value[0] = math::Clamp(-32767, a, 32767) & 0xffff;
_dest->value[1] = math::Clamp(-32767, b, 32767) & 0xffff;
_dest->value[2] = math::Clamp(-32767, c, 32767) & 0xffff;
}
// Specialize for rotations in order to normalize quaternions.
// Consecutive opposite quaternions are also fixed up in order to avoid checking
// for the smallest path during the NLerp runtime algorithm.
void CopyToAnimation(ozz::vector<SortingRotationKey>* _src,
ozz::span<QuaternionKey>* _dest, float _inv_duration) {
const size_t src_count = _src->size();
if (!src_count) {
return;
}
// Normalize quaternions.
// Also fixes-up successive opposite quaternions that would fail to take the
// shortest path during the normalized-lerp.
// Note that keys are still sorted per-track at that point, which allows this
// algorithm to process all consecutive keys.
size_t track = std::numeric_limits<size_t>::max();
const math::Quaternion identity = math::Quaternion::identity();
SortingRotationKey* src = &_src->front();
for (size_t i = 0; i < src_count; ++i) {
math::Quaternion normalized = NormalizeSafe(src[i].key.value, identity);
if (track != src[i].track) { // First key of the track.
if (normalized.w < 0.f) { // .w eq to a dot with identity quaternion.
normalized = -normalized; // Q an -Q are the same rotation.
}
} else { // Still on the same track: so fixes-up quaternion.
const math::Float4 prev(src[i - 1].key.value.x, src[i - 1].key.value.y,
src[i - 1].key.value.z, src[i - 1].key.value.w);
const math::Float4 curr(normalized.x, normalized.y, normalized.z,
normalized.w);
if (Dot(prev, curr) < 0.f) {
normalized = -normalized; // Q an -Q are the same rotation.
}
}
// Stores fixed-up quaternion.
src[i].key.value = normalized;
track = src[i].track;
}
// Sort.
std::sort(array_begin(*_src), array_end(*_src),
&SortingKeyLess<SortingRotationKey>);
// Fills rotation keys output.
for (size_t i = 0; i < src_count; ++i) {
const SortingRotationKey& skey = src[i];
QuaternionKey& dkey = (*_dest)[i];
dkey.ratio = skey.key.time * _inv_duration;
dkey.track = skey.track;
// Compress quaternion to destination container.
CompressQuat(skey.key.value, &dkey);
}
}
} // namespace
// Ensures _input's validity and allocates _animation.
// An animation needs to have at least two key frames per joint, the first at
// t = 0 and the last at t = duration. If at least one of those keys are not
// in the RawAnimation then the builder creates it.
unique_ptr<Animation> AnimationBuilder::operator()(
const RawAnimation& _input) const {
// Tests _raw_animation validity.
if (!_input.Validate()) {
return nullptr;
}
// Everything is fine, allocates and fills the animation.
// Nothing can fail now.
unique_ptr<Animation> animation = make_unique<Animation>();
// Sets duration.
const float duration = _input.duration;
const float inv_duration = 1.f / _input.duration;
animation->duration_ = duration;
// A _duration == 0 would create some division by 0 during sampling.
// Also we need at least to keys with different times, which cannot be done
// if duration is 0.
assert(duration > 0.f); // This case is handled by Validate().
// Sets tracks count. Can be safely casted to uint16_t as number of tracks as
// already been validated.
const uint16_t num_tracks = static_cast<uint16_t>(_input.num_tracks());
animation->num_tracks_ = num_tracks;
const uint16_t num_soa_tracks = Align(num_tracks, 4);
// Declares and preallocates tracks to sort.
size_t translations = 0, rotations = 0, scales = 0;
for (int i = 0; i < num_tracks; ++i) {
const RawAnimation::JointTrack& raw_track = _input.tracks[i];
translations += raw_track.translations.size() + 2; // +2 because worst case
rotations += raw_track.rotations.size() + 2; // needs to add the
scales += raw_track.scales.size() + 2; // first and last keys.
}
ozz::vector<SortingTranslationKey> sorting_translations;
sorting_translations.reserve(translations);
ozz::vector<SortingRotationKey> sorting_rotations;
sorting_rotations.reserve(rotations);
ozz::vector<SortingScaleKey> sorting_scales;
sorting_scales.reserve(scales);
// Filters RawAnimation keys and copies them to the output sorting structure.
uint16_t i = 0;
for (; i < num_tracks; ++i) {
const RawAnimation::JointTrack& raw_track = _input.tracks[i];
CopyRaw(raw_track.translations, i, duration, &sorting_translations);
CopyRaw(raw_track.rotations, i, duration, &sorting_rotations);
CopyRaw(raw_track.scales, i, duration, &sorting_scales);
}
// Add enough identity keys to match soa requirements.
for (; i < num_soa_tracks; ++i) {
typedef RawAnimation::TranslationKey SrcTKey;
PushBackIdentityKey<SrcTKey>(i, 0.f, &sorting_translations);
PushBackIdentityKey<SrcTKey>(i, duration, &sorting_translations);
typedef RawAnimation::RotationKey SrcRKey;
PushBackIdentityKey<SrcRKey>(i, 0.f, &sorting_rotations);
PushBackIdentityKey<SrcRKey>(i, duration, &sorting_rotations);
typedef RawAnimation::ScaleKey SrcSKey;
PushBackIdentityKey<SrcSKey>(i, 0.f, &sorting_scales);
PushBackIdentityKey<SrcSKey>(i, duration, &sorting_scales);
}
// Allocate animation members.
animation->Allocate(_input.name.length(), sorting_translations.size(),
sorting_rotations.size(), sorting_scales.size());
// Copy sorted keys to final animation.
CopyToAnimation(&sorting_translations, &animation->translations_,
inv_duration);
CopyToAnimation(&sorting_rotations, &animation->rotations_, inv_duration);
CopyToAnimation(&sorting_scales, &animation->scales_, inv_duration);
// Copy animation's name.
if (animation->name_) {
strcpy(animation->name_, _input.name.c_str());
}
return animation; // Success.
}
} // namespace offline
} // namespace animation
} // namespace ozz
@@ -0,0 +1,301 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "ozz/animation/offline/animation_optimizer.h"
#include <cassert>
#include <cstddef>
#include <functional>
// Internal include file
#define OZZ_INCLUDE_PRIVATE_HEADER // Allows to include private headers.
#include "animation/offline/decimate.h"
#include "ozz/animation/offline/raw_animation.h"
#include "ozz/animation/offline/raw_animation_utils.h"
#include "ozz/animation/runtime/skeleton.h"
#include "ozz/animation/runtime/skeleton_utils.h"
#include "ozz/base/containers/stack.h"
#include "ozz/base/containers/vector.h"
#include "ozz/base/maths/math_constant.h"
#include "ozz/base/maths/math_ex.h"
namespace ozz {
namespace animation {
namespace offline {
// Setup default values (favoring quality).
AnimationOptimizer::AnimationOptimizer() {}
namespace {
AnimationOptimizer::Setting GetJointSetting(
const AnimationOptimizer& _optimizer, int _joint) {
AnimationOptimizer::Setting setting = _optimizer.setting;
AnimationOptimizer::JointsSetting::const_iterator it =
_optimizer.joints_setting_override.find(_joint);
if (it != _optimizer.joints_setting_override.end()) {
setting = it->second;
}
return setting;
}
struct HierarchyBuilder {
HierarchyBuilder(const RawAnimation* _animation, const Skeleton* _skeleton,
const AnimationOptimizer* _optimizer)
: specs(_animation->tracks.size()),
animation(_animation),
optimizer(_optimizer) {
assert(_animation->num_tracks() == _skeleton->num_joints());
// Computes hierarchical scale, iterating skeleton forward (root to
// leaf).
IterateJointsDF(*_skeleton,
std::bind(&HierarchyBuilder::ComputeScaleForward, this,
std::placeholders::_1, std::placeholders::_2));
// Computes hierarchical length, iterating skeleton backward (leaf to root).
IterateJointsDFReverse(
*_skeleton, std::bind(&HierarchyBuilder::ComputeLengthBackward, this,
std::placeholders::_1, std::placeholders::_2));
}
struct Spec {
float length; // Length of a joint hierarchy (max of all child).
float scale; // Scale of a joint hierarchy (accumulated from all parents).
float tolerance; // Tolerance of a joint hierarchy (min of all child).
};
// Defines the length of a joint hierarchy (of all child).
ozz::vector<Spec> specs;
private:
// Extracts maximum translations and scales for each track/joint.
void ComputeScaleForward(int _joint, int _parent) {
Spec& joint_spec = specs[_joint];
// Compute joint maximum animated scale.
float max_scale = 0.f;
const RawAnimation::JointTrack& track = animation->tracks[_joint];
if (track.scales.size() != 0) {
for (size_t j = 0; j < track.scales.size(); ++j) {
const math::Float3& scale = track.scales[j].value;
const float max_element = math::Max(
math::Max(std::abs(scale.x), std::abs(scale.y)), std::abs(scale.z));
max_scale = math::Max(max_scale, max_element);
}
} else {
max_scale = 1.f; // Default scale.
}
// Accumulate with parent scale.
joint_spec.scale = max_scale;
if (_parent != Skeleton::kNoParent) {
const Spec& parent_spec = specs[_parent];
joint_spec.scale *= parent_spec.scale;
}
// Computes self setting distance and tolerance.
// Distance is now scaled with accumulated parent scale.
const AnimationOptimizer::Setting setting =
GetJointSetting(*optimizer, _joint);
joint_spec.length = setting.distance * specs[_joint].scale;
joint_spec.tolerance = setting.tolerance;
}
// Propagate child translations back to the root.
void ComputeLengthBackward(int _joint, int _parent) {
// Self translation doesn't matter if joint has no parent.
if (_parent == Skeleton::kNoParent) {
return;
}
// Compute joint maximum animated length.
float max_length_sq = 0.f;
const RawAnimation::JointTrack& track = animation->tracks[_joint];
for (size_t j = 0; j < track.translations.size(); ++j) {
max_length_sq =
math::Max(max_length_sq, LengthSqr(track.translations[j].value));
}
const float max_length = std::sqrt(max_length_sq);
const Spec& joint_spec = specs[_joint];
Spec& parent_spec = specs[_parent];
// Set parent hierarchical spec to its most impacting child, aka max
// length and min tolerance.
parent_spec.length = math::Max(
parent_spec.length, joint_spec.length + max_length * parent_spec.scale);
parent_spec.tolerance =
math::Min(parent_spec.tolerance, joint_spec.tolerance);
}
// Disables copy and assignment.
HierarchyBuilder(const HierarchyBuilder&);
void operator=(const HierarchyBuilder&);
// Targeted animation.
const RawAnimation* animation;
// Usefull to access settings and compute hierarchy length.
const AnimationOptimizer* optimizer;
};
class PositionAdapter {
public:
PositionAdapter(float _scale) : scale_(_scale) {}
bool Decimable(const RawAnimation::TranslationKey&) const { return true; }
RawAnimation::TranslationKey Lerp(
const RawAnimation::TranslationKey& _left,
const RawAnimation::TranslationKey& _right,
const RawAnimation::TranslationKey& _ref) const {
const float alpha = (_ref.time - _left.time) / (_right.time - _left.time);
assert(alpha >= 0.f && alpha <= 1.f);
const RawAnimation::TranslationKey key = {
_ref.time, LerpTranslation(_left.value, _right.value, alpha)};
return key;
}
float Distance(const RawAnimation::TranslationKey& _a,
const RawAnimation::TranslationKey& _b) const {
return Length(_a.value - _b.value) * scale_;
}
private:
float scale_;
};
class RotationAdapter {
public:
RotationAdapter(float _radius) : radius_(_radius) {}
bool Decimable(const RawAnimation::RotationKey&) const { return true; }
RawAnimation::RotationKey Lerp(const RawAnimation::RotationKey& _left,
const RawAnimation::RotationKey& _right,
const RawAnimation::RotationKey& _ref) const {
const float alpha = (_ref.time - _left.time) / (_right.time - _left.time);
assert(alpha >= 0.f && alpha <= 1.f);
const RawAnimation::RotationKey key = {
_ref.time, LerpRotation(_left.value, _right.value, alpha)};
return key;
}
float Distance(const RawAnimation::RotationKey& _left,
const RawAnimation::RotationKey& _right) const {
// Compute the shortest unsigned angle between the 2 quaternions.
// cos_half_angle is w component of a-1 * b.
const float cos_half_angle = Dot(_left.value, _right.value);
const float sine_half_angle =
std::sqrt(1.f - math::Min(1.f, cos_half_angle * cos_half_angle));
// Deduces distance between 2 points on a circle with radius and a given
// angle. Using half angle helps as it allows to have a right-angle
// triangle.
const float distance = 2.f * sine_half_angle * radius_;
return distance;
}
private:
float radius_;
};
class ScaleAdapter {
public:
ScaleAdapter(float _length) : length_(_length) {}
bool Decimable(const RawAnimation::ScaleKey&) const { return true; }
RawAnimation::ScaleKey Lerp(const RawAnimation::ScaleKey& _left,
const RawAnimation::ScaleKey& _right,
const RawAnimation::ScaleKey& _ref) const {
const float alpha = (_ref.time - _left.time) / (_right.time - _left.time);
assert(alpha >= 0.f && alpha <= 1.f);
const RawAnimation::ScaleKey key = {
_ref.time, LerpScale(_left.value, _right.value, alpha)};
return key;
}
float Distance(const RawAnimation::ScaleKey& _left,
const RawAnimation::ScaleKey& _right) const {
return Length(_left.value - _right.value) * length_;
}
private:
float length_;
};
} // namespace
bool AnimationOptimizer::operator()(const RawAnimation& _input,
const Skeleton& _skeleton,
RawAnimation* _output) const {
if (!_output) {
return false;
}
// Reset output animation to default.
*_output = RawAnimation();
// Validate animation.
if (!_input.Validate()) {
return false;
}
const int num_tracks = _input.num_tracks();
// Validates the skeleton matches the animation.
if (num_tracks != _skeleton.num_joints()) {
return false;
}
// First computes bone lengths, that will be used when filtering.
const HierarchyBuilder hierarchy(&_input, &_skeleton, this);
// Rebuilds output animation.
_output->name = _input.name;
_output->duration = _input.duration;
_output->tracks.resize(num_tracks);
for (int i = 0; i < num_tracks; ++i) {
const RawAnimation::JointTrack& input = _input.tracks[i];
RawAnimation::JointTrack& output = _output->tracks[i];
// Gets joint specs back.
const float joint_length = hierarchy.specs[i].length;
const int parent = _skeleton.joint_parents()[i];
const float parent_scale =
(parent != Skeleton::kNoParent) ? hierarchy.specs[parent].scale : 1.f;
const float tolerance = hierarchy.specs[i].tolerance;
// Filters independently T, R and S tracks.
// This joint translation is affected by parent scale.
const PositionAdapter tadap(parent_scale);
Decimate(input.translations, tadap, tolerance, &output.translations);
// This joint rotation affects children translations/length.
const RotationAdapter radap(joint_length);
Decimate(input.rotations, radap, tolerance, &output.rotations);
// This joint scale affects children translations/length.
const ScaleAdapter sadap(joint_length);
Decimate(input.scales, sadap, tolerance, &output.scales);
}
// Output animation is always valid though.
return _output->Validate();
}
} // namespace offline
} // namespace animation
} // namespace ozz
+136
View File
@@ -0,0 +1,136 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#ifndef OZZ_ANIMATION_OFFLINE_DECIMATE_H_
#define OZZ_ANIMATION_OFFLINE_DECIMATE_H_
#ifndef OZZ_INCLUDE_PRIVATE_HEADER
#error "This header is private, it cannot be included from public headers."
#endif // OZZ_INCLUDE_PRIVATE_HEADER
#include "ozz/base/containers/stack.h"
#include "ozz/base/containers/vector.h"
#include <cassert>
namespace ozz {
namespace animation {
namespace offline {
// Decimation algorithm based on RamerDouglasPeucker.
// https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
// _Track must have std::vector interface.
// Adapter must have the following interface:
// struct Adapter {
// bool Decimable(const Key&) const;
// Key Lerp(const Key& _left, const Key& _right, const Key& _ref) const;
// float Distance(const Key& _a, const Key& _b) const;
// };
template <typename _Track, typename _Adapter>
void Decimate(const _Track& _src, const _Adapter& _adapter, float _tolerance,
_Track* _dest) {
// Early out if not enough data.
if (_src.size() < 2) {
*_dest = _src;
return;
}
// Stack of segments to process.
typedef std::pair<size_t, size_t> Segment;
ozz::stack<Segment> segments;
// Bit vector of all points to included.
ozz::vector<bool> included(_src.size(), false);
// Pushes segment made from first and last points.
segments.push(Segment(0, _src.size() - 1));
included[0] = true;
included[_src.size() - 1] = true;
// Empties segments stack.
while (!segments.empty()) {
// Pops next segment to process.
const Segment segment = segments.top();
segments.pop();
// Looks for the furthest point from the segment.
float max = -1.f;
size_t candidate = segment.first;
typename _Track::const_reference left = _src[segment.first];
typename _Track::const_reference right = _src[segment.second];
for (size_t i = segment.first + 1; i < segment.second; ++i) {
assert(!included[i] && "Included points should be processed once only.");
typename _Track::const_reference test = _src[i];
if (!_adapter.Decimable(test)) {
candidate = i;
break;
} else {
const float distance =
_adapter.Distance(_adapter.Lerp(left, right, test), test);
if (distance > _tolerance && distance > max) {
max = distance;
candidate = i;
}
}
}
// If found, include the point and pushes the 2 new segments (before and
// after the new point).
if (candidate != segment.first) {
included[candidate] = true;
if (candidate - segment.first > 1) {
segments.push(Segment(segment.first, candidate));
}
if (segment.second - candidate > 1) {
segments.push(Segment(candidate, segment.second));
}
}
}
// Copy all included points.
_dest->clear();
for (size_t i = 0; i < _src.size(); ++i) {
if (included[i]) {
_dest->push_back(_src[i]);
}
}
// Removes last key if constant.
if (_dest->size() > 1) {
typename _Track::const_iterator end = _dest->end();
typename _Track::const_reference last = *(--end);
typename _Track::const_reference penultimate = *(--end);
const float distance = _adapter.Distance(penultimate, last);
if (_adapter.Decimable(last) && distance <= _tolerance) {
_dest->pop_back();
}
}
}
} // namespace offline
} // namespace animation
} // namespace ozz
#endif // OZZ_ANIMATION_OFFLINE_DECIMATE_H_
@@ -0,0 +1,141 @@
if(NOT ozz_build_fbx)
return()
endif()
add_library(ozz_animation_fbx STATIC
${PROJECT_SOURCE_DIR}/include/ozz/animation/offline/fbx/fbx.h
fbx.cc
${PROJECT_SOURCE_DIR}/include/ozz/animation/offline/fbx/fbx_animation.h
fbx_animation.cc
${PROJECT_SOURCE_DIR}/include/ozz/animation/offline/fbx/fbx_skeleton.h
fbx_skeleton.cc)
target_include_directories(ozz_animation_fbx
PUBLIC $<BUILD_INTERFACE:${FBX_INCLUDE_DIRS}>)
target_link_libraries(ozz_animation_fbx
ozz_animation_offline
debug ${FBX_LIBRARIES_DEBUG}
optimized ${FBX_LIBRARIES})
target_compile_options(ozz_animation_fbx
PUBLIC $<$<BOOL:${W_NULL_DEREFERENCE}>:-Wno-null-dereference>
PUBLIC $<$<BOOL:${W_PRAGMA_PACK}>:-Wno-pragma-pack>)
set_target_properties(ozz_animation_fbx PROPERTIES FOLDER "ozz/tools")
install(TARGETS ozz_animation_fbx DESTINATION lib)
fuse_target("ozz_animation_fbx")
add_executable(fbx2ozz
fbx2ozz.h
fbx2ozz.cc
fbx2ozz_anim.cc
fbx2ozz_skel.cc)
target_link_libraries(fbx2ozz
ozz_animation_tools
ozz_animation_fbx)
set_target_properties(fbx2ozz
PROPERTIES FOLDER "ozz/tools")
install(TARGETS fbx2ozz DESTINATION bin/tools)
# Gerenate binary data to a specific folder (${ozz_media_directory}/bin)
set(build_configurations
# Library data
"/fbx/pab/skeleton.fbx\;{\"skeleton\":{\"filename\":\"pab_skeleton.ozz\",\"import\":{\"enable\":true}},\"animations\":[]}\;output:pab_skeleton.ozz"
"/fbx/pab/atlas.fbx\;{\"skeleton\":{\"filename\":\"pab_skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"pab_atlas_raw.ozz\",\"raw\":true,\"optimize\":false}]}\;output:pab_atlas_raw.ozz\;depend:pab_skeleton.ozz"
"/fbx/pab/locomotions.fbx\;{\"skeleton\":{\"filename\":\"pab_skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"pab_*.ozz\"}]}\;output:pab_walk.ozz\;output:pab_jog.ozz\;output:pab_run.ozz\;depend:pab_skeleton.ozz"
"/fbx/pab/crossarms.fbx\;{\"skeleton\":{\"filename\":\"pab_skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"pab_crossarms.ozz\"}]}\;output:pab_crossarms.ozz\;depend:pab_skeleton.ozz"
"/fbx/pab/crackhead.fbx\;{\"skeleton\":{\"filename\":\"pab_skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"pab_crackhead.ozz\"},{\"filename\":\"pab_crackhead_additive.ozz\",\"additive\":true}]}\;output:pab_crackhead_additive.ozz\;output:pab_crackhead.ozz\;depend:pab_skeleton.ozz"
# Robot user channels
"/fbx/robot.fbx\;\;config_file:${PROJECT_SOURCE_DIR}/samples/user_channel/config.json\;output:robot_skeleton.ozz\;output:robot_animation.ozz\;output:robot_track_grasp.ozz"
# Baked animation
"/fbx/baked.fbx\;\;config_file:${PROJECT_SOURCE_DIR}/samples/baked/config.json\;output:baked_skeleton.ozz\;output:baked_animation.ozz"
# Ruby
"/fbx/sketchfab/ruby.fbx\;{\"skeleton\":{\"filename\":\"ruby_skeleton.ozz\",\"import\":{\"enable\":true}},\"animations\":[{\"filename\":\"ruby_animation.ozz\"}]}\;output:ruby_skeleton.ozz\;output:ruby_animation.ozz"
# Versioning
"/fbx/pab/skeleton.fbx\;{\"skeleton\":{\"filename\":\"versioning/raw_skeleton_v1_le.ozz\",\"import\":{\"enable\":true,\"raw\":true}},\"animations\":[]}\;output:versioning/raw_skeleton_v1_le.ozz\;option:--endian=little"
"/fbx/pab/skeleton.fbx\;{\"skeleton\":{\"filename\":\"versioning/raw_skeleton_v1_be.ozz\",\"import\":{\"enable\":true,\"raw\":true}},\"animations\":[]}\;output:versioning/raw_skeleton_v1_be.ozz\;option:--endian=big"
"/fbx/pab/skeleton.fbx\;{\"skeleton\":{\"filename\":\"versioning/skeleton_v2_le.ozz\",\"import\":{\"enable\":true}},\"animations\":[]}\;output:versioning/skeleton_v2_le.ozz\;option:--endian=little"
"/fbx/pab/skeleton.fbx\;{\"skeleton\":{\"filename\":\"versioning/skeleton_v2_be.ozz\",\"import\":{\"enable\":true}},\"animations\":[]}\;output:versioning/skeleton_v2_be.ozz\;option:--endian=big"
"/fbx/pab/run.fbx\;{\"skeleton\":{\"filename\":\"pab_skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"versioning/raw_animation_v3_le.ozz\",\"raw\":true}]}\;output:versioning/raw_animation_v3_le.ozz\;option:--endian=little\;depend:pab_skeleton.ozz"
"/fbx/pab/run.fbx\;{\"skeleton\":{\"filename\":\"pab_skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"versioning/raw_animation_v3_be.ozz\",\"raw\":true}]}\;output:versioning/raw_animation_v3_be.ozz\;option:--endian=big\;depend:pab_skeleton.ozz"
"/fbx/pab/run.fbx\;{\"skeleton\":{\"filename\":\"pab_skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"versioning/animation_v6_le.ozz\"}]}\;output:versioning/animation_v6_le.ozz\;option:--endian=little\;depend:pab_skeleton.ozz"
"/fbx/pab/run.fbx\;{\"skeleton\":{\"filename\":\"pab_skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"versioning/animation_v6_be.ozz\"}]}\;output:versioning/animation_v6_be.ozz\;option:--endian=big\;depend:pab_skeleton.ozz"
# Collada
"/collada/astro_max.dae\;{\"skeleton\":{\"filename\":\"astro_max_skeleton.ozz\",\"import\":{\"enable\":true}},\"animations\":[{\"filename\":\"astro_max_animation.ozz\"}]}\;output:astro_max_animation.ozz\;output:astro_max_skeleton.ozz"
"/collada/astro_maya.dae\;{\"skeleton\":{\"filename\":\"astro_maya_skeleton.ozz\",\"import\":{\"enable\":true}},\"animations\":[{\"filename\":\"astro_maya_animation.ozz\"}]}\;output:astro_maya_animation.ozz\;output:astro_maya_skeleton.ozz"
"/collada/seymour.dae\;{\"skeleton\":{\"filename\":\"seymour_skeleton.ozz\",\"import\":{\"enable\":true}},\"animations\":[{\"filename\":\"seymour_animation.ozz\"}]}\;output:seymour_animation.ozz\;output:seymour_skeleton.ozz"
)
# Generate a command for each line of the configuration
foreach(line ${build_configurations})
# First 3 elements are mandatory
list(GET line 0 src)
list(GET line 1 config)
# Optional elements
list(LENGTH line line_length)
# Loops over all remaining arguments to find outputs.
set(outputs "")
set(options "")
set(depends "")
set(config_file "")
# Loops over all elements to find parameters.
foreach(element ${line})
if (${element} MATCHES "output:(.*)")
string(SUBSTRING ${element} 7 -1 output)
list(APPEND outputs "${ozz_media_directory}/bin/${output}")
endif()
if (${element} MATCHES "depend:(.*)")
string(SUBSTRING ${element} 7 -1 depend)
list(APPEND depends "${ozz_media_directory}/bin/${depend}")
endif()
if (${element} MATCHES "option:(.*)")
string(SUBSTRING ${element} 7 -1 option)
list(APPEND options ${option})
endif()
if (${element} MATCHES "config_file:(.*)")
string(SUBSTRING ${element} 12 -1 config_file)
endif()
endforeach()
list(APPEND all_outputs ${outputs})
# output is required
if (NOT outputs)
message("Missing output file(s) list.")
endif()
# Dump config file
if(NOT config_file)
string(MD5 hash "${line}")
set(config_file "${ozz_temp_directory}/config_${hash}.json")
file(WRITE "${config_file}" "${config}")
else()
list(APPEND depends "${config_file}")
endif()
# Create build command
add_custom_command(
DEPENDS "${ozz_media_directory}${src}"
${depends}
$<$<AND:$<BOOL:${ozz_build_data}>,$<BOOL:${ozz_build_fbx}>>:fbx2ozz>
OUTPUT ${outputs}
COMMAND fbx2ozz
"--file=${ozz_media_directory}${src}"
"--config_file=${config_file}"
${options}
WORKING_DIRECTORY "${ozz_media_directory}/bin/"
VERBATIM)
endforeach()
# Creates a target to build data
add_custom_target(BUILD_DATA ALL
DEPENDS ${all_outputs}
VERBATIM)
+325
View File
@@ -0,0 +1,325 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "ozz/animation/offline/fbx/fbx.h"
#include "ozz/base/log.h"
#include "ozz/base/maths/transform.h"
#include "ozz/base/memory/allocator.h"
namespace ozz {
namespace animation {
namespace offline {
namespace fbx {
FbxManagerInstance::FbxManagerInstance() : fbx_manager_(nullptr) {
// Instantiate Fbx manager, mostly a memory manager.
fbx_manager_ = FbxManager::Create();
// Logs SDK version.
ozz::log::Log() << "FBX importer version " << fbx_manager_->GetVersion()
<< "." << std::endl;
}
FbxManagerInstance::~FbxManagerInstance() {
// Destroy the manager and all associated objects.
fbx_manager_->Destroy();
fbx_manager_ = nullptr;
}
FbxDefaultIOSettings::FbxDefaultIOSettings(const FbxManagerInstance& _manager)
: io_settings_(nullptr) {
io_settings_ = FbxIOSettings::Create(_manager, IOSROOT);
io_settings_->SetBoolProp(IMP_FBX_MATERIAL, false);
io_settings_->SetBoolProp(IMP_FBX_TEXTURE, false);
io_settings_->SetBoolProp(IMP_FBX_MODEL, false);
io_settings_->SetBoolProp(IMP_FBX_SHAPE, false);
io_settings_->SetBoolProp(IMP_FBX_LINK, false);
io_settings_->SetBoolProp(IMP_FBX_GOBO, false);
}
FbxDefaultIOSettings::~FbxDefaultIOSettings() {
io_settings_->Destroy();
io_settings_ = nullptr;
}
FbxAnimationIOSettings::FbxAnimationIOSettings(
const FbxManagerInstance& _manager)
: FbxDefaultIOSettings(_manager) {}
FbxSkeletonIOSettings::FbxSkeletonIOSettings(const FbxManagerInstance& _manager)
: FbxDefaultIOSettings(_manager) {
settings()->SetBoolProp(IMP_FBX_ANIMATION, false);
}
FbxSceneLoader::FbxSceneLoader(const char* _filename, const char* _password,
const FbxManagerInstance& _manager,
const FbxDefaultIOSettings& _io_settings)
: scene_(nullptr), converter_(nullptr) {
// Create an importer.
FbxImporter* importer = FbxImporter::Create(_manager, "ozz file importer");
const bool initialized = importer->Initialize(_filename, -1, _io_settings);
ImportScene(importer, initialized, _password, _manager, _io_settings);
// Destroy the importer
importer->Destroy();
}
FbxSceneLoader::FbxSceneLoader(FbxStream* _stream, const char* _password,
const FbxManagerInstance& _manager,
const FbxDefaultIOSettings& _io_settings)
: scene_(nullptr), converter_(nullptr) {
// Create an importer.
FbxImporter* importer = FbxImporter::Create(_manager, "ozz stream importer");
const bool initialized =
importer->Initialize(_stream, nullptr, -1, _io_settings);
ImportScene(importer, initialized, _password, _manager, _io_settings);
// Destroy the importer
importer->Destroy();
}
void FbxSceneLoader::ImportScene(FbxImporter* _importer,
const bool _initialized, const char* _password,
const FbxManagerInstance& _manager,
const FbxDefaultIOSettings& _io_settings) {
// Get the version of the FBX file format.
int major, minor, revision;
_importer->GetFileVersion(major, minor, revision);
if (!_initialized) // Problem with the file to be imported.
{
const FbxString error = _importer->GetStatus().GetErrorString();
ozz::log::Err() << "FbxImporter initialization failed with error: "
<< error.Buffer() << std::endl;
if (_importer->GetStatus().GetCode() == FbxStatus::eInvalidFileVersion) {
ozz::log::Err() << "FBX file version is " << major << "." << minor << "."
<< revision << "." << std::endl;
}
} else {
if (_importer->IsFBX()) {
ozz::log::Log() << "FBX file version is " << major << "." << minor << "."
<< revision << "." << std::endl;
}
// Load the scene.
scene_ = FbxScene::Create(_manager, "ozz scene");
bool imported = _importer->Import(scene_);
if (!imported && // The import file may have a password
_importer->GetStatus().GetCode() == FbxStatus::ePasswordError) {
_io_settings.settings()->SetStringProp(IMP_FBX_PASSWORD, _password);
_io_settings.settings()->SetBoolProp(IMP_FBX_PASSWORD_ENABLE, true);
// Retries to import the scene.
imported = _importer->Import(scene_);
if (!imported &&
_importer->GetStatus().GetCode() == FbxStatus::ePasswordError) {
ozz::log::Err() << "Incorrect password." << std::endl;
// scene_ will be destroyed because imported is false.
}
}
// Setup axis and system converter.
if (imported) {
FbxGlobalSettings& settings = scene_->GetGlobalSettings();
converter_ = New<FbxSystemConverter>(settings.GetAxisSystem(),
settings.GetSystemUnit());
}
// Clear the scene if import failed.
if (!imported) {
scene_->Destroy();
scene_ = nullptr;
}
}
}
FbxSceneLoader::~FbxSceneLoader() {
if (scene_) {
scene_->Destroy();
scene_ = nullptr;
}
if (converter_) {
ozz::Delete(converter_);
converter_ = nullptr;
}
}
namespace {
ozz::math::Float4x4 BuildAxisSystemMatrix(const FbxAxisSystem& _system) {
int sign;
ozz::math::SimdFloat4 up = ozz::math::simd_float4::y_axis();
ozz::math::SimdFloat4 at = ozz::math::simd_float4::z_axis();
// The EUpVector specifies which axis has the up and down direction in the
// system (typically this is the Y or Z axis). The sign of the EUpVector is
// applied to represent the direction (1 is up and -1 is down relative to the
// observer).
const FbxAxisSystem::EUpVector eup = _system.GetUpVector(sign);
switch (eup) {
case FbxAxisSystem::eXAxis: {
up = math::simd_float4::Load(1.f * sign, 0.f, 0.f, 0.f);
// If the up axis is X, the remain two axes will be Y And Z, so the
// ParityEven is Y, and the ParityOdd is Z
if (_system.GetFrontVector(sign) == FbxAxisSystem::eParityEven) {
at = math::simd_float4::Load(0.f, 1.f * sign, 0.f, 0.f);
} else {
at = math::simd_float4::Load(0.f, 0.f, 1.f * sign, 0.f);
}
break;
}
case FbxAxisSystem::eYAxis: {
up = math::simd_float4::Load(0.f, 1.f * sign, 0.f, 0.f);
// If the up axis is Y, the remain two axes will X And Z, so the
// ParityEven is X, and the ParityOdd is Z
if (_system.GetFrontVector(sign) == FbxAxisSystem::eParityEven) {
at = math::simd_float4::Load(1.f * sign, 0.f, 0.f, 0.f);
} else {
at = math::simd_float4::Load(0.f, 0.f, 1.f * sign, 0.f);
}
break;
}
case FbxAxisSystem::eZAxis: {
up = math::simd_float4::Load(0.f, 0.f, 1.f * sign, 0.f);
// If the up axis is Z, the remain two axes will X And Y, so the
// ParityEven is X, and the ParityOdd is Y
if (_system.GetFrontVector(sign) == FbxAxisSystem::eParityEven) {
at = math::simd_float4::Load(1.f * sign, 0.f, 0.f, 0.f);
} else {
at = math::simd_float4::Load(0.f, 1.f * sign, 0.f, 0.f);
}
break;
}
default: {
assert(false && "Invalid FbxAxisSystem");
break;
}
}
// If the front axis and the up axis are determined, the third axis will be
// automatically determined as the left one. The ECoordSystem enum is a
// parameter to determine the direction of the third axis just as the
// EUpVector sign. It determines if the axis system is right-handed or
// left-handed just as the enum values.
ozz::math::SimdFloat4 right;
if (_system.GetCoorSystem() == FbxAxisSystem::eRightHanded) {
right = math::Cross3(up, at);
} else {
right = math::Cross3(at, up);
}
const ozz::math::Float4x4 matrix = {
{right, up, at, ozz::math::simd_float4::w_axis()}};
return matrix;
}
} // namespace
FbxSystemConverter::FbxSystemConverter(const FbxAxisSystem& _from_axis,
const FbxSystemUnit& _from_unit) {
// Build axis system conversion matrix.
const math::Float4x4 from_matrix = BuildAxisSystemMatrix(_from_axis);
// Finds unit conversion ratio to meters, where GetScaleFactor() is in cm.
const float to_meters =
static_cast<float>(_from_unit.GetScaleFactor()) * .01f;
// Builds conversion matrices.
convert_ = Invert(from_matrix) *
math::Float4x4::Scaling(math::simd_float4::Load1(to_meters));
inverse_convert_ = Invert(convert_);
inverse_transpose_convert_ = Transpose(inverse_convert_);
}
math::Float4x4 FbxSystemConverter::ConvertMatrix(const FbxAMatrix& _m) const {
const ozz::math::Float4x4 m = {{
ozz::math::simd_float4::Load(
static_cast<float>(_m[0][0]), static_cast<float>(_m[0][1]),
static_cast<float>(_m[0][2]), static_cast<float>(_m[0][3])),
ozz::math::simd_float4::Load(
static_cast<float>(_m[1][0]), static_cast<float>(_m[1][1]),
static_cast<float>(_m[1][2]), static_cast<float>(_m[1][3])),
ozz::math::simd_float4::Load(
static_cast<float>(_m[2][0]), static_cast<float>(_m[2][1]),
static_cast<float>(_m[2][2]), static_cast<float>(_m[2][3])),
ozz::math::simd_float4::Load(
static_cast<float>(_m[3][0]), static_cast<float>(_m[3][1]),
static_cast<float>(_m[3][2]), static_cast<float>(_m[3][3])),
}};
return convert_ * m * inverse_convert_;
}
math::Float3 FbxSystemConverter::ConvertPoint(const FbxVector4& _p) const {
const math::SimdFloat4 p_in = math::simd_float4::Load(
static_cast<float>(_p[0]), static_cast<float>(_p[1]),
static_cast<float>(_p[2]), 1.f);
const math::SimdFloat4 p_out = convert_ * p_in;
math::Float3 ret;
math::Store3PtrU(p_out, &ret.x);
return ret;
}
math::Float3 FbxSystemConverter::ConvertVector(const FbxVector4& _p) const {
const math::SimdFloat4 p_in = math::simd_float4::Load(
static_cast<float>(_p[0]), static_cast<float>(_p[1]),
static_cast<float>(_p[2]), 0.f);
const math::SimdFloat4 p_out = inverse_transpose_convert_ * p_in;
math::Float3 ret;
math::Store3PtrU(p_out, &ret.x);
return ret;
}
bool FbxSystemConverter::ConvertTransform(const FbxAMatrix& _m,
math::Transform* _transform) const {
assert(_transform);
const math::Float4x4 matrix = ConvertMatrix(_m);
math::SimdFloat4 translation, rotation, scale;
if (ToAffine(matrix, &translation, &rotation, &scale)) {
ozz::math::Transform transform;
math::Store3PtrU(translation, &_transform->translation.x);
math::StorePtrU(math::Normalize4(rotation), &_transform->rotation.x);
math::Store3PtrU(scale, &_transform->scale.x);
return true;
}
// Failed to decompose matrix, reset transform to identity.
*_transform = ozz::math::Transform::identity();
return false;
}
} // namespace fbx
} // namespace offline
} // namespace animation
} // namespace ozz
@@ -0,0 +1,55 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "animation/offline/fbx/fbx2ozz.h"
#include "ozz/base/log.h"
int main(int _argc, const char** _argv) {
Fbx2OzzImporter converter;
return converter(_argc, _argv);
}
Fbx2OzzImporter::Fbx2OzzImporter()
: settings_(fbx_manager_), scene_loader_(nullptr) {}
Fbx2OzzImporter::~Fbx2OzzImporter() { ozz::Delete(scene_loader_); }
bool Fbx2OzzImporter::Load(const char* _filename) {
ozz::Delete(scene_loader_);
scene_loader_ = ozz::New<ozz::animation::offline::fbx::FbxSceneLoader>(
_filename, "", fbx_manager_, settings_);
if (!scene_loader_->scene()) {
ozz::log::Err() << "Failed to import file " << _filename << "."
<< std::endl;
ozz::Delete(scene_loader_);
scene_loader_ = nullptr;
return false;
}
return true;
}
@@ -0,0 +1,94 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#ifndef OZZ_ANIMATION_OFFLINE_FBX_FBX2OZZ_H_
#define OZZ_ANIMATION_OFFLINE_FBX_FBX2OZZ_H_
#include "ozz/animation/offline/tools/import2ozz.h"
#include "ozz/animation/offline/fbx/fbx.h"
// fbx2ozz is a command line tool that converts an animation imported from a
// fbx document to ozz runtime format.
//
// fbx2ozz extracts animated joints from a fbx document. Only the animated
// joints whose names match those of the ozz runtime skeleton given as argument
// are selected. Keyframes are then optimized, based on command line settings,
// and serialized as a runtime animation to an ozz binary archive.
//
// Use fbx2ozz integrated help command (fbx2ozz --help) for more details
// about available arguments.
class Fbx2OzzImporter : public ozz::animation::offline::OzzImporter {
public:
Fbx2OzzImporter();
~Fbx2OzzImporter();
private:
virtual bool Load(const char* _filename);
// Skeleton management
virtual bool Import(ozz::animation::offline::RawSkeleton* _skeleton,
const NodeType& _types);
// Animation management
virtual AnimationNames GetAnimationNames();
virtual bool Import(const char* _animation_name,
const ozz::animation::Skeleton& _skeleton,
float _sampling_rate,
ozz::animation::offline::RawAnimation* _animation);
// Track management
virtual NodeProperties GetNodeProperties(const char* _node_name);
virtual bool Import(const char* _animation_name, const char* _node_name,
const char* _track_name,
NodeProperty::Type _expected_type, float _sampling_rate,
ozz::animation::offline::RawFloatTrack* _track);
virtual bool Import(const char* _animation_name, const char* _node_name,
const char* _track_name,
NodeProperty::Type _expected_type, float _sampling_rate,
ozz::animation::offline::RawFloat2Track* _track);
virtual bool Import(const char* _animation_name, const char* _node_name,
const char* _track_name,
NodeProperty::Type _expected_type, float _sampling_rate,
ozz::animation::offline::RawFloat3Track* _track);
virtual bool Import(const char* _animation_name, const char* _node_name,
const char* _track_name,
NodeProperty::Type _expected_type, float _sampling_rate,
ozz::animation::offline::RawFloat4Track* _track);
// Fbx internal helpers
ozz::animation::offline::fbx::FbxManagerInstance fbx_manager_;
ozz::animation::offline::fbx::FbxAnimationIOSettings settings_;
ozz::animation::offline::fbx::FbxSceneLoader* scene_loader_;
};
#endif // OZZ_ANIMATION_OFFLINE_FBX_FBX2OZZ_H_
@@ -0,0 +1,118 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "animation/offline/fbx/fbx2ozz.h"
#include "ozz/animation/offline/fbx/fbx_animation.h"
#include "ozz/animation/offline/raw_animation.h"
#include "ozz/base/log.h"
Fbx2OzzImporter::AnimationNames Fbx2OzzImporter::GetAnimationNames() {
if (!scene_loader_) {
return AnimationNames();
}
return ozz::animation::offline::fbx::GetAnimationNames(*scene_loader_);
}
bool Fbx2OzzImporter::Import(
const char* _animation_name, const ozz::animation::Skeleton& _skeleton,
float _sampling_rate, ozz::animation::offline::RawAnimation* _animation) {
if (!_animation) {
return false;
}
*_animation = ozz::animation::offline::RawAnimation();
if (!scene_loader_) {
return false;
}
return ozz::animation::offline::fbx::ExtractAnimation(
_animation_name, *scene_loader_, _skeleton, _sampling_rate, _animation);
}
Fbx2OzzImporter::NodeProperties Fbx2OzzImporter::GetNodeProperties(
const char* _node_name) {
if (!scene_loader_) {
return NodeProperties();
}
return ozz::animation::offline::fbx::GetNodeProperties(*scene_loader_,
_node_name);
}
template <typename _RawTrack>
bool ImportImpl(ozz::animation::offline::fbx::FbxSceneLoader* _scene_loader,
const char* _animation_name, const char* _node_name,
const char* _track_name,
Fbx2OzzImporter::NodeProperty::Type _type, float _sampling_rate,
_RawTrack* _track) {
if (!_scene_loader) {
return false;
}
return ozz::animation::offline::fbx::ExtractTrack(
_animation_name, _node_name, _track_name, _type, *_scene_loader,
_sampling_rate, _track);
}
bool Fbx2OzzImporter::Import(const char* _animation_name,
const char* _node_name, const char* _track_name,
Fbx2OzzImporter::NodeProperty::Type _type,
float _sampling_rate,
ozz::animation::offline::RawFloatTrack* _track) {
return ImportImpl(scene_loader_, _animation_name, _node_name, _track_name,
_type, _sampling_rate, _track);
}
bool Fbx2OzzImporter::Import(const char* _animation_name,
const char* _node_name, const char* _track_name,
Fbx2OzzImporter::NodeProperty::Type _type,
float _sampling_rate,
ozz::animation::offline::RawFloat2Track* _track) {
return ImportImpl(scene_loader_, _animation_name, _node_name, _track_name,
_type, _sampling_rate, _track);
}
bool Fbx2OzzImporter::Import(const char* _animation_name,
const char* _node_name, const char* _track_name,
Fbx2OzzImporter::NodeProperty::Type _type,
float _sampling_rate,
ozz::animation::offline::RawFloat3Track* _track) {
return ImportImpl(scene_loader_, _animation_name, _node_name, _track_name,
_type, _sampling_rate, _track);
}
bool Fbx2OzzImporter::Import(const char* _animation_name,
const char* _node_name, const char* _track_name,
Fbx2OzzImporter::NodeProperty::Type _type,
float _sampling_rate,
ozz::animation::offline::RawFloat4Track* _track) {
return ImportImpl(scene_loader_, _animation_name, _node_name, _track_name,
_type, _sampling_rate, _track);
}
@@ -0,0 +1,57 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "animation/offline/fbx/fbx2ozz.h"
#include "ozz/animation/offline/fbx/fbx.h"
#include "ozz/animation/offline/fbx/fbx_skeleton.h"
#include "ozz/animation/offline/raw_skeleton.h"
#include "ozz/base/log.h"
bool Fbx2OzzImporter::Import(ozz::animation::offline::RawSkeleton* _skeleton,
const NodeType& _types) {
if (!_skeleton) {
return false;
}
// Reset skeleton.
*_skeleton = ozz::animation::offline::RawSkeleton();
if (!scene_loader_) {
return false;
}
if (!ozz::animation::offline::fbx::ExtractSkeleton(*scene_loader_, _types,
_skeleton)) {
ozz::log::Err() << "Fbx skeleton extraction failed." << std::endl;
return false;
}
return true;
}
@@ -0,0 +1,775 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "ozz/animation/offline/fbx/fbx_animation.h"
#include "ozz/animation/offline/fbx/fbx.h"
#include "ozz/animation/offline/raw_animation.h"
#include "ozz/animation/offline/raw_animation_utils.h"
#include "ozz/animation/offline/raw_track.h"
#include "ozz/animation/runtime/skeleton.h"
#include "ozz/animation/runtime/skeleton_utils.h"
#include "ozz/base/log.h"
#include "ozz/base/maths/math_ex.h"
#include "ozz/base/maths/transform.h"
namespace ozz {
namespace animation {
namespace offline {
namespace fbx {
namespace {
struct SamplingInfo {
float start;
float end;
float duration;
float frequency;
};
SamplingInfo ExtractSamplingInfo(FbxScene* _scene, FbxAnimStack* _anim_stack,
float _sampling_rate) {
SamplingInfo info;
// Extract animation duration.
FbxTimeSpan time_spawn;
const FbxTakeInfo* take_info = _scene->GetTakeInfo(_anim_stack->GetName());
if (take_info) {
time_spawn = take_info->mLocalTimeSpan;
} else {
_scene->GetGlobalSettings().GetTimelineDefaultTimeSpan(time_spawn);
}
// Get frame rate from the scene.
FbxTime::EMode mode = _scene->GetGlobalSettings().GetTimeMode();
const float scene_frame_rate =
static_cast<float>((mode == FbxTime::eCustom)
? _scene->GetGlobalSettings().GetCustomFrameRate()
: FbxTime::GetFrameRate(mode));
// Deduce sampling period.
// Scene frame rate is used when provided argument is <= 0.
float sampling_rate;
if (_sampling_rate > 0.f) {
sampling_rate = _sampling_rate;
log::LogV() << "Using sampling rate of " << sampling_rate << "hz."
<< std::endl;
} else {
sampling_rate = scene_frame_rate;
log::LogV() << "Using scene sampling rate of " << sampling_rate << "hz."
<< std::endl;
}
info.frequency = sampling_rate;
// Get scene start and end.
info.start = static_cast<float>(time_spawn.GetStart().GetSecondDouble());
info.end = static_cast<float>(time_spawn.GetStop().GetSecondDouble());
// Duration could be 0 if it's just a pose. In this case we'll set a default
// 1s duration.
if (info.end > info.start) {
info.duration = info.end - info.start;
} else {
info.duration = 1.f;
}
return info;
}
bool ExtractAnimation(FbxSceneLoader& _scene_loader, const SamplingInfo& _info,
const Skeleton& _skeleton, RawAnimation* _animation) {
FbxScene* scene = _scene_loader.scene();
assert(scene);
// Set animation data.
_animation->duration = _info.duration;
// Locates all skeleton nodes in the fbx scene. Some might be nullptr.
ozz::vector<FbxNode*> nodes;
for (int i = 0; i < _skeleton.num_joints(); i++) {
const char* joint_name = _skeleton.joint_names()[i];
nodes.push_back(scene->FindNodeByName(joint_name));
}
// Preallocates and initializes world matrices.
const FixedRateSamplingTime fixed_it(_info.duration, _info.frequency);
ozz::vector<float> times;
times.resize(fixed_it.num_keys());
ozz::vector<ozz::vector<ozz::math::Float4x4>> world_matrices;
world_matrices.resize(_skeleton.num_joints());
for (int i = 0; i < _skeleton.num_joints(); i++) {
world_matrices[i].resize(fixed_it.num_keys());
}
// Goes through the whole timeline to compute animated word matrices.
// Fbx sdk seems to compute nodes transformation for the whole scene, so it's
// much faster to query all nodes at once for the same time t.
FbxAnimEvaluator* evaluator = scene->GetAnimationEvaluator();
for (size_t k = 0; k < fixed_it.num_keys(); ++k) {
const float t = fixed_it.time(k);
times[k] = t;
// Goes through all nodes.
for (int i = 0; i < _skeleton.num_joints(); i++) {
FbxNode* node = nodes[i];
if (node) {
const FbxAMatrix fbx_matrix = evaluator->GetNodeGlobalTransform(
node, FbxTimeSeconds(t + _info.start));
const math::Float4x4 matrix =
_scene_loader.converter()->ConvertMatrix(fbx_matrix);
world_matrices[i][k] = matrix;
}
}
}
// Builds world matrices for non-animated joints.
// Skeleton is order with parents first, so it can be traversed in order.
for (int i = 0; i < _skeleton.num_joints(); i++) {
// Initializes non-animated joints.
FbxNode* node = nodes[i];
if (node == nullptr) {
ozz::log::LogV() << "No animation track found for joint \""
<< _skeleton.joint_names()[i]
<< "\". Using skeleton bind pose instead." << std::endl;
const math::Transform& bind_pose =
ozz::animation::GetJointLocalBindPose(_skeleton, i);
const math::SimdFloat4 t =
math::simd_float4::Load3PtrU(&bind_pose.translation.x);
const math::SimdFloat4 q =
math::simd_float4::LoadPtrU(&bind_pose.rotation.x);
const math::SimdFloat4 s =
math::simd_float4::Load3PtrU(&bind_pose.scale.x);
const math::Float4x4 local_matrix = math::Float4x4::FromAffine(t, q, s);
ozz::vector<math::Float4x4>& node_matrices = world_matrices[i];
const int16_t parent = _skeleton.joint_parents()[i];
if (parent != Skeleton::kNoParent) {
const ozz::vector<math::Float4x4>& parent_matrices =
world_matrices[parent];
assert(fixed_it.num_keys() == parent_matrices.size());
for (size_t k = 0; k < fixed_it.num_keys(); ++k) {
node_matrices[k] = parent_matrices[k] * local_matrix;
}
} else {
for (size_t k = 0; k < fixed_it.num_keys(); ++k) {
node_matrices[k] = local_matrix;
}
}
}
}
// Builds world inverse matrices.
ozz::vector<ozz::vector<ozz::math::Float4x4>> world_inv_matrices;
world_inv_matrices.resize(_skeleton.num_joints());
for (int i = 0; i < _skeleton.num_joints(); i++) {
const ozz::vector<math::Float4x4>& node_world_matrices = world_matrices[i];
ozz::vector<math::Float4x4>& node_world_inv_matrices =
world_inv_matrices[i];
node_world_inv_matrices.resize(fixed_it.num_keys());
for (size_t p = 0; p < fixed_it.num_keys(); ++p) {
node_world_inv_matrices[p] = Invert(node_world_matrices[p]);
}
}
// Builds local space animation tracks.
// Allocates all tracks with the same number of joints as the skeleton.
// Tracks that would not be found will be set to skeleton bind-pose
// transformation.
_animation->tracks.resize(_skeleton.num_joints());
for (int i = 0; i < _skeleton.num_joints(); i++) {
RawAnimation::JointTrack& track = _animation->tracks[i];
track.rotations.resize(fixed_it.num_keys());
track.translations.resize(fixed_it.num_keys());
track.scales.resize(fixed_it.num_keys());
const int16_t parent = _skeleton.joint_parents()[i];
ozz::vector<math::Float4x4>& node_world_matrices = world_matrices[i];
ozz::vector<math::Float4x4>& node_world_inv_matrices =
world_inv_matrices[parent != Skeleton::kNoParent ? parent : 0];
for (size_t n = 0; n < fixed_it.num_keys(); ++n) {
// Builds local matrix;
math::Float4x4 local_matrix;
if (parent != Skeleton::kNoParent) {
local_matrix = node_world_inv_matrices[n] * node_world_matrices[n];
} else {
local_matrix = node_world_matrices[n];
}
// Convert to transform structure.
math::SimdFloat4 t, q, s;
if (!ToAffine(local_matrix, &t, &q, &s)) {
ozz::log::Err() << "Failed to extract animation transform for joint\""
<< _skeleton.joint_names()[i]
<< "\" at t = " << times[n] << "s." << std::endl;
return false;
}
ozz::math::Transform transform;
math::Store3PtrU(t, &transform.translation.x);
math::StorePtrU(math::Normalize4(q), &transform.rotation.x);
math::Store3PtrU(s, &transform.scale.x);
// Fills corresponding track.
const float time = times[n];
const RawAnimation::TranslationKey tkey = {time, transform.translation};
track.translations[n] = tkey;
const RawAnimation::RotationKey rkey = {time, transform.rotation};
track.rotations[n] = rkey;
const RawAnimation::ScaleKey skey = {time, transform.scale};
track.scales[n] = skey;
}
}
return _animation->Validate();
}
template <typename _Type>
bool PropertyGetValueAsFloat(FbxPropertyValue& _property_value,
EFbxType _fbx_type, float* _value) {
_Type value;
assert(_property_value.GetSizeOf() == sizeof(_Type));
bool success = _property_value.Get(&value, _fbx_type);
*_value = static_cast<float>(value);
return success;
}
bool GetValue(FbxSceneLoader& _scene_loader, FbxPropertyValue& _property_value,
EFbxType _fbx_type, OzzImporter::NodeProperty::Type _type,
float* _value) {
(void)_scene_loader;
// Only supported types are enumerated, so this function should not be
// called for something else but kFloat1.
(void)_type;
assert(_type == OzzImporter::NodeProperty::kFloat1);
switch (_fbx_type) {
case eFbxBool: {
return PropertyGetValueAsFloat<bool>(_property_value, _fbx_type, _value);
}
case eFbxChar: {
return PropertyGetValueAsFloat<int8_t>(_property_value, _fbx_type,
_value);
}
case eFbxUChar: {
return PropertyGetValueAsFloat<uint8_t>(_property_value, _fbx_type,
_value);
}
case eFbxShort: {
return PropertyGetValueAsFloat<int16_t>(_property_value, _fbx_type,
_value);
}
case eFbxUShort: {
return PropertyGetValueAsFloat<uint16_t>(_property_value, _fbx_type,
_value);
}
case eFbxInt:
case eFbxEnum:
case eFbxEnumM: {
return PropertyGetValueAsFloat<int32_t>(_property_value, _fbx_type,
_value);
}
case eFbxUInt: {
return PropertyGetValueAsFloat<uint32_t>(_property_value, _fbx_type,
_value);
}
case eFbxFloat: {
return PropertyGetValueAsFloat<float>(_property_value, _fbx_type, _value);
}
case eFbxDouble: {
return PropertyGetValueAsFloat<double>(_property_value, _fbx_type,
_value);
}
default: {
// Only supported types are enumerated, so this function should not be
// called for something else.
assert(false);
return false;
}
}
}
bool GetValue(FbxSceneLoader& _scene_loader, FbxPropertyValue& _property_value,
EFbxType _fbx_type, OzzImporter::NodeProperty::Type _type,
ozz::math::Float2* _value) {
(void)_scene_loader;
// Only supported types are enumerated, so this function should not be
// called for something else but eFbxDouble2 / kFloat2.
(void)_type;
(void)_fbx_type;
assert(_fbx_type == eFbxDouble2 &&
_type == OzzImporter::NodeProperty::kFloat2);
double dvalue[2];
if (!_property_value.Get(&dvalue, eFbxDouble2)) {
return false;
}
_value->x = static_cast<float>(dvalue[0]);
_value->y = static_cast<float>(dvalue[1]);
return true;
}
bool GetValue(FbxSceneLoader& _scene_loader, FbxPropertyValue& _property_value,
EFbxType _fbx_type, OzzImporter::NodeProperty::Type _type,
ozz::math::Float3* _value) {
// Only supported types are enumerated, so this function should not be
// called for something else but eFbxDouble3 / (kFloat3, kPosision).
(void)_type;
(void)_fbx_type;
assert(_fbx_type == eFbxDouble3 &&
(_type == OzzImporter::NodeProperty::kFloat3 ||
_type == OzzImporter::NodeProperty::kPoint ||
_type == OzzImporter::NodeProperty::kVector));
FbxVector4 vec4;
if (!_property_value.Get(vec4.Buffer(), eFbxDouble3)) {
return false;
}
// Type needs scene / axis conversion
const FbxSystemConverter* conv = _scene_loader.converter();
switch (_type) {
default: {
// No conversion required
_value->x = static_cast<float>(vec4[0]);
_value->y = static_cast<float>(vec4[1]);
_value->z = static_cast<float>(vec4[2]);
break;
}
case OzzImporter::NodeProperty::kPoint: {
*_value = conv->ConvertPoint(vec4);
break;
}
case OzzImporter::NodeProperty::kVector: {
*_value = conv->ConvertVector(vec4);
break;
}
}
return true;
} // namespace
bool GetValue(FbxSceneLoader& _scene_loader, FbxPropertyValue& _property_value,
EFbxType _fbx_type, OzzImporter::NodeProperty::Type _type,
ozz::math::Float4* _value) {
(void)_scene_loader;
// Only supported types are enumerated, so this function should not be
// called for something else but eFbxDouble4 / kFloat4.
(void)_type;
(void)_fbx_type;
assert(_fbx_type == eFbxDouble4 &&
_type == OzzImporter::NodeProperty::kFloat4);
double dvalue[4];
if (!_property_value.Get(&dvalue, eFbxDouble4)) {
return false;
}
_value->x = static_cast<float>(dvalue[0]);
_value->y = static_cast<float>(dvalue[1]);
_value->z = static_cast<float>(dvalue[2]);
_value->w = static_cast<float>(dvalue[3]);
return true;
}
template <typename _Track>
bool ExtractCurve(FbxSceneLoader& _scene_loader, FbxProperty& _property,
EFbxType _fbx_type, OzzImporter::NodeProperty::Type _type,
const SamplingInfo& _info, _Track* _track) {
assert(_track->keyframes.size() == 0);
FbxScene* scene = _scene_loader.scene();
assert(scene);
FbxAnimEvaluator* evaluator = scene->GetAnimationEvaluator();
if (!_property.IsAnimated()) {
FbxPropertyValue& property_value =
evaluator->GetPropertyValue(_property, FbxTimeSeconds(0.));
typename _Track::ValueType value;
if (!GetValue(_scene_loader, property_value, _fbx_type, _type, &value)) {
return false;
}
// Build and push keyframe
const typename _Track::Keyframe key = {RawTrackInterpolation::kStep, 0.f,
value};
_track->keyframes.push_back(key);
} else {
const FixedRateSamplingTime fixed_it(_info.duration, _info.frequency);
_track->keyframes.resize(fixed_it.num_keys());
// Evaluate values at the specified time.
for (size_t k = 0; k < fixed_it.num_keys(); ++k) {
const float t = fixed_it.time(k);
FbxPropertyValue& property_value = evaluator->GetPropertyValue(
_property, FbxTimeSeconds(t + _info.start));
// It shouldn't fail as property type is known.
typename _Track::ValueType value;
if (!GetValue(_scene_loader, property_value, _fbx_type, _type, &value)) {
return false;
}
// Build and push keyframe
const typename _Track::Keyframe key = {RawTrackInterpolation::kLinear,
t / _info.duration, value};
_track->keyframes[k] = key;
}
}
return _track->Validate();
}
const char* FbxTypeToString(EFbxType _type) {
switch (_type) {
case eFbxUndefined:
return "eFbxUndefined - Unidentified";
case eFbxChar:
return "eFbxChar - 8 bit signed integer";
case eFbxUChar:
return "eFbxUChar - 8 bit unsigned integer";
case eFbxShort:
return "eFbxShort - 16 bit signed integer";
case eFbxUShort:
return "eFbxUShort - 16 bit unsigned integer";
case eFbxUInt:
return "eFbxUInt - 32 bit unsigned integer";
case eFbxLongLong:
return "eFbxLongLong - 64 bit signed integer";
case eFbxULongLong:
return "eFbxULongLong - 64 bit unsigned integer";
case eFbxHalfFloat:
return "eFbxHalfFloat - 16 bit floating point";
case eFbxBool:
return "eFbxBool - Boolean";
case eFbxInt:
return "eFbxInt - 32 bit signed integer";
case eFbxFloat:
return "eFbxFloat - Floating point value";
case eFbxDouble:
return "eFbxDouble - Double width floating point value";
case eFbxDouble2:
return "eFbxDouble2 - Vector of two double values";
case eFbxDouble3:
return "eFbxDouble3 - Vector of three double values";
case eFbxDouble4:
return "eFbxDouble4 - Vector of four double values";
case eFbxDouble4x4:
return "eFbxDouble4x4 - Four vectors of four double values";
case eFbxEnum:
return "eFbxEnum - Enumeration";
case eFbxEnumM:
return "eFbxEnumM - Enumeration allowing duplicated items";
case eFbxString:
return "eFbxString - string";
case eFbxTime:
return "eFbxTime - Time value";
case eFbxReference:
return "eFbxReference - Reference to object or property";
case eFbxBlob:
return "eFbxBlob - Binary data block type";
case eFbxDistance:
return "eFbxDistance - Distance";
case eFbxDateTime:
return "eFbxDateTime - Date and time";
default:
return "Unknown";
}
}
bool ExtractProperty(FbxSceneLoader& _scene_loader, const SamplingInfo& _info,
FbxProperty& _property,
OzzImporter::NodeProperty::Type _type,
RawFloatTrack* _track) {
const EFbxType fbx_type = _property.GetPropertyDataType().GetType();
switch (fbx_type) {
case eFbxChar:
case eFbxUChar:
case eFbxShort:
case eFbxUShort:
case eFbxInt:
case eFbxUInt:
case eFbxBool:
case eFbxFloat:
case eFbxDouble:
case eFbxEnum:
case eFbxEnumM: {
return ExtractCurve(_scene_loader, _property, fbx_type, _type, _info,
_track);
}
default: {
log::Err() << "Float track can't be imported from a track of type: "
<< FbxTypeToString(fbx_type) << "\"" << std::endl;
return false;
}
}
}
bool ExtractProperty(FbxSceneLoader& _scene_loader, const SamplingInfo& _info,
FbxProperty& _property,
OzzImporter::NodeProperty::Type _type,
RawFloat2Track* _track) {
const EFbxType fbx_type = _property.GetPropertyDataType().GetType();
switch (fbx_type) {
case eFbxDouble2: {
return ExtractCurve(_scene_loader, _property, fbx_type, _type, _info,
_track);
}
default: {
log::Err() << "Float2 track can't be imported from a track of type: "
<< FbxTypeToString(fbx_type) << "\"" << std::endl;
return false;
}
}
}
bool ExtractProperty(FbxSceneLoader& _scene_loader, const SamplingInfo& _info,
FbxProperty& _property,
OzzImporter::NodeProperty::Type _type,
RawFloat3Track* _track) {
const EFbxType fbx_type = _property.GetPropertyDataType().GetType();
switch (fbx_type) {
case eFbxDouble3: {
return ExtractCurve(_scene_loader, _property, fbx_type, _type, _info,
_track);
}
default: {
log::Err() << "Float3 track can't be imported from a track of type: "
<< FbxTypeToString(fbx_type) << "\"" << std::endl;
return false;
}
}
}
bool ExtractProperty(FbxSceneLoader& _scene_loader, const SamplingInfo& _info,
FbxProperty& _property,
OzzImporter::NodeProperty::Type _type,
RawFloat4Track* _track) {
const EFbxType fbx_type = _property.GetPropertyDataType().GetType();
switch (fbx_type) {
case eFbxDouble4: {
return ExtractCurve(_scene_loader, _property, fbx_type, _type, _info,
_track);
}
default: {
log::Err() << "Float4 track can't be imported from a track of type: "
<< FbxTypeToString(fbx_type) << "\"" << std::endl;
return false;
}
}
}
template <typename _Track>
bool ExtractTrackImpl(const char* _animation_name, const char* _node_name,
const char* _track_name,
OzzImporter::NodeProperty::Type _type,
FbxSceneLoader& _scene_loader, float _sampling_rate,
_Track* _track) {
FbxScene* scene = _scene_loader.scene();
assert(scene);
// Reset track
*_track = _Track();
FbxAnimStack* anim_stack =
scene->FindSrcObject<FbxAnimStack>(_animation_name);
if (!anim_stack) {
return false;
}
// Extract sampling info relative to the stack.
const SamplingInfo& info =
ExtractSamplingInfo(scene, anim_stack, _sampling_rate);
FbxNode* node = scene->FindNodeByName(_node_name);
if (!node) {
ozz::log::Err() << "Invalid node name \"" << _node_name << "\""
<< std::endl;
return false;
}
FbxProperty property = node->FindProperty(_track_name);
if (!property.IsValid()) {
ozz::log::Err() << "Invalid property name \"" << _track_name << "\""
<< std::endl;
return false;
}
return ExtractProperty(_scene_loader, info, property, _type, _track);
}
} // namespace
OzzImporter::AnimationNames GetAnimationNames(FbxSceneLoader& _scene_loader) {
OzzImporter::AnimationNames names;
const FbxScene* scene = _scene_loader.scene();
for (int i = 0; i < scene->GetSrcObjectCount<FbxAnimStack>(); ++i) {
FbxAnimStack* anim_stack = scene->GetSrcObject<FbxAnimStack>(i);
names.push_back(ozz::string(anim_stack->GetName()));
}
return names;
}
bool ExtractAnimation(const char* _animation_name,
FbxSceneLoader& _scene_loader, const Skeleton& _skeleton,
float _sampling_rate,
ozz::animation::offline::RawAnimation* _animation) {
FbxScene* scene = _scene_loader.scene();
assert(scene);
bool success = false;
FbxAnimStack* anim_stack =
scene->FindSrcObject<FbxAnimStack>(_animation_name);
if (anim_stack) {
// Extract sampling info relative to the stack.
const SamplingInfo& info =
ExtractSamplingInfo(scene, anim_stack, _sampling_rate);
// Setup Fbx animation evaluator.
scene->SetCurrentAnimationStack(anim_stack);
success = ExtractAnimation(_scene_loader, info, _skeleton, _animation);
}
// Clears output if something failed during import, avoids partial data.
if (!success) {
*_animation = ozz::animation::offline::RawAnimation();
}
return success;
}
OzzImporter::NodeProperties GetNodeProperties(FbxSceneLoader& _scene_loader,
const char* _node_name) {
OzzImporter::NodeProperties properties;
FbxScene* scene = _scene_loader.scene();
const FbxNode* node = scene->FindNodeByName(_node_name);
if (!node) {
ozz::log::LogV() << "Invalid node name \"" << _node_name << "\""
<< std::endl;
return properties;
}
for (FbxProperty fbx_property = node->GetFirstProperty();
fbx_property.IsValid();
fbx_property = node->GetNextProperty(fbx_property)) {
const char* ppt_name = fbx_property.GetNameAsCStr();
const EFbxType type = fbx_property.GetPropertyDataType().GetType();
switch (type) {
case eFbxChar:
case eFbxUChar:
case eFbxShort:
case eFbxUShort:
case eFbxInt:
case eFbxUInt:
case eFbxBool:
case eFbxFloat:
case eFbxDouble:
case eFbxEnum:
case eFbxEnumM: {
const OzzImporter::NodeProperty ppt = {
ppt_name, OzzImporter::NodeProperty::kFloat1};
properties.push_back(ppt);
break;
}
case eFbxDouble2: {
const OzzImporter::NodeProperty ppt = {
ppt_name, OzzImporter::NodeProperty::kFloat2};
properties.push_back(ppt);
break;
}
case eFbxDouble3: {
const OzzImporter::NodeProperty ppt = {
ppt_name, OzzImporter::NodeProperty::kFloat3};
properties.push_back(ppt);
break;
}
case eFbxDouble4: {
const OzzImporter::NodeProperty ppt = {
ppt_name, OzzImporter::NodeProperty::kFloat4};
properties.push_back(ppt);
break;
}
default: {
log::LogV() << "Node property \"" << ppt_name
<< "\" doesn't have a importable type\""
<< FbxTypeToString(type) << "\"" << std::endl;
break;
}
}
}
return properties;
}
bool ExtractTrack(const char* _animation_name, const char* _node_name,
const char* _track_name,
OzzImporter::NodeProperty::Type _type,
FbxSceneLoader& _scene_loader, float _sampling_rate,
RawFloatTrack* _track) {
return ExtractTrackImpl(_animation_name, _node_name, _track_name, _type,
_scene_loader, _sampling_rate, _track);
}
bool ExtractTrack(const char* _animation_name, const char* _node_name,
const char* _track_name,
OzzImporter::NodeProperty::Type _type,
FbxSceneLoader& _scene_loader, float _sampling_rate,
RawFloat2Track* _track) {
return ExtractTrackImpl(_animation_name, _node_name, _track_name, _type,
_scene_loader, _sampling_rate, _track);
}
bool ExtractTrack(const char* _animation_name, const char* _node_name,
const char* _track_name,
OzzImporter::NodeProperty::Type _type,
FbxSceneLoader& _scene_loader, float _sampling_rate,
RawFloat3Track* _track) {
return ExtractTrackImpl(_animation_name, _node_name, _track_name, _type,
_scene_loader, _sampling_rate, _track);
}
bool ExtractTrack(const char* _animation_name, const char* _node_name,
const char* _track_name,
OzzImporter::NodeProperty::Type _type,
FbxSceneLoader& _scene_loader, float _sampling_rate,
RawFloat4Track* _track) {
return ExtractTrackImpl(_animation_name, _node_name, _track_name, _type,
_scene_loader, _sampling_rate, _track);
}
} // namespace fbx
} // namespace offline
} // namespace animation
} // namespace ozz
@@ -0,0 +1,167 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "ozz/animation/offline/fbx/fbx_skeleton.h"
#include "ozz/animation/offline/raw_skeleton.h"
#include "ozz/base/log.h"
namespace ozz {
namespace animation {
namespace offline {
namespace fbx {
namespace {
enum RecurseReturn { kError, kSkeletonFound, kNoSkeleton };
bool IsTypeSelected(const OzzImporter::NodeType& _types,
FbxNodeAttribute::EType _node_type) {
// Early out to accept any node type
if (_types.any) {
return true;
}
switch (_node_type) {
// Skeleton
case FbxNodeAttribute::eSkeleton:
return _types.skeleton;
// Marker
case FbxNodeAttribute::eMarker:
return _types.marker;
// Geometry
case FbxNodeAttribute::eMesh:
case FbxNodeAttribute::eNurbs:
case FbxNodeAttribute::ePatch:
case FbxNodeAttribute::eNurbsCurve:
case FbxNodeAttribute::eTrimNurbsSurface:
case FbxNodeAttribute::eBoundary:
case FbxNodeAttribute::eNurbsSurface:
case FbxNodeAttribute::eShape:
case FbxNodeAttribute::eSubDiv:
case FbxNodeAttribute::eLine:
return _types.geometry;
// Camera
case FbxNodeAttribute::eCameraStereo:
case FbxNodeAttribute::eCamera:
return _types.camera;
// Light
case FbxNodeAttribute::eLight:
return _types.light;
// Others
case FbxNodeAttribute::eUnknown:
case FbxNodeAttribute::eNull:
case FbxNodeAttribute::eCameraSwitcher:
case FbxNodeAttribute::eOpticalReference:
case FbxNodeAttribute::eOpticalMarker:
case FbxNodeAttribute::eCachedEffect:
case FbxNodeAttribute::eLODGroup:
default:
return false;
}
}
RecurseReturn RecurseNode(FbxNode* _node, FbxSystemConverter* _converter,
const OzzImporter::NodeType& _types,
RawSkeleton* _skeleton, RawSkeleton::Joint* _parent,
FbxAMatrix _parent_global_inv) {
bool skeleton_found = false;
RawSkeleton::Joint* this_joint = nullptr;
// Process this node as a new joint if it has a joint compatible attribute.
FbxNodeAttribute* node_attribute = _node->GetNodeAttribute();
if (node_attribute &&
IsTypeSelected(_types, node_attribute->GetAttributeType())) {
skeleton_found = true;
RawSkeleton::Joint::Children* sibling = nullptr;
if (_parent) {
sibling = &_parent->children;
} else {
sibling = &_skeleton->roots;
}
// Adds a new child.
sibling->resize(sibling->size() + 1);
this_joint = &sibling->back(); // Will not be resized inside recursion.
this_joint->name = _node->GetName();
// Extract bind pose.
const FbxAMatrix node_global = _node->EvaluateGlobalTransform();
const FbxAMatrix node_local = _parent_global_inv * node_global;
if (!_converter->ConvertTransform(node_local, &this_joint->transform)) {
ozz::log::Err() << "Failed to extract skeleton transform for joint \""
<< this_joint->name << "\"." << std::endl;
return kError;
}
// This node is the new parent for further recursions.
_parent_global_inv = node_global.Inverse();
_parent = this_joint;
}
// Iterate node's children, even if this one wasn't processed.
for (int i = 0; i < _node->GetChildCount(); i++) {
FbxNode* child = _node->GetChild(i);
const RecurseReturn ret = RecurseNode(child, _converter, _types, _skeleton,
_parent, _parent_global_inv);
if (ret == kError) {
return ret;
}
skeleton_found |= (ret == kSkeletonFound);
}
return skeleton_found ? kSkeletonFound : kNoSkeleton;
}
} // namespace
bool ExtractSkeleton(FbxSceneLoader& _loader,
const OzzImporter::NodeType& _types,
RawSkeleton* _skeleton) {
RecurseReturn ret =
RecurseNode(_loader.scene()->GetRootNode(), _loader.converter(), _types,
_skeleton, nullptr, FbxAMatrix());
if (ret == kNoSkeleton) {
ozz::log::Err() << "No skeleton found in Fbx scene." << std::endl;
return false;
} else if (ret == kError) {
ozz::log::Err() << "Failed to extract skeleton." << std::endl;
return false;
}
return true;
}
} // namespace fbx
} // namespace offline
} // namespace animation
} // namespace ozz
@@ -0,0 +1,13 @@
if(NOT ozz_build_gltf)
return()
endif()
add_executable(gltf2ozz
gltf2ozz.cc)
target_link_libraries(gltf2ozz PRIVATE
ozz_animation_tools)
set_target_properties(gltf2ozz
PROPERTIES FOLDER "ozz/tools")
install(TARGETS gltf2ozz DESTINATION bin/tools)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,906 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#//---------------------------------------------------------------------------//
// Initial gltf2ozz implementation author: Alexander Dzhoganov //
// https://github.com/guillaumeblanc/ozz-animation/pull/70 //
//----------------------------------------------------------------------------//
#include <cassert>
#include <cstring>
#include "ozz/animation/offline/raw_animation_utils.h"
#include "ozz/animation/offline/tools/import2ozz.h"
#include "ozz/animation/runtime/skeleton.h"
#include "ozz/base/containers/map.h"
#include "ozz/base/containers/set.h"
#include "ozz/base/containers/vector.h"
#include "ozz/base/log.h"
#include "ozz/base/maths/math_ex.h"
#include "ozz/base/maths/simd_math.h"
#define TINYGLTF_IMPLEMENTATION
// No support for image loading or writing
#define TINYGLTF_NO_STB_IMAGE
#define TINYGLTF_NO_STB_IMAGE_WRITE
#define TINYGLTF_NO_EXTERNAL_IMAGE
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4702) // unreachable code
#pragma warning(disable : 4267) // conversion from 'size_t' to 'type'
#endif // _MSC_VER
#include "extern/tiny_gltf.h"
#ifdef _MSC_VER
#pragma warning(pop)
#endif // _MSC_VER
namespace {
template <typename _VectorType>
bool FixupNames(_VectorType& _data, const char* _pretty_name,
const char* _prefix_name) {
ozz::set<std::string> names;
for (size_t i = 0; i < _data.size(); ++i) {
bool renamed = false;
typename _VectorType::const_reference data = _data[i];
std::string name(data.name.c_str());
// Fixes unnamed animations.
if (name.length() == 0) {
renamed = true;
name = _prefix_name;
name += std::to_string(i);
}
// Fixes duplicated names, while it has duplicates
for (auto it = names.find(name); it != names.end(); it = names.find(name)) {
renamed = true;
name += "_";
name += std::to_string(i);
}
// Update names index.
if (!names.insert(name).second) {
assert(false && "Algorithm must ensure no duplicated animation names.");
}
if (renamed) {
ozz::log::LogV() << _pretty_name << " #" << i << " with name \""
<< data.name << "\" was renamed to \"" << name
<< "\" in order to avoid duplicates." << std::endl;
// Actually renames tinygltf data.
_data[i].name = name;
}
}
return true;
}
// Returns the address of a gltf buffer given an accessor.
// Performs basic checks to ensure the data is in the correct format
template <typename T>
ozz::span<const T> BufferView(const tinygltf::Model& _model,
const tinygltf::Accessor& _accessor) {
const int32_t component_size =
tinygltf::GetComponentSizeInBytes(_accessor.componentType);
const int32_t element_size =
component_size * tinygltf::GetTypeSizeInBytes(_accessor.type);
if (element_size != sizeof(T)) {
ozz::log::Err() << "Invalid buffer view access. Expected element size '"
<< sizeof(T) << " got " << element_size << " instead."
<< std::endl;
return ozz::span<const T>();
}
const tinygltf::BufferView& bufferView =
_model.bufferViews[_accessor.bufferView];
const tinygltf::Buffer& buffer = _model.buffers[bufferView.buffer];
const T* begin = reinterpret_cast<const T*>(
buffer.data.data() + bufferView.byteOffset + _accessor.byteOffset);
return ozz::span<const T>(begin, _accessor.count);
}
// Samples a linear animation channel
// There is an exact mapping between gltf and ozz keyframes so we just copy
// everything over.
template <typename _KeyframesType>
bool SampleLinearChannel(const tinygltf::Model& _model,
const tinygltf::Accessor& _output,
const ozz::span<const float>& _timestamps,
_KeyframesType* _keyframes) {
const size_t gltf_keys_count = _output.count;
if (gltf_keys_count == 0) {
_keyframes->clear();
return true;
}
typedef typename _KeyframesType::value_type::Value ValueType;
const ozz::span<const ValueType> values =
BufferView<ValueType>(_model, _output);
if (values.size_bytes() / sizeof(ValueType) != gltf_keys_count ||
_timestamps.size() != gltf_keys_count) {
ozz::log::Err() << "gltf format error, inconsistent number of keys."
<< std::endl;
return false;
}
_keyframes->reserve(_output.count);
for (size_t i = 0; i < _output.count; ++i) {
const typename _KeyframesType::value_type key{_timestamps[i], values[i]};
_keyframes->push_back(key);
}
return true;
}
// Samples a step animation channel
// There are twice-1 as many ozz keyframes as gltf keyframes
template <typename _KeyframesType>
bool SampleStepChannel(const tinygltf::Model& _model,
const tinygltf::Accessor& _output,
const ozz::span<const float>& _timestamps,
_KeyframesType* _keyframes) {
const size_t gltf_keys_count = _output.count;
if (gltf_keys_count == 0) {
_keyframes->clear();
return true;
}
typedef typename _KeyframesType::value_type::Value ValueType;
const ozz::span<const ValueType> values =
BufferView<ValueType>(_model, _output);
if (values.size_bytes() / sizeof(ValueType) != gltf_keys_count ||
_timestamps.size() != gltf_keys_count) {
ozz::log::Err() << "gltf format error, inconsistent number of keys."
<< std::endl;
return false;
}
// A step is created with 2 consecutive keys. Last step is a single key.
size_t numKeyframes = gltf_keys_count * 2 - 1;
_keyframes->resize(numKeyframes);
for (size_t i = 0; i < _output.count; i++) {
typename _KeyframesType::reference key = _keyframes->at(i * 2);
key.time = _timestamps[i];
key.value = values[i];
if (i < _output.count - 1) {
typename _KeyframesType::reference next_key = _keyframes->at(i * 2 + 1);
next_key.time = nexttowardf(_timestamps[i + 1], 0.f);
next_key.value = values[i];
}
}
return true;
}
// Samples a hermite spline in the form
// p(t) = (2t^3 - 3t^2 + 1)p0 + (t^3 - 2t^2 + t)m0 + (-2t^3 + 3t^2)p1 + (t^3 -
// t^2)m1 where t is a value between 0 and 1 p0 is the starting point at t = 0
// m0 is the scaled starting tangent at t = 0
// p1 is the ending point at t = 1
// m1 is the scaled ending tangent at t = 1
// p(t) is the resulting point value
template <typename T>
T SampleHermiteSpline(float _alpha, const T& p0, const T& m0, const T& p1,
const T& m1) {
assert(_alpha >= 0.f && _alpha <= 1.f);
const float t1 = _alpha;
const float t2 = _alpha * _alpha;
const float t3 = t2 * _alpha;
// a = 2t^3 - 3t^2 + 1
const float a = 2.0f * t3 - 3.0f * t2 + 1.0f;
// b = t^3 - 2t^2 + t
const float b = t3 - 2.0f * t2 + t1;
// c = -2t^3 + 3t^2
const float c = -2.0f * t3 + 3.0f * t2;
// d = t^3 - t^2
const float d = t3 - t2;
// p(t) = a * p0 + b * m0 + c * p1 + d * m1
T pt = p0 * a + m0 * b + p1 * c + m1 * d;
return pt;
}
// Samples a cubic-spline channel
// the number of keyframes is determined from the animation duration and given
// sample rate
template <typename _KeyframesType>
bool SampleCubicSplineChannel(const tinygltf::Model& _model,
const tinygltf::Accessor& _output,
const ozz::span<const float>& _timestamps,
float _sampling_rate, float _duration,
_KeyframesType* _keyframes) {
(void)_duration;
assert(_output.count % 3 == 0);
size_t gltf_keys_count = _output.count / 3;
if (gltf_keys_count == 0) {
_keyframes->clear();
return true;
}
typedef typename _KeyframesType::value_type::Value ValueType;
const ozz::span<const ValueType> values =
BufferView<ValueType>(_model, _output);
if (values.size_bytes() / (sizeof(ValueType) * 3) != gltf_keys_count ||
_timestamps.size() != gltf_keys_count) {
ozz::log::Err() << "gltf format error, inconsistent number of keys."
<< std::endl;
return false;
}
// Iterate keyframes at _sampling_rate steps, between first and last time
// stamps.
ozz::animation::offline::FixedRateSamplingTime fixed_it(
_timestamps[gltf_keys_count - 1] - _timestamps[0], _sampling_rate);
_keyframes->resize(fixed_it.num_keys());
size_t cubic_key0 = 0;
for (size_t k = 0; k < fixed_it.num_keys(); ++k) {
const float time = fixed_it.time(k) + _timestamps[0];
// Creates output key.
typename _KeyframesType::value_type key;
key.time = time;
// Makes sure time is in between the correct cubic keyframes.
while (_timestamps[cubic_key0 + 1] < time) {
cubic_key0++;
}
assert(_timestamps[cubic_key0] <= time &&
time <= _timestamps[cubic_key0 + 1]);
// Interpolate cubic key
const float t0 = _timestamps[cubic_key0]; // keyframe before time
const float t1 = _timestamps[cubic_key0 + 1]; // keyframe after time
const float alpha = (time - t0) / (t1 - t0);
const ValueType& p0 = values[cubic_key0 * 3 + 1];
const ValueType m0 = values[cubic_key0 * 3 + 2] * (t1 - t0);
const ValueType& p1 = values[(cubic_key0 + 1) * 3 + 1];
const ValueType m1 = values[(cubic_key0 + 1) * 3] * (t1 - t0);
key.value = SampleHermiteSpline(alpha, p0, m0, p1, m1);
// Pushes interpolated key.
_keyframes->at(k) = key;
}
return true;
}
template <typename _KeyframesType>
bool SampleChannel(const tinygltf::Model& _model,
const std::string& _interpolation,
const tinygltf::Accessor& _output,
const ozz::span<const float>& _timestamps,
float _sampling_rate, float _duration,
_KeyframesType* _keyframes) {
bool valid = false;
if (_interpolation == "LINEAR") {
valid = SampleLinearChannel(_model, _output, _timestamps, _keyframes);
} else if (_interpolation == "STEP") {
valid = SampleStepChannel(_model, _output, _timestamps, _keyframes);
} else if (_interpolation == "CUBICSPLINE") {
valid = SampleCubicSplineChannel(_model, _output, _timestamps,
_sampling_rate, _duration, _keyframes);
} else {
ozz::log::Err() << "Invalid or unknown interpolation type '"
<< _interpolation << "'." << std::endl;
valid = false;
}
// Check if sorted (increasing time, might not be stricly increasing).
if (valid) {
valid = std::is_sorted(_keyframes->begin(), _keyframes->end(),
[](typename _KeyframesType::const_reference _a,
typename _KeyframesType::const_reference _b) {
return _a.time < _b.time;
});
if (!valid) {
ozz::log::Log()
<< "gltf format error, keyframes are not sorted in increasing order."
<< std::endl;
}
}
// Remove keyframes with strictly equal times, keeping the first one.
if (valid) {
auto new_end = std::unique(_keyframes->begin(), _keyframes->end(),
[](typename _KeyframesType::const_reference _a,
typename _KeyframesType::const_reference _b) {
return _a.time == _b.time;
});
if (new_end != _keyframes->end()) {
_keyframes->erase(new_end, _keyframes->end());
ozz::log::Log() << "gltf format error, keyframe times are not unique. "
"Imported data were modified to remove keyframes at "
"consecutive equivalent times."
<< std::endl;
}
}
return valid;
}
ozz::animation::offline::RawAnimation::TranslationKey
CreateTranslationBindPoseKey(const tinygltf::Node& _node) {
ozz::animation::offline::RawAnimation::TranslationKey key;
key.time = 0.0f;
if (_node.translation.empty()) {
key.value = ozz::math::Float3::zero();
} else {
key.value = ozz::math::Float3(static_cast<float>(_node.translation[0]),
static_cast<float>(_node.translation[1]),
static_cast<float>(_node.translation[2]));
}
return key;
}
ozz::animation::offline::RawAnimation::RotationKey CreateRotationBindPoseKey(
const tinygltf::Node& _node) {
ozz::animation::offline::RawAnimation::RotationKey key;
key.time = 0.0f;
if (_node.rotation.empty()) {
key.value = ozz::math::Quaternion::identity();
} else {
key.value = ozz::math::Quaternion(static_cast<float>(_node.rotation[0]),
static_cast<float>(_node.rotation[1]),
static_cast<float>(_node.rotation[2]),
static_cast<float>(_node.rotation[3]));
}
return key;
}
ozz::animation::offline::RawAnimation::ScaleKey CreateScaleBindPoseKey(
const tinygltf::Node& _node) {
ozz::animation::offline::RawAnimation::ScaleKey key;
key.time = 0.0f;
if (_node.scale.empty()) {
key.value = ozz::math::Float3::one();
} else {
key.value = ozz::math::Float3(static_cast<float>(_node.scale[0]),
static_cast<float>(_node.scale[1]),
static_cast<float>(_node.scale[2]));
}
return key;
}
// Creates the default transform for a gltf node
bool CreateNodeTransform(const tinygltf::Node& _node,
ozz::math::Transform* _transform) {
*_transform = ozz::math::Transform::identity();
if (!_node.matrix.empty()) {
const ozz::math::Float4x4 matrix = {
{ozz::math::simd_float4::Load(static_cast<float>(_node.matrix[0]),
static_cast<float>(_node.matrix[1]),
static_cast<float>(_node.matrix[2]),
static_cast<float>(_node.matrix[3])),
ozz::math::simd_float4::Load(static_cast<float>(_node.matrix[4]),
static_cast<float>(_node.matrix[5]),
static_cast<float>(_node.matrix[6]),
static_cast<float>(_node.matrix[7])),
ozz::math::simd_float4::Load(static_cast<float>(_node.matrix[8]),
static_cast<float>(_node.matrix[9]),
static_cast<float>(_node.matrix[10]),
static_cast<float>(_node.matrix[11])),
ozz::math::simd_float4::Load(static_cast<float>(_node.matrix[12]),
static_cast<float>(_node.matrix[13]),
static_cast<float>(_node.matrix[14]),
static_cast<float>(_node.matrix[15]))}};
ozz::math::SimdFloat4 translation, rotation, scale;
if (ToAffine(matrix, &translation, &rotation, &scale)) {
ozz::math::Store3PtrU(translation, &_transform->translation.x);
ozz::math::StorePtrU(translation, &_transform->rotation.x);
ozz::math::Store3PtrU(translation, &_transform->scale.x);
return true;
}
ozz::log::Err() << "Failed to extract transformation from node \""
<< _node.name << "\"." << std::endl;
return false;
}
if (!_node.translation.empty()) {
_transform->translation =
ozz::math::Float3(static_cast<float>(_node.translation[0]),
static_cast<float>(_node.translation[1]),
static_cast<float>(_node.translation[2]));
}
if (!_node.rotation.empty()) {
_transform->rotation =
ozz::math::Quaternion(static_cast<float>(_node.rotation[0]),
static_cast<float>(_node.rotation[1]),
static_cast<float>(_node.rotation[2]),
static_cast<float>(_node.rotation[3]));
}
if (!_node.scale.empty()) {
_transform->scale = ozz::math::Float3(static_cast<float>(_node.scale[0]),
static_cast<float>(_node.scale[1]),
static_cast<float>(_node.scale[2]));
}
return true;
}
} // namespace
class GltfImporter : public ozz::animation::offline::OzzImporter {
public:
GltfImporter() {
// We don't care about image data but we have to provide this callback
// because we're not loading the stb library
auto image_loader = [](tinygltf::Image*, const int, std::string*,
std::string*, int, int, const unsigned char*, int,
void*) { return true; };
m_loader.SetImageLoader(image_loader, NULL);
}
private:
bool Load(const char* _filename) override {
bool success = false;
std::string errors;
std::string warnings;
// Finds file extension.
const char* separator = std::strrchr(_filename, '.');
const char* ext = separator != NULL ? separator + 1 : "";
// Tries to guess whether the input is a gltf json or a glb binary based on
// the file extension
if (std::strcmp(ext, "glb") == 0) {
success =
m_loader.LoadBinaryFromFile(&m_model, &errors, &warnings, _filename);
} else {
if (std::strcmp(ext, "gltf") != 0) {
ozz::log::Log() << "Unknown file extension '" << ext
<< "', assuming a JSON-formatted gltf." << std::endl;
}
success =
m_loader.LoadASCIIFromFile(&m_model, &errors, &warnings, _filename);
}
// Prints any errors or warnings emitted by the loader
if (!warnings.empty()) {
ozz::log::Log() << "glTF parsing warnings: " << warnings << std::endl;
}
if (!errors.empty()) {
ozz::log::Err() << "glTF parsing errors: " << errors << std::endl;
}
if (success) {
ozz::log::Log() << "glTF parsed successfully." << std::endl;
}
if (success) {
success &= FixupNames(m_model.scenes, "Scene", "scene_");
success &= FixupNames(m_model.nodes, "Node", "node_");
success &= FixupNames(m_model.animations, "Animation", "animation_");
}
return success;
}
// Given a skin find which of its joints is the skeleton root and return it
// returns -1 if the skin has no associated joints
int FindSkinRootJointIndex(const tinygltf::Skin& skin) {
if (skin.joints.empty()) {
return -1;
}
if (skin.skeleton != -1) {
return skin.skeleton;
}
ozz::map<int, int> parents;
for (int node : skin.joints) {
for (int child : m_model.nodes[node].children) {
parents[child] = node;
}
}
int root = skin.joints[0];
while (parents.find(root) != parents.end()) {
root = parents[root];
}
return root;
}
bool Import(ozz::animation::offline::RawSkeleton* _skeleton,
const NodeType& _types) override {
(void)_types;
if (m_model.scenes.empty()) {
ozz::log::Err() << "No scenes found." << std::endl;
return false;
}
// If no default scene has been set then take the first one spec does not
// disallow gltfs without a default scene but it makes more sense to keep
// going instead of throwing an error here
int defaultScene = m_model.defaultScene;
if (defaultScene == -1) {
defaultScene = 0;
}
tinygltf::Scene& scene = m_model.scenes[defaultScene];
ozz::log::LogV() << "Importing from default scene #" << defaultScene
<< " with name \"" << scene.name << "\"." << std::endl;
if (scene.nodes.empty()) {
ozz::log::Err() << "Scene has no node." << std::endl;
return false;
}
// Get all the skins belonging to this scene
ozz::vector<int> roots;
ozz::vector<tinygltf::Skin> skins = GetSkinsForScene(scene);
if (skins.empty()) {
ozz::log::Log() << "No skin exists in the scene, the whole scene graph "
"will be considered as a skeleton."
<< std::endl;
// Uses all scene nodes.
for (auto& node : scene.nodes) {
roots.push_back(node);
}
} else {
if (skins.size() > 1) {
ozz::log::Log() << "Multiple skins exist in the scene, they will all "
"be exported to a single skeleton."
<< std::endl;
}
// Uses all skins root
for (auto& skin : skins) {
const int root = FindSkinRootJointIndex(skin);
if (root == -1) {
continue;
}
roots.push_back(root);
}
}
// Remove nodes listed multiple times.
std::sort(roots.begin(), roots.end());
roots.erase(std::unique(roots.begin(), roots.end()), roots.end());
// Traverses the scene graph and record all joints starting from the roots.
_skeleton->roots.resize(roots.size());
for (size_t i = 0; i < roots.size(); ++i) {
const tinygltf::Node& root_node = m_model.nodes[roots[i]];
ozz::animation::offline::RawSkeleton::Joint& root_joint =
_skeleton->roots[i];
if (!ImportNode(root_node, &root_joint)) {
return false;
}
}
if (!_skeleton->Validate()) {
ozz::log::Err() << "Output skeleton failed validation. This is likely an "
"implementation issue."
<< std::endl;
return false;
}
return true;
}
// Recursively import a node's children
bool ImportNode(const tinygltf::Node& _node,
ozz::animation::offline::RawSkeleton::Joint* _joint) {
// Names joint.
_joint->name = _node.name.c_str();
// Fills transform.
if (!CreateNodeTransform(_node, &_joint->transform)) {
return false;
}
// Allocates all children at once.
_joint->children.resize(_node.children.size());
// Fills each child information.
for (size_t i = 0; i < _node.children.size(); ++i) {
const tinygltf::Node& child_node = m_model.nodes[_node.children[i]];
ozz::animation::offline::RawSkeleton::Joint& child_joint =
_joint->children[i];
if (!ImportNode(child_node, &child_joint)) {
return false;
}
}
return true;
}
// Returns all animations in the gltf document.
AnimationNames GetAnimationNames() override {
AnimationNames animNames;
for (size_t i = 0; i < m_model.animations.size(); ++i) {
tinygltf::Animation& animation = m_model.animations[i];
assert(animation.name.length() != 0);
animNames.push_back(animation.name.c_str());
}
return animNames;
}
bool Import(const char* _animation_name,
const ozz::animation::Skeleton& skeleton, float _sampling_rate,
ozz::animation::offline::RawAnimation* _animation) override {
if (_sampling_rate == 0.0f) {
_sampling_rate = 30.0f;
static bool samplingRateWarn = false;
if (!samplingRateWarn) {
ozz::log::LogV() << "The animation sampling rate is set to 0 "
"(automatic) but glTF does not carry scene frame "
"rate information. Assuming a sampling rate of "
<< _sampling_rate << "hz." << std::endl;
samplingRateWarn = true;
}
}
// Find the corresponding gltf animation
std::vector<tinygltf::Animation>::const_iterator gltf_animation =
std::find_if(begin(m_model.animations), end(m_model.animations),
[_animation_name](const tinygltf::Animation& _animation) {
return _animation.name == _animation_name;
});
assert(gltf_animation != end(m_model.animations));
_animation->name = gltf_animation->name.c_str();
// Animation duration is determined during sampling from the duration of the
// longest channel
_animation->duration = 0.0f;
const int num_joints = skeleton.num_joints();
_animation->tracks.resize(num_joints);
// gltf stores animations by splitting them in channels
// where each channel targets a node's property i.e. translation, rotation
// or scale. ozz expects animations to be stored per joint so we create a
// map where we record the associated channels for each joint
ozz::cstring_map<std::vector<const tinygltf::AnimationChannel*>>
channels_per_joint;
for (const tinygltf::AnimationChannel& channel : gltf_animation->channels) {
// Reject if no node is targetted.
if (channel.target_node == -1) {
continue;
}
// Reject if path isn't about skeleton animation.
bool valid_target = false;
for (const char* path : {"translation", "rotation", "scale"}) {
valid_target |= channel.target_path == path;
}
if (!valid_target) {
continue;
}
const tinygltf::Node& target_node = m_model.nodes[channel.target_node];
channels_per_joint[target_node.name.c_str()].push_back(&channel);
}
// For each joint get all its associated channels, sample them and record
// the samples in the joint track
const ozz::span<const char* const> joint_names = skeleton.joint_names();
for (int i = 0; i < num_joints; i++) {
auto& channels = channels_per_joint[joint_names[i]];
auto& track = _animation->tracks[i];
for (auto& channel : channels) {
auto& sampler = gltf_animation->samplers[channel->sampler];
if (!SampleAnimationChannel(m_model, sampler, channel->target_path,
_sampling_rate, &_animation->duration,
&track)) {
return false;
}
}
const tinygltf::Node* node = FindNodeByName(joint_names[i]);
assert(node != nullptr);
// Pads the bind pose transform for any joints which do not have an
// associated channel for this animation
if (track.translations.empty()) {
track.translations.push_back(CreateTranslationBindPoseKey(*node));
}
if (track.rotations.empty()) {
track.rotations.push_back(CreateRotationBindPoseKey(*node));
}
if (track.scales.empty()) {
track.scales.push_back(CreateScaleBindPoseKey(*node));
}
}
ozz::log::LogV() << "Processed animation '" << _animation->name
<< "' (tracks: " << _animation->tracks.size()
<< ", duration: " << _animation->duration << "s)."
<< std::endl;
if (!_animation->Validate()) {
ozz::log::Err() << "Animation '" << _animation->name
<< "' failed validation." << std::endl;
return false;
}
return true;
}
bool SampleAnimationChannel(
const tinygltf::Model& _model, const tinygltf::AnimationSampler& _sampler,
const std::string& _target_path, float _sampling_rate, float* _duration,
ozz::animation::offline::RawAnimation::JointTrack* _track) {
// Validate interpolation type.
if (_sampler.interpolation.empty()) {
ozz::log::Err() << "Invalid sampler interpolation." << std::endl;
return false;
}
auto& input = m_model.accessors[_sampler.input];
assert(input.maxValues.size() == 1);
// The max[0] property of the input accessor is the animation duration
// this is required to be present by the spec:
// "Animation Sampler's input accessor must have min and max properties
// defined."
const float duration = static_cast<float>(input.maxValues[0]);
// If this channel's duration is larger than the animation's duration
// then increase the animation duration to match.
if (duration > *_duration) {
*_duration = duration;
}
assert(input.type == TINYGLTF_TYPE_SCALAR);
auto& _output = m_model.accessors[_sampler.output];
assert(_output.type == TINYGLTF_TYPE_VEC3 ||
_output.type == TINYGLTF_TYPE_VEC4);
const ozz::span<const float> timestamps = BufferView<float>(_model, input);
if (timestamps.empty()) {
return true;
}
// Builds keyframes.
bool valid = false;
if (_target_path == "translation") {
valid =
SampleChannel(m_model, _sampler.interpolation, _output, timestamps,
_sampling_rate, duration, &_track->translations);
} else if (_target_path == "rotation") {
valid =
SampleChannel(m_model, _sampler.interpolation, _output, timestamps,
_sampling_rate, duration, &_track->rotations);
if (valid) {
// Normalize quaternions.
for (auto& key : _track->rotations) {
key.value = ozz::math::Normalize(key.value);
}
}
} else if (_target_path == "scale") {
valid =
SampleChannel(m_model, _sampler.interpolation, _output, timestamps,
_sampling_rate, duration, &_track->scales);
} else {
assert(false && "Invalid target path");
}
return valid;
}
// Returns all skins belonging to a given gltf scene
ozz::vector<tinygltf::Skin> GetSkinsForScene(
const tinygltf::Scene& _scene) const {
ozz::set<int> open;
ozz::set<int> found;
for (int nodeIndex : _scene.nodes) {
open.insert(nodeIndex);
}
while (!open.empty()) {
int nodeIndex = *open.begin();
found.insert(nodeIndex);
open.erase(nodeIndex);
auto& node = m_model.nodes[nodeIndex];
for (int childIndex : node.children) {
open.insert(childIndex);
}
}
ozz::vector<tinygltf::Skin> skins;
for (const tinygltf::Skin& skin : m_model.skins) {
if (!skin.joints.empty() && found.find(skin.joints[0]) != found.end()) {
skins.push_back(skin);
}
}
return skins;
}
const tinygltf::Node* FindNodeByName(const std::string& _name) const {
for (const tinygltf::Node& node : m_model.nodes) {
if (node.name == _name) {
return &node;
}
}
return nullptr;
}
// no support for user-defined tracks
NodeProperties GetNodeProperties(const char*) override {
return NodeProperties();
}
bool Import(const char*, const char*, const char*, NodeProperty::Type, float,
ozz::animation::offline::RawFloatTrack*) override {
return false;
}
bool Import(const char*, const char*, const char*, NodeProperty::Type, float,
ozz::animation::offline::RawFloat2Track*) override {
return false;
}
bool Import(const char*, const char*, const char*, NodeProperty::Type, float,
ozz::animation::offline::RawFloat3Track*) override {
return false;
}
bool Import(const char*, const char*, const char*, NodeProperty::Type, float,
ozz::animation::offline::RawFloat4Track*) override {
return false;
}
tinygltf::TinyGLTF m_loader;
tinygltf::Model m_model;
};
int main(int _argc, const char** _argv) {
GltfImporter converter;
return converter(_argc, _argv);
}
@@ -0,0 +1,102 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "ozz/animation/offline/raw_animation.h"
#include "ozz/animation/runtime/skeleton.h"
namespace ozz {
namespace animation {
namespace offline {
RawAnimation::RawAnimation() : duration(1.f) {}
namespace {
// Implements key frames' time range and ordering checks.
// See AnimationBuilder::Create for more details.
template <typename _Key>
static bool ValidateTrack(const typename ozz::vector<_Key>& _track,
float _duration) {
float previous_time = -1.f;
for (size_t k = 0; k < _track.size(); ++k) {
const float frame_time = _track[k].time;
// Tests frame's time is in range [0:duration].
if (frame_time < 0.f || frame_time > _duration) {
return false;
}
// Tests that frames are sorted.
if (frame_time <= previous_time) {
return false;
}
previous_time = frame_time;
}
return true; // Validated.
}
} // namespace
bool RawAnimation::JointTrack::Validate(float _duration) const {
return ValidateTrack<TranslationKey>(translations, _duration) &&
ValidateTrack<RotationKey>(rotations, _duration) &&
ValidateTrack<ScaleKey>(scales, _duration);
}
bool RawAnimation::Validate() const {
if (duration <= 0.f) { // Tests duration is valid.
return false;
}
if (tracks.size() > Skeleton::kMaxJoints) { // Tests number of tracks.
return false;
}
// Ensures that all key frames' time are valid, ie: in a strict ascending
// order and within range [0:duration].
bool valid = true;
for (size_t i = 0; valid && i < tracks.size(); ++i) {
valid = tracks[i].Validate(duration);
}
return valid; // *this is valid.
}
size_t RawAnimation::size() const {
size_t size = sizeof(*this);
// Accumulates keyframes size.
const size_t tracks_count = tracks.size();
for (size_t i = 0; i < tracks_count; ++i) {
size += tracks[i].translations.size() * sizeof(TranslationKey);
size += tracks[i].rotations.size() * sizeof(RotationKey);
size += tracks[i].scales.size() * sizeof(ScaleKey);
}
// Accumulates tracks.
size += tracks_count * sizeof(JointTrack);
size += name.size();
return size;
}
} // namespace offline
} // namespace animation
} // namespace ozz
@@ -0,0 +1,173 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "ozz/animation/offline/raw_animation.h"
#include "ozz/base/io/archive.h"
#include "ozz/base/maths/math_archive.h"
#include "ozz/base/containers/string_archive.h"
#include "ozz/base/containers/vector_archive.h"
#include "ozz/base/log.h"
namespace ozz {
namespace io {
void Extern<animation::offline::RawAnimation>::Save(
OArchive& _archive, const animation::offline::RawAnimation* _animations,
size_t _count) {
for (size_t i = 0; i < _count; ++i) {
const animation::offline::RawAnimation& animation = _animations[i];
_archive << animation.duration;
_archive << animation.tracks;
_archive << animation.name;
}
}
void Extern<animation::offline::RawAnimation>::Load(
IArchive& _archive, animation::offline::RawAnimation* _animations,
size_t _count, uint32_t _version) {
if (_version < 3) {
log::Err() << "Unsupported RawAnimation version " << _version << "."
<< std::endl;
return;
}
for (size_t i = 0; i < _count; ++i) {
animation::offline::RawAnimation& animation = _animations[i];
_archive >> animation.duration;
_archive >> animation.tracks;
_archive >> animation.name;
}
}
// RawAnimation::*Keys' version can be declared locally as it will be saved from
// this cpp file only.
OZZ_IO_TYPE_VERSION(1, animation::offline::RawAnimation::JointTrack)
template <>
struct Extern<animation::offline::RawAnimation::JointTrack> {
static void Save(OArchive& _archive,
const animation::offline::RawAnimation::JointTrack* _tracks,
size_t _count) {
for (size_t i = 0; i < _count; ++i) {
const animation::offline::RawAnimation::JointTrack& track = _tracks[i];
_archive << track.translations;
_archive << track.rotations;
_archive << track.scales;
}
}
static void Load(IArchive& _archive,
animation::offline::RawAnimation::JointTrack* _tracks,
size_t _count, uint32_t _version) {
(void)_version;
for (size_t i = 0; i < _count; ++i) {
animation::offline::RawAnimation::JointTrack& track = _tracks[i];
_archive >> track.translations;
_archive >> track.rotations;
_archive >> track.scales;
}
}
};
OZZ_IO_TYPE_VERSION(1, animation::offline::RawAnimation::TranslationKey)
template <>
struct Extern<animation::offline::RawAnimation::TranslationKey> {
static void Save(
OArchive& _archive,
const animation::offline::RawAnimation::TranslationKey* _keys,
size_t _count) {
for (size_t i = 0; i < _count; ++i) {
const animation::offline::RawAnimation::TranslationKey& key = _keys[i];
_archive << key.time;
_archive << key.value;
}
}
static void Load(IArchive& _archive,
animation::offline::RawAnimation::TranslationKey* _keys,
size_t _count, uint32_t _version) {
(void)_version;
for (size_t i = 0; i < _count; ++i) {
animation::offline::RawAnimation::TranslationKey& key = _keys[i];
_archive >> key.time;
_archive >> key.value;
}
}
};
OZZ_IO_TYPE_VERSION(1, animation::offline::RawAnimation::RotationKey)
template <>
struct Extern<animation::offline::RawAnimation::RotationKey> {
static void Save(OArchive& _archive,
const animation::offline::RawAnimation::RotationKey* _keys,
size_t _count) {
for (size_t i = 0; i < _count; ++i) {
const animation::offline::RawAnimation::RotationKey& key = _keys[i];
_archive << key.time;
_archive << key.value;
}
}
static void Load(IArchive& _archive,
animation::offline::RawAnimation::RotationKey* _keys,
size_t _count, uint32_t _version) {
(void)_version;
for (size_t i = 0; i < _count; ++i) {
animation::offline::RawAnimation::RotationKey& key = _keys[i];
_archive >> key.time;
_archive >> key.value;
}
}
};
OZZ_IO_TYPE_VERSION(1, animation::offline::RawAnimation::ScaleKey)
template <>
struct Extern<animation::offline::RawAnimation::ScaleKey> {
static void Save(OArchive& _archive,
const animation::offline::RawAnimation::ScaleKey* _keys,
size_t _count) {
for (size_t i = 0; i < _count; ++i) {
const animation::offline::RawAnimation::ScaleKey& key = _keys[i];
_archive << key.time;
_archive << key.value;
}
}
static void Load(IArchive& _archive,
animation::offline::RawAnimation::ScaleKey* _keys,
size_t _count, uint32_t _version) {
(void)_version;
for (size_t i = 0; i < _count; ++i) {
animation::offline::RawAnimation::ScaleKey& key = _keys[i];
_archive >> key.time;
_archive >> key.value;
}
}
};
} // namespace io
} // namespace ozz
@@ -0,0 +1,152 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "ozz/animation/offline/raw_animation_utils.h"
#include <algorithm>
#include <limits>
namespace ozz {
namespace animation {
namespace offline {
// Translation interpolation method.
// This must be the same Lerp as the one used by the sampling job.
math::Float3 LerpTranslation(const math::Float3& _a, const math::Float3& _b,
float _alpha) {
return math::Lerp(_a, _b, _alpha);
}
// Rotation interpolation method.
// This must be the same Lerp as the one used by the sampling job.
// The goal is to take the shortest path between _a and _b. This code replicates
// this behavior that is actually not done at runtime, but when building the
// animation.
math::Quaternion LerpRotation(const math::Quaternion& _a,
const math::Quaternion& _b, float _alpha) {
// Finds the shortest path. This is done by the AnimationBuilder for runtime
// animations.
const float dot = _a.x * _b.x + _a.y * _b.y + _a.z * _b.z + _a.w * _b.w;
return math::NLerp(_a, dot < 0.f ? -_b : _b, _alpha); // _b an -_b are the
// same rotation.
}
// Scale interpolation method.
// This must be the same Lerp as the one used by the sampling job.
math::Float3 LerpScale(const math::Float3& _a, const math::Float3& _b,
float _alpha) {
return math::Lerp(_a, _b, _alpha);
}
namespace {
// The next functions are used to sample a RawAnimation. This feature is not
// part of ozz sdk, as RawAnimation is a intermediate format used to build the
// runtime animation.
// Less comparator, used by search algorithm to walk through track sorted
// keyframes
template <typename _Key>
bool Less(const _Key& _left, const _Key& _right) {
return _left.time < _right.time;
}
// Samples a component (translation, rotation or scale) of a track.
template <typename _Track, typename _Lerp>
typename _Track::value_type::Value SampleComponent(const _Track& _track,
const _Lerp& _lerp,
float _time) {
if (_track.size() == 0) {
// Return identity if there's no key for this track.
return _Track::value_type::identity();
} else if (_time <= _track.front().time) {
// Returns the first keyframe if _time is before the first keyframe.
return _track.front().value;
} else if (_time >= _track.back().time) {
// Returns the last keyframe if _time is before the last keyframe.
return _track.back().value;
} else {
// Needs to interpolate the 2 keyframes before and after _time.
assert(_track.size() >= 2);
// First find the 2 keys.
const typename _Track::value_type cmp = {_time,
_Track::value_type::identity()};
typename _Track::const_pointer it =
std::lower_bound(array_begin(_track), array_end(_track), cmp,
Less<typename _Track::value_type>);
assert(it > array_begin(_track) && it < array_end(_track));
// Then interpolate them at t = _time.
const typename _Track::const_reference right = it[0];
const typename _Track::const_reference left = it[-1];
const float alpha = (_time - left.time) / (right.time - left.time);
return _lerp(left.value, right.value, alpha);
}
}
void SampleTrack_NoValidate(const RawAnimation::JointTrack& _track, float _time,
ozz::math::Transform* _transform) {
_transform->translation =
SampleComponent(_track.translations, LerpTranslation, _time);
_transform->rotation = SampleComponent(_track.rotations, LerpRotation, _time);
_transform->scale = SampleComponent(_track.scales, LerpScale, _time);
}
} // namespace
bool SampleTrack(const RawAnimation::JointTrack& _track, float _time,
ozz::math::Transform* _transform) {
if (!_track.Validate(std::numeric_limits<float>::infinity())) {
return false;
}
SampleTrack_NoValidate(_track, _time, _transform);
return true;
}
bool SampleAnimation(const RawAnimation& _animation, float _time,
const span<ozz::math::Transform>& _transforms) {
if (!_animation.Validate()) {
return false;
}
if (_animation.tracks.size() > _transforms.size()) {
return false;
}
for (size_t i = 0; i < _animation.tracks.size(); ++i) {
SampleTrack_NoValidate(_animation.tracks[i], _time, _transforms.begin() + i);
}
return true;
}
FixedRateSamplingTime::FixedRateSamplingTime(float _duration, float _frequency)
: duration_(_duration),
period_(1.f / _frequency),
num_keys_(static_cast<size_t>(std::ceil(1.f + _duration * _frequency))) {}
} // namespace offline
} // namespace animation
} // namespace ozz
@@ -0,0 +1,63 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "ozz/animation/offline/raw_skeleton.h"
#include "ozz/animation/runtime/skeleton.h"
namespace ozz {
namespace animation {
namespace offline {
RawSkeleton::RawSkeleton() {}
RawSkeleton::~RawSkeleton() {}
bool RawSkeleton::Validate() const {
if (num_joints() > Skeleton::kMaxJoints) {
return false;
}
return true;
}
namespace {
struct JointCounter {
JointCounter() : num_joints(0) {}
void operator()(const RawSkeleton::Joint&, const RawSkeleton::Joint*) {
++num_joints;
}
int num_joints;
};
} // namespace
// Iterates through all the root children and count them.
int RawSkeleton::num_joints() const {
return IterateJointsDF(*this, JointCounter()).num_joints;
}
} // namespace offline
} // namespace animation
} // namespace ozz
@@ -0,0 +1,86 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "ozz/animation/offline/raw_skeleton.h"
#include "ozz/base/io/archive.h"
#include "ozz/base/maths/math_archive.h"
#include "ozz/base/containers/string_archive.h"
#include "ozz/base/containers/vector_archive.h"
namespace ozz {
namespace io {
void Extern<animation::offline::RawSkeleton>::Save(
OArchive& _archive, const animation::offline::RawSkeleton* _skeletons,
size_t _count) {
for (size_t i = 0; i < _count; ++i) {
const animation::offline::RawSkeleton& skeleton = _skeletons[i];
_archive << skeleton.roots;
}
}
void Extern<animation::offline::RawSkeleton>::Load(
IArchive& _archive, animation::offline::RawSkeleton* _skeletons,
size_t _count, uint32_t _version) {
(void)_version;
for (size_t i = 0; i < _count; ++i) {
animation::offline::RawSkeleton& skeleton = _skeletons[i];
_archive >> skeleton.roots;
}
}
// RawSkeleton::Joint' version can be declared locally as it will be saved from
// this cpp file only.
OZZ_IO_TYPE_VERSION(1, animation::offline::RawSkeleton::Joint)
template <>
struct Extern<animation::offline::RawSkeleton::Joint> {
static void Save(OArchive& _archive,
const animation::offline::RawSkeleton::Joint* _joints,
size_t _count) {
for (size_t i = 0; i < _count; ++i) {
const animation::offline::RawSkeleton::Joint& joint = _joints[i];
_archive << joint.name;
_archive << joint.transform;
_archive << joint.children;
}
}
static void Load(IArchive& _archive,
animation::offline::RawSkeleton::Joint* _joints,
size_t _count, uint32_t _version) {
(void)_version;
for (size_t i = 0; i < _count; ++i) {
animation::offline::RawSkeleton::Joint& joint = _joints[i];
_archive >> joint.name;
_archive >> joint.transform;
_archive >> joint.children;
}
}
};
} // namespace io
} // namespace ozz
@@ -0,0 +1,122 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "ozz/animation/offline/raw_track.h"
#include <limits>
#include "ozz/base/io/archive.h"
#include "ozz/base/maths/math_archive.h"
#include "ozz/base/containers/string_archive.h"
#include "ozz/base/containers/vector_archive.h"
namespace ozz {
namespace io {
// Can be declared locally as it's only referenced from this file.
OZZ_IO_TYPE_VERSION_T1(1, typename _ValueType,
animation::offline::RawTrackKeyframe<_ValueType>)
template <typename _ValueType>
struct Extern<animation::offline::RawTrackKeyframe<_ValueType>> {
static void Save(
OArchive& _archive,
const animation::offline::RawTrackKeyframe<_ValueType>* _keyframes,
size_t _count) {
for (size_t i = 0; i < _count; ++i) {
const animation::offline::RawTrackKeyframe<_ValueType>& keyframe =
_keyframes[i];
const uint8_t interp = static_cast<uint8_t>(keyframe.interpolation);
_archive << interp;
_archive << keyframe.ratio;
_archive << keyframe.value;
}
}
static void Load(IArchive& _archive,
animation::offline::RawTrackKeyframe<_ValueType>* _keyframes,
size_t _count, uint32_t _version) {
(void)_version;
for (size_t i = 0; i < _count; ++i) {
animation::offline::RawTrackKeyframe<_ValueType>& keyframe =
_keyframes[i];
uint8_t interp;
_archive >> interp;
keyframe.interpolation =
static_cast<animation::offline::RawTrackInterpolation::Value>(interp);
_archive >> keyframe.ratio;
_archive >> keyframe.value;
}
}
};
} // namespace io
namespace animation {
namespace offline {
namespace internal {
template <typename _ValueType>
bool RawTrack<_ValueType>::Validate() const {
float previous_ratio = -1.f;
for (size_t k = 0; k < keyframes.size(); ++k) {
const float frame_ratio = keyframes[k].ratio;
// Tests frame's ratio is in range [0:1].
if (frame_ratio < 0.f || frame_ratio > 1.f) {
return false;
}
// Tests that frames are sorted.
if (frame_ratio <= previous_ratio) {
return false;
}
previous_ratio = frame_ratio;
}
return true; // Validated.
}
template <typename _ValueType>
void RawTrack<_ValueType>::Save(io::OArchive& _archive) const {
_archive << keyframes;
_archive << name;
}
template <typename _ValueType>
void RawTrack<_ValueType>::Load(io::IArchive& _archive, uint32_t _version) {
(void)_version;
assert(_version == 1);
_archive >> keyframes;
_archive >> name;
}
// Explicitly instantiate supported raw tracks.
template struct RawTrack<float>;
template struct RawTrack<math::Float2>;
template struct RawTrack<math::Float3>;
template struct RawTrack<math::Float4>;
template struct RawTrack<math::Quaternion>;
} // namespace internal
} // namespace offline
} // namespace animation
} // namespace ozz
@@ -0,0 +1,156 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "ozz/animation/offline/skeleton_builder.h"
#include <cstring>
#include "ozz/animation/offline/raw_skeleton.h"
#include "ozz/animation/runtime/skeleton.h"
#include "ozz/base/containers/vector.h"
#include "ozz/base/maths/soa_transform.h"
#include "ozz/base/memory/allocator.h"
namespace ozz {
namespace animation {
namespace offline {
namespace {
// Stores each traversed joint in a vector.
struct JointLister {
explicit JointLister(int _num_joints) { linear_joints.reserve(_num_joints); }
void operator()(const RawSkeleton::Joint& _current,
const RawSkeleton::Joint* _parent) {
// Looks for the "lister" parent.
int16_t parent = Skeleton::kNoParent;
if (_parent) {
// Start searching from the last joint.
int16_t j = static_cast<int16_t>(linear_joints.size()) - 1;
for (; j >= 0; --j) {
if (linear_joints[j].joint == _parent) {
parent = j;
break;
}
}
assert(parent >= 0);
}
const Joint listed = {&_current, parent};
linear_joints.push_back(listed);
}
struct Joint {
const RawSkeleton::Joint* joint;
int16_t parent;
};
// Array of joints in the traversed DAG order.
ozz::vector<Joint> linear_joints;
};
} // namespace
// Validates the RawSkeleton and fills a Skeleton.
// Uses RawSkeleton::IterateJointsDF to traverse in DAG depth-first order.
// Building skeleton hierarchy in depth first order make it easier to iterate a
// skeleton sub-hierarchy.
unique_ptr<ozz::animation::Skeleton> SkeletonBuilder::operator()(
const RawSkeleton& _raw_skeleton) const {
// Tests _raw_skeleton validity.
if (!_raw_skeleton.Validate()) {
return nullptr;
}
// Everything is fine, allocates and fills the skeleton.
// Will not fail.
unique_ptr<ozz::animation::Skeleton> skeleton = make_unique<Skeleton>();
const int num_joints = _raw_skeleton.num_joints();
// Iterates through all the joint of the raw skeleton and fills a sorted joint
// list.
// Iteration order defines runtime skeleton joint ordering.
JointLister lister(num_joints);
IterateJointsDF<JointLister&>(_raw_skeleton, lister);
assert(static_cast<int>(lister.linear_joints.size()) == num_joints);
// Computes name's buffer size.
size_t chars_size = 0;
for (int i = 0; i < num_joints; ++i) {
const RawSkeleton::Joint& current = *lister.linear_joints[i].joint;
chars_size += (current.name.size() + 1) * sizeof(char);
}
// Allocates all skeleton members.
char* cursor = skeleton->Allocate(chars_size, num_joints);
// Copy names. All names are allocated in a single buffer. Only the first name
// is set, all other names array entries must be initialized.
for (int i = 0; i < num_joints; ++i) {
const RawSkeleton::Joint& current = *lister.linear_joints[i].joint;
skeleton->joint_names_[i] = cursor;
strcpy(cursor, current.name.c_str());
cursor += (current.name.size() + 1) * sizeof(char);
}
// Transfers sorted joints hierarchy to the new skeleton.
for (int i = 0; i < num_joints; ++i) {
skeleton->joint_parents_[i] = lister.linear_joints[i].parent;
}
// Transfers t-poses.
const math::SimdFloat4 w_axis = math::simd_float4::w_axis();
const math::SimdFloat4 zero = math::simd_float4::zero();
const math::SimdFloat4 one = math::simd_float4::one();
for (int i = 0; i < skeleton->num_soa_joints(); ++i) {
math::SimdFloat4 translations[4];
math::SimdFloat4 scales[4];
math::SimdFloat4 rotations[4];
for (int j = 0; j < 4; ++j) {
if (i * 4 + j < num_joints) {
const RawSkeleton::Joint& src_joint =
*lister.linear_joints[i * 4 + j].joint;
translations[j] =
math::simd_float4::Load3PtrU(&src_joint.transform.translation.x);
rotations[j] = math::NormalizeSafe4(
math::simd_float4::LoadPtrU(&src_joint.transform.rotation.x),
w_axis);
scales[j] = math::simd_float4::Load3PtrU(&src_joint.transform.scale.x);
} else {
translations[j] = zero;
rotations[j] = w_axis;
scales[j] = one;
}
}
// Fills the SoaTransform structure.
math::Transpose4x3(translations,
&skeleton->joint_bind_poses_[i].translation.x);
math::Transpose4x4(rotations, &skeleton->joint_bind_poses_[i].rotation.x);
math::Transpose4x3(scales, &skeleton->joint_bind_poses_[i].scale.x);
}
return skeleton; // Success.
}
} // namespace offline
} // namespace animation
} // namespace ozz
@@ -0,0 +1,42 @@
if(NOT ozz_build_tools)
return()
endif()
add_subdirectory(${PROJECT_SOURCE_DIR}/extern/jsoncpp json)
add_library(ozz_animation_tools STATIC
${PROJECT_SOURCE_DIR}/include/ozz/animation/offline/tools/import2ozz.h
import2ozz.cc
import2ozz_anim.h
import2ozz_anim.cc
import2ozz_config.h
import2ozz_config.cc
import2ozz_skel.h
import2ozz_skel.cc
import2ozz_track.h
import2ozz_track.cc)
target_link_libraries(ozz_animation_tools
ozz_animation_offline
ozz_options
json)
set_target_properties(ozz_animation_tools
PROPERTIES FOLDER "ozz/tools")
install(TARGETS ozz_animation_tools DESTINATION lib)
fuse_target("ozz_animation_tools")
if(NOT EMSCRIPTEN)
add_executable(dump2ozz
dump2ozz.cc)
target_link_libraries(dump2ozz
ozz_animation_tools
ozz_options)
# Uses a fake import file to dump reference and default configurations.
add_custom_command(TARGET dump2ozz POST_BUILD
COMMAND dump2ozz "--file=${CMAKE_CURRENT_SOURCE_DIR}/dump2ozz.cc" "--config={\"skeleton\": {\"import\":{\"enable\":false}},\"animations\":[]}" "--config_dump_reference=${CMAKE_CURRENT_SOURCE_DIR}/reference.json" VERBATIM)
set_target_properties(dump2ozz
PROPERTIES FOLDER "ozz/tools")
endif()
@@ -0,0 +1,78 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "ozz/animation/offline/tools/import2ozz.h"
// Mocks OzzImporter so it can be used to dump default and reference
// configurations.
class DumpConverter : public ozz::animation::offline::OzzImporter {
public:
DumpConverter() {}
~DumpConverter() {}
private:
virtual bool Load(const char*) { return true; }
virtual bool Import(ozz::animation::offline::RawSkeleton*, const NodeType&) {
return true;
}
virtual AnimationNames GetAnimationNames() { return AnimationNames(); }
virtual bool Import(const char*, const ozz::animation::Skeleton&, float,
ozz::animation::offline::RawAnimation*) {
return true;
}
virtual NodeProperties GetNodeProperties(const char*) {
return NodeProperties();
}
virtual bool Import(const char*, const char*, const char*, NodeProperty::Type,
float, ozz::animation::offline::RawFloatTrack*) {
return true;
}
virtual bool Import(const char*, const char*, const char*, NodeProperty::Type,
float, ozz::animation::offline::RawFloat2Track*) {
return true;
}
virtual bool Import(const char*, const char*, const char*, NodeProperty::Type,
float, ozz::animation::offline::RawFloat3Track*) {
return true;
}
virtual bool Import(const char*, const char*, const char*, NodeProperty::Type,
float, ozz::animation::offline::RawFloat4Track*) {
return true;
}
};
int main(int _argc, const char** _argv) {
DumpConverter converter;
return converter(_argc, _argv);
}
@@ -0,0 +1,191 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "ozz/animation/offline/tools/import2ozz.h"
#include <json/json.h>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include "animation/offline/tools/import2ozz_anim.h"
#include "animation/offline/tools/import2ozz_config.h"
#include "animation/offline/tools/import2ozz_skel.h"
#include "ozz/base/io/stream.h"
#include "ozz/base/log.h"
#include "ozz/options/options.h"
// Declares command line options.
OZZ_OPTIONS_DECLARE_STRING(file, "Specifies input file", "", true)
static bool ValidateEndianness(const ozz::options::Option& _option,
int /*_argc*/) {
const ozz::options::StringOption& option =
static_cast<const ozz::options::StringOption&>(_option);
bool valid = std::strcmp(option.value(), "native") == 0 ||
std::strcmp(option.value(), "little") == 0 ||
std::strcmp(option.value(), "big") == 0;
if (!valid) {
ozz::log::Err() << "Invalid endianness option \"" << option << "\""
<< std::endl;
}
return valid;
}
OZZ_OPTIONS_DECLARE_STRING_FN(
endian,
"Selects output endianness mode. Can be \"native\" (same as current "
"platform), \"little\" or \"big\".",
"native", false, &ValidateEndianness)
ozz::Endianness InitializeEndianness() {
// Initializes output endianness from options.
ozz::Endianness endianness = ozz::GetNativeEndianness();
if (std::strcmp(OPTIONS_endian, "little") == 0) {
endianness = ozz::kLittleEndian;
} else if (std::strcmp(OPTIONS_endian, "big") == 0) {
endianness = ozz::kBigEndian;
}
ozz::log::LogV() << (endianness == ozz::kLittleEndian ? "Little" : "Big")
<< " endian output binary format selected." << std::endl;
return endianness;
}
static bool ValidateLogLevel(const ozz::options::Option& _option,
int /*_argc*/) {
const ozz::options::StringOption& option =
static_cast<const ozz::options::StringOption&>(_option);
bool valid = std::strcmp(option.value(), "verbose") == 0 ||
std::strcmp(option.value(), "standard") == 0 ||
std::strcmp(option.value(), "silent") == 0;
if (!valid) {
ozz::log::Err() << "Invalid log level option \"" << option << "\""
<< std::endl;
}
return valid;
}
OZZ_OPTIONS_DECLARE_STRING_FN(
log_level,
"Selects log level. Can be \"silent\", \"standard\" or \"verbose\".",
"standard", false, &ValidateLogLevel)
void InitializeLogLevel() {
ozz::log::Level log_level = ozz::log::GetLevel();
if (std::strcmp(OPTIONS_log_level, "silent") == 0) {
log_level = ozz::log::kSilent;
} else if (std::strcmp(OPTIONS_log_level, "standard") == 0) {
log_level = ozz::log::kStandard;
} else if (std::strcmp(OPTIONS_log_level, "verbose") == 0) {
log_level = ozz::log::kVerbose;
}
ozz::log::SetLevel(log_level);
ozz::log::LogV() << "Verbose log level activated." << std::endl;
}
namespace ozz {
namespace animation {
namespace offline {
int OzzImporter::operator()(int _argc, const char** _argv) {
// Parses arguments.
ozz::options::ParseResult parse_result = ozz::options::ParseCommandLine(
_argc, _argv, "2.0",
"Imports skeleton and animations from a file and converts it to ozz "
"binary raw or runtime data format.");
if (parse_result != ozz::options::kSuccess) {
return parse_result == ozz::options::kExitSuccess ? EXIT_SUCCESS
: EXIT_FAILURE;
}
// Initialize general executable options.
InitializeLogLevel();
const ozz::Endianness endianness = InitializeEndianness();
Json::Value config;
if (!ProcessConfiguration(&config)) {
// Specific error message are reported during configuration processing.
return EXIT_FAILURE;
}
// Ensures file to import actually exist.
if (!ozz::io::File::Exist(OPTIONS_file)) {
ozz::log::Err() << "File \"" << OPTIONS_file << "\" doesn't exist."
<< std::endl;
return EXIT_FAILURE;
}
// Imports animations from the document.
ozz::log::Log() << "Importing file \"" << OPTIONS_file << "\"" << std::endl;
if (!Load(OPTIONS_file)) {
ozz::log::Err() << "Failed to import file \"" << OPTIONS_file << "\"."
<< std::endl;
return EXIT_FAILURE;
}
// Handles skeleton import processing
if (!ImportSkeleton(config, this, endianness)) {
return EXIT_FAILURE;
}
// Handles animations import processing
if (!ImportAnimations(config, this, endianness)) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
ozz::string OzzImporter::BuildFilename(const char* _filename,
const char* _data_name) const {
// Fixup invalid characters for a path.
ozz::string data_name(_data_name);
for (const char c : {'<', '>', ':', '/', '\\', '|', '?', '*'}) {
std::replace(data_name.begin(), data_name.end(), c, '_');
}
// Replaces asterisk with data_name
bool used = false;
ozz::string output(_filename);
for (size_t asterisk = output.find('*'); asterisk != std::string::npos;
used = true, asterisk = output.find('*')) {
output.replace(asterisk, 1, data_name);
}
// Displays a log only if data name was renamed and used as a filename.
if (used && data_name != _data_name) {
ozz::log::Log() << "Resource name \"" << _data_name
<< "\" was changed to \"" << data_name
<< "\" in order to be used as a valid filename."
<< std::endl;
}
return output;
}
} // namespace offline
} // namespace animation
} // namespace ozz
@@ -0,0 +1,415 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "animation/offline/tools/import2ozz_anim.h"
#include <json/json.h>
#include <cstdlib>
#include <cstring>
#include "animation/offline/tools/import2ozz_config.h"
#include "animation/offline/tools/import2ozz_track.h"
#include "ozz/animation/offline/additive_animation_builder.h"
#include "ozz/animation/offline/animation_builder.h"
#include "ozz/animation/offline/animation_optimizer.h"
#include "ozz/animation/offline/raw_animation.h"
#include "ozz/animation/offline/raw_skeleton.h"
#include "ozz/animation/offline/skeleton_builder.h"
#include "ozz/animation/offline/tools/import2ozz.h"
#include "ozz/animation/runtime/animation.h"
#include "ozz/animation/runtime/skeleton.h"
#include "ozz/base/io/archive.h"
#include "ozz/base/io/stream.h"
#include "ozz/base/log.h"
#include "ozz/base/maths/soa_transform.h"
#include "ozz/base/memory/unique_ptr.h"
#include "ozz/options/options.h"
namespace ozz {
namespace animation {
namespace offline {
namespace {
void DisplaysOptimizationstatistics(const RawAnimation& _non_optimized,
const RawAnimation& _optimized) {
size_t opt_translations = 0, opt_rotations = 0, opt_scales = 0;
for (size_t i = 0; i < _optimized.tracks.size(); ++i) {
const RawAnimation::JointTrack& track = _optimized.tracks[i];
opt_translations += track.translations.size();
opt_rotations += track.rotations.size();
opt_scales += track.scales.size();
}
size_t non_opt_translations = 0, non_opt_rotations = 0, non_opt_scales = 0;
for (size_t i = 0; i < _non_optimized.tracks.size(); ++i) {
const RawAnimation::JointTrack& track = _non_optimized.tracks[i];
non_opt_translations += track.translations.size();
non_opt_rotations += track.rotations.size();
non_opt_scales += track.scales.size();
}
// Computes optimization ratios.
float translation_ratio = opt_translations != 0
? 1.f * non_opt_translations / opt_translations
: 0.f;
float rotation_ratio =
opt_rotations != 0 ? 1.f * non_opt_rotations / opt_rotations : 0.f;
float scale_ratio = opt_scales != 0 ? 1.f * non_opt_scales / opt_scales : 0.f;
ozz::log::LogV log;
ozz::log::FloatPrecision precision_scope(log, 1);
log << "Optimization stage results:" << std::endl;
log << " - Translations: " << translation_ratio << ":1" << std::endl;
log << " - Rotations: " << rotation_ratio << ":1" << std::endl;
log << " - Scales: " << scale_ratio << ":1" << std::endl;
}
unique_ptr<ozz::animation::Skeleton> LoadSkeleton(const char* _path) {
// Reads the skeleton from the binary ozz stream.
unique_ptr<ozz::animation::Skeleton> skeleton;
{
if (*_path == 0) {
ozz::log::Err() << "Missing input skeleton file from json config."
<< std::endl;
return nullptr;
}
ozz::log::LogV() << "Opens input skeleton ozz binary file: " << _path
<< std::endl;
ozz::io::File file(_path, "rb");
if (!file.opened()) {
ozz::log::Err() << "Failed to open input skeleton ozz binary file: \""
<< _path << "\"" << std::endl;
return nullptr;
}
ozz::io::IArchive archive(&file);
// File could contain a RawSkeleton or a Skeleton.
if (archive.TestTag<RawSkeleton>()) {
ozz::log::LogV() << "Reading RawSkeleton from file." << std::endl;
// Reading the skeleton cannot file.
RawSkeleton raw_skeleton;
archive >> raw_skeleton;
// Builds runtime skeleton.
ozz::log::LogV() << "Builds runtime skeleton." << std::endl;
SkeletonBuilder builder;
skeleton = builder(raw_skeleton);
if (!skeleton) {
ozz::log::Err() << "Failed to build runtime skeleton." << std::endl;
return nullptr;
}
} else if (archive.TestTag<Skeleton>()) {
// Reads input archive to the runtime skeleton.
// This operation cannot fail.
skeleton = make_unique<Skeleton>();
archive >> *skeleton;
} else {
ozz::log::Err() << "Failed to read input skeleton from binary file: "
<< _path << std::endl;
return nullptr;
}
}
return skeleton;
}
vector<math::Transform> SkeletonBindPoseSoAToAoS(const Skeleton& _skeleton) {
// Copy skeleton bind pose to AoS form.
vector<math::Transform> transforms(_skeleton.num_joints());
for (int i = 0; i < _skeleton.num_soa_joints(); ++i) {
const math::SoaTransform& soa_transform = _skeleton.joint_bind_poses()[i];
math::SimdFloat4 translation[4];
math::SimdFloat4 rotation[4];
math::SimdFloat4 scale[4];
math::Transpose3x4(&soa_transform.translation.x, translation);
math::Transpose4x4(&soa_transform.rotation.x, rotation);
math::Transpose3x4(&soa_transform.scale.x, scale);
for (int j = 0; j < 4 && i * 4 + j < _skeleton.num_joints(); ++j) {
math::Transform& out = transforms[i * 4 + j];
math::Store3PtrU(translation[j], &out.translation.x);
math::StorePtrU(rotation[j], &out.rotation.x);
math::Store3PtrU(scale[j], &out.scale.x);
}
}
return transforms;
}
bool Export(OzzImporter& _importer, const RawAnimation& _input_animation,
const Skeleton& _skeleton, const Json::Value& _config,
const ozz::Endianness _endianness) {
// Raw animation to build and output. Initial setup is just a copy.
RawAnimation raw_animation = _input_animation;
// Optimizes animation if option is enabled.
// Must be done before converting to additive, to be sure hierarchy length is
// valid when optimizing.
if (_config["optimize"].asBool()) {
ozz::log::Log() << "Optimizing animation." << std::endl;
AnimationOptimizer optimizer;
// Setup optimizer from config parameters.
const Json::Value& tolerances = _config["optimization_settings"];
optimizer.setting.tolerance = tolerances["tolerance"].asFloat();
optimizer.setting.distance = tolerances["distance"].asFloat();
// Builds per joint settings.
const Json::Value& joints_config = tolerances["override"];
for (Json::ArrayIndex i = 0; i < joints_config.size(); ++i) {
const Json::Value& joint_config = joints_config[i];
// Prepares setting.
AnimationOptimizer::Setting setting;
setting.tolerance = joint_config["tolerance"].asFloat();
setting.distance = joint_config["distance"].asFloat();
// Push it for all matching joints.
// Settings are overwritten if one has already been pushed.
bool found = false;
const char* name_pattern = joint_config["name"].asCString();
for (int j = 0; j < _skeleton.num_joints(); ++j) {
const char* joint_name = _skeleton.joint_names()[j];
if (strmatch(joint_name, name_pattern)) {
found = true;
ozz::log::LogV() << "Found joint \"" << joint_name
<< "\" matching pattern \"" << name_pattern
<< "\" for joint optimization setting override."
<< std::endl;
const AnimationOptimizer::JointsSetting::value_type entry(j, setting);
const bool newly =
optimizer.joints_setting_override.insert(entry).second;
if (!newly) {
ozz::log::Log() << "Redundant optimization setting for pattern \""
<< name_pattern << "\"" << std::endl;
}
}
}
if (!found) {
ozz::log::Log()
<< "No joint found for optimization setting for pattern \""
<< name_pattern << "\"" << std::endl;
}
}
RawAnimation raw_optimized_animation;
if (!optimizer(raw_animation, _skeleton, &raw_optimized_animation)) {
ozz::log::Err() << "Failed to optimize animation." << std::endl;
return false;
}
// Displays optimization statistics.
DisplaysOptimizationstatistics(raw_animation, raw_optimized_animation);
// Brings data back to the raw animation.
raw_animation = raw_optimized_animation;
} else {
ozz::log::LogV() << "Optimization for animation \"" << _input_animation.name
<< "\" is disabled." << std::endl;
}
// Make delta animation if requested.
if (_config["additive"].asBool()) {
ozz::log::Log() << "Makes additive animation." << std::endl;
AdditiveAnimationBuilder additive_builder;
RawAnimation raw_additive;
AdditiveReferenceEnum::Value reference;
bool enum_found = AdditiveReference::GetEnumFromName(
_config["additive_reference"].asCString(), &reference);
assert(enum_found); // Already checked on config side.
bool succeeded = false;
if (enum_found && reference == AdditiveReferenceEnum::kSkeleton) {
const vector<math::Transform> transforms =
SkeletonBindPoseSoAToAoS(_skeleton);
succeeded =
additive_builder(raw_animation, make_span(transforms), &raw_additive);
} else {
succeeded = additive_builder(raw_animation, &raw_additive);
}
if (!succeeded) {
ozz::log::Err() << "Failed to make additive animation." << std::endl;
return false;
}
// Now use additive animation.
raw_animation = raw_additive;
}
// Builds runtime animation.
unique_ptr<Animation> animation;
if (!_config["raw"].asBool()) {
ozz::log::Log() << "Builds runtime animation." << std::endl;
AnimationBuilder builder;
animation = builder(raw_animation);
if (!animation) {
ozz::log::Err() << "Failed to build runtime animation." << std::endl;
return false;
}
}
{
// Prepares output stream. File is a RAII so it will close automatically
// at the end of this scope. Once the file is opened, nothing should fail
// as it would leave an invalid file on the disk.
// Builds output filename.
ozz::string filename = _importer.BuildFilename(
_config["filename"].asCString(), raw_animation.name.c_str());
ozz::log::LogV() << "Opens output file: \"" << filename << "\""
<< std::endl;
ozz::io::File file(filename.c_str(), "wb");
if (!file.opened()) {
ozz::log::Err() << "Failed to open output file: \"" << filename << "\""
<< std::endl;
return false;
}
// Initializes output archive.
ozz::io::OArchive archive(&file, _endianness);
// Fills output archive with the animation.
if (_config["raw"].asBool()) {
ozz::log::Log() << "Outputs RawAnimation to binary archive." << std::endl;
archive << raw_animation;
} else {
ozz::log::Log() << "Outputs Animation to binary archive." << std::endl;
archive << *animation;
}
}
ozz::log::LogV() << "Animation binary archive successfully outputted."
<< std::endl;
return true;
} // namespace
bool ProcessAnimation(OzzImporter& _importer, const char* _animation_name,
const Skeleton& _skeleton, const Json::Value& _config,
const ozz::Endianness _endianness) {
RawAnimation animation;
ozz::log::Log() << "Extracting animation \"" << _animation_name << "\""
<< std::endl;
if (!_importer.Import(_animation_name, _skeleton,
_config["sampling_rate"].asFloat(), &animation)) {
ozz::log::Err() << "Failed to import animation \"" << _animation_name
<< "\"" << std::endl;
return false;
} else {
// Give animation a name
animation.name = _animation_name;
return Export(_importer, animation, _skeleton, _config, _endianness);
}
}
} // namespace
AdditiveReference::EnumNames AdditiveReference::GetNames() {
static const char* kNames[] = {"animation", "skeleton"};
const EnumNames enum_names = {OZZ_ARRAY_SIZE(kNames), kNames};
return enum_names;
}
bool ImportAnimations(const Json::Value& _config, OzzImporter* _importer,
const ozz::Endianness _endianness) {
const Json::Value& skeleton_config = _config["skeleton"];
const Json::Value& animations_config = _config["animations"];
if (animations_config.size() == 0) {
ozz::log::Log() << "Configuration contains no animation import "
"definition, animations import will be skipped."
<< std::endl;
return true;
}
// Get all available animation names.
const OzzImporter::AnimationNames& import_animation_names =
_importer->GetAnimationNames();
// Are there animations available
if (import_animation_names.empty()) {
ozz::log::Err() << "No animation found." << std::endl;
return true;
}
// Iterates all imported animations, build and output them.
bool success = true;
// Import skeleton instance.
unique_ptr<Skeleton> skeleton(
LoadSkeleton(skeleton_config["filename"].asCString()));
success &= skeleton.get() != nullptr;
// Loop though all existing animations, and export those who match
// configuration.
for (Json::ArrayIndex i = 0; success && i < animations_config.size(); ++i) {
const Json::Value& animation_config = animations_config[i];
const char* clip_match = animation_config["clip"].asCString();
if (*clip_match == 0) {
ozz::log::Log() << "No clip name provided. Animation import "
"will be skipped."
<< std::endl;
continue;
}
bool matched = false;
for (size_t j = 0; success && j < import_animation_names.size(); ++j) {
const char* animation_name = import_animation_names[j].c_str();
if (!strmatch(animation_name, clip_match)) {
continue;
}
matched = true;
success = ProcessAnimation(*_importer, animation_name, *skeleton,
animation_config, _endianness);
const Json::Value& tracks_config = animation_config["tracks"];
for (Json::ArrayIndex t = 0; success && t < tracks_config.size(); ++t) {
success = ProcessTracks(*_importer, animation_name, *skeleton,
tracks_config[t], _endianness);
}
}
// Don't display any message if no animation is supposed to be imported.
if (!matched && *clip_match != 0) {
ozz::log::Log() << "No matching animation found for \"" << clip_match
<< "\"." << std::endl;
}
}
return success;
}
} // namespace offline
} // namespace animation
} // namespace ozz
@@ -0,0 +1,60 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#ifndef OZZ_ANIMATION_OFFLINE_TOOLS_IMPORT2OZZ_ANIM_H_
#define OZZ_ANIMATION_OFFLINE_TOOLS_IMPORT2OZZ_ANIM_H_
#include "ozz/base/endianness.h"
#include "ozz/base/platform.h"
#include "animation/offline/tools/import2ozz_config.h"
#include "ozz/animation/offline/tools/import2ozz.h"
namespace Json {
class Value;
}
namespace ozz {
namespace animation {
namespace offline {
class OzzImporter;
bool ImportAnimations(const Json::Value& _config, OzzImporter* _importer,
const ozz::Endianness _endianness);
// Additive reference enum to config string conversions.
struct AdditiveReferenceEnum {
enum Value { kAnimation, kSkeleton };
};
struct AdditiveReference
: JsonEnum<AdditiveReference, AdditiveReferenceEnum::Value> {
static EnumNames GetNames();
};
} // namespace offline
} // namespace animation
} // namespace ozz
#endif // OZZ_ANIMATION_OFFLINE_TOOLS_IMPORT2OZZ_ANIM_H_
@@ -0,0 +1,552 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "animation/offline/tools/import2ozz_config.h"
#include <cstring>
#include <fstream>
#include <sstream>
#include "animation/offline/tools/import2ozz_anim.h"
#include "animation/offline/tools/import2ozz_track.h"
#include "ozz/animation/offline/tools/import2ozz.h"
#include "ozz/animation/offline/animation_optimizer.h"
#include "ozz/animation/offline/track_optimizer.h"
#include "ozz/base/containers/string.h"
#include "ozz/base/log.h"
#include "ozz/options/options.h"
#include <json/json.h>
bool ValidateExclusiveConfigOption(const ozz::options::Option& _option,
int _argc);
OZZ_OPTIONS_DECLARE_STRING_FN(
config, "Specifies input configuration string in json format", "", false,
&ValidateExclusiveConfigOption)
OZZ_OPTIONS_DECLARE_STRING_FN(
config_file, "Specifies input configuration file in json format", "", false,
&ValidateExclusiveConfigOption)
// Validate exclusive config options.
bool ValidateExclusiveConfigOption(const ozz::options::Option& _option,
int _argc) {
(void)_option;
(void)_argc;
bool not_exclusive =
OPTIONS_config_file.value()[0] != 0 && OPTIONS_config.value()[0] != 0;
if (not_exclusive) {
ozz::log::Err() << "--config and --config_file are exclusive options."
<< std::endl;
}
return !not_exclusive;
}
OZZ_OPTIONS_DECLARE_STRING(
config_dump_reference,
"Dumps reference json configuration to specified file.", "", false)
namespace ozz {
namespace animation {
namespace offline {
namespace {
template <typename _Type>
struct ToJsonType;
template <>
struct ToJsonType<int> {
static Json::ValueType type() { return Json::intValue; }
};
template <>
struct ToJsonType<unsigned int> {
static Json::ValueType type() { return Json::uintValue; }
};
template <>
struct ToJsonType<float> {
static Json::ValueType type() { return Json::realValue; }
};
template <>
struct ToJsonType<const char*> {
static Json::ValueType type() { return Json::stringValue; }
};
template <>
struct ToJsonType<bool> {
static Json::ValueType type() { return Json::booleanValue; }
};
const char* JsonTypeToString(Json::ValueType _type) {
switch (_type) {
case Json::nullValue:
return "null";
case Json::intValue:
return "integer";
case Json::uintValue:
return "unsigned integer";
case Json::realValue:
return "float";
case Json::stringValue:
return "UTF-8 string";
case Json::booleanValue:
return "boolean";
case Json::arrayValue:
return "array";
case Json::objectValue:
return "object";
default:
assert(false && "unknown json type");
return "unknown";
}
}
bool IsCompatibleType(Json::ValueType _type, Json::ValueType _expected) {
switch (_expected) {
case Json::nullValue:
return _type == Json::nullValue;
case Json::intValue:
return _type == Json::intValue || _type == Json::uintValue;
case Json::uintValue:
return _type == Json::uintValue;
case Json::realValue:
return _type == Json::realValue || _type == Json::intValue ||
_type == Json::uintValue;
case Json::stringValue:
return _type == Json::stringValue;
case Json::booleanValue:
return _type == Json::booleanValue;
case Json::arrayValue:
return _type == Json::arrayValue;
case Json::objectValue:
return _type == Json::objectValue;
default:
assert(false && "unknown json type");
return false;
}
}
bool MakeDefaultArray(Json::Value& _parent, const char* _name,
const char* _comment, bool _empty) {
// Check if exists first.
const bool existed = _parent.isMember(_name);
// Create in any case
Json::Value* member = &_parent[_name];
if (!existed) {
member->resize(_empty ? 0 : 1);
assert(member->isArray());
}
// Pushes comment if there's not one already.
if (*_comment != 0 && !member->hasComment(Json::commentBefore)) {
member->setComment(std::string("// ") + _comment, Json::commentBefore);
}
return existed;
}
bool MakeDefaultObject(Json::Value& _parent, const char* _name,
const char* _comment) {
// Check if exists first.
const bool existed = _parent.isMember(_name);
// Create in any case
Json::Value* member = &_parent[_name];
if (!existed) {
*member = Json::Value(Json::objectValue);
assert(member->isObject());
}
// Pushes comment if there's not one already.
if (*_comment != 0 && !member->hasComment(Json::commentBefore)) {
member->setComment(std::string("// ") + _comment, Json::commentBefore);
}
return existed;
}
template <typename _Type>
bool MakeDefault(Json::Value& _parent, const char* _name, _Type _value,
const char* _comment) {
// Check if exists first.
const bool existed = _parent.isMember(_name);
// Create in any case
Json::Value* member = &_parent[_name];
if (!existed) {
*member = _value;
assert(IsCompatibleType(member->type(), ToJsonType<_Type>::type()));
}
// Pushes comment if there's not one already.
if (*_comment != 0 && !member->hasComment(Json::commentAfterOnSameLine)) {
member->setComment(std::string("// ") + _comment,
Json::commentAfterOnSameLine);
}
return existed;
}
bool SanitizeSkeletonJointTypes(Json::Value& _root, bool _all_options) {
(void)_all_options;
MakeDefault(_root, "skeleton", true,
"Uses skeleton nodes as skeleton joints.");
MakeDefault(_root, "marker", false, "Uses marker nodes as skeleton joints.");
MakeDefault(_root, "camera", false, "Uses camera nodes as skeleton joints.");
MakeDefault(_root, "geometry", false,
"Uses geometry nodes as skeleton joints.");
MakeDefault(_root, "light", false, "Uses light nodes as skeleton joints.");
MakeDefault(_root, "null", false, "Uses null nodes as skeleton joints.");
MakeDefault(_root, "any", false,
"Uses any node type as skeleton joints, including those listed "
"above and any other.");
return true;
}
bool SanitizeSkeletonImport(Json::Value& _root, bool _all_options) {
(void)_all_options;
MakeDefault(
_root, "enable", true,
"Imports (from source data file) and writes skeleton output file.");
MakeDefault(_root, "raw", false, "Outputs raw skeleton.");
MakeDefaultObject(
_root, "types",
"Define nodes types that should be considered as skeleton joints.");
SanitizeSkeletonJointTypes(_root["types"], _all_options);
return true;
}
bool SanitizeSkeleton(Json::Value& _root, bool _all_options) {
MakeDefault(_root, "filename", "skeleton.ozz",
"Specifies skeleton input/output filename. The file will be "
"outputted if import is true. It will also be used as an input "
"reference during animations import.");
MakeDefaultObject(_root, "import", "Define skeleton import settings.");
SanitizeSkeletonImport(_root["import"], _all_options);
return true;
}
bool SanitizeOptimizationSetting(Json::Value& _root) {
const AnimationOptimizer::Setting default_setting;
MakeDefault(_root, "tolerance", default_setting.tolerance,
"The maximum error that an optimization is allowed to generate "
"on a whole joint hierarchy.");
MakeDefault(_root, "distance", default_setting.distance,
"The distance (from the joint) at which error is measured. This "
"allows to emulate effect on skinning.");
return true;
}
bool SanitizeJointsSetting(Json::Value& _root) {
MakeDefault(_root, "name", "*",
"Joint name. Wildcard characters \'*\' and \'?\' are supported");
SanitizeOptimizationSetting(_root);
return true;
}
bool SanitizeOptimizationSettings(Json::Value& _root, bool _all_options) {
SanitizeOptimizationSetting(_root);
MakeDefaultArray(_root, "override", "Per joint optimization setting override",
!_all_options);
Json::Value& joints = _root["override"];
for (Json::ArrayIndex i = 0; i < joints.size(); ++i) {
if (!SanitizeJointsSetting(joints[i])) {
return false;
}
}
return true;
}
bool SanitizeTrackImport(Json::Value& _root) {
MakeDefault(_root, "filename", "*.ozz",
"Specifies track output filename(s). Use a \'*\' character "
"to specify part(s) of the filename that should be replaced by "
"the track (aka \"joint_name-property_name\") name.");
MakeDefault(_root, "joint_name", "*",
"Name of the joint that contains the property to import. "
"Wildcard characters '*' and '?' are supported.");
MakeDefault(_root, "property_name", "*",
"Name of the property to import. Wildcard characters '*' and '?' "
"are supported.");
MakeDefault(_root, "type", "float1",
"Type of the property, can be float1 to float4, point and vector "
"(aka float3 with scene unit and axis conversion).");
const char* type_name = _root["type"].asCString();
if (!PropertyTypeConfig::IsValidEnumName(type_name)) {
ozz::log::Err() << "Invalid value \"" << type_name
<< "\" for import track type property. Type can be float1 "
"to float4, point and vector (aka float3 with scene "
"unit and axis conversion)."
<< std::endl;
return false;
}
MakeDefault(_root, "raw", false, "Outputs raw track.");
MakeDefault(_root, "optimize", true, "Activates keyframes optimization.");
MakeDefault(_root, "optimization_tolerance", TrackOptimizer().tolerance,
"Optimization tolerance");
return true;
}
/*
bool SanitizeTrackMotion(Json::Value& _root) {
MakeDefault(_root, "joint_name", "",
"Name of the joint that contains the property to import. "
"Wildcard characters '*' and '?' are supported.");
MakeDefault(_root, "output", "*.ozz",
"Specifies track output file(s). Use a \'*\' character to "
"specify part(s) of the filename that should be replaced by the "
"joint_name.");
MakeDefault(_root, "optimization_tolerance",
TrackOptimizer().tolerance,
"Optimization tolerance");
return true;
}*/
bool SanitizeTrack(Json::Value& _root, bool _all_options) {
MakeDefaultArray(_root, "properties", "Properties to import.", !_all_options);
Json::Value& imports = _root["properties"];
for (Json::ArrayIndex i = 0; i < imports.size(); ++i) {
if (!SanitizeTrackImport(imports[i])) {
return false;
}
}
/*
MakeDefaultArray(_root, "motions", "Motions tracks to generate.",
!_all_options);
Json::Value& motions = _root["motions"];
for (Json::ArrayIndex i = 0; i < motions.size(); ++i) {
if (!SanitizeTrackMotion(motions[i])) {
return false;
}
}*/
return true;
}
bool SanitizeAnimation(Json::Value& _root, bool _all_options) {
MakeDefault(_root, "clip", "*",
"Specifies clip name (take) of the animation to import from the "
"source file. Wildcard characters \'*\' and \'?\' are supported");
MakeDefault(_root, "filename", "*.ozz",
"Specifies animation output filename. Use a \'*\' character to "
"specify part(s) of the filename that should be replaced by the "
"clip name.");
MakeDefault(_root, "raw", false, "Outputs raw animation.");
MakeDefault(
_root, "additive", false,
"Creates a delta animation that can be used for additive blending.");
MakeDefault(_root, "additive_reference", "animation",
"Select reference pose to use to build additive/delta animation. "
"Can be \"animation\" to use the 1st animation keyframe as "
"reference, or \"skeleton\" to use skeleton bind pose.");
if (!AdditiveReference::IsValidEnumName(
_root["additive_reference"].asCString())) {
ozz::log::Err() << "Invalid additive reference pose \""
<< _root["additive_reference"].asCString() << "\". \""
<< "Can be \"animation\" to use the 1st animation keyframe "
"as reference, or \"skeleton\" to use skeleton bind "
"pose."
<< std::endl;
return false;
}
MakeDefault(_root, "sampling_rate", 0.f,
"Selects animation sampling rate in hertz. Set a value <= 0 to "
"use imported scene default frame rate.");
MakeDefault(_root, "optimize", true,
"Activates keyframes reduction optimization.");
SanitizeOptimizationSettings(_root["optimization_settings"], _all_options);
MakeDefaultArray(_root, "tracks", "Tracks to build.", !_all_options);
Json::Value& tracks = _root["tracks"];
for (Json::ArrayIndex i = 0; i < tracks.size(); ++i) {
if (!SanitizeTrack(tracks[i], _all_options)) {
return false;
}
}
return true;
} // namespace
bool SanitizeRoot(Json::Value& _root, bool _all_options) {
// Skeleton
MakeDefaultObject(_root, "skeleton", "Skeleton to import");
SanitizeSkeleton(_root["skeleton"], _all_options);
// Animations.
// Forces array creation as it's expected for the defaultconfiguration.
MakeDefaultArray(_root, "animations", "Animations to import.", false);
Json::Value& animations = _root["animations"];
for (Json::ArrayIndex i = 0; i < animations.size(); ++i) {
if (!SanitizeAnimation(animations[i], _all_options)) {
return false;
}
}
return true;
}
bool RecursiveCheck(const Json::Value& _root, const Json::Value& _expected,
ozz::string _name) {
if (!IsCompatibleType(_root.type(), _expected.type())) {
// It's a failure to have a wrong member type.
ozz::log::Err() << "Invalid type \"" << JsonTypeToString(_root.type())
<< "\" for json member \"" << _name << "\". \""
<< JsonTypeToString(_expected.type()) << "\" expected."
<< std::endl;
return false;
}
if (_root.isArray()) {
assert(_expected.isArray());
for (Json::ArrayIndex i = 0; i < _root.size(); ++i) {
std::ostringstream istr;
istr << "[" << i << "]";
if (!RecursiveCheck(_root[i], _expected[0], _name + istr.str().c_str())) {
return false;
}
}
} else if (_root.isObject()) {
assert(_expected.isObject());
for (Json::Value::iterator it = _root.begin(); it != _root.end(); it++) {
const std::string& name = it.name();
if (!_expected.isMember(name)) {
ozz::log::Err() << "Invalid json member \""
<< _name + "." + name.c_str() << "\"." << std::endl;
return false;
}
const Json::Value& expected_member = _expected[name];
if (!RecursiveCheck(*it, expected_member, _name + "." + name.c_str())) {
return false;
}
}
}
return true;
}
std::string ToString(const Json::Value& _value) {
// Format configuration
Json::StreamWriterBuilder builder;
builder["indentation"] = " ";
builder["precision"] = 4;
return Json::writeString(builder, _value);
}
bool DumpConfig(const char* _path, const Json::Value& _config) {
if (_path[0] != 0) {
ozz::log::LogV() << "Opens config file to dump: " << _path << std::endl;
std::ofstream file(_path);
if (!file.is_open()) {
ozz::log::Err() << "Failed to open config file to dump: \"" << _path
<< "\"" << std::endl;
return false;
}
const std::string& document = ToString(_config);
file << document;
}
return true;
}
} // namespace
bool CompareName(const char* _a, const char* _b) {
return std::strcmp(_a, _b) == 0;
}
bool ProcessConfiguration(Json::Value* _config) {
if (!_config) {
return false;
}
// Use {} as a default config, otherwise take the one specified as argument.
std::string config_string = "{}";
// Takes config from program options.
if (OPTIONS_config.value()[0] != 0) {
config_string = OPTIONS_config.value();
} else if (OPTIONS_config_file.value()[0] != 0) {
ozz::log::LogV() << "Opens config file: \"" << OPTIONS_config_file << "\"."
<< std::endl;
std::ifstream file(OPTIONS_config_file.value());
if (!file.is_open()) {
ozz::log::Err() << "Failed to open config file: \"" << OPTIONS_config_file
<< "\"." << std::endl;
return false;
}
config_string.assign(std::istreambuf_iterator<char>(file),
std::istreambuf_iterator<char>());
} else {
ozz::log::Log() << "No configuration provided, using default configuration."
<< std::endl;
}
Json::Reader json_builder;
if (!json_builder.parse(config_string, *_config, true)) {
ozz::log::Err() << "Error while parsing configuration string: "
<< json_builder.getFormattedErrorMessages() << std::endl;
return false;
}
// Build the reference config to compare it with provided one and detect
// unexpected members.
Json::Value ref_config;
if (!SanitizeRoot(ref_config, true)) {
assert(false && "Failed to sanitized default configuration.");
}
// All format errors are reported within that function
if (!RecursiveCheck(*_config, ref_config, "root")) {
return false;
}
// Sanitized provided config.
if (!SanitizeRoot(*_config, false)) {
return false;
}
// Dumps the config to LogV now it's sanitized.
if (ozz::log::GetLevel() >= ozz::log::kVerbose) {
const std::string& document = ToString(*_config);
ozz::log::LogV() << "Sanitized configuration:" << std::endl
<< document << std::endl;
}
// Dumps reference config to file.
if (!DumpConfig(OPTIONS_config_dump_reference.value(), ref_config)) {
return false;
}
return true;
}
} // namespace offline
} // namespace animation
} // namespace ozz
@@ -0,0 +1,84 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#ifndef OZZ_ANIMATION_OFFLINE_TOOLS_IMPORT2OZZ_CONFIG_H_
#define OZZ_ANIMATION_OFFLINE_TOOLS_IMPORT2OZZ_CONFIG_H_
#include "ozz/base/platform.h"
#include <json/json-forwards.h>
namespace ozz {
namespace animation {
namespace offline {
// Get the sanitized (all members are set, with the right types) configuration.
bool ProcessConfiguration(Json::Value* _config);
// Internal function used to compare enum names.
bool CompareName(const char* _a, const char* _b);
// Struct allowing inheriting class to provide enum names.
template <typename _Type, typename _Enum>
struct JsonEnum {
// Struct allowing inheriting class to provide enum names.
struct EnumNames {
size_t count;
const char** names;
};
static bool GetEnumFromName(const char* _name, _Enum* _enum) {
const EnumNames enums = _Type::GetNames();
for (size_t i = 0; i < enums.count; ++i) {
if (CompareName(enums.names[i], _name)) {
*_enum = static_cast<_Enum>(i);
return true;
}
}
return false;
}
static const char* GetEnumName(_Enum _enum) {
const EnumNames enums = _Type::GetNames();
assert(static_cast<size_t>(_enum) < enums.count);
return enums.names[_enum];
}
static bool IsValidEnumName(const char* _name) {
const EnumNames enums = _Type::GetNames();
bool valid = false;
for (size_t i = 0; !valid && i < enums.count; ++i) {
valid = CompareName(enums.names[i], _name);
}
return valid;
}
};
} // namespace offline
} // namespace animation
} // namespace ozz
#endif // OZZ_ANIMATION_OFFLINE_TOOLS_IMPORT2OZZ_CONFIG_H_
@@ -0,0 +1,197 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "animation/offline/tools/import2ozz_skel.h"
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include "animation/offline/tools/import2ozz_config.h"
#include "ozz/animation/offline/tools/import2ozz.h"
#include "ozz/animation/offline/raw_skeleton.h"
#include "ozz/animation/offline/skeleton_builder.h"
#include "ozz/animation/runtime/skeleton.h"
#include "ozz/base/containers/map.h"
#include "ozz/base/containers/set.h"
#include "ozz/base/io/archive.h"
#include "ozz/base/io/stream.h"
#include "ozz/base/memory/unique_ptr.h"
#include "ozz/base/log.h"
#include <json/json.h>
namespace ozz {
namespace animation {
namespace offline {
namespace {
// Uses a set to detect names uniqueness.
typedef ozz::set<const char*, ozz::str_less> Names;
bool ValidateJointNamesUniquenessRecurse(
const RawSkeleton::Joint::Children& _joints, Names* _names) {
for (size_t i = 0; i < _joints.size(); ++i) {
const RawSkeleton::Joint& joint = _joints[i];
const char* name = joint.name.c_str();
if (!_names->insert(name).second) {
ozz::log::Err()
<< "Skeleton contains at least one non-unique joint name \"" << name
<< "\", which is not supported." << std::endl;
return false;
}
if (!ValidateJointNamesUniquenessRecurse(_joints[i].children, _names)) {
return false;
}
}
return true;
}
bool ValidateJointNamesUniqueness(const RawSkeleton& _skeleton) {
Names joint_names;
return ValidateJointNamesUniquenessRecurse(_skeleton.roots, &joint_names);
}
void LogHierarchy(const RawSkeleton::Joint::Children& _children,
int _depth = 0) {
const std::streamsize pres = ozz::log::LogV().stream().precision();
for (size_t i = 0; i < _children.size(); ++i) {
const RawSkeleton::Joint& joint = _children[i];
ozz::log::LogV() << std::setw(_depth) << std::setfill('.') << "";
ozz::log::LogV() << joint.name.c_str() << std::setprecision(4)
<< " t: " << joint.transform.translation.x << ", "
<< joint.transform.translation.y << ", "
<< joint.transform.translation.z
<< " r: " << joint.transform.rotation.x << ", "
<< joint.transform.rotation.y << ", "
<< joint.transform.rotation.z << ", "
<< joint.transform.rotation.w
<< " s: " << joint.transform.scale.x << ", "
<< joint.transform.scale.y << ", "
<< joint.transform.scale.z << std::endl;
// Recurse
LogHierarchy(joint.children, _depth + 1);
}
ozz::log::LogV() << std::setprecision(static_cast<int>(pres));
}
} // namespace
bool ImportSkeleton(const Json::Value& _config, OzzImporter* _importer,
const ozz::Endianness _endianness) {
const Json::Value& skeleton_config = _config["skeleton"];
const Json::Value& import_config = skeleton_config["import"];
// First check that we're actually expecting to import a skeleton.
if (!import_config["enable"].asBool()) {
ozz::log::Log() << "Skeleton build disabled, import will be skipped."
<< std::endl;
return true;
}
// Setup node types import properties.
const Json::Value& types_config = import_config["types"];
OzzImporter::NodeType types = {0};
types.skeleton = types_config["skeleton"].asBool();
types.marker = types_config["marker"].asBool();
types.camera = types_config["camera"].asBool();
types.geometry = types_config["geometry"].asBool();
types.light = types_config["light"].asBool();
types.any = types_config["any"].asBool();
RawSkeleton raw_skeleton;
if (!_importer->Import(&raw_skeleton, types)) {
ozz::log::Err() << "Failed to import skeleton." << std::endl;
return false;
}
// Log skeleton hierarchy
if (ozz::log::GetLevel() == ozz::log::kVerbose) {
LogHierarchy(raw_skeleton.roots);
}
// Non unique joint names are not supported.
if (!(ValidateJointNamesUniqueness(raw_skeleton))) {
// Log Err is done by the validation function.
return false;
}
// Needs to be done before opening the output file, so that if it fails then
// there's no invalid file outputted.
unique_ptr<Skeleton> skeleton;
if (!import_config["raw"].asBool()) {
// Builds runtime skeleton.
ozz::log::Log() << "Builds runtime skeleton." << std::endl;
SkeletonBuilder builder;
skeleton = builder(raw_skeleton);
if (!skeleton) {
ozz::log::Err() << "Failed to build runtime skeleton." << std::endl;
return false;
}
}
// Prepares output stream. File is a RAII so it will close automatically at
// the end of this scope.
// Once the file is opened, nothing should fail as it would leave an invalid
// file on the disk.
{
const char* filename = skeleton_config["filename"].asCString();
ozz::log::Log() << "Opens output file: " << filename << std::endl;
ozz::io::File file(filename, "wb");
if (!file.opened()) {
ozz::log::Err() << "Failed to open output file: \"" << filename << "\"."
<< std::endl;
return false;
}
// Initializes output archive.
ozz::io::OArchive archive(&file, _endianness);
// Fills output archive with the skeleton.
if (import_config["raw"].asBool()) {
ozz::log::Log() << "Outputs RawSkeleton to binary archive." << std::endl;
archive << raw_skeleton;
} else {
ozz::log::Log() << "Outputs Skeleton to binary archive." << std::endl;
archive << *skeleton;
}
ozz::log::Log() << "Skeleton binary archive successfully outputted."
<< std::endl;
}
return true;
}
} // namespace offline
} // namespace animation
} // namespace ozz
@@ -0,0 +1,50 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#ifndef OZZ_ANIMATION_OFFLINE_TOOLS_IMPORT2OZZ_SKEL_H_
#define OZZ_ANIMATION_OFFLINE_TOOLS_IMPORT2OZZ_SKEL_H_
#include "ozz/base/endianness.h"
#include "ozz/base/platform.h"
namespace Json {
class Value;
}
namespace ozz {
namespace animation {
namespace offline {
class OzzImporter;
bool ImportSkeleton(const Json::Value& _config, OzzImporter* _importer,
const ozz::Endianness _endianness);
} // namespace offline
} // namespace animation
} // namespace ozz
#endif // OZZ_ANIMATION_OFFLINE_TOOLS_IMPORT2OZZ_SKEL_H_
@@ -0,0 +1,358 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "animation/offline/tools/import2ozz_track.h"
#include <json/json.h>
#include <cstdlib>
#include <cstring>
#include "animation/offline/tools/import2ozz_config.h"
#include "ozz/animation/offline/raw_track.h"
#include "ozz/animation/offline/tools/import2ozz.h"
#include "ozz/animation/offline/track_builder.h"
#include "ozz/animation/offline/track_optimizer.h"
#include "ozz/animation/runtime/skeleton.h"
#include "ozz/animation/runtime/track.h"
#include "ozz/base/io/archive.h"
#include "ozz/base/io/stream.h"
#include "ozz/base/log.h"
#include "ozz/base/memory/unique_ptr.h"
#include "ozz/options/options.h"
namespace ozz {
namespace animation {
namespace offline {
namespace {
template <typename _Track>
void DisplaysOptimizationstatistics(const _Track& _non_optimized,
const _Track& _optimized) {
const size_t opt = _optimized.keyframes.size();
const size_t non_opt = _non_optimized.keyframes.size();
// Computes optimization ratios.
float ratio = opt != 0 ? 1.f * non_opt / opt : 0.f;
ozz::log::LogV log;
ozz::log::FloatPrecision precision_scope(log, 1);
log << "Optimization stage results: " << ratio << ":1" << std::endl;
}
bool IsCompatiblePropertyType(OzzImporter::NodeProperty::Type _src,
OzzImporter::NodeProperty::Type _dest) {
if (_src == _dest) {
return true;
}
switch (_src) {
case OzzImporter::NodeProperty::kFloat3:
return _dest == OzzImporter::NodeProperty::kPoint ||
_dest == OzzImporter::NodeProperty::kVector;
case OzzImporter::NodeProperty::kPoint:
case OzzImporter::NodeProperty::kVector:
return _dest == OzzImporter::NodeProperty::kFloat3;
default:
return false;
}
}
template <typename _RawTrack>
struct RawTrackToTrack;
template <>
struct RawTrackToTrack<RawFloatTrack> {
typedef FloatTrack Track;
};
template <>
struct RawTrackToTrack<RawFloat2Track> {
typedef Float2Track Track;
};
template <>
struct RawTrackToTrack<RawFloat3Track> {
typedef Float3Track Track;
};
template <>
struct RawTrackToTrack<RawFloat4Track> {
typedef Float4Track Track;
};
template <typename _RawTrack>
bool Export(OzzImporter& _importer, const _RawTrack& _raw_track,
const Json::Value& _config, const ozz::Endianness _endianness) {
// Raw track to build and output.
_RawTrack raw_track;
// Optimizes track if option is enabled.
if (_config["optimize"].asBool()) {
ozz::log::LogV() << "Optimizing track." << std::endl;
TrackOptimizer optimizer;
optimizer.tolerance = _config["optimization_tolerance"].asFloat();
_RawTrack raw_optimized_track;
if (!optimizer(_raw_track, &raw_optimized_track)) {
ozz::log::Err() << "Failed to optimize track." << std::endl;
return false;
}
// Displays optimization statistics.
DisplaysOptimizationstatistics(_raw_track, raw_optimized_track);
// Brings data back to the raw track.
raw_track = raw_optimized_track;
} else {
ozz::log::LogV() << "Optimization for track \"" << _raw_track.name
<< "\" is disabled." << std::endl;
}
// Builds runtime track.
unique_ptr<typename RawTrackToTrack<_RawTrack>::Track> track;
if (!_config["raw"].asBool()) {
ozz::log::LogV() << "Builds runtime track." << std::endl;
TrackBuilder builder;
track = builder(raw_track);
if (!track) {
ozz::log::Err() << "Failed to build runtime track." << std::endl;
return false;
}
}
{
// Prepares output stream. Once the file is opened, nothing should fail as
// it would leave an invalid file on the disk.
// Builds output filename.
const ozz::string filename = _importer.BuildFilename(
_config["filename"].asCString(), _raw_track.name.c_str());
ozz::log::LogV() << "Opens output file: " << filename << std::endl;
ozz::io::File file(filename.c_str(), "wb");
if (!file.opened()) {
ozz::log::Err() << "Failed to open output file: " << filename
<< std::endl;
return false;
}
// Initializes output archive.
ozz::io::OArchive archive(&file, _endianness);
// Fills output archive with the track.
if (_config["raw"].asBool()) {
ozz::log::LogV() << "Outputs RawTrack to binary archive." << std::endl;
archive << raw_track;
} else {
ozz::log::LogV() << "Outputs Track to binary archive." << std::endl;
archive << *track;
}
}
ozz::log::LogV() << "Track binary archive successfully outputted."
<< std::endl;
return true;
}
template <typename _TrackType>
bool ProcessImportTrackType(
OzzImporter& _importer, const char* _animation_name,
const char* _joint_name, const OzzImporter::NodeProperty& _property,
const OzzImporter::NodeProperty::Type _expected_type,
const Json::Value& _import_config, const ozz::Endianness _endianness) {
bool success = true;
ozz::log::Log() << "Extracting animation track \"" << _joint_name << ":"
<< _property.name.c_str() << "\" from animation \""
<< _animation_name << "\"." << std::endl;
_TrackType track;
success &=
_importer.Import(_animation_name, _joint_name, _property.name.c_str(),
_expected_type, 0, &track);
if (success) {
// Give the track a name
track.name = _joint_name;
track.name += '-';
track.name += _property.name.c_str();
success &= Export(_importer, track, _import_config, _endianness);
} else {
ozz::log::Err() << "Failed to import track \"" << _joint_name << ":"
<< _property.name << "\"" << std::endl;
}
return success;
}
bool ProcessImportTrack(OzzImporter& _importer, const char* _animation_name,
const Skeleton& _skeleton,
const Json::Value& _import_config,
const ozz::Endianness _endianness) {
// Early out if no name is specified
const char* joint_name_match = _import_config["joint_name"].asCString();
const char* ppt_name_match = _import_config["property_name"].asCString();
// Process every joint that matches.
bool success = true;
bool joint_found = false;
for (int s = 0; success && s < _skeleton.num_joints(); ++s) {
const char* joint_name = _skeleton.joint_names()[s];
if (!strmatch(joint_name, joint_name_match)) {
continue;
}
joint_found = true;
// Node found, need to find matching properties now.
bool ppt_found = false;
const OzzImporter::NodeProperties properties =
_importer.GetNodeProperties(joint_name);
for (size_t p = 0; p < properties.size(); ++p) {
const OzzImporter::NodeProperty& property = properties[p];
// Checks property name matches
const char* property_name = property.name.c_str();
ozz::log::LogV() << "Inspecting property " << joint_name << ":"
<< property_name << "\"." << std::endl;
if (!strmatch(property_name, ppt_name_match)) {
continue;
}
// Checks property type matches
const char* expected_type_name = _import_config["type"].asCString();
OzzImporter::NodeProperty::Type expected_type =
OzzImporter::NodeProperty::kFloat1;
bool valid_type = PropertyTypeConfig::GetEnumFromName(expected_type_name,
&expected_type);
(void)valid_type;
assert(valid_type &&
"Type should have been checked during config validation");
bool compatible_type =
IsCompatiblePropertyType(property.type, expected_type);
if (!compatible_type) {
ozz::log::Log() << "Incompatible type \"" << expected_type_name
<< "\" for matching property \"" << joint_name << ":"
<< property_name << "\" of type \""
<< PropertyTypeConfig::GetEnumName(property.type)
<< "\"." << std::endl;
continue;
}
ozz::log::LogV() << "Found matching property \"" << joint_name << ":"
<< property_name << "\" of type \""
<< PropertyTypeConfig::GetEnumName(property.type)
<< "\"." << std::endl;
// A property has been found.
ppt_found = true;
// Import property depending on its type.
switch (property.type) {
case OzzImporter::NodeProperty::kFloat1: {
success &= ProcessImportTrackType<RawFloatTrack>(
_importer, _animation_name, joint_name, property, expected_type,
_import_config, _endianness);
break;
}
case OzzImporter::NodeProperty::kFloat2: {
success &= ProcessImportTrackType<RawFloat2Track>(
_importer, _animation_name, joint_name, property, expected_type,
_import_config, _endianness);
break;
}
case OzzImporter::NodeProperty::kFloat3:
case OzzImporter::NodeProperty::kPoint:
case OzzImporter::NodeProperty::kVector: {
success &= ProcessImportTrackType<RawFloat3Track>(
_importer, _animation_name, joint_name, property, expected_type,
_import_config, _endianness);
break;
}
case OzzImporter::NodeProperty::kFloat4: {
success &= ProcessImportTrackType<RawFloat4Track>(
_importer, _animation_name, joint_name, property, expected_type,
_import_config, _endianness);
break;
}
default: {
assert(false && "Unknown property type.");
success = false;
break;
}
}
}
if (!ppt_found) {
ozz::log::Log() << "No property found for track import definition \""
<< joint_name_match << ":" << ppt_name_match << "\"."
<< std::endl;
}
}
if (!joint_found) {
ozz::log::Log() << "No joint found for track import definition \""
<< joint_name_match << "\"." << std::endl;
}
return success;
}
/*
bool ProcessMotionTrack(OzzImporter& _importer,
const char* _animation_name, const Skeleton&
_skeleton, const Json::Value& _motion) { return true;
}*/
} // namespace
bool ProcessTracks(OzzImporter& _importer, const char* _animation_name,
const Skeleton& _skeleton, const Json::Value& _config,
const ozz::Endianness _endianness) {
bool success = true;
const Json::Value& imports = _config["properties"];
for (Json::ArrayIndex i = 0; success && i < imports.size(); ++i) {
success &= ProcessImportTrack(_importer, _animation_name, _skeleton,
imports[i], _endianness);
}
/*
const Json::Value& motions = _config["motions"];
for (Json::ArrayIndex i = 0; success && i < motions.size(); ++i) {
success &=
ProcessMotionTrack(_importer, _animation_name, _skeleton,
motions[i]);
}*/
return success;
}
PropertyTypeConfig::EnumNames PropertyTypeConfig::GetNames() {
static const char* kNames[] = {"float1", "float2", "float3",
"float4", "point", "vector"};
const EnumNames enum_names = {OZZ_ARRAY_SIZE(kNames), kNames};
return enum_names;
}
} // namespace offline
} // namespace animation
} // namespace ozz
@@ -0,0 +1,59 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#ifndef OZZ_ANIMATION_OFFLINE_TOOLS_IMPORT2OZZ_TRACK_H_
#define OZZ_ANIMATION_OFFLINE_TOOLS_IMPORT2OZZ_TRACK_H_
#include "ozz/base/endianness.h"
#include "ozz/base/platform.h"
#include "animation/offline/tools/import2ozz_config.h"
#include "ozz/animation/offline/tools/import2ozz.h"
namespace Json {
class Value;
}
namespace ozz {
namespace animation {
class Skeleton;
namespace offline {
class OzzImporter;
bool ProcessTracks(OzzImporter& _importer, const char* _animation_name,
const Skeleton& _skeleton, const Json::Value& _config,
const ozz::Endianness _endianness);
// Property type enum to config string conversions.
struct PropertyTypeConfig
: JsonEnum<PropertyTypeConfig, OzzImporter::NodeProperty::Type> {
static EnumNames GetNames();
};
} // namespace offline
} // namespace animation
} // namespace ozz
#endif // OZZ_ANIMATION_OFFLINE_TOOLS_IMPORT2OZZ_TRACK_H_
@@ -0,0 +1,70 @@
{
// Skeleton to import
"skeleton" :
{
"filename" : "skeleton.ozz", // Specifies skeleton input/output filename. The file will be outputted if import is true. It will also be used as an input reference during animations import.
// Define skeleton import settings.
"import" :
{
"enable" : true, // Imports (from source data file) and writes skeleton output file.
"raw" : false, // Outputs raw skeleton.
// Define nodes types that should be considered as skeleton joints.
"types" :
{
"skeleton" : true, // Uses skeleton nodes as skeleton joints.
"marker" : false, // Uses marker nodes as skeleton joints.
"camera" : false, // Uses camera nodes as skeleton joints.
"geometry" : false, // Uses geometry nodes as skeleton joints.
"light" : false, // Uses light nodes as skeleton joints.
"null" : false, // Uses null nodes as skeleton joints.
"any" : false // Uses any node type as skeleton joints, including those listed above and any other.
}
}
},
// Animations to import.
"animations" :
[
{
"clip" : "*", // Specifies clip name (take) of the animation to import from the source file. Wildcard characters '*' and '?' are supported
"filename" : "*.ozz", // Specifies animation output filename. Use a '*' character to specify part(s) of the filename that should be replaced by the clip name.
"raw" : false, // Outputs raw animation.
"additive" : false, // Creates a delta animation that can be used for additive blending.
"additive_reference" : "animation", // Select reference pose to use to build additive/delta animation. Can be "animation" to use the 1st animation keyframe as reference, or "skeleton" to use skeleton bind pose.
"sampling_rate" : 0, // Selects animation sampling rate in hertz. Set a value <= 0 to use imported scene default frame rate.
"optimize" : true, // Activates keyframes reduction optimization.
"optimization_settings" :
{
"tolerance" : 0.001, // The maximum error that an optimization is allowed to generate on a whole joint hierarchy.
"distance" : 0.1, // The distance (from the joint) at which error is measured. This allows to emulate effect on skinning.
// Per joint optimization setting override
"override" :
[
{
"name" : "*", // Joint name. Wildcard characters '*' and '?' are supported
"tolerance" : 0.001, // The maximum error that an optimization is allowed to generate on a whole joint hierarchy.
"distance" : 0.1 // The distance (from the joint) at which error is measured. This allows to emulate effect on skinning.
}
]
},
// Tracks to build.
"tracks" :
[
{
// Properties to import.
"properties" :
[
{
"filename" : "*.ozz", // Specifies track output filename(s). Use a '*' character to specify part(s) of the filename that should be replaced by the track (aka "joint_name-property_name") name.
"joint_name" : "*", // Name of the joint that contains the property to import. Wildcard characters '*' and '?' are supported.
"property_name" : "*", // Name of the property to import. Wildcard characters '*' and '?' are supported.
"type" : "float1", // Type of the property, can be float1 to float4, point and vector (aka float3 with scene unit and axis conversion).
"raw" : false, // Outputs raw track.
"optimize" : true, // Activates keyframes optimization.
"optimization_tolerance" : 0.001 // Optimization tolerance
}
]
}
]
}
]
}
@@ -0,0 +1,208 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "ozz/animation/offline/track_builder.h"
#include <cassert>
#include <cmath>
#include <cstring>
#include <limits>
#include "ozz/base/memory/allocator.h"
#include "ozz/animation/offline/raw_track.h"
#include "ozz/animation/runtime/track.h"
namespace ozz {
namespace animation {
namespace offline {
namespace {
template <typename _RawTrack>
void PatchBeginEndKeys(const _RawTrack& _input,
typename _RawTrack::Keyframes* keyframes) {
if (_input.keyframes.empty()) {
const typename _RawTrack::ValueType default_value =
animation::internal::TrackPolicy<
typename _RawTrack::ValueType>::identity();
const typename _RawTrack::Keyframe begin = {RawTrackInterpolation::kLinear,
0.f, default_value};
keyframes->push_back(begin);
const typename _RawTrack::Keyframe end = {RawTrackInterpolation::kLinear,
1.f, default_value};
keyframes->push_back(end);
} else if (_input.keyframes.size() == 1) {
const typename _RawTrack::Keyframe& src_key = _input.keyframes.front();
const typename _RawTrack::Keyframe begin = {RawTrackInterpolation::kLinear,
0.f, src_key.value};
keyframes->push_back(begin);
const typename _RawTrack::Keyframe end = {RawTrackInterpolation::kLinear,
1.f, src_key.value};
keyframes->push_back(end);
} else {
// Copy all source data.
// Push an initial and last keys if they don't exist.
if (_input.keyframes.front().ratio != 0.f) {
const typename _RawTrack::Keyframe& src_key = _input.keyframes.front();
const typename _RawTrack::Keyframe begin = {
RawTrackInterpolation::kLinear, 0.f, src_key.value};
keyframes->push_back(begin);
}
for (size_t i = 0; i < _input.keyframes.size(); ++i) {
keyframes->push_back(_input.keyframes[i]);
}
if (_input.keyframes.back().ratio != 1.f) {
const typename _RawTrack::Keyframe& src_key = _input.keyframes.back();
const typename _RawTrack::Keyframe end = {RawTrackInterpolation::kLinear,
1.f, src_key.value};
keyframes->push_back(end);
}
}
}
template <typename _Keyframes>
void Fixup(_Keyframes* _keyframes) {
// Nothing to do by default.
(void)_keyframes;
}
} // namespace
// Ensures _input's validity and allocates _animation.
// An animation needs to have at least two key frames per joint, the first at
// t = 0 and the last at t = 1. If at least one of those keys are not
// in the RawAnimation then the builder creates it.
template <typename _RawTrack, typename _Track>
unique_ptr<_Track> TrackBuilder::Build(const _RawTrack& _input) const {
// Tests _raw_animation validity.
if (!_input.Validate()) {
return unique_ptr<_Track>();
}
// Everything is fine, allocates and fills the animation.
// Nothing can fail now.
unique_ptr<_Track> track = make_unique<_Track>();
// Copy data to temporary prepared data structure
typename _RawTrack::Keyframes keyframes;
// Guessing a worst size to avoid realloc.
const size_t worst_size =
_input.keyframes.size() * 2 + // * 2 in case all keys are kStep
2; // + 2 for first and last keys
keyframes.reserve(worst_size);
// Ensure there's a key frame at the start and end of the track (required for
// sampling).
PatchBeginEndKeys(_input, &keyframes);
// Fixup values, ex: successive opposite quaternions that would fail to take
// the shortest path during the normalized-lerp.
Fixup(&keyframes);
// Allocates output track.
const size_t name_len = _input.name.size();
track->Allocate(keyframes.size(), _input.name.size());
// Copy all keys to output.
assert(keyframes.size() == track->ratios_.size() &&
keyframes.size() == track->values_.size() &&
keyframes.size() <= track->steps_.size() * 8);
memset(track->steps_.data(), 0, track->steps_.size_bytes());
for (size_t i = 0; i < keyframes.size(); ++i) {
const typename _RawTrack::Keyframe& src_key = keyframes[i];
track->ratios_[i] = src_key.ratio;
track->values_[i] = src_key.value;
track->steps_[i / 8] |=
(src_key.interpolation == RawTrackInterpolation::kStep) << (i & 7);
}
// Copy track's name.
if (name_len) {
strcpy(track->name_, _input.name.c_str());
}
return track; // Success.
}
unique_ptr<FloatTrack> TrackBuilder::operator()(
const RawFloatTrack& _input) const {
return Build<RawFloatTrack, FloatTrack>(_input);
}
unique_ptr<Float2Track> TrackBuilder::operator()(
const RawFloat2Track& _input) const {
return Build<RawFloat2Track, Float2Track>(_input);
}
unique_ptr<Float3Track> TrackBuilder::operator()(
const RawFloat3Track& _input) const {
return Build<RawFloat3Track, Float3Track>(_input);
}
unique_ptr<Float4Track> TrackBuilder::operator()(
const RawFloat4Track& _input) const {
return Build<RawFloat4Track, Float4Track>(_input);
}
namespace {
// Fixes-up successive opposite quaternions that would fail to take the shortest
// path during the lerp.
template <>
void Fixup<RawQuaternionTrack::Keyframes>(
RawQuaternionTrack::Keyframes* _keyframes) {
assert(_keyframes->size() >= 2);
const math::Quaternion identity = math::Quaternion::identity();
for (size_t i = 0; i < _keyframes->size(); ++i) {
RawQuaternionTrack::ValueType& src_key = _keyframes->at(i).value;
// Normalizes input quaternion.
src_key = NormalizeSafe(src_key, identity);
// Ensures quaternions are all on the same hemisphere.
if (i == 0) {
if (src_key.w < 0.f) {
src_key = -src_key; // Q an -Q are the same rotation.
}
} else {
RawQuaternionTrack::ValueType& prev_key = _keyframes->at(i - 1).value;
const float dot = src_key.x * prev_key.x + src_key.y * prev_key.y +
src_key.z * prev_key.z + src_key.w * prev_key.w;
if (dot < 0.f) {
src_key = -src_key; // Q an -Q are the same rotation.
}
}
}
}
} // namespace
unique_ptr<QuaternionTrack> TrackBuilder::operator()(
const RawQuaternionTrack& _input) const {
return Build<RawQuaternionTrack, QuaternionTrack>(_input);
}
} // namespace offline
} // namespace animation
} // namespace ozz
@@ -0,0 +1,130 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "ozz/animation/offline/track_optimizer.h"
#include <cassert>
#include <cstddef>
// Internal include file
#define OZZ_INCLUDE_PRIVATE_HEADER // Allows to include private headers.
#include "animation/offline/decimate.h"
#include "ozz/base/maths/math_ex.h"
#include "ozz/animation/offline/raw_track.h"
// Needs runtime track to access TrackPolicy.
#include "ozz/animation/runtime/track.h"
namespace ozz {
namespace animation {
namespace offline {
// Setup default values (favoring quality).
TrackOptimizer::TrackOptimizer() : tolerance(1e-3f) { // 1 mm.
}
namespace {
template <typename _KeyFrame>
struct Adapter {
typedef typename _KeyFrame::ValueType ValueType;
typedef typename animation::internal::TrackPolicy<ValueType> Policy;
Adapter() {}
bool Decimable(const _KeyFrame& _key) const {
// RawTrackInterpolation::kStep keyframes aren't optimized, as steps can't
// be interpolated.
return _key.interpolation != RawTrackInterpolation::kStep;
}
_KeyFrame Lerp(const _KeyFrame& _left, const _KeyFrame& _right,
const _KeyFrame& _ref) const {
assert(Decimable(_ref));
const float alpha =
(_ref.ratio - _left.ratio) / (_right.ratio - _left.ratio);
assert(alpha >= 0.f && alpha <= 1.f);
const _KeyFrame key = {_ref.interpolation, _ref.ratio,
Policy::Lerp(_left.value, _right.value, alpha)};
return key;
}
float Distance(const _KeyFrame& _a, const _KeyFrame& _b) const {
return Policy::Distance(_a.value, _b.value);
}
};
template <typename _Track>
inline bool Optimize(float _tolerance, const _Track& _input, _Track* _output) {
if (!_output) {
return false;
}
// Reset output animation to default.
*_output = _Track();
// Validate animation.
if (!_input.Validate()) {
return false;
}
// Copy name
_output->name = _input.name;
// Optimizes.
const Adapter<typename _Track::Keyframe> adapter;
Decimate(_input.keyframes, adapter, _tolerance, &_output->keyframes);
// Output animation is always valid though.
return _output->Validate();
}
} // namespace
bool TrackOptimizer::operator()(const RawFloatTrack& _input,
RawFloatTrack* _output) const {
return Optimize(tolerance, _input, _output);
}
bool TrackOptimizer::operator()(const RawFloat2Track& _input,
RawFloat2Track* _output) const {
return Optimize(tolerance, _input, _output);
}
bool TrackOptimizer::operator()(const RawFloat3Track& _input,
RawFloat3Track* _output) const {
return Optimize(tolerance, _input, _output);
}
bool TrackOptimizer::operator()(const RawFloat4Track& _input,
RawFloat4Track* _output) const {
return Optimize(tolerance, _input, _output);
}
bool TrackOptimizer::operator()(const RawQuaternionTrack& _input,
RawQuaternionTrack* _output) const {
return Optimize(1.f - std::cos(.5f * tolerance), _input, _output);
}
} // namespace offline
} // namespace animation
} // namespace ozz