Initial commit
This commit is contained in:
+7
@@ -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)
|
||||
@@ -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
|
||||
@@ -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 Ramer–Douglas–Peucker.
|
||||
// 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)
|
||||
@@ -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, |d1−d2| (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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
Reference in New Issue
Block a user