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
+7
View File
@@ -0,0 +1,7 @@
# Directory src is available from the implementation files.
include_directories(./)
add_subdirectory(animation)
add_subdirectory(geometry)
add_subdirectory(base)
add_subdirectory(options)
+2
View File
@@ -0,0 +1,2 @@
add_subdirectory(offline)
add_subdirectory(runtime)
@@ -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
@@ -0,0 +1,36 @@
add_library(ozz_animation STATIC
${PROJECT_SOURCE_DIR}/include/ozz/animation/runtime/animation.h
animation.cc
animation_keyframe.h
${PROJECT_SOURCE_DIR}/include/ozz/animation/runtime/animation_utils.h
animation_utils.cc
${PROJECT_SOURCE_DIR}/include/ozz/animation/runtime/blending_job.h
blending_job.cc
${PROJECT_SOURCE_DIR}/include/ozz/animation/runtime/ik_aim_job.h
ik_aim_job.cc
${PROJECT_SOURCE_DIR}/include/ozz/animation/runtime/ik_two_bone_job.h
ik_two_bone_job.cc
${PROJECT_SOURCE_DIR}/include/ozz/animation/runtime/local_to_model_job.h
local_to_model_job.cc
${PROJECT_SOURCE_DIR}/include/ozz/animation/runtime/sampling_job.h
sampling_job.cc
${PROJECT_SOURCE_DIR}/include/ozz/animation/runtime/skeleton.h
skeleton.cc
${PROJECT_SOURCE_DIR}/include/ozz/animation/runtime/skeleton_utils.h
skeleton_utils.cc
${PROJECT_SOURCE_DIR}/include/ozz/animation/runtime/track.h
track.cc
${PROJECT_SOURCE_DIR}/include/ozz/animation/runtime/track_sampling_job.h
track_sampling_job.cc
${PROJECT_SOURCE_DIR}/include/ozz/animation/runtime/track_triggering_job.h
${PROJECT_SOURCE_DIR}/include/ozz/animation/runtime/track_triggering_job_trait.h
track_triggering_job.cc)
target_link_libraries(ozz_animation
ozz_base)
set_target_properties(ozz_animation
PROPERTIES FOLDER "ozz")
install(TARGETS ozz_animation DESTINATION lib)
fuse_target("ozz_animation")
@@ -0,0 +1,203 @@
//----------------------------------------------------------------------------//
// //
// 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/runtime/animation.h"
#include <cassert>
#include <cstring>
#include "ozz/base/io/archive.h"
#include "ozz/base/log.h"
#include "ozz/base/maths/math_archive.h"
#include "ozz/base/maths/math_ex.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 {
Animation::Animation() : duration_(0.f), num_tracks_(0), name_(nullptr) {}
Animation::~Animation() { Deallocate(); }
void Animation::Allocate(size_t _name_len, size_t _translation_count,
size_t _rotation_count, size_t _scale_count) {
// Distributes buffer memory while ensuring proper alignment (serves larger
// alignment values first).
static_assert(alignof(Float3Key) >= alignof(QuaternionKey) &&
alignof(QuaternionKey) >= alignof(Float3Key) &&
alignof(Float3Key) >= alignof(char),
"Must serve larger alignment values first)");
assert(name_ == nullptr && translations_.size() == 0 &&
rotations_.size() == 0 && scales_.size() == 0);
// Compute overall size and allocate a single buffer for all the data.
const size_t buffer_size = (_name_len > 0 ? _name_len + 1 : 0) +
_translation_count * sizeof(Float3Key) +
_rotation_count * sizeof(QuaternionKey) +
_scale_count * sizeof(Float3Key);
span<char> buffer = {static_cast<char*>(memory::default_allocator()->Allocate(
buffer_size, alignof(Float3Key))),
buffer_size};
// Fix up pointers. Serves larger alignment values first.
translations_ = fill_span<Float3Key>(buffer, _translation_count);
rotations_ = fill_span<QuaternionKey>(buffer, _rotation_count);
scales_ = fill_span<Float3Key>(buffer, _scale_count);
// Let name be nullptr if animation has no name. Allows to avoid allocating
// this buffer in the constructor of empty animations.
name_ =
_name_len > 0 ? fill_span<char>(buffer, _name_len + 1).data() : nullptr;
assert(buffer.empty() && "Whole buffer should be consumned");
}
void Animation::Deallocate() {
memory::default_allocator()->Deallocate(
as_writable_bytes(translations_).data());
name_ = nullptr;
translations_ = {};
rotations_ = {};
scales_ = {};
}
size_t Animation::size() const {
const size_t size = sizeof(*this) + translations_.size_bytes() +
rotations_.size_bytes() + scales_.size_bytes();
return size;
}
void Animation::Save(ozz::io::OArchive& _archive) const {
_archive << duration_;
_archive << static_cast<int32_t>(num_tracks_);
const size_t name_len = name_ ? std::strlen(name_) : 0;
_archive << static_cast<int32_t>(name_len);
const ptrdiff_t translation_count = translations_.size();
_archive << static_cast<int32_t>(translation_count);
const ptrdiff_t rotation_count = rotations_.size();
_archive << static_cast<int32_t>(rotation_count);
const ptrdiff_t scale_count = scales_.size();
_archive << static_cast<int32_t>(scale_count);
_archive << ozz::io::MakeArray(name_, name_len);
for (const Float3Key& key : translations_) {
_archive << key.ratio;
_archive << key.track;
_archive << ozz::io::MakeArray(key.value);
}
for (const QuaternionKey& key : rotations_) {
_archive << key.ratio;
uint16_t track = key.track;
_archive << track;
uint8_t largest = key.largest;
_archive << largest;
bool sign = key.sign;
_archive << sign;
_archive << ozz::io::MakeArray(key.value);
}
for (const Float3Key& key : scales_) {
_archive << key.ratio;
_archive << key.track;
_archive << ozz::io::MakeArray(key.value);
}
}
void Animation::Load(ozz::io::IArchive& _archive, uint32_t _version) {
// Destroy animation in case it was already used before.
Deallocate();
duration_ = 0.f;
num_tracks_ = 0;
// No retro-compatibility with anterior versions.
if (_version != 6) {
log::Err() << "Unsupported Animation version " << _version << "."
<< std::endl;
return;
}
_archive >> duration_;
int32_t num_tracks;
_archive >> num_tracks;
num_tracks_ = num_tracks;
int32_t name_len;
_archive >> name_len;
int32_t translation_count;
_archive >> translation_count;
int32_t rotation_count;
_archive >> rotation_count;
int32_t scale_count;
_archive >> scale_count;
Allocate(name_len, translation_count, rotation_count, scale_count);
if (name_) { // nullptr name_ is supported.
_archive >> ozz::io::MakeArray(name_, name_len);
name_[name_len] = 0;
}
for (Float3Key& key : translations_) {
_archive >> key.ratio;
_archive >> key.track;
_archive >> ozz::io::MakeArray(key.value);
}
for (QuaternionKey& key : rotations_) {
_archive >> key.ratio;
uint16_t track;
_archive >> track;
key.track = track;
uint8_t largest;
_archive >> largest;
key.largest = largest & 3;
bool sign;
_archive >> sign;
key.sign = sign & 1;
_archive >> ozz::io::MakeArray(key.value);
}
for (Float3Key& key : scales_) {
_archive >> key.ratio;
_archive >> key.track;
_archive >> ozz::io::MakeArray(key.value);
}
}
} // namespace animation
} // namespace ozz
@@ -0,0 +1,80 @@
//----------------------------------------------------------------------------//
// //
// 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_RUNTIME_ANIMATION_KEYFRAME_H_
#define OZZ_ANIMATION_RUNTIME_ANIMATION_KEYFRAME_H_
#include "ozz/base/platform.h"
#ifndef OZZ_INCLUDE_PRIVATE_HEADER
#error "This header is private, it cannot be included from public headers."
#endif // OZZ_INCLUDE_PRIVATE_HEADER
namespace ozz {
namespace animation {
// Define animation key frame types (translation, rotation, scale). Every type
// as the same base made of the key time ratio and it's track index. This is
// required as key frames are not sorted per track, but sorted by ratio to favor
// cache coherency. Key frame values are compressed, according on their type.
// Decompression is efficient because it's done on SoA data and cached during
// sampling.
// Defines the float3 key frame type, used for translations and scales.
// Translation values are stored as half precision floats with 16 bits per
// component.
struct Float3Key {
float ratio;
uint16_t track;
uint16_t value[3];
};
// Defines the rotation key frame type.
// Rotation value is a quaternion. Quaternion are normalized, which means each
// component is in range [0:1]. This property allows to quantize the 3
// components to 3 signed integer 16 bits values. The 4th component is restored
// at runtime, using the knowledge that |w| = sqrt(1 - (a^2 + b^2 + c^2)).
// The sign of this 4th component is stored using 1 bit taken from the track
// member.
//
// In more details, compression algorithm stores the 3 smallest components of
// the quaternion and restores the largest. The 3 smallest can be pre-multiplied
// by sqrt(2) to gain some precision indeed.
//
// Quantization could be reduced to 11-11-10 bits as often used for animation
// key frames, but in this case RotationKey structure would induce 16 bits of
// padding.
struct QuaternionKey {
float ratio;
uint16_t track : 13; // The track this key frame belongs to.
uint16_t largest : 2; // The largest component of the quaternion.
uint16_t sign : 1; // The sign of the largest component. 1 for negative.
int16_t value[3]; // The quantized value of the 3 smallest components.
};
} // namespace animation
} // namespace ozz
#endif // OZZ_ANIMATION_RUNTIME_ANIMATION_KEYFRAME_H_
@@ -0,0 +1,62 @@
//----------------------------------------------------------------------------//
// //
// 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/runtime/animation_utils.h"
// Internal include file
#define OZZ_INCLUDE_PRIVATE_HEADER // Allows to include private headers.
#include "animation/runtime/animation_keyframe.h"
namespace ozz {
namespace animation {
template <typename _Key>
inline int CountKeyframesImpl(const span<const _Key>& _keys, int _track) {
if (_track < 0) {
return static_cast<int>(_keys.size());
}
int count = 0;
for (const _Key& key : _keys) {
if (key.track == _track) {
++count;
}
}
return count;
}
int CountTranslationKeyframes(const Animation& _animation, int _track) {
return CountKeyframesImpl(_animation.translations(), _track);
}
int CountRotationKeyframes(const Animation& _animation, int _track) {
return CountKeyframesImpl(_animation.rotations(), _track);
}
int CountScaleKeyframes(const Animation& _animation, int _track) {
return CountKeyframesImpl(_animation.scales(), _track);
}
} // namespace animation
} // namespace ozz
@@ -0,0 +1,457 @@
//----------------------------------------------------------------------------//
// //
// 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/runtime/blending_job.h"
#include <cassert>
#include <cstddef>
#include "ozz/animation/runtime/skeleton.h"
#include "ozz/base/maths/math_ex.h"
#include "ozz/base/maths/soa_transform.h"
namespace ozz {
namespace animation {
BlendingJob::Layer::Layer() : weight(0.f) {}
BlendingJob::BlendingJob() : threshold(.1f) {}
namespace {
bool ValidateLayer(const BlendingJob::Layer& _layer, size_t _min_range) {
bool valid = true;
// Tests transforms validity.
valid &= _layer.transform.size() >= _min_range;
// Joint weights are optional.
if (!_layer.joint_weights.empty()) {
valid &= _layer.joint_weights.size() >= _min_range;
} else {
valid &= _layer.joint_weights.empty();
}
return valid;
}
} // namespace
bool BlendingJob::Validate() const {
// Don't need any early out, as jobs are valid in most of the performance
// critical cases.
// Tests are written in multiple lines in order to avoid branches.
bool valid = true;
// Test for valid threshold).
valid &= threshold > 0.f;
// Test for nullptr begin pointers.
// Blending layers are mandatory, additive aren't.
valid &= !bind_pose.empty();
valid &= !output.empty();
// The bind pose size defines the ranges of transforms to blend, so all
// other buffers should be bigger.
const size_t min_range = bind_pose.size();
valid &= output.size() >= min_range;
// Validates layers.
for (const Layer& layer : layers) {
valid &= ValidateLayer(layer, min_range);
}
// Validates additive layers.
for (const Layer& layer : additive_layers) {
valid &= ValidateLayer(layer, min_range);
}
return valid;
}
namespace {
// Macro that defines the process of blending the 1st pass.
#define OZZ_BLEND_1ST_PASS(_in, _simd_weight, _out) \
do { \
_out->translation = _in.translation * _simd_weight; \
_out->rotation = _in.rotation * _simd_weight; \
_out->scale = _in.scale * _simd_weight; \
} while (void(0), 0)
// Macro that defines the process of blending any pass but the first.
#define OZZ_BLEND_N_PASS(_in, _simd_weight, _out) \
do { \
/* Blends translation. */ \
_out->translation = _out->translation + _in.translation * _simd_weight; \
/* Blends rotations, negates opposed quaternions to be sure to choose*/ \
/* the shortest path between the two.*/ \
const math::SimdInt4 sign = math::Sign(Dot(_out->rotation, _in.rotation)); \
const math::SoaQuaternion rotation = { \
math::Xor(_in.rotation.x, sign), math::Xor(_in.rotation.y, sign), \
math::Xor(_in.rotation.z, sign), math::Xor(_in.rotation.w, sign)}; \
_out->rotation = _out->rotation + rotation * _simd_weight; \
/* Blends scales.*/ \
_out->scale = _out->scale + _in.scale * _simd_weight; \
} while (void(0), 0)
// Macro that defines the process of adding a pass.
#define OZZ_ADD_PASS(_in, _simd_weight, _out) \
do { \
_out.translation = _out.translation + _in.translation * _simd_weight; \
/* Interpolate quaternion between identity and src.rotation.*/ \
/* Quaternion sign is fixed up, so that lerp takes the shortest path.*/ \
const math::SimdInt4 sign = math::Sign(_in.rotation.w); \
const math::SoaQuaternion rotation = { \
math::Xor(_in.rotation.x, sign), math::Xor(_in.rotation.y, sign), \
math::Xor(_in.rotation.z, sign), math::Xor(_in.rotation.w, sign)}; \
const math::SoaQuaternion interp_quat = { \
rotation.x * _simd_weight, rotation.y * _simd_weight, \
rotation.z * _simd_weight, (rotation.w - one) * _simd_weight + one}; \
_out.rotation = NormalizeEst(interp_quat) * _out.rotation; \
_out.scale = \
_out.scale * (one_minus_weight_f3 + (_in.scale * _simd_weight)); \
} while (void(0), 0)
// Macro that defines the process of subtracting a pass.
#define OZZ_SUB_PASS(_in, _simd_weight, _out) \
do { \
_out.translation = _out.translation - _in.translation * _simd_weight; \
/* Interpolate quaternion between identity and src.rotation.*/ \
/* Quaternion sign is fixed up, so that lerp takes the shortest path.*/ \
const math::SimdInt4 sign = math::Sign(_in.rotation.w); \
const math::SoaQuaternion rotation = { \
math::Xor(_in.rotation.x, sign), math::Xor(_in.rotation.y, sign), \
math::Xor(_in.rotation.z, sign), math::Xor(_in.rotation.w, sign)}; \
const math::SoaQuaternion interp_quat = { \
rotation.x * _simd_weight, rotation.y * _simd_weight, \
rotation.z * _simd_weight, (rotation.w - one) * _simd_weight + one}; \
_out.rotation = Conjugate(NormalizeEst(interp_quat)) * _out.rotation; \
const math::SoaFloat3 rcp_scale = { \
math::RcpEst(math::MAdd(_in.scale.x, _simd_weight, one_minus_weight)), \
math::RcpEst(math::MAdd(_in.scale.y, _simd_weight, one_minus_weight)), \
math::RcpEst( \
math::MAdd(_in.scale.z, _simd_weight, one_minus_weight))}; \
_out.scale = _out.scale * rcp_scale; \
} while (void(0), 0)
// Defines parameters that are passed through blending stages.
struct ProcessArgs {
ProcessArgs(const BlendingJob& _job)
: job(_job),
num_soa_joints(_job.bind_pose.size()),
num_passes(0),
num_partial_passes(0),
accumulated_weight(0.f) {
// The range of all buffers has already been validated.
assert(job.output.size() >= num_soa_joints);
assert(OZZ_ARRAY_SIZE(accumulated_weights) >= num_soa_joints);
}
// Allocates enough space to store a accumulated weights per-joint.
// It will be initialized by the first pass processed, if any.
// This is quite big for a stack allocation (4 byte * maximum number of
// joints). This is one of the reasons why the number of joints is limited
// by the API.
// Note that this array is used with SoA data.
// This is the first argument in order to avoid wasting too much space with
// alignment padding.
math::SimdFloat4 accumulated_weights[Skeleton::kMaxSoAJoints];
// The job to process.
const BlendingJob& job;
// The number of transforms to process as defined by the size of the bind
// pose.
size_t num_soa_joints;
// Number of processed blended passes (excluding passes with a weight <= 0.f),
// including partial passes.
int num_passes;
// Number of processed partial blending passes (aka with a weight per-joint).
int num_partial_passes;
// The accumulated weight of all layers.
float accumulated_weight;
private:
// Disables assignment operators.
ProcessArgs(const ProcessArgs&);
void operator=(const ProcessArgs&);
};
// Blends all layers of the job to its output.
void BlendLayers(ProcessArgs* _args) {
assert(_args);
// Iterates through all layers and blend them to the output.
for (const BlendingJob::Layer& layer : _args->job.layers) {
// Asserts buffer sizes, which must never fail as it has been validated.
assert(layer.transform.size() >= _args->num_soa_joints);
assert(layer.joint_weights.empty() ||
(layer.joint_weights.size() >= _args->num_soa_joints));
// Skip irrelevant layers.
if (layer.weight <= 0.f) {
continue;
}
// Accumulates global weights.
_args->accumulated_weight += layer.weight;
const math::SimdFloat4 layer_weight =
math::simd_float4::Load1(layer.weight);
if (!layer.joint_weights.empty()) {
// This layer has per-joint weights.
++_args->num_partial_passes;
if (_args->num_passes == 0) {
for (size_t i = 0; i < _args->num_soa_joints; ++i) {
const math::SoaTransform& src = layer.transform[i];
math::SoaTransform* dest = _args->job.output.begin() + i;
const math::SimdFloat4 weight =
layer_weight * math::Max0(layer.joint_weights[i]);
_args->accumulated_weights[i] = weight;
OZZ_BLEND_1ST_PASS(src, weight, dest);
}
} else {
for (size_t i = 0; i < _args->num_soa_joints; ++i) {
const math::SoaTransform& src = layer.transform[i];
math::SoaTransform* dest = _args->job.output.begin() + i;
const math::SimdFloat4 weight =
layer_weight * math::Max0(layer.joint_weights[i]);
_args->accumulated_weights[i] =
_args->accumulated_weights[i] + weight;
OZZ_BLEND_N_PASS(src, weight, dest);
}
}
} else {
// This is a full layer.
if (_args->num_passes == 0) {
for (size_t i = 0; i < _args->num_soa_joints; ++i) {
const math::SoaTransform& src = layer.transform[i];
math::SoaTransform* dest = _args->job.output.begin() + i;
_args->accumulated_weights[i] = layer_weight;
OZZ_BLEND_1ST_PASS(src, layer_weight, dest);
}
} else {
for (size_t i = 0; i < _args->num_soa_joints; ++i) {
const math::SoaTransform& src = layer.transform[i];
math::SoaTransform* dest = _args->job.output.begin() + i;
_args->accumulated_weights[i] =
_args->accumulated_weights[i] + layer_weight;
OZZ_BLEND_N_PASS(src, layer_weight, dest);
}
}
}
// One more pass blended.
++_args->num_passes;
}
}
// Blends bind pose to the output if accumulated weight is less than the
// threshold value.
void BlendBindPose(ProcessArgs* _args) {
assert(_args);
// Asserts buffer sizes, which must never fail as it has been validated.
assert(_args->job.bind_pose.size() >= _args->num_soa_joints);
if (_args->num_partial_passes == 0) {
// No partial blending pass detected, threshold can be tested globally.
const float bp_weight = _args->job.threshold - _args->accumulated_weight;
if (bp_weight > 0.f) { // The bind-pose is needed if it has a weight.
if (_args->num_passes == 0) {
// Strictly copying bind-pose.
_args->accumulated_weight = 1.f;
for (size_t i = 0; i < _args->num_soa_joints; ++i) {
_args->job.output[i] = _args->job.bind_pose[i];
}
} else {
// Updates global accumulated weight, but not per-joint weight any more
// because normalization stage will be global also.
_args->accumulated_weight = _args->job.threshold;
const math::SimdFloat4 simd_bp_weight =
math::simd_float4::Load1(bp_weight);
for (size_t i = 0; i < _args->num_soa_joints; ++i) {
const math::SoaTransform& src = _args->job.bind_pose[i];
math::SoaTransform* dest = _args->job.output.begin() + i;
OZZ_BLEND_N_PASS(src, simd_bp_weight, dest);
}
}
}
} else {
// Blending passes contain partial blending, threshold must be tested for
// each joint.
const math::SimdFloat4 threshold =
math::simd_float4::Load1(_args->job.threshold);
// There's been at least 1 pass as num_partial_passes != 0.
assert(_args->num_passes != 0);
for (size_t i = 0; i < _args->num_soa_joints; ++i) {
const math::SoaTransform& src = _args->job.bind_pose[i];
math::SoaTransform* dest = _args->job.output.begin() + i;
const math::SimdFloat4 bp_weight =
math::Max0(threshold - _args->accumulated_weights[i]);
_args->accumulated_weights[i] =
math::Max(threshold, _args->accumulated_weights[i]);
OZZ_BLEND_N_PASS(src, bp_weight, dest);
}
}
}
// Normalizes output rotations. Quaternion length cannot be zero as opposed
// quaternions have been fixed up during blending passes.
// Translations and scales are already normalized because weights were
// pre-multiplied by the normalization ratio.
void Normalize(ProcessArgs* _args) {
assert(_args);
if (_args->num_partial_passes == 0) {
// Normalization of a non-partial blending requires to apply the same
// division to all joints.
const math::SimdFloat4 ratio =
math::simd_float4::Load1(1.f / _args->accumulated_weight);
for (size_t i = 0; i < _args->num_soa_joints; ++i) {
math::SoaTransform& dest = _args->job.output[i];
dest.rotation = NormalizeEst(dest.rotation);
dest.translation = dest.translation * ratio;
dest.scale = dest.scale * ratio;
}
} else {
// Partial blending normalization requires to compute the divider per-joint.
const math::SimdFloat4 one = math::simd_float4::one();
for (size_t i = 0; i < _args->num_soa_joints; ++i) {
const math::SimdFloat4 ratio = one / _args->accumulated_weights[i];
math::SoaTransform& dest = _args->job.output[i];
dest.rotation = NormalizeEst(dest.rotation);
dest.translation = dest.translation * ratio;
dest.scale = dest.scale * ratio;
}
}
}
// Process additive blending pass.
void AddLayers(ProcessArgs* _args) {
assert(_args);
// Iterates through all layers and blend them to the output.
for (const BlendingJob::Layer& layer : _args->job.additive_layers) {
// Asserts buffer sizes, which must never fail as it has been validated.
assert(layer.transform.size() >= _args->num_soa_joints);
assert(layer.joint_weights.empty() ||
(layer.joint_weights.size() >= _args->num_soa_joints));
// Prepares constants.
const math::SimdFloat4 one = math::simd_float4::one();
if (layer.weight > 0.f) {
// Weight is positive, need to perform additive blending.
const math::SimdFloat4 layer_weight =
math::simd_float4::Load1(layer.weight);
if (!layer.joint_weights.empty()) {
// This layer has per-joint weights.
for (size_t i = 0; i < _args->num_soa_joints; ++i) {
const math::SoaTransform& src = layer.transform[i];
math::SoaTransform& dest = _args->job.output[i];
const math::SimdFloat4 weight =
layer_weight * math::Max0(layer.joint_weights[i]);
const math::SimdFloat4 one_minus_weight = one - weight;
const math::SoaFloat3 one_minus_weight_f3 = {
one_minus_weight, one_minus_weight, one_minus_weight};
OZZ_ADD_PASS(src, weight, dest);
}
} else {
// This is a full layer.
const math::SimdFloat4 one_minus_weight = one - layer_weight;
const math::SoaFloat3 one_minus_weight_f3 = {
one_minus_weight, one_minus_weight, one_minus_weight};
for (size_t i = 0; i < _args->num_soa_joints; ++i) {
const math::SoaTransform& src = layer.transform[i];
math::SoaTransform& dest = _args->job.output[i];
OZZ_ADD_PASS(src, layer_weight, dest);
}
}
} else if (layer.weight < 0.f) {
// Weight is negative, need to perform subtractive blending.
const math::SimdFloat4 layer_weight =
math::simd_float4::Load1(-layer.weight);
if (!layer.joint_weights.empty()) {
// This layer has per-joint weights.
for (size_t i = 0; i < _args->num_soa_joints; ++i) {
const math::SoaTransform& src = layer.transform[i];
math::SoaTransform& dest = _args->job.output[i];
const math::SimdFloat4 weight =
layer_weight * math::Max0(layer.joint_weights[i]);
const math::SimdFloat4 one_minus_weight = one - weight;
OZZ_SUB_PASS(src, weight, dest);
}
} else {
// This is a full layer.
const math::SimdFloat4 one_minus_weight = one - layer_weight;
for (size_t i = 0; i < _args->num_soa_joints; ++i) {
const math::SoaTransform& src = layer.transform[i];
math::SoaTransform& dest = _args->job.output[i];
OZZ_SUB_PASS(src, layer_weight, dest);
}
}
} else {
// Skip layer as its weight is 0.
}
}
}
} // namespace
bool BlendingJob::Run() const {
if (!Validate()) {
return false;
}
// Initializes blended parameters that are exchanged across blend stages.
ProcessArgs process_args(*this);
// Blends all layers to the job output buffers.
BlendLayers(&process_args);
// Applies bind pose.
BlendBindPose(&process_args);
// Normalizes output.
Normalize(&process_args);
// Process additive blending.
AddLayers(&process_args);
return true;
}
} // namespace animation
} // namespace ozz
@@ -0,0 +1,222 @@
//----------------------------------------------------------------------------//
// //
// 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/runtime/ik_aim_job.h"
#include <cassert>
#include "ozz/base/maths/math_ex.h"
#include "ozz/base/maths/simd_quaternion.h"
using namespace ozz::math;
namespace ozz {
namespace animation {
IKAimJob::IKAimJob()
: target(simd_float4::zero()),
forward(simd_float4::x_axis()),
offset(simd_float4::zero()),
up(simd_float4::y_axis()),
pole_vector(simd_float4::y_axis()),
twist_angle(0.f),
weight(1.f),
joint(nullptr),
joint_correction(nullptr),
reached(nullptr) {}
bool IKAimJob::Validate() const {
bool valid = true;
valid &= joint != nullptr;
valid &= joint_correction != nullptr;
valid &= ozz::math::AreAllTrue1(ozz::math::IsNormalizedEst3(forward));
return valid;
}
namespace {
// When there's an offset, the forward vector needs to be recomputed.
// The idea is to find the vector that will allow the point at offset position
// to aim at target position. This vector starts at joint position. It ends on a
// line perpendicular to pivot-offset line, at the intersection with the sphere
// defined by target position (centered on joint position). See geogebra
// diagram: media/doc/src/ik_aim_offset.ggb
bool ComputeOffsettedForward(_SimdFloat4 _forward, _SimdFloat4 _offset,
_SimdFloat4 _target,
SimdFloat4* _offsetted_forward) {
// AO is projected offset vector onto the normalized forward vector.
assert(ozz::math::AreAllTrue1(ozz::math::IsNormalizedEst3(_forward)));
const SimdFloat4 AOl = Dot3(_forward, _offset);
// Compute square length of ac using Pythagorean theorem.
const SimdFloat4 ACl2 = Length3Sqr(_offset) - AOl * AOl;
// Square length of target vector, aka circle radius.
const SimdFloat4 r2 = Length3Sqr(_target);
// If offset is outside of the sphere defined by target length, the target
// isn't reachable.
if (AreAllTrue1(CmpGt(ACl2, r2))) {
return false;
}
// AIl is the length of the vector from offset to sphere intersection.
const SimdFloat4 AIl = SqrtX(r2 - ACl2);
// The distance from offset position to the intersection with the sphere is
// (AIl - AOl) Intersection point on the sphere can thus be computed.
*_offsetted_forward = _offset + _forward * SplatX(AIl - AOl);
return true;
}
} // namespace
bool IKAimJob::Run() const {
if (!Validate()) {
return false;
}
using math::Float4x4;
using math::SimdFloat4;
using math::SimdQuaternion;
// If matrices aren't invertible, they'll be all 0 (ozz::math
// implementation), which will result in identity correction quaternions.
SimdInt4 invertible;
const Float4x4 inv_joint = Invert(*joint, &invertible);
// Computes joint to target vector, in joint local-space (_js).
const SimdFloat4 joint_to_target_js = TransformPoint(inv_joint, target);
const SimdFloat4 joint_to_target_js_len2 = Length3Sqr(joint_to_target_js);
// Recomputes forward vector to account for offset.
// If the offset is further than target, it won't be reachable.
SimdFloat4 offsetted_forward;
bool lreached = ComputeOffsettedForward(forward, offset, joint_to_target_js,
&offsetted_forward);
// Copies reachability result.
// If offsetted forward vector doesn't exists, target position cannot be
// aimed.
if (reached != nullptr) {
*reached = lreached;
}
if (!lreached ||
AreAllTrue1(CmpEq(joint_to_target_js_len2, simd_float4::zero()))) {
// Target can't be reached or is too close to joint position to find a
// direction.
*joint_correction = SimdQuaternion::identity();
return true;
}
// Calculates joint_to_target_rot_ss quaternion which solves for
// offsetted_forward vector rotating onto the target.
const SimdQuaternion joint_to_target_rot_js =
SimdQuaternion::FromVectors(offsetted_forward, joint_to_target_js);
// Calculates rotate_plane_js quaternion which aligns joint up to the pole
// vector.
const SimdFloat4 corrected_up_js =
TransformVector(joint_to_target_rot_js, up);
// Compute (and normalize) reference and pole planes normals.
const SimdFloat4 pole_vector_js = TransformVector(inv_joint, pole_vector);
const SimdFloat4 ref_joint_normal_js =
Cross3(pole_vector_js, joint_to_target_js);
const SimdFloat4 joint_normal_js =
Cross3(corrected_up_js, joint_to_target_js);
const SimdFloat4 ref_joint_normal_js_len2 = Length3Sqr(ref_joint_normal_js);
const SimdFloat4 joint_normal_js_len2 = Length3Sqr(joint_normal_js);
const SimdFloat4 denoms =
SetZ(SetY(joint_to_target_js_len2, joint_normal_js_len2),
ref_joint_normal_js_len2);
SimdFloat4 rotate_plane_axis_js;
SimdQuaternion rotate_plane_js;
// Computing rotation axis and plane requires valid normals.
if (AreAllTrue3(CmpNe(denoms, simd_float4::zero()))) {
const SimdFloat4 rsqrts =
RSqrtEstNR(SetZ(SetY(joint_to_target_js_len2, joint_normal_js_len2),
ref_joint_normal_js_len2));
// Computes rotation axis, which is either joint_to_target_js or
// -joint_to_target_js depending on rotation direction.
rotate_plane_axis_js = joint_to_target_js * SplatX(rsqrts);
// Computes angle cosine between the 2 normalized plane normals.
const SimdFloat4 rotate_plane_cos_angle = Dot3(
joint_normal_js * SplatY(rsqrts), ref_joint_normal_js * SplatZ(rsqrts));
const SimdFloat4 axis_flip =
And(SplatX(Dot3(ref_joint_normal_js, corrected_up_js)),
simd_int4::mask_sign());
const SimdFloat4 rotate_plane_axis_flipped_js =
Xor(rotate_plane_axis_js, axis_flip);
// Builds quaternion along rotation axis.
const SimdFloat4 one = simd_float4::one();
rotate_plane_js = SimdQuaternion::FromAxisCosAngle(
rotate_plane_axis_flipped_js, Clamp(-one, rotate_plane_cos_angle, one));
} else {
rotate_plane_axis_js = joint_to_target_js * SplatX(RSqrtEstXNR(denoms));
rotate_plane_js = SimdQuaternion::identity();
}
// Twists rotation plane.
SimdQuaternion twisted;
if (twist_angle != 0.f) {
// If a twist angle is provided, rotation angle is rotated around joint to
// target vector.
const SimdQuaternion twist_ss = SimdQuaternion::FromAxisAngle(
rotate_plane_axis_js, simd_float4::Load1(twist_angle));
twisted = twist_ss * rotate_plane_js * joint_to_target_rot_js;
} else {
twisted = rotate_plane_js * joint_to_target_rot_js;
}
// Weights output quaternion.
// Fix up quaternions so w is always positive, which is required for NLerp
// (with identity quaternion) to lerp the shortest path.
const SimdFloat4 twisted_fu =
Xor(twisted.xyzw, And(simd_int4::mask_sign(),
CmpLt(SplatW(twisted.xyzw), simd_float4::zero())));
if (weight < 1.f) {
// NLerp start and mid joint rotations.
const SimdFloat4 identity = simd_float4::w_axis();
const SimdFloat4 simd_weight = Max0(simd_float4::Load1(weight));
joint_correction->xyzw =
NormalizeEst4(Lerp(identity, twisted.xyzw, simd_weight));
} else {
// Quaternion doesn't need interpolation
joint_correction->xyzw = twisted_fu;
}
return true;
}
} // namespace animation
} // namespace ozz
@@ -0,0 +1,392 @@
//----------------------------------------------------------------------------//
// //
// 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/runtime/ik_two_bone_job.h"
#include <cassert>
#include "ozz/base/log.h"
#include "ozz/base/maths/math_ex.h"
#include "ozz/base/maths/simd_quaternion.h"
using namespace ozz::math;
namespace ozz {
namespace animation {
IKTwoBoneJob::IKTwoBoneJob()
: target(math::simd_float4::zero()),
mid_axis(math::simd_float4::z_axis()),
pole_vector(math::simd_float4::y_axis()),
twist_angle(0.f),
soften(1.f),
weight(1.f),
start_joint(nullptr),
mid_joint(nullptr),
end_joint(nullptr),
start_joint_correction(nullptr),
mid_joint_correction(nullptr),
reached(nullptr) {}
bool IKTwoBoneJob::Validate() const {
bool valid = true;
valid &= start_joint && mid_joint && end_joint;
valid &= start_joint_correction && mid_joint_correction;
valid &= ozz::math::AreAllTrue1(ozz::math::IsNormalizedEst3(mid_axis));
return valid;
}
namespace {
// Local data structure used to share constant data accross ik stages.
struct IKConstantSetup {
IKConstantSetup(const IKTwoBoneJob& _job) {
// Prepares constants
one = simd_float4::one();
mask_sign = simd_int4::mask_sign();
m_one = Xor(one, mask_sign);
// Computes inverse matrices required to change to start and mid spaces.
// If matrices aren't invertible, they'll be all 0 (ozz::math
// implementation), which will result in identity correction quaternions.
SimdInt4 invertible;
(void)invertible;
inv_start_joint = Invert(*_job.start_joint, &invertible);
const Float4x4 inv_mid_joint = Invert(*_job.mid_joint, &invertible);
// Transform some positions to mid joint space (_ms)
const SimdFloat4 start_ms =
TransformPoint(inv_mid_joint, _job.start_joint->cols[3]);
const SimdFloat4 end_ms =
TransformPoint(inv_mid_joint, _job.end_joint->cols[3]);
// Transform some positions to start joint space (_ss)
const SimdFloat4 mid_ss =
TransformPoint(inv_start_joint, _job.mid_joint->cols[3]);
const SimdFloat4 end_ss =
TransformPoint(inv_start_joint, _job.end_joint->cols[3]);
// Computes bones vectors and length in mid and start spaces.
// Start joint position will be treated as 0 because all joints are
// expressed in start joint space.
start_mid_ms = -start_ms;
mid_end_ms = end_ms;
start_mid_ss = mid_ss;
const SimdFloat4 mid_end_ss = end_ss - mid_ss;
const SimdFloat4 start_end_ss = end_ss;
start_mid_ss_len2 = Length3Sqr(start_mid_ss);
mid_end_ss_len2 = Length3Sqr(mid_end_ss);
start_end_ss_len2 = Length3Sqr(start_end_ss);
}
// Constants
SimdFloat4 one;
SimdFloat4 m_one;
SimdInt4 mask_sign;
// Inverse matrices
Float4x4 inv_start_joint;
// Bones vectors and length in mid and start spaces (_ms and _ss).
SimdFloat4 start_mid_ms;
SimdFloat4 mid_end_ms;
SimdFloat4 start_mid_ss;
SimdFloat4 start_mid_ss_len2;
SimdFloat4 mid_end_ss_len2;
SimdFloat4 start_end_ss_len2;
};
// Smoothen target position when it's further that a ratio of the joint chain
// length, and start to target length isn't 0.
// Inspired by http://www.softimageblog.com/archives/108
// and http://www.ryanjuckett.com/programming/analytic-two-bone-ik-in-2d/
bool SoftenTarget(const IKTwoBoneJob& _job, const IKConstantSetup& _setup,
SimdFloat4* _start_target_ss,
SimdFloat4* _start_target_ss_len2) {
// Hanlde position in start joint space (_ss)
const SimdFloat4 start_target_original_ss =
TransformPoint(_setup.inv_start_joint, _job.target);
const SimdFloat4 start_target_original_ss_len2 =
Length3Sqr(start_target_original_ss);
const SimdFloat4 lengths =
Sqrt(SetZ(SetY(_setup.start_mid_ss_len2, _setup.mid_end_ss_len2),
start_target_original_ss_len2));
const SimdFloat4 start_mid_ss_len = lengths;
const SimdFloat4 mid_end_ss_len = SplatY(lengths);
const SimdFloat4 start_target_original_ss_len = SplatZ(lengths);
const SimdFloat4 bone_len_diff_abs =
AndNot(start_mid_ss_len - mid_end_ss_len, _setup.mask_sign);
const SimdFloat4 bones_chain_len = start_mid_ss_len + mid_end_ss_len;
const SimdFloat4 da = // da.yzw needs to be 0
bones_chain_len *
Clamp(simd_float4::zero(), simd_float4::LoadX(_job.soften), _setup.one);
const SimdFloat4 ds = bones_chain_len - da;
// Sotftens target position if it is further than a ratio (_soften) of the
// whole bone chain length. Needs to check also that ds and
// start_target_original_ss_len2 are != 0, because they're used as a
// denominator.
// x = start_target_original_ss_len > da
// y = start_target_original_ss_len > 0
// z = start_target_original_ss_len > bone_len_diff_abs
// w = ds > 0
const SimdFloat4 left = SetW(start_target_original_ss_len, ds);
const SimdFloat4 right = SetZ(da, bone_len_diff_abs);
const SimdInt4 comp = CmpGt(left, right);
const int comp_mask = MoveMask(comp);
// xyw all 1, z is untested.
if ((comp_mask & 0xb) == 0xb) {
// Finds interpolation ratio (aka alpha).
const SimdFloat4 alpha = (start_target_original_ss_len - da) * RcpEstX(ds);
// Approximate an exponential function with : 1-(3^4)/(alpha+3)^4
// The derivative must be 1 for x = 0, and y must never exceeds 1.
// Negative x aren't used.
const SimdFloat4 three = simd_float4::Load1(3.f);
const SimdFloat4 op = SetY(three, alpha + three);
const SimdFloat4 op2 = op * op;
const SimdFloat4 op4 = op2 * op2;
const SimdFloat4 ratio = op4 * RcpEstX(SplatY(op4));
// Recomputes start_target_ss vector and length.
const SimdFloat4 start_target_ss_len = da + ds - ds * ratio;
*_start_target_ss_len2 = start_target_ss_len * start_target_ss_len;
*_start_target_ss =
start_target_original_ss *
SplatX(start_target_ss_len * RcpEstX(start_target_original_ss_len));
} else {
*_start_target_ss = start_target_original_ss;
*_start_target_ss_len2 = start_target_original_ss_len2;
}
// The maximum distance we can reach is the soften bone chain length: da
// (stored in !x). The minimum distance we can reach is the absolute value of
// the difference of the 2 bone lengths, |d1d2| (stored in z). x is 0 and z
// is 1, yw are untested.
return (comp_mask & 0x5) == 0x4;
}
SimdQuaternion ComputeMidJoint(const IKTwoBoneJob& _job,
const IKConstantSetup& _setup,
_SimdFloat4 _start_target_ss_len2) {
// Computes expected angle at mid_ss joint, using law of cosine (generalized
// Pythagorean).
// c^2 = a^2 + b^2 - 2ab cosC
// cosC = (a^2 + b^2 - c^2) / 2ab
// Computes both corrected and initial mid joint angles
// cosine within a single SimdFloat4 (corrected is x component, initial is y).
const SimdFloat4 start_mid_end_sum_ss_len2 =
_setup.start_mid_ss_len2 + _setup.mid_end_ss_len2;
const SimdFloat4 start_mid_end_ss_half_rlen =
SplatX(simd_float4::Load1(.5f) *
RSqrtEstXNR(_setup.start_mid_ss_len2 * _setup.mid_end_ss_len2));
// Cos value needs to be clamped, as it will exit expected range if
// start_target_ss_len2 is longer than the triangle can be (start_mid_ss +
// mid_end_ss).
const SimdFloat4 mid_cos_angles_unclamped =
(SplatX(start_mid_end_sum_ss_len2) -
SetY(_start_target_ss_len2, _setup.start_end_ss_len2)) *
start_mid_end_ss_half_rlen;
const SimdFloat4 mid_cos_angles =
Clamp(_setup.m_one, mid_cos_angles_unclamped, _setup.one);
// Computes corrected angle
const SimdFloat4 mid_corrected_angle = ACosX(mid_cos_angles);
// Computes initial angle.
// The sign of this angle needs to be decided. It's considered negative if
// mid-to-end joint is bent backward (mid_axis direction dictates valid
// bent direction).
const SimdFloat4 bent_side_ref = Cross3(_setup.start_mid_ms, _job.mid_axis);
const SimdInt4 bent_side_flip = SplatX(
CmpLt(Dot3(bent_side_ref, _setup.mid_end_ms), simd_float4::zero()));
const SimdFloat4 mid_initial_angle =
Xor(ACosX(SplatY(mid_cos_angles)), And(bent_side_flip, _setup.mask_sign));
// Finally deduces initial to corrected angle difference.
const SimdFloat4 mid_angles_diff = mid_corrected_angle - mid_initial_angle;
// Builds queternion.
return SimdQuaternion::FromAxisAngle(_job.mid_axis, mid_angles_diff);
}
SimdQuaternion ComputeStartJoint(const IKTwoBoneJob& _job,
const IKConstantSetup& _setup,
const SimdQuaternion& _mid_rot_ms,
_SimdFloat4 _start_target_ss,
_SimdFloat4 _start_target_ss_len2) {
// Pole vector in start joint space (_ss)
const SimdFloat4 pole_ss =
TransformVector(_setup.inv_start_joint, _job.pole_vector);
// start_mid_ss with quaternion mid_rot_ms applied.
const SimdFloat4 mid_end_ss_final = TransformVector(
_setup.inv_start_joint,
TransformVector(*_job.mid_joint,
TransformVector(_mid_rot_ms, _setup.mid_end_ms)));
const SimdFloat4 start_end_ss_final = _setup.start_mid_ss + mid_end_ss_final;
// Quaternion for rotating the effector onto the target
const SimdQuaternion end_to_target_rot_ss =
SimdQuaternion::FromVectors(start_end_ss_final, _start_target_ss);
// Calculates rotate_plane_ss quaternion which aligns joint chain plane to
// the reference plane (pole vector). This can only be computed if start
// target axis is valid (not 0 length)
// -------------------------------------------------
SimdQuaternion start_rot_ss = end_to_target_rot_ss;
if (AreAllTrue1(CmpGt(_start_target_ss_len2, simd_float4::zero()))) {
// Computes each plane normal.
const ozz::math::SimdFloat4 ref_plane_normal_ss =
Cross3(_start_target_ss, pole_ss);
const ozz::math::SimdFloat4 ref_plane_normal_ss_len2 =
ozz::math::Length3Sqr(ref_plane_normal_ss);
// Computes joint chain plane normal, which is the same as mid joint axis
// (same triangle).
const ozz::math::SimdFloat4 mid_axis_ss =
TransformVector(_setup.inv_start_joint,
TransformVector(*_job.mid_joint, _job.mid_axis));
const ozz::math::SimdFloat4 joint_plane_normal_ss =
TransformVector(end_to_target_rot_ss, mid_axis_ss);
const ozz::math::SimdFloat4 joint_plane_normal_ss_len2 =
ozz::math::Length3Sqr(joint_plane_normal_ss);
// Computes all reciprocal square roots at once.
const SimdFloat4 rsqrts =
RSqrtEstNR(SetZ(SetY(_start_target_ss_len2, ref_plane_normal_ss_len2),
joint_plane_normal_ss_len2));
// Computes angle cosine between the 2 normalized normals.
const SimdFloat4 rotate_plane_cos_angle =
ozz::math::Dot3(ref_plane_normal_ss * SplatY(rsqrts),
joint_plane_normal_ss * SplatZ(rsqrts));
// Computes rotation axis, which is either start_target_ss or
// -start_target_ss depending on rotation direction.
const SimdFloat4 rotate_plane_axis_ss = _start_target_ss * SplatX(rsqrts);
const SimdFloat4 start_axis_flip =
And(SplatX(Dot3(joint_plane_normal_ss, pole_ss)), _setup.mask_sign);
const SimdFloat4 rotate_plane_axis_flipped_ss =
Xor(rotate_plane_axis_ss, start_axis_flip);
// Builds quaternion along rotation axis.
const SimdQuaternion rotate_plane_ss = SimdQuaternion::FromAxisCosAngle(
rotate_plane_axis_flipped_ss,
Clamp(_setup.m_one, rotate_plane_cos_angle, _setup.one));
if (_job.twist_angle != 0.f) {
// If a twist angle is provided, rotation angle is rotated along
// rotation plane axis.
const SimdQuaternion twist_ss = SimdQuaternion::FromAxisAngle(
rotate_plane_axis_ss, simd_float4::Load1(_job.twist_angle));
start_rot_ss = twist_ss * rotate_plane_ss * end_to_target_rot_ss;
} else {
start_rot_ss = rotate_plane_ss * end_to_target_rot_ss;
}
}
return start_rot_ss;
}
void WeightOutput(const IKTwoBoneJob& _job, const IKConstantSetup& _setup,
const SimdQuaternion& _start_rot,
const SimdQuaternion& _mid_rot) {
const SimdFloat4 zero = simd_float4::zero();
// Fix up quaternions so w is always positive, which is required for NLerp
// (with identity quaternion) to lerp the shortest path.
const SimdFloat4 start_rot_fu =
Xor(_start_rot.xyzw,
And(_setup.mask_sign, CmpLt(SplatW(_start_rot.xyzw), zero)));
const SimdFloat4 mid_rot_fu = Xor(
_mid_rot.xyzw, And(_setup.mask_sign, CmpLt(SplatW(_mid_rot.xyzw), zero)));
if (_job.weight < 1.f) {
// NLerp start and mid joint rotations.
const SimdFloat4 identity = simd_float4::w_axis();
const SimdFloat4 simd_weight = Max(zero, simd_float4::Load1(_job.weight));
// Lerp
const SimdFloat4 start_lerp = Lerp(identity, start_rot_fu, simd_weight);
const SimdFloat4 mid_lerp = Lerp(identity, mid_rot_fu, simd_weight);
// Normalize
const SimdFloat4 rsqrts =
RSqrtEstNR(SetY(Length4Sqr(start_lerp), Length4Sqr(mid_lerp)));
_job.start_joint_correction->xyzw = start_lerp * SplatX(rsqrts);
_job.mid_joint_correction->xyzw = mid_lerp * SplatY(rsqrts);
} else {
// Quatenions don't need interpolation
_job.start_joint_correction->xyzw = start_rot_fu;
_job.mid_joint_correction->xyzw = mid_rot_fu;
}
}
} // namespace
bool IKTwoBoneJob::Run() const {
if (!Validate()) {
return false;
}
// Early out if weight is 0.
if (weight <= 0.f) {
// No correction.
*start_joint_correction = *mid_joint_correction =
SimdQuaternion::identity();
// Target isn't reached.
if (reached) {
*reached = false;
}
return true;
}
// Prepares constant ik data.
const IKConstantSetup setup(*this);
// Finds soften target position.
SimdFloat4 start_target_ss;
SimdFloat4 start_target_ss_len2;
const bool lreached =
SoftenTarget(*this, setup, &start_target_ss, &start_target_ss_len2);
if (reached) {
*reached = lreached && weight >= 1.f;
}
// Calculate mid_rot_local quaternion which solves for the mid_ss joint
// rotation.
const SimdQuaternion mid_rot_ms =
ComputeMidJoint(*this, setup, start_target_ss_len2);
// Calculates end_to_target_rot_ss quaternion which solves for effector
// rotating onto the target.
const SimdQuaternion start_rot_ss = ComputeStartJoint(
*this, setup, mid_rot_ms, start_target_ss, start_target_ss_len2);
// Finally apply weight and output quaternions.
WeightOutput(*this, setup, start_rot_ss, mid_rot_ms);
return true;
}
} // namespace animation
} // namespace ozz
@@ -0,0 +1,113 @@
//----------------------------------------------------------------------------//
// //
// 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/runtime/local_to_model_job.h"
#include <cassert>
#include "ozz/base/maths/math_ex.h"
#include "ozz/base/maths/simd_math.h"
#include "ozz/base/maths/soa_float4x4.h"
#include "ozz/base/maths/soa_transform.h"
#include "ozz/animation/runtime/skeleton.h"
namespace ozz {
namespace animation {
LocalToModelJob::LocalToModelJob()
: skeleton(nullptr),
root(nullptr),
from(Skeleton::kNoParent),
to(Skeleton::kMaxJoints),
from_excluded(false) {}
bool LocalToModelJob::Validate() const {
// Don't need any early out, as jobs are valid in most of the performance
// critical cases.
// Tests are written in multiple lines in order to avoid branches.
bool valid = true;
// Test for nullptr begin pointers.
if (!skeleton) {
return false;
}
const size_t num_joints = static_cast<size_t>(skeleton->num_joints());
const size_t num_soa_joints = (num_joints + 3) / 4;
// Test input and output ranges, implicitly tests for nullptr end pointers.
valid &= input.size() >= num_soa_joints;
valid &= output.size() >= num_joints;
return valid;
}
bool LocalToModelJob::Run() const {
if (!Validate()) {
return false;
}
const span<const int16_t>& parents = skeleton->joint_parents();
// Initializes an identity matrix that will be used to compute roots model
// matrices without requiring a branch.
const math::Float4x4 identity = math::Float4x4::identity();
const math::Float4x4* root_matrix = (root == nullptr) ? &identity : root;
// Applies hierarchical transformation.
// Loop ends after "to".
const int end = math::Min(to + 1, skeleton->num_joints());
// Begins iteration from "from", or the next joint if "from" is excluded.
// Process next joint if end is not reach. parents[begin] >= from is true as
// long as "begin" is a child of "from".
for (int i = math::Max(from + from_excluded, 0),
process = i < end && (!from_excluded || parents[i] >= from);
process;) {
// Builds soa matrices from soa transforms.
const math::SoaTransform& transform = input[i / 4];
const math::SoaFloat4x4 local_soa_matrices = math::SoaFloat4x4::FromAffine(
transform.translation, transform.rotation, transform.scale);
// Converts to aos matrices.
math::Float4x4 local_aos_matrices[4];
math::Transpose16x16(&local_soa_matrices.cols[0].x,
local_aos_matrices->cols);
// parents[i] >= from is true as long as "i" is a child of "from".
for (const int soa_end = (i + 4) & ~3; i < soa_end && process;
++i, process = i < end && parents[i] >= from) {
const int parent = parents[i];
const math::Float4x4* parent_matrix =
parent == Skeleton::kNoParent ? root_matrix : &output[parent];
output[i] = *parent_matrix * local_aos_matrices[i & 3];
}
}
return true;
}
} // namespace animation
} // namespace ozz
@@ -0,0 +1,452 @@
//----------------------------------------------------------------------------//
// //
// 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/runtime/sampling_job.h"
#include <cassert>
#include "ozz/animation/runtime/animation.h"
#include "ozz/base/maths/math_constant.h"
#include "ozz/base/maths/math_ex.h"
#include "ozz/base/maths/soa_transform.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 internal {
struct InterpSoaFloat3 {
math::SimdFloat4 ratio[2];
math::SoaFloat3 value[2];
};
struct InterpSoaQuaternion {
math::SimdFloat4 ratio[2];
math::SoaQuaternion value[2];
};
} // namespace internal
bool SamplingJob::Validate() const {
// Don't need any early out, as jobs are valid in most of the performance
// critical cases.
// Tests are written in multiple lines in order to avoid branches.
bool valid = true;
// Test for nullptr pointers.
if (!animation || !cache) {
return false;
}
valid &= !output.empty();
const int num_soa_tracks = animation->num_soa_tracks();
valid &= output.size() >= static_cast<size_t>(num_soa_tracks);
// Tests cache size.
valid &= cache->max_soa_tracks() >= num_soa_tracks;
return valid;
}
namespace {
// Loops through the sorted key frames and update cache structure.
template <typename _Key>
void UpdateCacheCursor(float _ratio, int _num_soa_tracks,
const ozz::span<const _Key>& _keys, int* _cursor,
int* _cache, unsigned char* _outdated) {
assert(_num_soa_tracks >= 1);
const int num_tracks = _num_soa_tracks * 4;
assert(_keys.begin() + num_tracks * 2 <= _keys.end());
const _Key* cursor = nullptr;
if (!*_cursor) {
// Initializes interpolated entries with the first 2 sets of key frames.
// The sorting algorithm ensures that the first 2 key frames of a track
// are consecutive.
for (int i = 0; i < _num_soa_tracks; ++i) {
const int in_index0 = i * 4; // * soa size
const int in_index1 = in_index0 + num_tracks; // 2nd row.
const int out_index = i * 4 * 2;
_cache[out_index + 0] = in_index0 + 0;
_cache[out_index + 1] = in_index1 + 0;
_cache[out_index + 2] = in_index0 + 1;
_cache[out_index + 3] = in_index1 + 1;
_cache[out_index + 4] = in_index0 + 2;
_cache[out_index + 5] = in_index1 + 2;
_cache[out_index + 6] = in_index0 + 3;
_cache[out_index + 7] = in_index1 + 3;
}
cursor = _keys.begin() + num_tracks * 2; // New cursor position.
// All entries are outdated. It cares to only flag valid soa entries as
// this is the exit condition of other algorithms.
const int num_outdated_flags = (_num_soa_tracks + 7) / 8;
for (int i = 0; i < num_outdated_flags - 1; ++i) {
_outdated[i] = 0xff;
}
_outdated[num_outdated_flags - 1] =
0xff >> (num_outdated_flags * 8 - _num_soa_tracks);
} else {
cursor = _keys.begin() + *_cursor; // Might be == end()
assert(cursor >= _keys.begin() + num_tracks * 2 && cursor <= _keys.end());
}
// Search for the keys that matches _ratio.
// Iterates while the cache is not updated with left and right keys required
// for interpolation at time ratio _ratio, for all tracks. Thanks to the
// keyframe sorting, the loop can end as soon as it finds a key greater that
// _ratio. It will mean that all the keys lower than _ratio have been
// processed, meaning all cache entries are up to date.
while (cursor < _keys.end() &&
_keys[_cache[cursor->track * 2 + 1]].ratio <= _ratio) {
// Flag this soa entry as outdated.
_outdated[cursor->track / 32] |= (1 << ((cursor->track & 0x1f) / 4));
// Updates cache.
const int base = cursor->track * 2;
_cache[base] = _cache[base + 1];
_cache[base + 1] = static_cast<int>(cursor - _keys.begin());
// Process next key.
++cursor;
}
assert(cursor <= _keys.end());
// Updates cursor output.
*_cursor = static_cast<int>(cursor - _keys.begin());
}
template <typename _Key, typename _InterpKey, typename _Decompress>
void UpdateInterpKeyframes(int _num_soa_tracks,
const ozz::span<const _Key>& _keys,
const int* _interp, uint8_t* _outdated,
_InterpKey* _interp_keys,
const _Decompress& _decompress) {
const int num_outdated_flags = (_num_soa_tracks + 7) / 8;
for (int j = 0; j < num_outdated_flags; ++j) {
uint8_t outdated = _outdated[j];
_outdated[j] = 0; // Reset outdated entries as all will be processed.
for (int i = j * 8; outdated; ++i, outdated >>= 1) {
if (!(outdated & 1)) {
continue;
}
const int base = i * 4 * 2; // * soa size * 2 keys
// Decompress left side keyframes and store them in soa structures.
const _Key& k00 = _keys[_interp[base + 0]];
const _Key& k10 = _keys[_interp[base + 2]];
const _Key& k20 = _keys[_interp[base + 4]];
const _Key& k30 = _keys[_interp[base + 6]];
_interp_keys[i].ratio[0] =
math::simd_float4::Load(k00.ratio, k10.ratio, k20.ratio, k30.ratio);
_decompress(k00, k10, k20, k30, &_interp_keys[i].value[0]);
// Decompress right side keyframes and store them in soa structures.
const _Key& k01 = _keys[_interp[base + 1]];
const _Key& k11 = _keys[_interp[base + 3]];
const _Key& k21 = _keys[_interp[base + 5]];
const _Key& k31 = _keys[_interp[base + 7]];
_interp_keys[i].ratio[1] =
math::simd_float4::Load(k01.ratio, k11.ratio, k21.ratio, k31.ratio);
_decompress(k01, k11, k21, k31, &_interp_keys[i].value[1]);
}
}
}
inline void DecompressFloat3(const Float3Key& _k0, const Float3Key& _k1,
const Float3Key& _k2, const Float3Key& _k3,
math::SoaFloat3* _soa_float3) {
_soa_float3->x = math::HalfToFloat(math::simd_int4::Load(
_k0.value[0], _k1.value[0], _k2.value[0], _k3.value[0]));
_soa_float3->y = math::HalfToFloat(math::simd_int4::Load(
_k0.value[1], _k1.value[1], _k2.value[1], _k3.value[1]));
_soa_float3->z = math::HalfToFloat(math::simd_int4::Load(
_k0.value[2], _k1.value[2], _k2.value[2], _k3.value[2]));
}
// Defines a mapping table that defines components assignation in the output
// quaternion.
constexpr int kCpntMapping[4][4] = {
{0, 0, 1, 2}, {0, 0, 1, 2}, {0, 1, 0, 2}, {0, 1, 2, 0}};
void DecompressQuaternion(const QuaternionKey& _k0, const QuaternionKey& _k1,
const QuaternionKey& _k2, const QuaternionKey& _k3,
math::SoaQuaternion* _quaternion) {
// Selects proper mapping for each key.
const int* m0 = kCpntMapping[_k0.largest];
const int* m1 = kCpntMapping[_k1.largest];
const int* m2 = kCpntMapping[_k2.largest];
const int* m3 = kCpntMapping[_k3.largest];
// Prepares an array of input values, according to the mapping required to
// restore quaternion largest component.
alignas(16) int cmp_keys[4][4] = {
{_k0.value[m0[0]], _k1.value[m1[0]], _k2.value[m2[0]], _k3.value[m3[0]]},
{_k0.value[m0[1]], _k1.value[m1[1]], _k2.value[m2[1]], _k3.value[m3[1]]},
{_k0.value[m0[2]], _k1.value[m1[2]], _k2.value[m2[2]], _k3.value[m3[2]]},
{_k0.value[m0[3]], _k1.value[m1[3]], _k2.value[m2[3]], _k3.value[m3[3]]},
};
// Resets largest component to 0. Overwritting here avoids 16 branchings
// above.
cmp_keys[_k0.largest][0] = 0;
cmp_keys[_k1.largest][1] = 0;
cmp_keys[_k2.largest][2] = 0;
cmp_keys[_k3.largest][3] = 0;
// Rebuilds quaternion from quantized values.
const math::SimdFloat4 kInt2Float =
math::simd_float4::Load1(1.f / (32767.f * math::kSqrt2));
math::SimdFloat4 cpnt[4] = {
kInt2Float *
math::simd_float4::FromInt(math::simd_int4::LoadPtr(cmp_keys[0])),
kInt2Float *
math::simd_float4::FromInt(math::simd_int4::LoadPtr(cmp_keys[1])),
kInt2Float *
math::simd_float4::FromInt(math::simd_int4::LoadPtr(cmp_keys[2])),
kInt2Float *
math::simd_float4::FromInt(math::simd_int4::LoadPtr(cmp_keys[3])),
};
// Get back length of 4th component. Favors performance over accuracy by using
// x * RSqrtEst(x) instead of Sqrt(x).
// ww0 cannot be 0 because we 're recomputing the largest component.
const math::SimdFloat4 dot = cpnt[0] * cpnt[0] + cpnt[1] * cpnt[1] +
cpnt[2] * cpnt[2] + cpnt[3] * cpnt[3];
const math::SimdFloat4 ww0 = math::Max(math::simd_float4::Load1(1e-16f),
math::simd_float4::one() - dot);
const math::SimdFloat4 w0 = ww0 * math::RSqrtEst(ww0);
// Re-applies 4th component' s sign.
const math::SimdInt4 sign = math::ShiftL(
math::simd_int4::Load(_k0.sign, _k1.sign, _k2.sign, _k3.sign), 31);
const math::SimdFloat4 restored = math::Or(w0, sign);
// Re-injects the largest component inside the SoA structure.
cpnt[_k0.largest] = math::Or(
cpnt[_k0.largest], math::And(restored, math::simd_int4::mask_f000()));
cpnt[_k1.largest] = math::Or(
cpnt[_k1.largest], math::And(restored, math::simd_int4::mask_0f00()));
cpnt[_k2.largest] = math::Or(
cpnt[_k2.largest], math::And(restored, math::simd_int4::mask_00f0()));
cpnt[_k3.largest] = math::Or(
cpnt[_k3.largest], math::And(restored, math::simd_int4::mask_000f()));
// Stores result.
_quaternion->x = cpnt[0];
_quaternion->y = cpnt[1];
_quaternion->z = cpnt[2];
_quaternion->w = cpnt[3];
}
void Interpolates(float _anim_ratio, int _num_soa_tracks,
const internal::InterpSoaFloat3* _translations,
const internal::InterpSoaQuaternion* _rotations,
const internal::InterpSoaFloat3* _scales,
math::SoaTransform* _output) {
const math::SimdFloat4 anim_ratio = math::simd_float4::Load1(_anim_ratio);
for (int i = 0; i < _num_soa_tracks; ++i) {
// Prepares interpolation coefficients.
const math::SimdFloat4 interp_t_ratio =
(anim_ratio - _translations[i].ratio[0]) *
math::RcpEst(_translations[i].ratio[1] - _translations[i].ratio[0]);
const math::SimdFloat4 interp_r_ratio =
(anim_ratio - _rotations[i].ratio[0]) *
math::RcpEst(_rotations[i].ratio[1] - _rotations[i].ratio[0]);
const math::SimdFloat4 interp_s_ratio =
(anim_ratio - _scales[i].ratio[0]) *
math::RcpEst(_scales[i].ratio[1] - _scales[i].ratio[0]);
// Processes interpolations.
// The lerp of the rotation uses the shortest path, because opposed
// quaternions were negated during animation build stage (AnimationBuilder).
_output[i].translation = Lerp(_translations[i].value[0],
_translations[i].value[1], interp_t_ratio);
_output[i].rotation = NLerpEst(_rotations[i].value[0],
_rotations[i].value[1], interp_r_ratio);
_output[i].scale =
Lerp(_scales[i].value[0], _scales[i].value[1], interp_s_ratio);
}
}
} // namespace
SamplingJob::SamplingJob() : ratio(0.f), animation(nullptr), cache(nullptr) {}
bool SamplingJob::Run() const {
if (!Validate()) {
return false;
}
const int num_soa_tracks = animation->num_soa_tracks();
if (num_soa_tracks == 0) { // Early out if animation contains no joint.
return true;
}
// Clamps ratio in range [0,duration].
const float anim_ratio = math::Clamp(0.f, ratio, 1.f);
// Step the cache to this potentially new animation and ratio.
assert(cache->max_soa_tracks() >= num_soa_tracks);
cache->Step(*animation, anim_ratio);
// Fetch key frames from the animation to the cache a r = anim_ratio.
// Then updates outdated soa hot values.
UpdateCacheCursor(anim_ratio, num_soa_tracks, animation->translations(),
&cache->translation_cursor_, cache->translation_keys_,
cache->outdated_translations_);
UpdateInterpKeyframes(num_soa_tracks, animation->translations(),
cache->translation_keys_, cache->outdated_translations_,
cache->soa_translations_, &DecompressFloat3);
UpdateCacheCursor(anim_ratio, num_soa_tracks, animation->rotations(),
&cache->rotation_cursor_, cache->rotation_keys_,
cache->outdated_rotations_);
UpdateInterpKeyframes(num_soa_tracks, animation->rotations(),
cache->rotation_keys_, cache->outdated_rotations_,
cache->soa_rotations_, &DecompressQuaternion);
UpdateCacheCursor(anim_ratio, num_soa_tracks, animation->scales(),
&cache->scale_cursor_, cache->scale_keys_,
cache->outdated_scales_);
UpdateInterpKeyframes(num_soa_tracks, animation->scales(), cache->scale_keys_,
cache->outdated_scales_, cache->soa_scales_,
&DecompressFloat3);
// Interpolates soa hot data.
Interpolates(anim_ratio, num_soa_tracks, cache->soa_translations_,
cache->soa_rotations_, cache->soa_scales_, output.begin());
return true;
}
SamplingCache::SamplingCache()
: max_soa_tracks_(0),
soa_translations_(
nullptr) { // soa_translations_ is the allocation pointer.
Invalidate();
}
SamplingCache::SamplingCache(int _max_tracks)
: max_soa_tracks_(0),
soa_translations_(
nullptr) { // soa_translations_ is the allocation pointer.
Resize(_max_tracks);
}
SamplingCache::~SamplingCache() {
// Deallocates everything at once.
memory::default_allocator()->Deallocate(soa_translations_);
}
void SamplingCache::Resize(int _max_tracks) {
using internal::InterpSoaFloat3;
using internal::InterpSoaQuaternion;
// Reset existing data.
Invalidate();
memory::default_allocator()->Deallocate(soa_translations_);
// Updates maximum supported soa tracks.
max_soa_tracks_ = (_max_tracks + 3) / 4;
// Allocate all cache data at once in a single allocation.
// Alignment is guaranteed because memory is dispatch from the highest
// alignment requirement (Soa data: SimdFloat4) to the lowest (outdated
// flag: unsigned char).
// Computes allocation size.
const size_t max_tracks = max_soa_tracks_ * 4;
const size_t num_outdated = (max_soa_tracks_ + 7) / 8;
const size_t size =
sizeof(InterpSoaFloat3) * max_soa_tracks_ +
sizeof(InterpSoaQuaternion) * max_soa_tracks_ +
sizeof(InterpSoaFloat3) * max_soa_tracks_ +
sizeof(int) * max_tracks * 2 * 3 + // 2 keys * (trans + rot + scale).
sizeof(uint8_t) * 3 * num_outdated;
// Allocates all at once.
memory::Allocator* allocator = memory::default_allocator();
char* alloc_begin = reinterpret_cast<char*>(
allocator->Allocate(size, alignof(InterpSoaFloat3)));
char* alloc_cursor = alloc_begin;
// Distributes buffer memory while ensuring proper alignment (serves larger
// alignment values first).
static_assert(alignof(InterpSoaFloat3) >= alignof(InterpSoaQuaternion) &&
alignof(InterpSoaQuaternion) >= alignof(InterpSoaFloat3) &&
alignof(InterpSoaFloat3) >= alignof(int) &&
alignof(int) >= alignof(uint8_t),
"Must serve larger alignment values first)");
soa_translations_ = reinterpret_cast<InterpSoaFloat3*>(alloc_cursor);
assert(IsAligned(soa_translations_, alignof(InterpSoaFloat3)));
alloc_cursor += sizeof(InterpSoaFloat3) * max_soa_tracks_;
soa_rotations_ = reinterpret_cast<InterpSoaQuaternion*>(alloc_cursor);
assert(IsAligned(soa_rotations_, alignof(InterpSoaQuaternion)));
alloc_cursor += sizeof(InterpSoaQuaternion) * max_soa_tracks_;
soa_scales_ = reinterpret_cast<InterpSoaFloat3*>(alloc_cursor);
assert(IsAligned(soa_scales_, alignof(InterpSoaFloat3)));
alloc_cursor += sizeof(InterpSoaFloat3) * max_soa_tracks_;
translation_keys_ = reinterpret_cast<int*>(alloc_cursor);
assert(IsAligned(translation_keys_, alignof(int)));
alloc_cursor += sizeof(int) * max_tracks * 2;
rotation_keys_ = reinterpret_cast<int*>(alloc_cursor);
alloc_cursor += sizeof(int) * max_tracks * 2;
scale_keys_ = reinterpret_cast<int*>(alloc_cursor);
alloc_cursor += sizeof(int) * max_tracks * 2;
outdated_translations_ = reinterpret_cast<uint8_t*>(alloc_cursor);
assert(IsAligned(outdated_translations_, alignof(uint8_t)));
alloc_cursor += sizeof(uint8_t) * num_outdated;
outdated_rotations_ = reinterpret_cast<uint8_t*>(alloc_cursor);
alloc_cursor += sizeof(uint8_t) * num_outdated;
outdated_scales_ = reinterpret_cast<uint8_t*>(alloc_cursor);
alloc_cursor += sizeof(uint8_t) * num_outdated;
assert(alloc_cursor == alloc_begin + size);
}
void SamplingCache::Step(const Animation& _animation, float _ratio) {
// The cache is invalidated if animation has changed or if it is being rewind.
if (animation_ != &_animation || _ratio < ratio_) {
animation_ = &_animation;
translation_cursor_ = 0;
rotation_cursor_ = 0;
scale_cursor_ = 0;
}
ratio_ = _ratio;
}
void SamplingCache::Invalidate() {
animation_ = nullptr;
ratio_ = 0.f;
translation_cursor_ = 0;
rotation_cursor_ = 0;
scale_cursor_ = 0;
}
} // namespace animation
} // namespace ozz
+161
View File
@@ -0,0 +1,161 @@
//----------------------------------------------------------------------------//
// //
// 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/runtime/skeleton.h"
#include <cstring>
#include "ozz/base/io/archive.h"
#include "ozz/base/log.h"
#include "ozz/base/maths/math_ex.h"
#include "ozz/base/maths/soa_math_archive.h"
#include "ozz/base/maths/soa_transform.h"
#include "ozz/base/memory/allocator.h"
namespace ozz {
namespace animation {
Skeleton::Skeleton() {}
Skeleton::~Skeleton() { Deallocate(); }
char* Skeleton::Allocate(size_t _chars_size, size_t _num_joints) {
// Distributes buffer memory while ensuring proper alignment (serves larger
// alignment values first).
static_assert(alignof(math::SoaTransform) >= alignof(char*) &&
alignof(char*) >= alignof(int16_t) &&
alignof(int16_t) >= alignof(char),
"Must serve larger alignment values first)");
assert(joint_bind_poses_.size() == 0 && joint_names_.size() == 0 &&
joint_parents_.size() == 0);
// Early out if no joint.
if (_num_joints == 0) {
return nullptr;
}
// Bind poses have SoA format
const size_t num_soa_joints = (_num_joints + 3) / 4;
const size_t joint_bind_poses_size =
num_soa_joints * sizeof(math::SoaTransform);
const size_t names_size = _num_joints * sizeof(char*);
const size_t joint_parents_size = _num_joints * sizeof(int16_t);
const size_t buffer_size =
names_size + _chars_size + joint_parents_size + joint_bind_poses_size;
// Allocates whole buffer.
span<char> buffer = {static_cast<char*>(memory::default_allocator()->Allocate(
buffer_size, alignof(math::SoaTransform))),
buffer_size};
// Serves larger alignment values first.
// Bind pose first, biggest alignment.
joint_bind_poses_ = fill_span<math::SoaTransform>(buffer, num_soa_joints);
// Then names array, second biggest alignment.
joint_names_ = fill_span<char*>(buffer, _num_joints);
// Parents, third biggest alignment.
joint_parents_ = fill_span<int16_t>(buffer, _num_joints);
// Remaning buffer will be used to store joint names.
assert(buffer.size_bytes() == _chars_size &&
"Whole buffer should be consumned");
return buffer.data();
}
void Skeleton::Deallocate() {
memory::default_allocator()->Deallocate(as_writable_bytes(joint_bind_poses_).data());
joint_bind_poses_ = {};
joint_names_ = {};
joint_parents_ = {};
}
void Skeleton::Save(ozz::io::OArchive& _archive) const {
const int32_t num_joints = this->num_joints();
// Early out if skeleton's empty.
_archive << num_joints;
if (!num_joints) {
return;
}
// Stores names. They are all concatenated in the same buffer, starting at
// joint_names_[0].
size_t chars_count = 0;
for (int i = 0; i < num_joints; ++i) {
chars_count += (std::strlen(joint_names_[i]) + 1) * sizeof(char);
}
_archive << static_cast<int32_t>(chars_count);
_archive << ozz::io::MakeArray(joint_names_[0], chars_count);
_archive << ozz::io::MakeArray(joint_parents_);
_archive << ozz::io::MakeArray(joint_bind_poses_);
}
void Skeleton::Load(ozz::io::IArchive& _archive, uint32_t _version) {
// Deallocate skeleton in case it was already used before.
Deallocate();
if (_version != 2) {
log::Err() << "Unsupported Skeleton version " << _version << "."
<< std::endl;
return;
}
int32_t num_joints;
_archive >> num_joints;
// Early out if skeleton's empty.
if (!num_joints) {
return;
}
// Read names.
int32_t chars_count;
_archive >> chars_count;
// Allocates all skeleton data members.
char* cursor = Allocate(chars_count, num_joints);
// Reads name's buffer, they are all contiguous in the same buffer.
_archive >> ozz::io::MakeArray(cursor, chars_count);
// Fixes up array of pointers. Stops at num_joints - 1, so that it doesn't
// read memory past the end of the buffer.
for (int i = 0; i < num_joints - 1; ++i) {
joint_names_[i] = cursor;
cursor += std::strlen(joint_names_[i]) + 1;
}
// num_joints is > 0, as this was tested at the beginning of the function.
joint_names_[num_joints - 1] = cursor;
_archive >> ozz::io::MakeArray(joint_parents_);
_archive >> ozz::io::MakeArray(joint_bind_poses_);
}
} // namespace animation
} // namespace ozz
@@ -0,0 +1,64 @@
//----------------------------------------------------------------------------//
// //
// 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/runtime/skeleton_utils.h"
#include "ozz/base/maths/soa_transform.h"
#include <assert.h>
namespace ozz {
namespace animation {
// Unpacks skeleton bind pose stored in soa format by the skeleton.
ozz::math::Transform GetJointLocalBindPose(const Skeleton& _skeleton,
int _joint) {
assert(_joint >= 0 && _joint < _skeleton.num_joints() &&
"Joint index out of range.");
const ozz::math::SoaTransform& soa_transform =
_skeleton.joint_bind_poses()[_joint / 4];
// Transpose SoA data to AoS.
ozz::math::SimdFloat4 translations[4];
ozz::math::Transpose3x4(&soa_transform.translation.x, translations);
ozz::math::SimdFloat4 rotations[4];
ozz::math::Transpose4x4(&soa_transform.rotation.x, rotations);
ozz::math::SimdFloat4 scales[4];
ozz::math::Transpose3x4(&soa_transform.scale.x, scales);
// Stores to the Transform object.
math::Transform bind_pose;
const int offset = _joint % 4;
ozz::math::Store3PtrU(translations[offset], &bind_pose.translation.x);
ozz::math::StorePtrU(rotations[offset], &bind_pose.rotation.x);
ozz::math::Store3PtrU(scales[offset], &bind_pose.scale.x);
return bind_pose;
}
} // namespace animation
} // namespace ozz
+153
View File
@@ -0,0 +1,153 @@
//----------------------------------------------------------------------------//
// //
// 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/runtime/track.h"
#include <cassert>
#include "ozz/base/io/archive.h"
#include "ozz/base/log.h"
#include "ozz/base/maths/math_archive.h"
#include "ozz/base/maths/math_ex.h"
#include "ozz/base/memory/allocator.h"
namespace ozz {
namespace animation {
namespace internal {
template <typename _ValueType>
Track<_ValueType>::Track() : name_(nullptr) {}
template <typename _ValueType>
Track<_ValueType>::~Track() {
Deallocate();
}
template <typename _ValueType>
void Track<_ValueType>::Allocate(size_t _keys_count, size_t _name_len) {
assert(ratios_.size() == 0 && values_.size() == 0);
// Distributes buffer memory while ensuring proper alignment (serves larger
// alignment values first).
static_assert(alignof(_ValueType) >= alignof(float) &&
alignof(float) >= alignof(uint8_t),
"Must serve larger alignment values first)");
// Compute overall size and allocate a single buffer for all the data.
const size_t buffer_size = _keys_count * sizeof(_ValueType) + // values
_keys_count * sizeof(float) + // ratios
(_keys_count + 7) * sizeof(uint8_t) / 8 + // steps
(_name_len > 0 ? _name_len + 1 : 0);
span<char> buffer = {static_cast<char*>(memory::default_allocator()->Allocate(
buffer_size, alignof(_ValueType))),
buffer_size};
// Fix up pointers. Serves larger alignment values first.
values_ = fill_span<_ValueType>(buffer, _keys_count);
ratios_ = fill_span<float>(buffer, _keys_count);
steps_ = fill_span<uint8_t>(buffer, (_keys_count + 7) / 8);
// Let name be nullptr if track has no name. Allows to avoid allocating this
// buffer in the constructor of empty animations.
name_ =
_name_len > 0 ? fill_span<char>(buffer, _name_len + 1).data() : nullptr;
assert(buffer.empty() && "Whole buffer should be consumned");
}
template <typename _ValueType>
void Track<_ValueType>::Deallocate() {
// Deallocate everything at once.
memory::default_allocator()->Deallocate(as_writable_bytes(values_).data());
values_ = {};
ratios_ = {};
steps_ = {};
name_ = nullptr;
}
template <typename _ValueType>
size_t Track<_ValueType>::size() const {
const size_t size = sizeof(*this) + values_.size_bytes() +
ratios_.size_bytes() + steps_.size_bytes();
return size;
}
template <typename _ValueType>
void Track<_ValueType>::Save(ozz::io::OArchive& _archive) const {
uint32_t num_keys = static_cast<uint32_t>(ratios_.size());
_archive << num_keys;
const size_t name_len = name_ ? std::strlen(name_) : 0;
_archive << static_cast<int32_t>(name_len);
_archive << ozz::io::MakeArray(ratios_);
_archive << ozz::io::MakeArray(values_);
_archive << ozz::io::MakeArray(steps_);
_archive << ozz::io::MakeArray(name_, name_len);
}
template <typename _ValueType>
void Track<_ValueType>::Load(ozz::io::IArchive& _archive, uint32_t _version) {
// Destroy animation in case it was already used before.
Deallocate();
if (_version > 1) {
log::Err() << "Unsupported Track version " << _version << "." << std::endl;
return;
}
uint32_t num_keys;
_archive >> num_keys;
int32_t name_len;
_archive >> name_len;
Allocate(num_keys, name_len);
_archive >> ozz::io::MakeArray(ratios_);
_archive >> ozz::io::MakeArray(values_);
_archive >> ozz::io::MakeArray(steps_);
if (name_) { // nullptr name_ is supported.
_archive >> ozz::io::MakeArray(name_, name_len);
name_[name_len] = 0;
}
}
// Explicitly instantiate supported tracks.
template class Track<float>;
template class Track<math::Float2>;
template class Track<math::Float3>;
template class Track<math::Float4>;
template class Track<math::Quaternion>;
} // namespace internal
} // namespace animation
} // namespace ozz
@@ -0,0 +1,104 @@
//----------------------------------------------------------------------------//
// //
// 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/runtime/track_sampling_job.h"
#include "ozz/animation/runtime/track.h"
#include "ozz/base/maths/math_ex.h"
#include <algorithm>
#include <cassert>
namespace ozz {
namespace animation {
namespace internal {
template <typename _Track>
TrackSamplingJob<_Track>::TrackSamplingJob()
: ratio(0.f), track(nullptr), result(nullptr) {}
template <typename _Track>
bool TrackSamplingJob<_Track>::Validate() const {
bool success = true;
success &= result != nullptr;
success &= track != nullptr;
return success;
}
template <typename _Track>
bool TrackSamplingJob<_Track>::Run() const {
if (!Validate()) {
return false;
}
// Clamps ratio in range [0,1].
const float clamped_ratio = math::Clamp(0.f, ratio, 1.f);
// Search keyframes to interpolate.
const span<const float> ratios = track->ratios();
const span<const ValueType> values = track->values();
assert(ratios.size() == values.size() &&
track->steps().size() * 8 >= values.size());
// Default track returns identity.
if (ratios.size() == 0) {
*result = internal::TrackPolicy<ValueType>::identity();
return true;
}
// Search for the first key frame with a ratio value greater than input ratio.
// Our ratio is between this one and the previous one.
const float* ptk1 = std::upper_bound(ratios.begin(), ratios.end(), clamped_ratio);
// Deduce keys indices.
const size_t id1 = ptk1 - ratios.begin();
const size_t id0 = id1 - 1;
const bool id0step = (track->steps()[id0 / 8] & (1 << (id0 & 7))) != 0;
if (id0step || ptk1 == ratios.end()) {
*result = values[id0];
} else {
// Lerp relevant keys.
const float tk0 = ratios[id0];
const float tk1 = ratios[id1];
assert(clamped_ratio >= tk0 && clamped_ratio < tk1 && tk0 != tk1);
const float alpha = (clamped_ratio - tk0) / (tk1 - tk0);
const ValueType& vk0 = values[id0];
const ValueType& vk1 = values[id1];
*result = internal::TrackPolicy<ValueType>::Lerp(vk0, vk1, alpha);
}
return true;
}
// Explicitly instantiate supported tracks.
template struct TrackSamplingJob<FloatTrack>;
template struct TrackSamplingJob<Float2Track>;
template struct TrackSamplingJob<Float3Track>;
template struct TrackSamplingJob<Float4Track>;
template struct TrackSamplingJob<QuaternionTrack>;
} // namespace internal
} // namespace animation
} // namespace ozz
@@ -0,0 +1,181 @@
//----------------------------------------------------------------------------//
// //
// 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/runtime/track_triggering_job.h"
#include "ozz/animation/runtime/track.h"
#include <algorithm>
#include <cassert>
namespace ozz {
namespace animation {
TrackTriggeringJob::TrackTriggeringJob()
: from(0.f), to(0.f), threshold(0.f), track(nullptr), iterator(nullptr) {}
bool TrackTriggeringJob::Validate() const {
bool valid = true;
valid &= track != nullptr;
valid &= iterator != nullptr;
return valid;
}
bool TrackTriggeringJob::Run() const {
if (!Validate()) {
return false;
}
// Triggering can only happen in a valid range of ratio.
if (from == to) {
*iterator = end();
return true;
}
*iterator = Iterator(this);
return true;
}
namespace {
inline bool DetectEdge(ptrdiff_t _i0, ptrdiff_t _i1, bool _forward,
const TrackTriggeringJob& _job,
TrackTriggeringJob::Edge* _edge) {
const span<const float>& values = _job.track->values();
const float vk0 = values[_i0];
const float vk1 = values[_i1];
bool detected = false;
if (vk0 <= _job.threshold && vk1 > _job.threshold) {
// Rising edge
_edge->rising = _forward;
detected = true;
} else if (vk0 > _job.threshold && vk1 <= _job.threshold) {
// Falling edge
_edge->rising = !_forward;
detected = true;
}
if (detected) {
const span<const float>& ratios = _job.track->ratios();
const span<const uint8_t>& steps = _job.track->steps();
const bool step = (steps[_i0 / 8] & (1 << (_i0 & 7))) != 0;
if (step) {
_edge->ratio = ratios[_i1];
} else {
assert(vk0 != vk1); // Won't divide by 0
if (_i1 == 0) {
_edge->ratio = 0.f;
} else {
// Finds where the curve crosses threshold value.
// This is the lerp equation, where we know the result and look for
// alpha, aka un-lerp.
const float alpha = (_job.threshold - vk0) / (vk1 - vk0);
// Remaps to keyframes actual times.
const float tk0 = ratios[_i0];
const float tk1 = ratios[_i1];
_edge->ratio = math::Lerp(tk0, tk1, alpha);
}
}
}
return detected;
}
} // namespace
TrackTriggeringJob::Iterator::Iterator(const TrackTriggeringJob* _job)
: job_(_job) {
// Outer loop initialization.
outer_ = floorf(job_->from);
// Search could start more closely to the "from" ratio, but it's not possible
// to ensure that floating point precision will not lead to missing a key
// (when from/to range is far from 0). This is less good in algorithmic
// complexity, but for consistency of forward and backward triggering, it's
// better to let iterator ++ implementation filter included and excluded
// edges.
inner_ = job_->from < job_->to ? 0 : _job->track->ratios().size() - 1;
// Evaluates first edge.
++*this;
}
const TrackTriggeringJob::Iterator& TrackTriggeringJob::Iterator::operator++() {
assert(*this != job_->end() && "Can't increment end iterator.");
const span<const float>& ratios = job_->track->ratios();
const ptrdiff_t num_keys = ratios.size();
if (job_->to > job_->from) {
for (; outer_ < job_->to; outer_ += 1.f) {
for (; inner_ < num_keys; ++inner_) {
const ptrdiff_t i0 = inner_ == 0 ? num_keys - 1 : inner_ - 1;
if (DetectEdge(i0, inner_, true, *job_, &edge_)) {
edge_.ratio += outer_; // Convert to global ratio space.
if (edge_.ratio >= job_->from &&
(edge_.ratio < job_->to || job_->to >= 1.f + outer_)) {
++inner_;
return *this; // Yield found edge.
}
// Won't find any further edge.
if (ratios[inner_] + outer_ >= job_->to) {
break;
}
}
}
inner_ = 0; // Ready for next loop.
}
} else {
for (; outer_ + 1.f > job_->to; outer_ -= 1.f) {
for (; inner_ >= 0; --inner_) {
const ptrdiff_t i0 = inner_ == 0 ? num_keys - 1 : inner_ - 1;
if (DetectEdge(i0, inner_, false, *job_, &edge_)) {
edge_.ratio += outer_; // Convert to global ratio space.
if (edge_.ratio >= job_->to &&
(edge_.ratio < job_->from || job_->from >= 1.f + outer_)) {
--inner_;
return *this; // Yield found edge.
}
}
// Won't find any further edge.
if (ratios[inner_] + outer_ <= job_->to) {
break;
}
}
inner_ = ratios.size() - 1; // Ready for next loop.
}
}
// Set iterator to end position.
*this = job_->end();
return *this;
}
} // namespace animation
} // namespace ozz
+64
View File
@@ -0,0 +1,64 @@
add_library(ozz_base STATIC
${PROJECT_SOURCE_DIR}/include/ozz/base/endianness.h
${PROJECT_SOURCE_DIR}/include/ozz/base/gtest_helper.h
${PROJECT_SOURCE_DIR}/include/ozz/base/memory/allocator.h
${PROJECT_SOURCE_DIR}/include/ozz/base/memory/unique_ptr.h
memory/allocator.cc
${PROJECT_SOURCE_DIR}/include/ozz/base/platform.h
${PROJECT_SOURCE_DIR}/include/ozz/base/span.h
platform.cc
${PROJECT_SOURCE_DIR}/include/ozz/base/log.h
log.cc
${PROJECT_SOURCE_DIR}/include/ozz/base/containers/intrusive_list.h
${PROJECT_SOURCE_DIR}/include/ozz/base/containers/deque.h
${PROJECT_SOURCE_DIR}/include/ozz/base/containers/list.h
${PROJECT_SOURCE_DIR}/include/ozz/base/containers/map.h
${PROJECT_SOURCE_DIR}/include/ozz/base/containers/queue.h
${PROJECT_SOURCE_DIR}/include/ozz/base/containers/set.h
${PROJECT_SOURCE_DIR}/include/ozz/base/containers/stack.h
${PROJECT_SOURCE_DIR}/include/ozz/base/containers/string.h
${PROJECT_SOURCE_DIR}/include/ozz/base/containers/string_archive.h
${PROJECT_SOURCE_DIR}/include/ozz/base/containers/unordered_map.h
${PROJECT_SOURCE_DIR}/include/ozz/base/containers/unordered_set.h
containers/string_archive.cc
${PROJECT_SOURCE_DIR}/include/ozz/base/containers/vector.h
${PROJECT_SOURCE_DIR}/include/ozz/base/containers/vector_archive.h
${PROJECT_SOURCE_DIR}/include/ozz/base/containers/std_allocator.h
${PROJECT_SOURCE_DIR}/include/ozz/base/io/archive.h
io/archive.cc
${PROJECT_SOURCE_DIR}/include/ozz/base/io/archive_traits.h
${PROJECT_SOURCE_DIR}/include/ozz/base/io/stream.h
io/stream.cc
${PROJECT_SOURCE_DIR}/include/ozz/base/maths/box.h
maths/box.cc
${PROJECT_SOURCE_DIR}/include/ozz/base/maths/gtest_math_helper.h
${PROJECT_SOURCE_DIR}/include/ozz/base/maths/internal/simd_math_config.h
${PROJECT_SOURCE_DIR}/include/ozz/base/maths/internal/simd_math_ref-inl.h
${PROJECT_SOURCE_DIR}/include/ozz/base/maths/internal/simd_math_sse-inl.h
${PROJECT_SOURCE_DIR}/include/ozz/base/maths/math_ex.h
${PROJECT_SOURCE_DIR}/include/ozz/base/maths/math_constant.h
${PROJECT_SOURCE_DIR}/include/ozz/base/maths/quaternion.h
${PROJECT_SOURCE_DIR}/include/ozz/base/maths/rect.h
${PROJECT_SOURCE_DIR}/include/ozz/base/maths/simd_math.h
maths/simd_math.cc
${PROJECT_SOURCE_DIR}/include/ozz/base/maths/simd_quaternion.h
${PROJECT_SOURCE_DIR}/include/ozz/base/maths/soa_float.h
${PROJECT_SOURCE_DIR}/include/ozz/base/maths/soa_quaternion.h
${PROJECT_SOURCE_DIR}/include/ozz/base/maths/soa_transform.h
${PROJECT_SOURCE_DIR}/include/ozz/base/maths/soa_float4x4.h
${PROJECT_SOURCE_DIR}/include/ozz/base/maths/transform.h
${PROJECT_SOURCE_DIR}/include/ozz/base/maths/vec_float.h
${PROJECT_SOURCE_DIR}/include/ozz/base/maths/math_archive.h
maths/math_archive.cc
${PROJECT_SOURCE_DIR}/include/ozz/base/maths/soa_math_archive.h
maths/soa_math_archive.cc
${PROJECT_SOURCE_DIR}/include/ozz/base/maths/simd_math_archive.h
maths/simd_math_archive.cc)
target_include_directories(ozz_base PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include>)
set_target_properties(ozz_base PROPERTIES FOLDER "ozz")
install(TARGETS ozz_base DESTINATION lib)
fuse_target("ozz_base")
@@ -0,0 +1,75 @@
//----------------------------------------------------------------------------//
// //
// 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/base/containers/string_archive.h"
#include "ozz/base/io/archive.h"
#include "ozz/base/maths/math_ex.h"
namespace ozz {
namespace io {
void Extern<string>::Save(OArchive& _archive, const string* _values,
size_t _count) {
for (size_t i = 0; i < _count; i++) {
const ozz::string& string = _values[i];
// Get size excluding null terminating character.
uint32_t size = static_cast<uint32_t>(string.size());
_archive << size;
_archive << ozz::io::MakeArray(string.c_str(), size);
}
}
void Extern<string>::Load(IArchive& _archive, string* _values, size_t _count,
uint32_t _version) {
(void)_version;
for (size_t i = 0; i < _count; i++) {
ozz::string& string = _values[i];
// Ensure an existing string is reseted.
string.clear();
uint32_t size;
_archive >> size;
string.reserve(size);
// Prepares temporary buffer used for reading.
char buffer[128];
for (size_t to_read = size; to_read != 0;) {
// Read from the archive to the local temporary buffer.
const size_t to_read_this_loop =
math::Min(to_read, OZZ_ARRAY_SIZE(buffer));
_archive >> ozz::io::MakeArray(buffer, to_read_this_loop);
to_read -= to_read_this_loop;
// Append to the string.
string.append(buffer, to_read_this_loop);
}
}
}
} // namespace io
} // namespace ozz
+57
View File
@@ -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 "ozz/base/io/archive.h"
#include <cassert>
namespace ozz {
namespace io {
// OArchive implementation.
OArchive::OArchive(Stream* _stream, Endianness _endianness)
: stream_(_stream), endian_swap_(_endianness != GetNativeEndianness()) {
assert(stream_ && stream_->opened() &&
"_stream argument must point a valid opened stream.");
// Save as a single byte as it does not need to be swapped.
uint8_t endianness = static_cast<uint8_t>(_endianness);
*this << endianness;
}
// IArchive implementation.
IArchive::IArchive(Stream* _stream) : stream_(_stream), endian_swap_(false) {
assert(stream_ && stream_->opened() &&
"_stream argument must point a valid opened stream.");
// Endianness was saved as a single byte, as it does not need to be swapped.
uint8_t endianness;
*this >> endianness;
endian_swap_ = endianness != GetNativeEndianness();
}
} // namespace io
} // namespace ozz
+217
View File
@@ -0,0 +1,217 @@
//----------------------------------------------------------------------------//
// //
// 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/base/io/stream.h"
#include <cassert>
#include <cstdio>
#include <cstring>
#include <limits>
#include "ozz/base/maths/math_ex.h"
#include "ozz/base/memory/allocator.h"
namespace ozz {
namespace io {
// Starts File implementation.
bool File::Exist(const char* _filename) {
FILE* file = std::fopen(_filename, "r");
if (file) {
std::fclose(file);
return true;
}
return false;
}
File::File(const char* _filename, const char* _mode)
: file_(std::fopen(_filename, _mode)) {}
File::File(void* _file) : file_(_file) {}
File::~File() { Close(); }
void File::Close() {
if (file_) {
std::FILE* file = reinterpret_cast<std::FILE*>(file_);
std::fclose(file);
file_ = nullptr;
}
}
bool File::opened() const { return file_ != nullptr; }
size_t File::Read(void* _buffer, size_t _size) {
std::FILE* file = reinterpret_cast<std::FILE*>(file_);
return std::fread(_buffer, 1, _size, file);
}
size_t File::Write(const void* _buffer, size_t _size) {
std::FILE* file = reinterpret_cast<std::FILE*>(file_);
return std::fwrite(_buffer, 1, _size, file);
}
int File::Seek(int _offset, Origin _origin) {
int origins[] = {SEEK_CUR, SEEK_END, SEEK_SET};
if (_origin >= static_cast<int>(OZZ_ARRAY_SIZE(origins))) {
return -1;
}
std::FILE* file = reinterpret_cast<std::FILE*>(file_);
return std::fseek(file, _offset, origins[_origin]);
}
int File::Tell() const {
std::FILE* file = reinterpret_cast<std::FILE*>(file_);
const long current = std::ftell(file);
return static_cast<int>(current);
}
size_t File::Size() const {
std::FILE* file = reinterpret_cast<std::FILE*>(file_);
const long current = std::ftell(file);
assert(current >= 0);
int seek = std::fseek(file, 0, SEEK_END);
assert(seek == 0);
(void)seek;
const long end = std::ftell(file);
assert(end >= 0);
seek = std::fseek(file, current, SEEK_SET);
assert(seek == 0);
return static_cast<size_t>(end);
}
// Starts MemoryStream implementation.
const size_t MemoryStream::kBufferSizeIncrement = 16 << 10;
const size_t MemoryStream::kMaxSize = std::numeric_limits<int>::max();
MemoryStream::MemoryStream()
: buffer_(nullptr), alloc_size_(0), end_(0), tell_(0) {}
MemoryStream::~MemoryStream() {
ozz::memory::default_allocator()->Deallocate(buffer_);
buffer_ = nullptr;
}
bool MemoryStream::opened() const { return true; }
size_t MemoryStream::Read(void* _buffer, size_t _size) {
// A read cannot set file position beyond the end of the file.
// A read cannot exceed the maximum Stream size.
if (tell_ > end_ || _size > kMaxSize) {
return 0;
}
const int read_size = math::Min(end_ - tell_, static_cast<int>(_size));
std::memcpy(_buffer, buffer_ + tell_, read_size);
tell_ += read_size;
return read_size;
}
size_t MemoryStream::Write(const void* _buffer, size_t _size) {
if (_size > kMaxSize || tell_ > static_cast<int>(kMaxSize - _size)) {
// A write cannot exceed the maximum Stream size.
return 0;
}
if (tell_ > end_) {
// The fseek() function shall allow the file-position indicator to be set
// beyond the end of existing data in the file. If data is later written at
// this point, subsequent reads of data in the gap shall return bytes with
// the value 0 until data is actually written into the gap.
if (!Resize(tell_)) {
return 0;
}
// Fills the gap with 0's.
const size_t gap = tell_ - end_;
std::memset(buffer_ + end_, 0, gap);
end_ = tell_;
}
const int size = static_cast<int>(_size);
const int tell_end = tell_ + size;
if (Resize(tell_end)) {
end_ = math::Max(tell_end, end_);
std::memcpy(buffer_ + tell_, _buffer, _size);
tell_ += size;
return _size;
}
return 0;
}
int MemoryStream::Seek(int _offset, Origin _origin) {
int origin;
switch (_origin) {
case kCurrent:
origin = tell_;
break;
case kEnd:
origin = end_;
break;
case kSet:
origin = 0;
break;
default:
return -1;
}
// Exit if seeking before file begin or beyond max file size.
if (origin < -_offset ||
(_offset > 0 && origin > static_cast<int>(kMaxSize - _offset))) {
return -1;
}
// So tell_ is moved but end_ pointer is not moved until something is later
// written.
tell_ = origin + _offset;
return 0;
}
int MemoryStream::Tell() const { return tell_; }
size_t MemoryStream::Size() const { return static_cast<size_t>(end_); }
bool MemoryStream::Resize(size_t _size) {
if (_size > alloc_size_) {
// Resize to the next multiple of kBufferSizeIncrement, requires
// kBufferSizeIncrement to be a power of 2.
static_assert(
(MemoryStream::kBufferSizeIncrement & (kBufferSizeIncrement - 1)) == 0,
"kBufferSizeIncrement must be a power of 2");
const size_t new_size = ozz::Align(_size, kBufferSizeIncrement);
char* new_buffer = reinterpret_cast<char*>(
ozz::memory::default_allocator()->Allocate(new_size, 16));
std::memcpy(new_buffer, buffer_, alloc_size_);
ozz::memory::default_allocator()->Deallocate(buffer_);
buffer_ = new_buffer;
alloc_size_ = new_size;
}
return _size == 0 || buffer_ != nullptr;
}
} // namespace io
} // namespace ozz
+79
View File
@@ -0,0 +1,79 @@
//----------------------------------------------------------------------------//
// //
// 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/base/log.h"
#include <iomanip>
#include <sstream>
#include "ozz/base/memory/allocator.h"
namespace ozz {
namespace log {
// Default log level initialization.
namespace {
Level log_level = kStandard;
}
Level SetLevel(Level _level) {
const Level previous_level = log_level;
log_level = _level;
return previous_level;
}
Level GetLevel() { return log_level; }
LogV::LogV() : Logger(std::clog, kVerbose) {}
Log::Log() : Logger(std::clog, kStandard) {}
Out::Out() : Logger(std::cout, kStandard) {}
Err::Err() : Logger(std::cerr, kStandard) {}
Logger::Logger(std::ostream& _stream, Level _level)
: stream_(_level <= GetLevel() ? _stream : *ozz::New<std::ostringstream>()),
local_stream_(&stream_ != &_stream) {}
Logger::~Logger() {
if (local_stream_) {
ozz::Delete(&stream_);
}
}
FloatPrecision::FloatPrecision(const Logger& _logger, int _precision)
: precision_(_logger.stream().precision(_precision)),
format_(_logger.stream().setf(std::ios_base::fixed,
std::ios_base::floatfield)),
stream_(_logger.stream()) {}
FloatPrecision::~FloatPrecision() {
stream_.precision(precision_);
stream_.setf(format_, std::ios_base::floatfield);
}
} // namespace log
} // namespace ozz
+75
View File
@@ -0,0 +1,75 @@
//----------------------------------------------------------------------------//
// //
// 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/base/maths/box.h"
#include <limits>
#include "ozz/base/maths/math_ex.h"
#include "ozz/base/maths/simd_math.h"
namespace ozz {
namespace math {
Box::Box()
: min(std::numeric_limits<float>::max()),
max(-std::numeric_limits<float>::max()) {}
Box::Box(const Float3* _points, size_t _stride, size_t _count) {
assert(_stride >= sizeof(Float3) &&
"_stride must be greater or equal to sizeof(Float3)");
Float3 local_min(std::numeric_limits<float>::max());
Float3 local_max(-std::numeric_limits<float>::max());
const Float3* end = PointerStride(_points, _stride * _count);
for (; _points < end; _points = PointerStride(_points, _stride)) {
local_min = Min(local_min, *_points);
local_max = Max(local_max, *_points);
}
min = local_min;
max = local_max;
}
Box TransformBox(const Float4x4& _matrix, const Box& _box) {
const SimdFloat4 min = simd_float4::Load3PtrU(&_box.min.x);
const SimdFloat4 max = simd_float4::Load3PtrU(&_box.max.x);
// Transforms min and max.
const SimdFloat4 ta = TransformPoint(_matrix, min);
const SimdFloat4 tb = TransformPoint(_matrix, max);
// Finds new min and max and store them in box.
Box tbox;
math::Store3PtrU(Min(ta, tb), &tbox.min.x);
math::Store3PtrU(Max(ta, tb), &tbox.max.x);
return tbox;
}
} // namespace math
} // namespace ozz
+125
View File
@@ -0,0 +1,125 @@
//----------------------------------------------------------------------------//
// //
// 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/base/maths/math_archive.h"
#include <cassert>
#include "ozz/base/io/archive.h"
#include "ozz/base/maths/box.h"
#include "ozz/base/maths/quaternion.h"
#include "ozz/base/maths/rect.h"
#include "ozz/base/maths/transform.h"
#include "ozz/base/maths/vec_float.h"
namespace ozz {
namespace io {
void Extern<math::Float2>::Save(OArchive& _archive, const math::Float2* _values,
size_t _count) {
_archive << MakeArray(&_values->x, 2 * _count);
}
void Extern<math::Float2>::Load(IArchive& _archive, math::Float2* _values,
size_t _count, uint32_t _version) {
(void)_version;
_archive >> MakeArray(&_values->x, 2 * _count);
}
void Extern<math::Float3>::Save(OArchive& _archive, const math::Float3* _values,
size_t _count) {
_archive << MakeArray(&_values->x, 3 * _count);
}
void Extern<math::Float3>::Load(IArchive& _archive, math::Float3* _values,
size_t _count, uint32_t _version) {
(void)_version;
_archive >> MakeArray(&_values->x, 3 * _count);
}
void Extern<math::Float4>::Save(OArchive& _archive, const math::Float4* _values,
size_t _count) {
_archive << MakeArray(&_values->x, 4 * _count);
}
void Extern<math::Float4>::Load(IArchive& _archive, math::Float4* _values,
size_t _count, uint32_t _version) {
(void)_version;
_archive >> MakeArray(&_values->x, 4 * _count);
}
void Extern<math::Quaternion>::Save(OArchive& _archive,
const math::Quaternion* _values,
size_t _count) {
_archive << MakeArray(&_values->x, 4 * _count);
}
void Extern<math::Quaternion>::Load(IArchive& _archive,
math::Quaternion* _values, size_t _count,
uint32_t _version) {
(void)_version;
_archive >> MakeArray(&_values->x, 4 * _count);
}
void Extern<math::Transform>::Save(OArchive& _archive,
const math::Transform* _values,
size_t _count) {
_archive << MakeArray(&_values->translation.x, 10 * _count);
}
void Extern<math::Transform>::Load(IArchive& _archive, math::Transform* _values,
size_t _count, uint32_t _version) {
(void)_version;
_archive >> MakeArray(&_values->translation.x, 10 * _count);
}
void Extern<math::Box>::Save(OArchive& _archive, const math::Box* _values,
size_t _count) {
_archive << MakeArray(&_values->min.x, 6 * _count);
}
void Extern<math::Box>::Load(IArchive& _archive, math::Box* _values,
size_t _count, uint32_t _version) {
(void)_version;
_archive >> MakeArray(&_values->min.x, 6 * _count);
}
void Extern<math::RectFloat>::Save(OArchive& _archive,
const math::RectFloat* _values,
size_t _count) {
_archive << MakeArray(&_values->left, 4 * _count);
}
void Extern<math::RectFloat>::Load(IArchive& _archive, math::RectFloat* _values,
size_t _count, uint32_t _version) {
(void)_version;
_archive >> MakeArray(&_values->left, 4 * _count);
}
void Extern<math::RectInt>::Save(OArchive& _archive,
const math::RectInt* _values, size_t _count) {
_archive << MakeArray(&_values->left, 4 * _count);
}
void Extern<math::RectInt>::Load(IArchive& _archive, math::RectInt* _values,
size_t _count, uint32_t _version) {
(void)_version;
_archive >> MakeArray(&_values->left, 4 * _count);
}
} // namespace io
} // namespace ozz
+61
View File
@@ -0,0 +1,61 @@
//----------------------------------------------------------------------------//
// //
// 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/base/maths/simd_math.h"
namespace ozz {
namespace math {
// Select compile time name of the simd implementation
#if defined(OZZ_SIMD_AVX2) && defined(OZZ_SIMD_FMA)
#define _OZZ_SIMD_IMPLEMENTATION "AVX2-FMA"
#elif defined(OZZ_SIMD_AVX2)
#define _OZZ_SIMD_IMPLEMENTATION "AVX2"
#elif defined(OZZ_SIMD_AVX)
#define _OZZ_SIMD_IMPLEMENTATION "AVX"
#elif defined(OZZ_SIMD_SSE4_2)
#define _OZZ_SIMD_IMPLEMENTATION "SSE4.2"
#elif defined(OZZ_SIMD_SSE4_1)
#define _OZZ_SIMD_IMPLEMENTATION "SSE4.1"
#elif defined(OZZ_SIMD_SSSE3)
#define _OZZ_SIMD_IMPLEMENTATION "SSSE3"
#elif defined(OZZ_SIMD_SSE3)
#define _OZZ_SIMD_IMPLEMENTATION "SSE3"
#elif defined(OZZ_SIMD_SSEx)
#define _OZZ_SIMD_IMPLEMENTATION "SSE2"
#elif defined(OZZ_SIMD_REF)
#define _OZZ_SIMD_IMPLEMENTATION "Reference"
#else
// Not defined
#endif
#pragma message("Ozz libraries were built with " _OZZ_SIMD_IMPLEMENTATION \
" SIMD math implementation")
const char* SimdImplementationName() { return _OZZ_SIMD_IMPLEMENTATION; }
} // namespace math
} // namespace ozz
@@ -0,0 +1,70 @@
//----------------------------------------------------------------------------//
// //
// 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/base/maths/simd_math_archive.h"
#include <cassert>
#include "ozz/base/io/archive.h"
namespace ozz {
namespace io {
void Extern<math::SimdFloat4>::Save(OArchive& _archive,
const math::SimdFloat4* _values,
size_t _count) {
_archive << MakeArray(reinterpret_cast<const float*>(_values), 4 * _count);
}
void Extern<math::SimdFloat4>::Load(IArchive& _archive,
math::SimdFloat4* _values, size_t _count,
uint32_t _version) {
(void)_version;
_archive >> MakeArray(reinterpret_cast<float*>(_values), 4 * _count);
}
void Extern<math::SimdInt4>::Save(OArchive& _archive,
const math::SimdInt4* _values,
size_t _count) {
_archive << MakeArray(reinterpret_cast<const int*>(_values), 4 * _count);
}
void Extern<math::SimdInt4>::Load(IArchive& _archive, math::SimdInt4* _values,
size_t _count, uint32_t _version) {
(void)_version;
_archive >> MakeArray(reinterpret_cast<int*>(_values), 4 * _count);
}
void Extern<math::Float4x4>::Save(OArchive& _archive,
const math::Float4x4* _values,
size_t _count) {
_archive << MakeArray(reinterpret_cast<const float*>(_values), 16 * _count);
}
void Extern<math::Float4x4>::Load(IArchive& _archive, math::Float4x4* _values,
size_t _count, uint32_t _version) {
(void)_version;
_archive >> MakeArray(reinterpret_cast<float*>(_values), 16 * _count);
}
} // namespace io
} // namespace ozz
@@ -0,0 +1,115 @@
//----------------------------------------------------------------------------//
// //
// 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/base/maths/soa_math_archive.h"
#include "ozz/base/io/archive.h"
#include "ozz/base/maths/soa_float.h"
#include "ozz/base/maths/soa_float4x4.h"
#include "ozz/base/maths/soa_quaternion.h"
#include "ozz/base/maths/soa_transform.h"
namespace ozz {
namespace io {
void Extern<math::SoaFloat2>::Save(OArchive& _archive,
const math::SoaFloat2* _values,
size_t _count) {
_archive << MakeArray(reinterpret_cast<const float*>(&_values->x),
2 * 4 * _count);
}
void Extern<math::SoaFloat2>::Load(IArchive& _archive, math::SoaFloat2* _values,
size_t _count, uint32_t _version) {
(void)_version;
_archive >> MakeArray(reinterpret_cast<float*>(&_values->x), 2 * 4 * _count);
}
void Extern<math::SoaFloat3>::Save(OArchive& _archive,
const math::SoaFloat3* _values,
size_t _count) {
_archive << MakeArray(reinterpret_cast<const float*>(&_values->x),
3 * 4 * _count);
}
void Extern<math::SoaFloat3>::Load(IArchive& _archive, math::SoaFloat3* _values,
size_t _count, uint32_t _version) {
(void)_version;
_archive >> MakeArray(reinterpret_cast<float*>(&_values->x), 3 * 4 * _count);
}
void Extern<math::SoaFloat4>::Save(OArchive& _archive,
const math::SoaFloat4* _values,
size_t _count) {
_archive << MakeArray(reinterpret_cast<const float*>(&_values->x),
4 * 4 * _count);
}
void Extern<math::SoaFloat4>::Load(IArchive& _archive, math::SoaFloat4* _values,
size_t _count, uint32_t _version) {
(void)_version;
_archive >> MakeArray(reinterpret_cast<float*>(&_values->x), 4 * 4 * _count);
}
void Extern<math::SoaQuaternion>::Save(OArchive& _archive,
const math::SoaQuaternion* _values,
size_t _count) {
_archive << MakeArray(reinterpret_cast<const float*>(&_values->x),
4 * 4 * _count);
}
void Extern<math::SoaQuaternion>::Load(IArchive& _archive,
math::SoaQuaternion* _values,
size_t _count, uint32_t _version) {
(void)_version;
_archive >> MakeArray(reinterpret_cast<float*>(&_values->x), 4 * 4 * _count);
}
void Extern<math::SoaFloat4x4>::Save(OArchive& _archive,
const math::SoaFloat4x4* _values,
size_t _count) {
_archive << MakeArray(reinterpret_cast<const float*>(&_values->cols[0].x),
4 * 4 * 4 * _count);
}
void Extern<math::SoaFloat4x4>::Load(IArchive& _archive,
math::SoaFloat4x4* _values, size_t _count,
uint32_t _version) {
(void)_version;
_archive >> MakeArray(reinterpret_cast<float*>(&_values->cols[0].x),
4 * 4 * 4 * _count);
}
void Extern<math::SoaTransform>::Save(OArchive& _archive,
const math::SoaTransform* _values,
size_t _count) {
_archive << MakeArray(reinterpret_cast<const float*>(&_values->translation.x),
10 * 4 * _count);
}
void Extern<math::SoaTransform>::Load(IArchive& _archive,
math::SoaTransform* _values,
size_t _count, uint32_t _version) {
(void)_version;
_archive >> MakeArray(reinterpret_cast<float*>(&_values->translation.x),
10 * 4 * _count);
}
} // namespace io
} // namespace ozz
+111
View File
@@ -0,0 +1,111 @@
//----------------------------------------------------------------------------//
// //
// 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/base/memory/allocator.h"
#include <memory.h>
#include <atomic>
#include <cassert>
#include <cstdlib>
#include "ozz/base/maths/math_ex.h"
namespace ozz {
namespace memory {
namespace {
struct Header {
void* unaligned;
size_t size;
};
} // namespace
// Implements the basic heap allocator.
// Will trace allocation count and assert in case of a memory leak.
class HeapAllocator : public Allocator {
public:
HeapAllocator() { allocation_count_.store(0); }
~HeapAllocator() {
assert(allocation_count_.load() == 0 && "Memory leak detected");
}
protected:
void* Allocate(size_t _size, size_t _alignment) {
// Allocates enough memory to store the header + required alignment space.
const size_t to_allocate = _size + sizeof(Header) + _alignment - 1;
char* unaligned = reinterpret_cast<char*>(malloc(to_allocate));
if (!unaligned) {
return nullptr;
}
char* aligned = ozz::Align(unaligned + sizeof(Header), _alignment);
assert(aligned + _size <= unaligned + to_allocate); // Don't overrun.
// Set the header
Header* header = reinterpret_cast<Header*>(aligned - sizeof(Header));
assert(reinterpret_cast<char*>(header) >= unaligned);
header->unaligned = unaligned;
header->size = _size;
// Allocation's succeeded.
++allocation_count_;
return aligned;
}
void Deallocate(void* _block) {
if (_block) {
Header* header = reinterpret_cast<Header*>(
reinterpret_cast<char*>(_block) - sizeof(Header));
free(header->unaligned);
// Deallocation completed.
--allocation_count_;
}
}
private:
// Internal allocation count used to track memory leaks.
// Should equals 0 at destruction time.
std::atomic_int allocation_count_;
};
namespace {
// Instantiates the default heap allocator->
HeapAllocator g_heap_allocator;
// Instantiates the default heap allocator pointer.
Allocator* g_default_allocator = &g_heap_allocator;
} // namespace
// Implements default allocator accessor.
Allocator* default_allocator() { return g_default_allocator; }
// Implements default allocator setter.
Allocator* SetDefaulAllocator(Allocator* _allocator) {
Allocator* previous = g_default_allocator;
g_default_allocator = _allocator;
return previous;
}
} // namespace memory
} // namespace ozz
+54
View File
@@ -0,0 +1,54 @@
//----------------------------------------------------------------------------//
// //
// 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/base/platform.h"
namespace ozz {
bool strmatch(const char* _str, const char* _pattern) {
for (; *_pattern; ++_str, ++_pattern) {
if (*_pattern == '?') {
if (!*_str) {
return false;
}
} else if (*_pattern == '*') {
if (strmatch(_str, _pattern + 1)) {
return true;
}
if (*_str && strmatch(_str + 1, _pattern)) {
return true;
}
return false;
} else {
if (*_str != *_pattern) {
return false;
}
}
}
return !*_str && !*_pattern;
}
} // namespace ozz
+1
View File
@@ -0,0 +1 @@
add_subdirectory(runtime)
@@ -0,0 +1,11 @@
add_library(ozz_geometry STATIC
${PROJECT_SOURCE_DIR}/include/ozz/geometry/runtime/skinning_job.h
skinning_job.cc)
target_link_libraries(ozz_geometry
ozz_base)
set_target_properties(ozz_geometry
PROPERTIES FOLDER "ozz")
install(TARGETS ozz_geometry DESTINATION lib)
fuse_target("ozz_geometry")
@@ -0,0 +1,500 @@
//----------------------------------------------------------------------------//
// //
// 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/geometry/runtime/skinning_job.h"
#include <cassert>
#include "ozz/base/maths/simd_math.h"
namespace ozz {
namespace geometry {
SkinningJob::SkinningJob()
: vertex_count(0),
influences_count(0),
joint_indices_stride(0),
joint_weights_stride(0),
in_positions_stride(0),
in_normals_stride(0),
in_tangents_stride(0),
out_positions_stride(0),
out_normals_stride(0),
out_tangents_stride(0) {}
bool SkinningJob::Validate() const {
// Start validation of all parameters.
bool valid = true;
// Checks influences bounds.
valid &= influences_count > 0;
// Checks joints matrices, required.
valid &= !joint_matrices.empty();
// Prepares local variables used to compute buffer size.
const int vertex_count_minus_1 = vertex_count > 0 ? vertex_count - 1 : 0;
const int vertex_count_at_least_1 = vertex_count > 0;
// Checks indices, required.
valid &= joint_indices.size_bytes() >=
joint_indices_stride * vertex_count_minus_1 +
sizeof(uint16_t) * influences_count * vertex_count_at_least_1;
// Checks weights, required if influences_count > 1.
if (influences_count != 1) {
valid &=
joint_weights.size_bytes() >=
joint_weights_stride * vertex_count_minus_1 +
sizeof(float) * (influences_count - 1) * vertex_count_at_least_1;
}
// Checks positions, mandatory.
valid &= in_positions.size_bytes() >=
in_positions_stride * vertex_count_minus_1 +
sizeof(float) * 3 * vertex_count_at_least_1;
valid &= !out_positions.empty();
valid &= out_positions.size_bytes() >=
out_positions_stride * vertex_count_minus_1 +
sizeof(float) * 3 * vertex_count_at_least_1;
// Checks normals, optional.
if (!in_normals.empty()) {
valid &= in_normals.size_bytes() >=
in_normals_stride * vertex_count_minus_1 +
sizeof(float) * 3 * vertex_count_at_least_1;
valid &= !out_normals.empty();
valid &= out_normals.size_bytes() >=
out_normals_stride * vertex_count_minus_1 +
sizeof(float) * 3 * vertex_count_at_least_1;
// Checks tangents, optional but requires normals.
if (!in_tangents.empty()) {
valid &= in_tangents.size_bytes() >=
in_tangents_stride * vertex_count_minus_1 +
sizeof(float) * 3 * vertex_count_at_least_1;
valid &= !out_tangents.empty();
valid &= out_tangents.size_bytes() >=
out_tangents_stride * vertex_count_minus_1 +
sizeof(float) * 3 * vertex_count_at_least_1;
}
} else {
// Tangents are not supported if normals are not there.
valid &= in_tangents.empty();
}
return valid;
}
// For performance optimization reasons, every skinning variants (positions,
// positions + normals, 1 to n influences...) are implemented as separate
// specialized functions.
// To cope with the error prone aspect of implementing every function, we
// define a skeleton code (SKINNING_FN) for the skinning loop, which internally
// calls MACRO that are shared or specialized according to skinning variants.
// Defines the skeleton code for the per vertex skinning loop.
#define SKINNING_FN(_type, _it, _inf) \
void SKINNING_FN_NAME(_type, _it, _inf)(const SkinningJob& _job) { \
ASSERT_##_type() ASSERT_##_it() INIT_##_type() INIT_W##_inf() \
const int loops = _job.vertex_count - 1; \
for (int i = 0; i < loops; ++i) { \
PREPARE_##_inf##_INNER(_it) TRANSFORM_##_type##_INNER() NEXT_##_type() \
NEXT_W##_inf() \
} \
PREPARE_##_inf##_OUTER(_it) TRANSFORM_##_type##_OUTER() \
}
// Defines skinning function name.
#define SKINNING_FN_NAME(_type, _it, _inf) Skinning##_type##_it##_inf
// Implements pre-conditions assertions.
#define ASSERT_P() \
assert(_job.vertex_count && !_job.in_positions.empty() && \
_job.in_normals.empty());
#define ASSERT_PN() \
assert(_job.vertex_count && !_job.in_positions.empty() && \
!_job.in_normals.empty() && _job.in_tangents.empty());
#define ASSERT_PNT() \
assert(_job.vertex_count && !_job.in_positions.empty() && \
!_job.in_normals.empty() && !_job.in_tangents.empty());
#define ASSERT_NOIT()
#define ASSERT_IT() assert(!_job.joint_inverse_transpose_matrices.empty());
// Implements loop initializations for positions, ...
#define INIT_P() \
const uint16_t* joint_indices = _job.joint_indices.begin(); \
const float* in_positions = _job.in_positions.begin(); \
float* out_positions = _job.out_positions.begin();
#define INIT_PN() \
INIT_P(); \
const float* in_normals = _job.in_normals.begin(); \
float* out_normals = _job.out_normals.begin();
#define INIT_PNT() \
INIT_PN(); \
const float* in_tangents = _job.in_tangents.begin(); \
float* out_tangents = _job.out_tangents.begin();
// Implements loop initializations for weights.
// Note that if the number of influences per vertex is 1, then there's no weight
// as it's implicitly 1.
#define INIT_W1()
#define INIT_W2() \
const math::SimdFloat4 one = math::simd_float4::one(); \
const float* joint_weights = _job.joint_weights.begin();
#define INIT_W3() INIT_W2()
#define INIT_W4() INIT_W2()
#define INIT_WN() INIT_W2()
// Implements pointer striding.
#define NEXT(_type, _current, _stride) \
reinterpret_cast<_type>(reinterpret_cast<uintptr_t>(_current) + _stride)
#define NEXT_W1()
#define NEXT_W2() \
joint_weights = NEXT(const float*, joint_weights, _job.joint_weights_stride);
#define NEXT_W3() NEXT_W2()
#define NEXT_W4() NEXT_W2()
#define NEXT_WN() NEXT_W2()
#define NEXT_P() \
joint_indices = \
NEXT(const uint16_t*, joint_indices, _job.joint_indices_stride); \
in_positions = NEXT(const float*, in_positions, _job.in_positions_stride); \
out_positions = NEXT(float*, out_positions, _job.out_positions_stride);
#define NEXT_PN() \
NEXT_P(); \
in_normals = NEXT(const float*, in_normals, _job.in_normals_stride); \
out_normals = NEXT(float*, out_normals, _job.out_normals_stride);
#define NEXT_PNT() \
NEXT_PN(); \
in_tangents = NEXT(const float*, in_tangents, _job.in_tangents_stride); \
out_tangents = NEXT(float*, out_tangents, _job.out_tangents_stride);
// Implements weighted matrix preparation.
// _INNER functions are intended to be used inside the vertex loop. They take
// advantage of the fact that the buffers they are reading from contain enough
// remaining data to use more optimized SIMD load functions. At the opposite,
// _OUTER functions restrict access to data that are sure to be readable from
// the buffer.
#define PREPARE_1_INNER(_it) \
const uint16_t i0 = joint_indices[0]; \
const math::Float4x4& transform = _job.joint_matrices[i0]; \
PREPARE_##_it##_1()
#define PREPARE_1_OUTER(_it) PREPARE_1_INNER(_it)
#define PREPARE_NOIT() \
const math::Float4x4& it_transform = transform; \
(void)it_transform;
#define PREPARE_NOIT_1() PREPARE_NOIT()
#define PREPARE_IT_1() \
const math::Float4x4& it_transform = \
_job.joint_inverse_transpose_matrices[i0];
#define PREPARE_2_INNER(_it) \
const math::SimdFloat4 w0 = math::simd_float4::Load1PtrU(joint_weights + 0); \
const uint16_t i0 = joint_indices[0]; \
const uint16_t i1 = joint_indices[1]; \
const math::Float4x4& m0 = _job.joint_matrices[i0]; \
const math::Float4x4& m1 = _job.joint_matrices[i1]; \
const math::SimdFloat4 w1 = one - w0; \
const math::Float4x4 transform = \
math::ColumnMultiply(m0, w0) + math::ColumnMultiply(m1, w1); \
PREPARE_##_it##_2()
#define PREPARE_NOIT_2() PREPARE_NOIT()
#define PREPARE_IT_2() \
const math::Float4x4& mit0 = _job.joint_inverse_transpose_matrices[i0]; \
const math::Float4x4& mit1 = _job.joint_inverse_transpose_matrices[i1]; \
const math::Float4x4 it_transform = \
math::ColumnMultiply(mit0, w0) + math::ColumnMultiply(mit1, w1);
#define PREPARE_2_OUTER(_it) PREPARE_2_INNER(_it)
#define PREPARE_3_CONCAT(_it) \
const uint16_t i0 = joint_indices[0]; \
const uint16_t i1 = joint_indices[1]; \
const uint16_t i2 = joint_indices[2]; \
const math::Float4x4& m0 = _job.joint_matrices[i0]; \
const math::Float4x4& m1 = _job.joint_matrices[i1]; \
const math::Float4x4& m2 = _job.joint_matrices[i2]; \
const math::SimdFloat4 w2 = one - (w0 + w1); \
const math::Float4x4 transform = math::ColumnMultiply(m0, w0) + \
math::ColumnMultiply(m1, w1) + \
math::ColumnMultiply(m2, w2); \
PREPARE_##_it##_3()
#define PREPARE_NOIT_3() PREPARE_NOIT()
#define PREPARE_IT_3() \
const math::Float4x4& mit0 = _job.joint_inverse_transpose_matrices[i0]; \
const math::Float4x4& mit1 = _job.joint_inverse_transpose_matrices[i1]; \
const math::Float4x4& mit2 = _job.joint_inverse_transpose_matrices[i2]; \
const math::Float4x4 it_transform = math::ColumnMultiply(mit0, w0) + \
math::ColumnMultiply(mit1, w1) + \
math::ColumnMultiply(mit2, w2);
#define PREPARE_3_INNER(_it) \
const math::SimdFloat4 w = math::simd_float4::LoadPtrU(joint_weights); \
const math::SimdFloat4 w0 = math::SplatX(w); \
const math::SimdFloat4 w1 = math::SplatY(w); \
PREPARE_3_CONCAT(_it)
#define PREPARE_3_OUTER(_it) \
const math::SimdFloat4 w0 = math::simd_float4::Load1PtrU(joint_weights + 0); \
const math::SimdFloat4 w1 = math::simd_float4::Load1PtrU(joint_weights + 1); \
PREPARE_3_CONCAT(_it)
#define PREPARE_4_CONCAT(_it) \
const uint16_t i0 = joint_indices[0]; \
const uint16_t i1 = joint_indices[1]; \
const uint16_t i2 = joint_indices[2]; \
const uint16_t i3 = joint_indices[3]; \
const math::Float4x4& m0 = _job.joint_matrices[i0]; \
const math::Float4x4& m1 = _job.joint_matrices[i1]; \
const math::Float4x4& m2 = _job.joint_matrices[i2]; \
const math::Float4x4& m3 = _job.joint_matrices[i3]; \
const math::SimdFloat4 w3 = one - (w0 + w1 + w2); \
const math::Float4x4 transform = \
math::ColumnMultiply(m0, w0) + math::ColumnMultiply(m1, w1) + \
math::ColumnMultiply(m2, w2) + math::ColumnMultiply(m3, w3); \
PREPARE_##_it##_4()
#define PREPARE_NOIT_4() PREPARE_NOIT()
#define PREPARE_IT_4() \
const math::Float4x4& mit0 = _job.joint_inverse_transpose_matrices[i0]; \
const math::Float4x4& mit1 = _job.joint_inverse_transpose_matrices[i1]; \
const math::Float4x4& mit2 = _job.joint_inverse_transpose_matrices[i2]; \
const math::Float4x4& mit3 = _job.joint_inverse_transpose_matrices[i3]; \
const math::Float4x4 it_transform = \
math::ColumnMultiply(mit0, w0) + math::ColumnMultiply(mit1, w1) + \
math::ColumnMultiply(mit2, w2) + math::ColumnMultiply(mit3, w3);
#define PREPARE_4_INNER(_it) \
const math::SimdFloat4 w = math::simd_float4::LoadPtrU(joint_weights); \
const math::SimdFloat4 w0 = math::SplatX(w); \
const math::SimdFloat4 w1 = math::SplatY(w); \
const math::SimdFloat4 w2 = math::SplatZ(w); \
PREPARE_4_CONCAT(_it)
#define PREPARE_4_OUTER(_it) \
const math::SimdFloat4 w0 = math::simd_float4::Load1PtrU(joint_weights + 0); \
const math::SimdFloat4 w1 = math::simd_float4::Load1PtrU(joint_weights + 1); \
const math::SimdFloat4 w2 = math::simd_float4::Load1PtrU(joint_weights + 2); \
PREPARE_4_CONCAT(_it)
#define PREPARE_NOIT_N() \
math::SimdFloat4 wsum = math::simd_float4::Load1PtrU(joint_weights + 0); \
math::Float4x4 transform = \
math::ColumnMultiply(_job.joint_matrices[joint_indices[0]], wsum); \
const int last = _job.influences_count - 1; \
for (int j = 1; j < last; ++j) { \
const math::SimdFloat4 w = \
math::simd_float4::Load1PtrU(joint_weights + j); \
wsum = wsum + w; \
transform = transform + math::ColumnMultiply( \
_job.joint_matrices[joint_indices[j]], w); \
} \
transform = \
transform + math::ColumnMultiply( \
_job.joint_matrices[joint_indices[last]], one - wsum); \
PREPARE_NOIT()
#define PREPARE_IT_N() \
math::SimdFloat4 wsum = math::simd_float4::Load1PtrU(joint_weights + 0); \
const uint16_t i0 = joint_indices[0]; \
math::Float4x4 transform = \
math::ColumnMultiply(_job.joint_matrices[i0], wsum); \
math::Float4x4 it_transform = \
math::ColumnMultiply(_job.joint_inverse_transpose_matrices[i0], wsum); \
const int last = _job.influences_count - 1; \
for (int j = 1; j < last; ++j) { \
const uint16_t ij = joint_indices[j]; \
const math::SimdFloat4 w = \
math::simd_float4::Load1PtrU(joint_weights + j); \
wsum = wsum + w; \
transform = transform + math::ColumnMultiply(_job.joint_matrices[ij], w); \
it_transform = \
it_transform + \
math::ColumnMultiply(_job.joint_inverse_transpose_matrices[ij], w); \
} \
const math::SimdFloat4 wlast = one - wsum; \
const int ilast = joint_indices[last]; \
transform = \
transform + math::ColumnMultiply(_job.joint_matrices[ilast], wlast); \
it_transform = \
it_transform + math::ColumnMultiply( \
_job.joint_inverse_transpose_matrices[ilast], wlast);
#define PREPARE_N_INNER(_it) PREPARE_##_it##_N()
#define PREPARE_N_OUTER(_it) PREPARE_##_it##_N()
// Implement point and vector transformation. _INNER and _OUTER have the same
// meaning as defined for the PREPARE functions.
#define TRANSFORM_P_INNER() \
const math::SimdFloat4 in_p = math::simd_float4::LoadPtrU(in_positions); \
const math::SimdFloat4 out_p = TransformPoint(transform, in_p); \
math::Store3PtrU(out_p, out_positions);
#define TRANSFORM_PN_INNER() \
TRANSFORM_P_INNER(); \
const math::SimdFloat4 in_n = math::simd_float4::LoadPtrU(in_normals); \
const math::SimdFloat4 out_n = TransformVector(it_transform, in_n); \
math::Store3PtrU(out_n, out_normals);
#define TRANSFORM_PNT_INNER() \
TRANSFORM_PN_INNER(); \
const math::SimdFloat4 in_t = math::simd_float4::LoadPtrU(in_tangents); \
const math::SimdFloat4 out_t = TransformVector(it_transform, in_t); \
math::Store3PtrU(out_t, out_tangents);
#define TRANSFORM_P_OUTER() \
const math::SimdFloat4 in_p = math::simd_float4::Load3PtrU(in_positions); \
const math::SimdFloat4 out_p = TransformPoint(transform, in_p); \
math::Store3PtrU(out_p, out_positions);
#define TRANSFORM_PN_OUTER() \
TRANSFORM_P_OUTER(); \
const math::SimdFloat4 in_n = math::simd_float4::Load3PtrU(in_normals); \
const math::SimdFloat4 out_n = TransformVector(it_transform, in_n); \
math::Store3PtrU(out_n, out_normals);
#define TRANSFORM_PNT_OUTER() \
TRANSFORM_PN_OUTER(); \
const math::SimdFloat4 in_t = math::simd_float4::Load3PtrU(in_tangents); \
const math::SimdFloat4 out_t = TransformVector(it_transform, in_t); \
math::Store3PtrU(out_t, out_tangents);
// Instantiates all skinning function variants.
SKINNING_FN(P, NOIT, 1)
SKINNING_FN(PN, NOIT, 1)
SKINNING_FN(PNT, NOIT, 1)
SKINNING_FN(PN, IT, 1)
SKINNING_FN(PNT, IT, 1)
SKINNING_FN(P, NOIT, 2)
SKINNING_FN(PN, NOIT, 2)
SKINNING_FN(PNT, NOIT, 2)
SKINNING_FN(PN, IT, 2)
SKINNING_FN(PNT, IT, 2)
SKINNING_FN(P, NOIT, 3)
SKINNING_FN(PN, NOIT, 3)
SKINNING_FN(PNT, NOIT, 3)
SKINNING_FN(PN, IT, 3)
SKINNING_FN(PNT, IT, 3)
SKINNING_FN(P, NOIT, 4)
SKINNING_FN(PN, NOIT, 4)
SKINNING_FN(PNT, NOIT, 4)
SKINNING_FN(PN, IT, 4)
SKINNING_FN(PNT, IT, 4)
SKINNING_FN(P, NOIT, N)
SKINNING_FN(PN, NOIT, N)
SKINNING_FN(PNT, NOIT, N)
SKINNING_FN(PN, IT, N)
SKINNING_FN(PNT, IT, N)
// Defines a matrix of skinning function pointers. This matrix will then be
// indexed according to skinning jobs parameters.
typedef void (*SkiningFct)(const SkinningJob&);
static const SkiningFct kSkinningFct[2][5][3] = {
{
{&SKINNING_FN_NAME(P, NOIT, 1), &SKINNING_FN_NAME(PN, NOIT, 1),
&SKINNING_FN_NAME(PNT, NOIT, 1)},
{&SKINNING_FN_NAME(P, NOIT, 2), &SKINNING_FN_NAME(PN, NOIT, 2),
&SKINNING_FN_NAME(PNT, NOIT, 2)},
{&SKINNING_FN_NAME(P, NOIT, 3), &SKINNING_FN_NAME(PN, NOIT, 3),
&SKINNING_FN_NAME(PNT, NOIT, 3)},
{&SKINNING_FN_NAME(P, NOIT, 4), &SKINNING_FN_NAME(PN, NOIT, 4),
&SKINNING_FN_NAME(PNT, NOIT, 4)},
{&SKINNING_FN_NAME(P, NOIT, N), &SKINNING_FN_NAME(PN, NOIT, N),
&SKINNING_FN_NAME(PNT, NOIT, N)},
},
{
{&SKINNING_FN_NAME(P, NOIT, 1), &SKINNING_FN_NAME(PN, IT, 1),
&SKINNING_FN_NAME(PNT, IT, 1)},
{&SKINNING_FN_NAME(P, NOIT, 2), &SKINNING_FN_NAME(PN, IT, 2),
&SKINNING_FN_NAME(PNT, IT, 2)},
{&SKINNING_FN_NAME(P, NOIT, 3), &SKINNING_FN_NAME(PN, IT, 3),
&SKINNING_FN_NAME(PNT, IT, 3)},
{&SKINNING_FN_NAME(P, NOIT, 4), &SKINNING_FN_NAME(PN, IT, 4),
&SKINNING_FN_NAME(PNT, IT, 4)},
{&SKINNING_FN_NAME(P, NOIT, N), &SKINNING_FN_NAME(PN, IT, N),
&SKINNING_FN_NAME(PNT, IT, N)},
}};
// Implements job Run function.
bool SkinningJob::Run() const {
// Exit with an error if job is invalid.
if (!Validate()) {
return false;
}
// Early out if no vertex. This isn't an error.
// Skinning function algorithm doesn't support the case.
if (vertex_count == 0) {
return true;
}
// Find skinning function index.
const size_t it = !joint_inverse_transpose_matrices.empty();
assert(it < OZZ_ARRAY_SIZE(kSkinningFct));
const size_t inf =
static_cast<size_t>(influences_count) > OZZ_ARRAY_SIZE(kSkinningFct[0])
? OZZ_ARRAY_SIZE(kSkinningFct[0]) - 1
: influences_count - 1;
assert(inf < OZZ_ARRAY_SIZE(kSkinningFct[0]));
const size_t fct = !in_normals.empty() + !in_tangents.empty();
assert(fct < OZZ_ARRAY_SIZE(kSkinningFct[0][0]));
// Calls skinning function. Cannot fail because job is valid.
kSkinningFct[it][inf][fct](*this);
return true;
}
} // namespace geometry
} // namespace ozz
+11
View File
@@ -0,0 +1,11 @@
add_library(ozz_options STATIC
../../include/ozz/options/options.h
options.cc)
target_include_directories(ozz_options PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include>)
set_target_properties(ozz_options PROPERTIES FOLDER "ozz")
install(TARGETS ozz_options DESTINATION lib)
fuse_target("ozz_options")
+657
View File
@@ -0,0 +1,657 @@
//----------------------------------------------------------------------------//
// //
// 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/options/options.h"
#include <algorithm>
#include <cassert>
#include <cctype>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <new>
#include <sstream>
namespace ozz {
namespace options {
namespace internal {
namespace {
// Declares and instantiates a global object that aims to delete the global
// BaseRegistrer parser. This global BaseRegistrer parser is allocated when
// options are being registered or when ParseCommandLine is called.
static class GlobalRegistrer {
public:
~GlobalRegistrer() {
if (parser_) {
parser_->~Parser();
parser_ = nullptr;
}
}
static Parser* Construct() {
if (!parser_) {
parser_ = new (placeholder_) Parser;
}
return parser_;
}
static Parser* parser() { return parser_; }
private:
// Take advantage of the default initialization (to 0) of file-scope level
// variables to ensure that registration does not depend on the initialization
// order of any other variable.
static Parser* parser_;
// The global parser is allocated from the static section in order to avoid
// any heap allocation before entering the main (and after exiting it also).
// Uses an array of pointers (void*) in order to ensure a native minimum
// alignment.
static void* placeholder_[];
} g_global_registrer;
// Default initialization to 0 only. See parser_ member document above.
Parser* GlobalRegistrer::parser_;
// Compute placeholder_ array size and performs default initialization only.
// See placeholder_ member document above.
static const size_t kParserPlaceholerSize =
(sizeof(Parser) + sizeof(void*) - 1) / sizeof(void*);
void* GlobalRegistrer::placeholder_[kParserPlaceholerSize];
} // namespace
// Implements Registrer constructor and destructor.
template <typename _Option>
Registrer<_Option>::Registrer(const char* _name, const char* _help,
typename _Option::Type _default, bool _required,
typename _Option::ValidateFn _fn)
: _Option(_name, _help, _default, _required, _fn) {
if (!internal::g_global_registrer.Construct()->RegisterOption(this)) {
std::cerr << "Failed to register option " << _name << std::endl;
}
}
template <typename _Option>
Registrer<_Option>::~Registrer() {
Parser* parser = internal::g_global_registrer.parser();
if (parser) {
parser->UnregisterOption(this);
}
}
// Explicit instantiation of all supported types of Registrer.
template class Registrer<TypedOption<bool>>;
template class Registrer<TypedOption<int>>;
template class Registrer<TypedOption<float>>;
template class Registrer<TypedOption<const char*>>;
} // namespace internal
// Construct the parser if no option is registered.
// This local parser will be deleted automatically. This allows to query the
// executable path and name, as well as the usage and version strings even if
// no option is registered.
ParseResult ParseCommandLine(int _argc, const char* const* _argv,
const char* _version, const char* _usage) {
// Need to instantiate a local parser if no option is registered.
Parser* parser = internal::g_global_registrer.Construct();
// Set usage and version strings and parse command line.
parser->set_usage(_usage);
parser->set_version(_version);
return parser->Parse(_argc, _argv);
}
// A nullptr parser means that no option is registered and that ParseCommandLine
// has not been called.
std::string ParsedExecutablePath() {
Parser* parser = internal::g_global_registrer.parser();
if (!parser) {
return std::string();
}
return parser->executable_path();
}
// See Parser::ExecutableName().
const char* ParsedExecutableName() {
Parser* parser = internal::g_global_registrer.parser();
if (!parser) {
return "";
}
return parser->executable_name();
}
// See Parser::Usage().
const char* ParsedExecutableUsage() {
Parser* parser = internal::g_global_registrer.parser();
if (!parser) {
return "";
}
return parser->usage();
}
namespace {
// Portable case insensitive string comparison functions.
int StrICmp(const char* _left, const char* _right) {
int l, r;
do {
l = static_cast<unsigned char>(std::tolower(*(_left++)));
r = static_cast<unsigned char>(std::tolower(*(_right++)));
} while (l && (l == r));
return l - r;
}
int StrNICmp(const char* _left, const char* _right, size_t _count) {
if (_count) {
int l, r;
do {
l = static_cast<unsigned char>(std::tolower(*(_left++)));
r = static_cast<unsigned char>(std::tolower(*(_right++)));
} while (--_count && l && (l == r));
return l - r;
}
return 0;
}
// Returns the first character after _option, or nullptr if option has not been
// found.
const char* ParseOption(const char* _argv, const char* _prefix,
const char* _option) {
const size_t prefix_len = std::strlen(_prefix);
const size_t option_len = std::strlen(_option);
// All options start with --.
if (StrNICmp(_argv, _prefix, prefix_len) != 0) {
return nullptr;
}
_argv += prefix_len;
if (!StrNICmp(_argv, _option, option_len)) {
return _argv + option_len;
}
return nullptr;
}
bool Parse(const char* _argv, const char* _option, bool* _value) {
assert(_value && _option && _argv);
const char* option_end = ParseOption(_argv, "--", _option);
if (!option_end) {
// Test for the explicit option of form --no*.
option_end = ParseOption(_argv, "--no", _option);
if (option_end && *option_end == '\0') {
*_value = false;
return true;
}
return false;
}
// Option _option was found, now checks for a valid value.
if (*option_end == '\0') {
// Implicit boolean options have no trailing characters.
*_value = true;
return true;
} else if (*option_end == '=') {
// Explicit options values are set after the '=' character.
// Using StrICmp function ensures an exact match, ie no trailing characters.
for (++option_end; std::isspace(*option_end);
++option_end) { // Trims spaces.
}
const char* true_options[] = {"yes", "true", "1", "t", "y"};
for (size_t i = 0; i < sizeof(true_options) / sizeof(true_options[0]);
i++) {
if (!StrICmp(option_end, true_options[i])) {
*_value = true;
return true;
}
}
const char* false_options[] = {"no", "false", "0", "f", "n"};
for (size_t i = 0; i < sizeof(false_options) / sizeof(false_options[0]);
++i) {
if (!StrICmp(option_end, false_options[i])) {
*_value = false;
return true;
}
}
}
return false; // Option was not found or is invalid.
}
bool Parse(const char* _argv, const char* _option, float* _value) {
assert(_value && _option && _argv);
const char* option_end = ParseOption(_argv, "--", _option);
if (option_end && *option_end == '=') {
for (++option_end; std::isspace(*option_end);
++option_end) { // Trims spaces.
}
char* found;
double double_value = std::strtod(option_end, &found);
// No trailing characters are allowed.
if (found != option_end && *found == '\0') {
*_value = static_cast<float>(double_value);
return true;
}
}
return false; // Option was not found or is invalid.
}
bool Parse(const char* _argv, const char* _option, int* _value) {
assert(_value && _option && _argv);
const char* option_end = ParseOption(_argv, "--", _option);
if (option_end && *option_end == '=') {
for (++option_end; std::isspace(*option_end);
++option_end) { // Trims spaces.
}
char* found;
long long_value = std::strtol(option_end, &found, 10);
// No trailing characters are allowed. If base is 0,
if (found != option_end && *found == '\0') {
*_value = static_cast<int>(long_value);
return true;
}
}
return false; // Option was not found or is invalid.
}
bool Parse(const char* _argv, const char* _option, const char** _value) {
assert(_value && _option && _argv);
const char* option_end = ParseOption(_argv, "--", _option);
if (option_end && *option_end == '=') {
for (++option_end; std::isspace(*option_end);
++option_end) { // Trims spaces.
}
*_value = option_end;
return true;
}
return false; // Option was not found or is invalid.
}
// Format option type using template specialization.
template <typename _Type>
const char* FormatOptionType();
// Specialization of FormatOptionType for all supported types.
template <>
const char* FormatOptionType<bool>() {
return "bool";
}
template <>
const char* FormatOptionType<float>() {
return "float";
}
template <>
const char* FormatOptionType<int>() {
return "int";
}
template <>
const char* FormatOptionType<const char*>() {
return "string";
}
// Validate exclusive options.
bool ValidateExclusiveOption(const Option& _option, int _argc) {
if (static_cast<const BoolOption&>(_option).value() && _argc != 1) {
std::cout << "Option \"" << _option.name()
<< "\" is an exclusive option. "
"It must not be used with any other option."
<< std::endl;
// This is an exclusive flag.
return false;
}
return true;
}
} // namespace
Option::Option(const char* _name, const char* _help, bool _required,
ValidateFn _validate)
: name_(_name ? _name : ""),
help_(_help ? _help : "..."),
required_(_required),
parsed_(false),
validate_(_validate) {}
Option::~Option() {}
bool Option::Validate(int _argc) {
if (validate_) {
return (*validate_)(*this, _argc);
}
return true;
}
bool Option::Parse(const char* _argv) {
if (ParseImpl(_argv) && !parsed_) { // Fails if argument's already specified.
parsed_ = true;
return true;
}
return false;
}
void Option::RestoreDefault() {
parsed_ = false;
RestoreDefaultImpl(); // Restores actual value.
}
template <typename _Type>
bool TypedOption<_Type>::ParseImpl(const char* _argv) {
return ozz::options::Parse(_argv, name(), &value_);
}
template <typename _Type>
std::string TypedOption<_Type>::FormatDefault() const {
std::stringstream str;
str << "\"" << std::boolalpha << default_ << "\"";
return str.str();
}
template <typename _Type>
const char* TypedOption<_Type>::FormatType() const {
return ozz::options::FormatOptionType<_Type>();
}
// Explicit instantiation of all supported types of TypedOption.
template class TypedOption<bool>;
template class TypedOption<int>;
template class TypedOption<float>;
template class TypedOption<const char*>;
Parser::Parser()
: options_count_(0),
builtin_options_count_(0),
executable_path_begin_(""),
executable_path_end_(executable_path_begin_ + 1),
executable_name_(""),
version_(nullptr),
usage_(nullptr),
builtin_version_("version", "Displays application version", false, false,
&ValidateExclusiveOption),
builtin_help_("help", "Displays help", false, false,
&ValidateExclusiveOption) {
// Set default values.
set_version(nullptr);
set_usage(nullptr);
// Registers built-in options.
RegisterOption(&builtin_version_);
RegisterOption(&builtin_help_);
builtin_options_count_ = options_count_;
}
Parser::~Parser() {
UnregisterOption(&builtin_version_);
UnregisterOption(&builtin_help_);
}
ParseResult Parser::Parse(int _argc, const char* const* _argv) {
if (_argc < 1 || !_argv) {
return kExitFailure;
}
// Select the left most '/' or '\\' separator.
const char* path_end =
std::max(std::strrchr(_argv[0], '/'), strrchr(_argv[0], '\\'));
if (path_end) {
++path_end; // Includes the last separator.
executable_path_begin_ = _argv[0];
executable_path_end_ = path_end;
executable_name_ = path_end;
} else {
executable_path_begin_ = "";
executable_path_end_ = executable_path_begin_ + 1;
executable_name_ = _argv[0];
}
// The first argument is skipped because it is the program path.
++_argv;
--_argc;
// Hides all arguments after a "--" argument, substitutes argc to argc_trunc.
int argc_trunc = 0;
for (; argc_trunc < _argc && std::strcmp(_argv[argc_trunc], "--") != 0;
++argc_trunc) {
}
// Restores built-in options to their default value in case parsing in done
// multiple times.
for (int i = 0; i < options_count_; ++i) {
options_[i]->RestoreDefault();
}
// Iterates all arguments and all options.
ParseResult result = kSuccess;
for (int i = 0; i < argc_trunc; ++i) {
const char* argv = _argv[i];
// Empty arguments aren't consider invalid.
if (*argv == 0) {
continue;
}
int j = 0;
for (; j < options_count_; ++j) {
if (options_[j]->Parse(argv)) {
break; // Also breaks if argument is duplicated.
}
}
// An invalid (or duplicated) command line argument is a fatal failure.
if (j == options_count_) {
std::cout << "Invalid command line argument:\"" << argv << "\"."
<< std::endl;
result = kExitFailure;
break;
}
}
// Validate build-in options first.
// They need to be validated and tested first, as they have priority over
// others, even required once.
if (!builtin_help_.Validate(argc_trunc) ||
!builtin_version_.Validate(argc_trunc)) {
result = kExitFailure;
}
// Display built-in help.
if (result == kSuccess && builtin_help_) {
Help();
result = kExitSuccess;
}
// Display built-in version.
if (result == kSuccess && builtin_version_) {
std::cout << "version " << version() << std::endl;
result = kExitSuccess;
}
// Ensures all required options were specified in the command line.
if (result == kSuccess) {
for (int i = 0; i < options_count_; ++i) {
if (!options_[i]->statisfied()) {
std::cout << "Required option \"" << options_[i]->name()
<< "\" is not specified." << std::endl;
result = kExitFailure;
break;
}
}
}
// Validates all options.
if (result == kSuccess) {
for (int i = 0; i < options_count_; ++i) {
if (!options_[i]->Validate(argc_trunc)) {
result = kExitFailure;
break;
}
}
}
// Also displays help if an error occurred.
if (result == kExitFailure) {
Help();
}
return result;
}
void Parser::Help() {
std::cout << std::endl;
std::cout << executable_name() << " version " << version() << std::endl;
std::cout << usage() << std::endl;
std::cout << std::endl;
// Displays usage line.
std::cout << "Usage:" << std::endl;
std::cout << executable_name();
for (int i = 0; i < options_count_; ++i) {
const Option& option = *options_[i];
std::cout << ' ';
if (option.required()) {
std::cout << '[';
}
std::cout << "--" << option.name();
if (option.required()) {
std::cout << ']';
}
}
std::cout << std::endl;
// Displays option details.
std::cout << "\nWhere:" << std::endl;
for (int i = 0; i < options_count_; ++i) {
const Option& option = *options_[i];
const std::string option_str =
std::string(" --") + option.name() + "=<" + option.FormatType() + ">";
std::cout << std::setiosflags(std::ios::left) << std::setw(28) << option_str
<< std::resetiosflags(std::ios::left) << option.help()
<< "(default is " << option.FormatDefault() << ")" << std::endl;
}
const char* how_to =
"\n"
"Syntax:\n"
"To set an option from the command line use the form --option=value for\n"
"non-boolean options, and --option/--nooption for booleans.\n"
"For example, \"foo --var=46\" will set \"var\" variable to 46.\n"
"If \"var\" type is not compatible with the specified argument (in this\n"
"case not an integer, a float or a string), then this help message\n"
"is displayed and application exits.\n"
"\n"
"Boolean options can be set using different syntax:\n"
"- to set a boolean option to true: \"--var\", \"--var=true\", "
"\"--var=t\","
" \"--var=yes\", \"--var=y\", \"--var=1\".\n"
"- to set a boolean option to false: \"--novar\", \"--var=false\","
" \"--var=f\", \"--var=no\", \"--var=n\", \"--var=0\".\n"
"Consistently using single-form --option/--nooption is recommended "
"though.";
std::cout << how_to << std::endl;
}
namespace {
// Sort required options first, and then based on their names.
bool sort_options(Option* _left, Option* _right) {
return (_left->required() && !_right->required()) ||
(_left->required() == _right->required() &&
std::strcmp(_left->name(), _right->name()) < 0);
}
} // namespace
bool Parser::RegisterOption(Option* _option) {
if (!_option) {
return false;
}
if (options_count_ == sizeof(options_) / sizeof(options_[0])) {
return false;
}
// Tests for duplicate options.
if (std::count(options_, options_end(), _option) != 0) {
return false;
}
// Empty (or nullptr) names aren't allowed.
if (_option->name()[0] == '\0') {
std::cerr << "Empty (or nullptr) names aren't allowed." << std::endl;
return false;
}
// Test for duplicate options' name.
for (int i = 0; i < options_count_; ++i) {
if (StrICmp(options_[i]->name(), _option->name()) == 0) {
std::cerr << "Option name:\"" << _option->name()
<< "\" already registered." << std::endl;
return false;
}
}
// Adds the option and maintains lexical order.
options_[options_count_++] = _option;
std::inplace_merge(options_, options_end() - 1, options_end(), &sort_options);
return true;
}
bool Parser::UnregisterOption(Option* _option) {
if (!_option) {
return false;
}
// Finds and removes _option from the collection.
Option** it = std::remove(options_, options_end(), _option);
if (it != options_end()) {
return --options_count_ == builtin_options_count_;
}
return false;
}
int Parser::max_options() const {
return sizeof(options_) / sizeof(options_[0]) - builtin_options_count_;
}
void Parser::set_usage(const char* _usage) {
usage_ = _usage ? _usage : "unspecified usage";
}
void Parser::set_version(const char* _version) {
version_ = _version ? _version : "unspecified version";
}
const char* Parser::usage() const { return usage_; }
const char* Parser::version() const { return version_; }
std::string Parser::executable_path() const {
return std::string(executable_path_begin_, executable_path_end_);
}
const char* Parser::executable_name() const { return executable_name_; }
} // namespace options
} // namespace ozz