Initial commit

This commit is contained in:
Martin Felis
2021-11-11 21:22:24 +01:00
commit b78045ffe7
812 changed files with 421882 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
add_subdirectory(offline)
add_subdirectory(runtime)
@@ -0,0 +1,127 @@
add_executable(test_animation_builder
animation_builder_tests.cc)
target_link_libraries(test_animation_builder
ozz_animation_offline
gtest)
set_target_properties(test_animation_builder PROPERTIES FOLDER "ozz/tests/animation_offline")
add_test(NAME test_animation_builder COMMAND test_animation_builder)
add_executable(test_animation_optimizer
animation_optimizer_tests.cc)
target_link_libraries(test_animation_optimizer
ozz_animation_offline
gtest)
set_target_properties(test_animation_optimizer PROPERTIES FOLDER "ozz/tests/animation_offline")
add_test(NAME test_animation_optimizer COMMAND test_animation_optimizer)
add_executable(test_raw_animation_utils
raw_animation_utils_tests.cc)
target_link_libraries(test_raw_animation_utils
ozz_animation_offline
gtest)
set_target_properties(test_raw_animation_utils PROPERTIES FOLDER "ozz/tests/animation_offline")
add_test(NAME test_raw_animation_utils COMMAND test_raw_animation_utils)
add_executable(test_additive_animation_builder
additive_animation_builder_tests.cc)
target_link_libraries(test_additive_animation_builder
ozz_animation_offline
gtest)
set_target_properties(test_additive_animation_builder PROPERTIES FOLDER "ozz/tests/animation_offline")
add_test(NAME test_additive_animation_builder COMMAND test_additive_animation_builder)
add_executable(test_skeleton_builder
skeleton_builder_tests.cc)
target_link_libraries(test_skeleton_builder
ozz_animation_offline
gtest)
set_target_properties(test_skeleton_builder PROPERTIES FOLDER "ozz/tests/animation_offline")
add_test(NAME test_skeleton_builder COMMAND test_skeleton_builder)
add_executable(test_raw_skeleton_archive
raw_skeleton_archive_tests.cc)
target_link_libraries(test_raw_skeleton_archive
ozz_animation_offline
gtest)
set_target_properties(test_raw_skeleton_archive PROPERTIES FOLDER "ozz/tests/animation_offline")
add_test(NAME test_raw_skeleton_archive COMMAND test_raw_skeleton_archive)
add_executable(test_track_builder
track_builder_tests.cc)
target_link_libraries(test_track_builder
ozz_animation_offline
ozz_animation
ozz_base
gtest)
set_target_properties(test_track_builder PROPERTIES FOLDER "ozz/tests/animation_offline")
add_test(NAME test_track_builder COMMAND test_track_builder)
add_executable(test_track_optimizer
track_optimizer_tests.cc)
target_link_libraries(test_track_optimizer
ozz_animation_offline
ozz_animation
ozz_base
gtest)
set_target_properties(test_track_optimizer PROPERTIES FOLDER "ozz/tests/animation_offline")
add_test(NAME test_track_optimizer COMMAND test_track_optimizer)
add_executable(test_raw_skeleton_archive_versioning
raw_skeleton_archive_versioning_tests.cc)
target_link_libraries(test_raw_skeleton_archive_versioning
ozz_animation_offline
ozz_options
gtest)
set_target_properties(test_raw_skeleton_archive_versioning PROPERTIES FOLDER "ozz/tests/animation_offline")
add_test(NAME test_raw_skeleton_archive_versioning_le COMMAND test_raw_skeleton_archive_versioning "--file=${ozz_media_directory}/bin/versioning/raw_skeleton_v1_le.ozz" "--joints=67" "--root_name=Hips")
add_test(NAME test_raw_skeleton_archive_versioning_be COMMAND test_raw_skeleton_archive_versioning "--file=${ozz_media_directory}/bin/versioning/raw_skeleton_v1_be.ozz" "--joints=67" "--root_name=Hips")
add_executable(test_raw_animation_archive
raw_animation_archive_tests.cc)
target_link_libraries(test_raw_animation_archive
ozz_animation_offline
gtest)
set_target_properties(test_raw_animation_archive PROPERTIES FOLDER "ozz/tests/animation_offline")
add_test(NAME test_raw_animation_archive COMMAND test_raw_animation_archive)
add_executable(test_raw_animation_archive_versioning
raw_animation_archive_versioning_tests.cc)
target_link_libraries(test_raw_animation_archive_versioning
ozz_animation_offline
ozz_options
gtest)
set_target_properties(test_raw_animation_archive_versioning PROPERTIES FOLDER "ozz/tests/animation_offline")
add_test(NAME test_raw_animation_archive_versioning_le COMMAND test_raw_animation_archive_versioning "--file=${ozz_media_directory}/bin/versioning/raw_animation_v3_le.ozz" "--tracks=67" "--duration=.66666667" "--name=run")
add_test(NAME test_raw_animation_archive_versioning_be COMMAND test_raw_animation_archive_versioning "--file=${ozz_media_directory}/bin/versioning/raw_animation_v3_be.ozz" "--tracks=67" "--duration=.66666667" "--name=run")
# Previous incompatbile versions.
add_test(NAME test_raw_animation_archive_versioning_le_older2 COMMAND test_raw_animation_archive_versioning "--file=${ozz_media_directory}/bin/versioning/raw_animation_v2_le.ozz" "--tracks=0" "--duration=1" "--name=")
add_test(NAME test_raw_animation_archive_versioning_le_older1 COMMAND test_raw_animation_archive_versioning "--file=${ozz_media_directory}/bin/versioning/raw_animation_v1_le.ozz" "--tracks=0" "--duration=1" "--name=")
# RawTrack
add_executable(test_raw_track_archive
raw_track_archive_tests.cc)
target_link_libraries(test_raw_track_archive
ozz_animation_offline
gtest)
set_target_properties(test_raw_track_archive PROPERTIES FOLDER "ozz/tests/animation_offline")
add_test(NAME test_raw_track_archive COMMAND test_raw_track_archive)
# ozz_animation_offline fuse tests
set_source_files_properties(${PROJECT_BINARY_DIR}/src_fused/ozz_animation_offline.cc PROPERTIES GENERATED 1)
add_executable(test_fuse_animation_offline
animation_builder_tests.cc
${PROJECT_BINARY_DIR}/src_fused/ozz_animation_offline.cc)
add_dependencies(test_fuse_animation_offline BUILD_FUSE_ozz_animation_offline)
target_link_libraries(test_fuse_animation_offline
ozz_animation
gtest)
add_test(NAME test_fuse_animation_offline COMMAND test_fuse_animation_offline)
set_target_properties(test_fuse_animation_offline PROPERTIES FOLDER "ozz/tests/animation_offline")
add_subdirectory(fbx)
add_subdirectory(gltf)
add_subdirectory(tools)
@@ -0,0 +1,317 @@
//----------------------------------------------------------------------------//
// //
// 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 "gtest/gtest.h"
#include "ozz/base/maths/gtest_math_helper.h"
#include "ozz/base/maths/math_constant.h"
#include "ozz/base/maths/transform.h"
#include "ozz/animation/offline/raw_animation.h"
using ozz::animation::offline::AdditiveAnimationBuilder;
using ozz::animation::offline::RawAnimation;
TEST(Error, AdditiveAnimationBuilder) {
AdditiveAnimationBuilder builder;
{ // nullptr output.
RawAnimation input;
EXPECT_TRUE(input.Validate());
// Builds animation
EXPECT_FALSE(builder(input, nullptr));
}
{ // Invalid input animation.
RawAnimation input;
input.duration = -1.f;
EXPECT_FALSE(input.Validate());
// Builds animation
RawAnimation output;
output.duration = -1.f;
output.tracks.resize(1);
EXPECT_FALSE(builder(input, &output));
EXPECT_FLOAT_EQ(output.duration, RawAnimation().duration);
EXPECT_EQ(output.num_tracks(), 0);
}
{ // Invalid input animation with custom refpose
RawAnimation input;
input.duration = 1.f;
input.tracks.resize(1);
// Builds animation
RawAnimation output;
output.duration = -1.f;
output.tracks.resize(1);
ozz::span<ozz::math::Transform> empty_ref_pose_range;
EXPECT_FALSE(builder(input, empty_ref_pose_range, &output));
EXPECT_FLOAT_EQ(output.duration, RawAnimation().duration);
EXPECT_EQ(output.num_tracks(), 0);
}
}
TEST(Build, AdditiveAnimationBuilder) {
AdditiveAnimationBuilder builder;
RawAnimation input;
input.duration = 1.f;
input.tracks.resize(3);
// First track is empty
{
// input.tracks[0]
}
// 2nd track
// 1 key at the beginning
{
const RawAnimation::TranslationKey key = {0.f,
ozz::math::Float3(2.f, 3.f, 4.f)};
input.tracks[1].translations.push_back(key);
}
{
const RawAnimation::RotationKey key = {
0.f, ozz::math::Quaternion(.70710677f, 0.f, 0.f, .70710677f)};
input.tracks[1].rotations.push_back(key);
}
{
const RawAnimation::ScaleKey key = {0.f, ozz::math::Float3(5.f, 6.f, 7.f)};
input.tracks[1].scales.push_back(key);
}
// 3rd track
// 2 keys after the beginning
{
const RawAnimation::TranslationKey key0 = {
.5f, ozz::math::Float3(2.f, 3.f, 4.f)};
input.tracks[2].translations.push_back(key0);
const RawAnimation::TranslationKey key1 = {
.7f, ozz::math::Float3(20.f, 30.f, 40.f)};
input.tracks[2].translations.push_back(key1);
}
{
const RawAnimation::RotationKey key0 = {
.5f, ozz::math::Quaternion(.70710677f, 0.f, 0.f, .70710677f)};
input.tracks[2].rotations.push_back(key0);
const RawAnimation::RotationKey key1 = {
.7f, ozz::math::Quaternion(-.70710677f, 0.f, 0.f, .70710677f)};
input.tracks[2].rotations.push_back(key1);
}
{
const RawAnimation::ScaleKey key0 = {.5f, ozz::math::Float3(5.f, 6.f, 7.f)};
input.tracks[2].scales.push_back(key0);
const RawAnimation::ScaleKey key1 = {.7f,
ozz::math::Float3(50.f, 60.f, 70.f)};
input.tracks[2].scales.push_back(key1);
}
// Builds animation with very little tolerance.
{
RawAnimation output;
ASSERT_TRUE(builder(input, &output));
EXPECT_EQ(output.num_tracks(), 3);
// 1st track.
{
EXPECT_EQ(output.tracks[0].translations.size(), 0u);
EXPECT_EQ(output.tracks[0].rotations.size(), 0u);
EXPECT_EQ(output.tracks[0].scales.size(), 0u);
}
// 2nd track.
{
const RawAnimation::JointTrack::Translations& translations =
output.tracks[1].translations;
EXPECT_EQ(translations.size(), 1u);
EXPECT_FLOAT_EQ(translations[0].time, 0.f);
EXPECT_FLOAT3_EQ(translations[0].value, 0.f, 0.f, 0.f);
const RawAnimation::JointTrack::Rotations& rotations =
output.tracks[1].rotations;
EXPECT_EQ(rotations.size(), 1u);
EXPECT_FLOAT_EQ(rotations[0].time, 0.f);
EXPECT_QUATERNION_EQ(rotations[0].value, 0.f, 0.f, 0.f, 1.f);
const RawAnimation::JointTrack::Scales& scales = output.tracks[1].scales;
EXPECT_EQ(scales.size(), 1u);
EXPECT_FLOAT_EQ(scales[0].time, 0.f);
EXPECT_FLOAT3_EQ(scales[0].value, 1.f, 1.f, 1.f);
}
// 3rd track.
{
const RawAnimation::JointTrack::Translations& translations =
output.tracks[2].translations;
EXPECT_EQ(translations.size(), 2u);
EXPECT_FLOAT_EQ(translations[0].time, .5f);
EXPECT_FLOAT3_EQ(translations[0].value, 0.f, 0.f, 0.f);
EXPECT_FLOAT_EQ(translations[1].time, .7f);
EXPECT_FLOAT3_EQ(translations[1].value, 18.f, 27.f, 36.f);
const RawAnimation::JointTrack::Rotations& rotations =
output.tracks[2].rotations;
EXPECT_EQ(rotations.size(), 2u);
EXPECT_FLOAT_EQ(rotations[0].time, .5f);
EXPECT_QUATERNION_EQ(rotations[0].value, 0.f, 0.f, 0.f, 1.f);
EXPECT_FLOAT_EQ(rotations[1].time, .7f);
EXPECT_QUATERNION_EQ(rotations[1].value, -1.f, 0.f, 0.f, 0.f);
const RawAnimation::JointTrack::Scales& scales = output.tracks[2].scales;
EXPECT_EQ(scales.size(), 2u);
EXPECT_FLOAT_EQ(scales[0].time, .5f);
EXPECT_FLOAT3_EQ(scales[0].value, 1.f, 1.f, 1.f);
EXPECT_FLOAT_EQ(scales[1].time, .7f);
EXPECT_FLOAT3_EQ(scales[1].value, 10.f, 10.f, 10.f);
}
}
}
TEST(BuildRefPose, AdditiveAnimationBuilder) {
AdditiveAnimationBuilder builder;
RawAnimation input;
input.duration = 1.f;
input.tracks.resize(3);
// First track is empty
{
// input.tracks[0]
}
// 2nd track
// 1 key at the beginning
{
const RawAnimation::TranslationKey key = {0.f,
ozz::math::Float3(2.f, 3.f, 4.f)};
input.tracks[1].translations.push_back(key);
}
{
const RawAnimation::RotationKey key = {
0.f, ozz::math::Quaternion(.70710677f, 0.f, 0.f, .70710677f)};
input.tracks[1].rotations.push_back(key);
}
{
const RawAnimation::ScaleKey key = {0.f, ozz::math::Float3(5.f, 6.f, 7.f)};
input.tracks[1].scales.push_back(key);
}
// 3rd track
// 2 keys after the beginning
{
const RawAnimation::TranslationKey key0 = {
.5f, ozz::math::Float3(2.f, 3.f, 4.f)};
input.tracks[2].translations.push_back(key0);
const RawAnimation::TranslationKey key1 = {
.7f, ozz::math::Float3(20.f, 30.f, 40.f)};
input.tracks[2].translations.push_back(key1);
}
{
const RawAnimation::RotationKey key0 = {
.5f, ozz::math::Quaternion(.70710677f, 0.f, 0.f, .70710677f)};
input.tracks[2].rotations.push_back(key0);
const RawAnimation::RotationKey key1 = {
.7f, ozz::math::Quaternion(-.70710677f, 0.f, 0.f, .70710677f)};
input.tracks[2].rotations.push_back(key1);
}
{
const RawAnimation::ScaleKey key0 = {.5f, ozz::math::Float3(5.f, 6.f, 7.f)};
input.tracks[2].scales.push_back(key0);
const RawAnimation::ScaleKey key1 = {.7f,
ozz::math::Float3(50.f, 60.f, 70.f)};
input.tracks[2].scales.push_back(key1);
}
// Builds animation with a custom refpose & very little tolerance
{
ozz::math::Transform ref_pose[3];
ref_pose[0] = ozz::math::Transform::identity();
ref_pose[1].translation = ozz::math::Float3(1.f, 1.f, 1.f);
ref_pose[1].rotation =
ozz::math::Quaternion(0.f, 0.f, .70710677f, .70710677f);
ref_pose[1].scale = ozz::math::Float3(1.f, -1.f, 2.f);
ref_pose[2].translation = input.tracks[2].translations[0].value;
ref_pose[2].rotation = input.tracks[2].rotations[0].value;
ref_pose[2].scale = input.tracks[2].scales[0].value;
RawAnimation output;
ASSERT_TRUE(
builder(input, ozz::span<ozz::math::Transform>(ref_pose), &output));
EXPECT_EQ(output.num_tracks(), 3);
// 1st track.
{
EXPECT_EQ(output.tracks[0].translations.size(), 0u);
EXPECT_EQ(output.tracks[0].rotations.size(), 0u);
EXPECT_EQ(output.tracks[0].scales.size(), 0u);
}
// 2nd track.
{
const RawAnimation::JointTrack::Translations& translations =
output.tracks[1].translations;
EXPECT_EQ(translations.size(), 1u);
EXPECT_FLOAT_EQ(translations[0].time, 0.f);
EXPECT_FLOAT3_EQ(translations[0].value, 1.f, 2.f, 3.f);
const RawAnimation::JointTrack::Rotations& rotations =
output.tracks[1].rotations;
EXPECT_EQ(rotations.size(), 1u);
EXPECT_FLOAT_EQ(rotations[0].time, 0.f);
EXPECT_QUATERNION_EQ(rotations[0].value, .5f, .5f, -.5f, .5f);
const RawAnimation::JointTrack::Scales& scales = output.tracks[1].scales;
EXPECT_EQ(scales.size(), 1u);
EXPECT_FLOAT_EQ(scales[0].time, 0.f);
EXPECT_FLOAT3_EQ(scales[0].value, 5.f, -6.f, 3.5f);
}
// 3rd track.
{
const RawAnimation::JointTrack::Translations& translations =
output.tracks[2].translations;
EXPECT_EQ(translations.size(), 2u);
EXPECT_FLOAT_EQ(translations[0].time, .5f);
EXPECT_FLOAT3_EQ(translations[0].value, 0.f, 0.f, 0.f);
EXPECT_FLOAT_EQ(translations[1].time, .7f);
EXPECT_FLOAT3_EQ(translations[1].value, 18.f, 27.f, 36.f);
const RawAnimation::JointTrack::Rotations& rotations =
output.tracks[2].rotations;
EXPECT_EQ(rotations.size(), 2u);
EXPECT_FLOAT_EQ(rotations[0].time, .5f);
EXPECT_QUATERNION_EQ(rotations[0].value, 0.f, 0.f, 0.f, 1.f);
EXPECT_FLOAT_EQ(rotations[1].time, .7f);
EXPECT_QUATERNION_EQ(rotations[1].value, -1.f, 0.f, 0.f, 0.f);
const RawAnimation::JointTrack::Scales& scales = output.tracks[2].scales;
EXPECT_EQ(scales.size(), 2u);
EXPECT_FLOAT_EQ(scales[0].time, .5f);
EXPECT_FLOAT3_EQ(scales[0].value, 1.f, 1.f, 1.f);
EXPECT_FLOAT_EQ(scales[1].time, .7f);
EXPECT_FLOAT3_EQ(scales[1].value, 10.f, 10.f, 10.f);
}
}
}
@@ -0,0 +1,347 @@
//----------------------------------------------------------------------------//
// //
// 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 "gtest/gtest.h"
#include "ozz/base/maths/gtest_math_helper.h"
#include "ozz/base/maths/soa_transform.h"
#include "ozz/base/memory/unique_ptr.h"
#include "ozz/animation/offline/raw_animation.h"
#include "ozz/animation/runtime/animation.h"
#include "ozz/animation/runtime/sampling_job.h"
#include "ozz/animation/runtime/skeleton.h"
using ozz::animation::Animation;
using ozz::animation::offline::AnimationBuilder;
using ozz::animation::offline::RawAnimation;
TEST(Error, AnimationBuilder) {
// Instantiates a builder objects with default parameters.
AnimationBuilder builder;
{ // Building an empty Animation fails because animation duration
// must be >= 0.
RawAnimation raw_animation;
raw_animation.duration = -1.f; // Negative duration.
EXPECT_FALSE(raw_animation.Validate());
// Builds animation
EXPECT_TRUE(!builder(raw_animation));
}
{ // Building an empty Animation fails because animation duration
// must be >= 0.
RawAnimation raw_animation;
raw_animation.duration = 0.f; // Invalid duration.
EXPECT_FALSE(raw_animation.Validate());
// Builds animation
EXPECT_TRUE(!builder(raw_animation));
}
{ // Building an animation with too much tracks fails.
RawAnimation raw_animation;
raw_animation.duration = 1.f;
raw_animation.tracks.resize(ozz::animation::Skeleton::kMaxJoints + 1);
EXPECT_FALSE(raw_animation.Validate());
// Builds animation
EXPECT_TRUE(!builder(raw_animation));
}
{ // Building default animation succeeds.
RawAnimation raw_animation;
EXPECT_EQ(raw_animation.duration, 1.f);
EXPECT_TRUE(raw_animation.Validate());
// Builds animation
ozz::unique_ptr<Animation> anim(builder(raw_animation));
EXPECT_TRUE(anim);
}
{ // Building an animation with max joints succeeds.
RawAnimation raw_animation;
raw_animation.tracks.resize(ozz::animation::Skeleton::kMaxJoints);
EXPECT_EQ(raw_animation.num_tracks(), ozz::animation::Skeleton::kMaxJoints);
EXPECT_TRUE(raw_animation.Validate());
// Builds animation
ozz::unique_ptr<Animation> anim(builder(raw_animation));
EXPECT_TRUE(anim);
}
}
TEST(Build, AnimationBuilder) {
// Instantiates a builder objects with default parameters.
AnimationBuilder builder;
{ // Building an Animation with unsorted keys fails.
RawAnimation raw_animation;
raw_animation.duration = 1.f;
raw_animation.tracks.resize(1);
// Adds 2 unordered keys
RawAnimation::TranslationKey first_key = {.8f, ozz::math::Float3::zero()};
raw_animation.tracks[0].translations.push_back(first_key);
RawAnimation::TranslationKey second_key = {.2f, ozz::math::Float3::zero()};
raw_animation.tracks[0].translations.push_back(second_key);
// Builds animation
EXPECT_FALSE(builder(raw_animation));
}
{ // Building an Animation with invalid key frame's time fails.
RawAnimation raw_animation;
raw_animation.duration = 1.f;
raw_animation.tracks.resize(1);
// Adds a key with a time greater than animation duration.
RawAnimation::TranslationKey first_key = {2.f, ozz::math::Float3::zero()};
raw_animation.tracks[0].translations.push_back(first_key);
// Builds animation
EXPECT_FALSE(builder(raw_animation));
}
{ // Building an Animation with unsorted key frame's time fails.
RawAnimation raw_animation;
raw_animation.duration = 1.f;
raw_animation.tracks.resize(2);
// Adds 2 unsorted keys.
RawAnimation::TranslationKey first_key = {0.7f, ozz::math::Float3::zero()};
raw_animation.tracks[0].translations.push_back(first_key);
RawAnimation::TranslationKey second_key = {0.1f, ozz::math::Float3::zero()};
raw_animation.tracks[0].translations.push_back(second_key);
// Builds animation
EXPECT_FALSE(builder(raw_animation));
}
{ // Building an Animation with equal key frame's time fails.
RawAnimation raw_animation;
raw_animation.duration = 1.f;
raw_animation.tracks.resize(2);
// Adds 2 unsorted keys.
RawAnimation::TranslationKey key = {0.7f, ozz::math::Float3::zero()};
raw_animation.tracks[0].translations.push_back(key);
raw_animation.tracks[0].translations.push_back(key);
// Builds animation
EXPECT_FALSE(builder(raw_animation));
}
{ // Building a valid Animation with empty tracks succeeds.
RawAnimation raw_animation;
raw_animation.duration = 46.f;
raw_animation.tracks.resize(46);
// Builds animation
ozz::unique_ptr<Animation> anim(builder(raw_animation));
EXPECT_TRUE(anim);
EXPECT_EQ(anim->duration(), 46.f);
EXPECT_EQ(anim->num_tracks(), 46);
}
{ // Building a valid Animation with 1 track succeeds.
RawAnimation raw_animation;
raw_animation.duration = 46.f;
raw_animation.tracks.resize(1);
RawAnimation::TranslationKey first_key = {0.7f, ozz::math::Float3::zero()};
raw_animation.tracks[0].translations.push_back(first_key);
// Builds animation
ozz::unique_ptr<Animation> anim(builder(raw_animation));
EXPECT_TRUE(anim);
EXPECT_EQ(anim->duration(), 46.f);
EXPECT_EQ(anim->num_tracks(), 1);
}
}
TEST(Name, AnimationBuilder) {
// Instantiates a builder objects with default parameters.
AnimationBuilder builder;
{ // Building an unnamed animation.
RawAnimation raw_animation;
raw_animation.duration = 1.f;
raw_animation.tracks.resize(46);
// Builds animation
ozz::unique_ptr<Animation> anim(builder(raw_animation));
EXPECT_TRUE(anim);
// Should
EXPECT_STREQ(anim->name(), "");
}
{ // Building an unnamed animation.
RawAnimation raw_animation;
raw_animation.duration = 1.f;
raw_animation.tracks.resize(46);
raw_animation.name = "46";
// Builds animation
ozz::unique_ptr<Animation> anim(builder(raw_animation));
EXPECT_TRUE(anim);
// Should
EXPECT_STREQ(anim->name(), "46");
}
}
TEST(Sort, AnimationBuilder) {
// Instantiates a builder objects with default parameters.
AnimationBuilder builder;
{
RawAnimation raw_animation;
raw_animation.duration = 1.f;
raw_animation.tracks.resize(4);
// Raw animation inputs.
// 0 1
// --------------------
// 0 - A B |
// 1 - C D E |
// 2 - F G H I J
// 3 - K L M N |
// Final animation.
// 0 1
// --------------------
// 0 - 0 4 11
// 1 - 1 5 8 12
// 2 - 2 6 9 14 16
// 3 - 3 7 10 13 15
RawAnimation::TranslationKey a = {0.f * raw_animation.duration,
ozz::math::Float3(1.f, 0.f, 0.f)};
raw_animation.tracks[0].translations.push_back(a);
RawAnimation::TranslationKey b = {.4f * raw_animation.duration,
ozz::math::Float3(3.f, 0.f, 0.f)};
raw_animation.tracks[0].translations.push_back(b);
RawAnimation::TranslationKey c = {0.f * raw_animation.duration,
ozz::math::Float3(2.f, 0.f, 0.f)};
raw_animation.tracks[1].translations.push_back(c);
RawAnimation::TranslationKey d = {0.2f * raw_animation.duration,
ozz::math::Float3(6.f, 0.f, 0.f)};
raw_animation.tracks[1].translations.push_back(d);
RawAnimation::TranslationKey e = {0.4f * raw_animation.duration,
ozz::math::Float3(8.f, 0.f, 0.f)};
raw_animation.tracks[1].translations.push_back(e);
RawAnimation::TranslationKey f = {0.f * raw_animation.duration,
ozz::math::Float3(12.f, 0.f, 0.f)};
raw_animation.tracks[2].translations.push_back(f);
RawAnimation::TranslationKey g = {.2f * raw_animation.duration,
ozz::math::Float3(11.f, 0.f, 0.f)};
raw_animation.tracks[2].translations.push_back(g);
RawAnimation::TranslationKey h = {.6f * raw_animation.duration,
ozz::math::Float3(9.f, 0.f, 0.f)};
raw_animation.tracks[2].translations.push_back(h);
RawAnimation::TranslationKey i = {.8f * raw_animation.duration,
ozz::math::Float3(7.f, 0.f, 0.f)};
raw_animation.tracks[2].translations.push_back(i);
RawAnimation::TranslationKey j = {1.f * raw_animation.duration,
ozz::math::Float3(5.f, 0.f, 0.f)};
raw_animation.tracks[2].translations.push_back(j);
RawAnimation::TranslationKey k = {0.f * raw_animation.duration,
ozz::math::Float3(1.f, 0.f, 0.f)};
raw_animation.tracks[3].translations.push_back(k);
RawAnimation::TranslationKey l = {.2f * raw_animation.duration,
ozz::math::Float3(2.f, 0.f, 0.f)};
raw_animation.tracks[3].translations.push_back(l);
RawAnimation::TranslationKey m = {.4f * raw_animation.duration,
ozz::math::Float3(3.f, 0.f, 0.f)};
raw_animation.tracks[3].translations.push_back(m);
RawAnimation::TranslationKey n = {.6f * raw_animation.duration,
ozz::math::Float3(4.f, 0.f, 0.f)};
raw_animation.tracks[3].translations.push_back(n);
// Builds animation
ozz::unique_ptr<Animation> animation(builder(raw_animation));
ASSERT_TRUE(animation);
// Duration must be maintained.
EXPECT_EQ(animation->duration(), raw_animation.duration);
// Needs to sample to test the animation.
ozz::animation::SamplingJob job;
ozz::animation::SamplingCache cache(1);
ozz::math::SoaTransform output[1];
job.animation = animation.get();
job.cache = &cache;
job.output = output;
// Samples and compares the two animations
{ // Samples at t = 0
job.ratio = 0.f;
job.Run();
EXPECT_SOAFLOAT3_EQ_EST(output[0].translation, 1.f, 2.f, 12.f, 1.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f);
}
{ // Samples at t = .2
job.ratio = .2f;
job.Run();
EXPECT_SOAFLOAT3_EQ_EST(output[0].translation, 2.f, 6.f, 11.f, 2.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f);
}
{ // Samples at t = .4
job.ratio = .4f;
job.Run();
EXPECT_SOAFLOAT3_EQ_EST(output[0].translation, 3.f, 8.f, 10.f, 3.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f);
}
{ // Samples at t = .6
job.ratio = .6f;
job.Run();
EXPECT_SOAFLOAT3_EQ_EST(output[0].translation, 3.f, 8.f, 9.f, 4.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f);
}
{ // Samples at t = .8
job.ratio = .8f;
job.Run();
EXPECT_SOAFLOAT3_EQ_EST(output[0].translation, 3.f, 8.f, 7.f, 4.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f);
}
{ // Samples at t = 1
job.ratio = 1.f;
job.Run();
EXPECT_SOAFLOAT3_EQ_EST(output[0].translation, 3.f, 8.f, 5.f, 4.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f);
}
}
}
@@ -0,0 +1,672 @@
//----------------------------------------------------------------------------//
// //
// 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 "gtest/gtest.h"
#include "ozz/base/maths/math_constant.h"
#include "ozz/base/memory/unique_ptr.h"
#include "ozz/animation/offline/animation_builder.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/runtime/skeleton.h"
using ozz::animation::Skeleton;
using ozz::animation::offline::AnimationOptimizer;
using ozz::animation::offline::RawAnimation;
using ozz::animation::offline::RawSkeleton;
using ozz::animation::offline::SkeletonBuilder;
TEST(Error, AnimationOptimizer) {
AnimationOptimizer optimizer;
{ // nullptr output.
RawAnimation input;
Skeleton skeleton;
EXPECT_TRUE(input.Validate());
// Builds animation
EXPECT_FALSE(optimizer(input, skeleton, nullptr));
}
{ // Invalid input animation.
RawSkeleton raw_skeleton;
raw_skeleton.roots.resize(1);
SkeletonBuilder skeleton_builder;
ozz::unique_ptr<Skeleton> skeleton(skeleton_builder(raw_skeleton));
ASSERT_TRUE(skeleton);
RawAnimation input;
input.duration = -1.f;
EXPECT_FALSE(input.Validate());
// Builds animation
RawAnimation output;
output.duration = -1.f;
output.tracks.resize(1);
EXPECT_FALSE(optimizer(input, *skeleton, &output));
EXPECT_FLOAT_EQ(output.duration, RawAnimation().duration);
EXPECT_EQ(output.num_tracks(), 0);
}
{ // Invalid skeleton.
Skeleton skeleton;
RawAnimation input;
input.tracks.resize(1);
EXPECT_TRUE(input.Validate());
// Builds animation
RawAnimation output;
EXPECT_FALSE(optimizer(input, skeleton, &output));
EXPECT_FLOAT_EQ(output.duration, RawAnimation().duration);
EXPECT_EQ(output.num_tracks(), 0);
}
}
TEST(Name, AnimationOptimizer) {
// Prepares a skeleton.
RawSkeleton raw_skeleton;
SkeletonBuilder skeleton_builder;
ozz::unique_ptr<Skeleton> skeleton(skeleton_builder(raw_skeleton));
ASSERT_TRUE(skeleton);
AnimationOptimizer optimizer;
RawAnimation input;
input.name = "Test_Animation";
input.duration = 1.f;
ASSERT_TRUE(input.Validate());
RawAnimation output;
ASSERT_TRUE(optimizer(input, *skeleton, &output));
EXPECT_EQ(output.num_tracks(), 0);
EXPECT_STRCASEEQ(output.name.c_str(), "Test_Animation");
}
TEST(Optimize, AnimationOptimizer) {
// Prepares a skeleton.
RawSkeleton raw_skeleton;
raw_skeleton.roots.resize(1);
raw_skeleton.roots[0].children.resize(1);
raw_skeleton.roots[0].children[0].children.resize(1);
raw_skeleton.roots[0].children[0].children[0].children.resize(2);
SkeletonBuilder skeleton_builder;
ozz::unique_ptr<Skeleton> skeleton(skeleton_builder(raw_skeleton));
ASSERT_TRUE(skeleton);
// Disable non hierarchical optimizations
AnimationOptimizer optimizer;
// Disables vertex distance.
optimizer.setting.distance = 0.f;
RawAnimation input;
input.duration = 1.f;
input.tracks.resize(5);
// Translations on track 0.
{
RawAnimation::TranslationKey key = {0.f, ozz::math::Float3(4.f, 0.f, 0.f)};
input.tracks[0].translations.push_back(key);
}
// Translations on track 1.
{
RawAnimation::TranslationKey key = {0.f, ozz::math::Float3(0.f, 0.f, 0.f)};
input.tracks[1].translations.push_back(key);
}
// Translations on track 2.
{
RawAnimation::TranslationKey key = {0.f, ozz::math::Float3(5.f, 0.f, 0.f)};
input.tracks[2].translations.push_back(key);
}
{
RawAnimation::TranslationKey key = {.1f, ozz::math::Float3(6.f, 0.f, 0.f)};
input.tracks[2].translations.push_back(key);
}
{ // Creates an variation.
RawAnimation::TranslationKey key = {.2f, ozz::math::Float3(7.1f, 0.f, 0.f)};
input.tracks[2].translations.push_back(key);
}
{
RawAnimation::TranslationKey key = {.3f, ozz::math::Float3(8.f, 0.f, 0.f)};
input.tracks[2].translations.push_back(key);
}
// Translations on track 3.
{
RawAnimation::TranslationKey key = {0.f, ozz::math::Float3(16.f, 0.f, 0.f)};
input.tracks[3].translations.push_back(key);
}
// Translations on track 4.
{
RawAnimation::TranslationKey key = {0.f, ozz::math::Float3(32.f, 0.f, 0.f)};
input.tracks[4].translations.push_back(key);
}
ASSERT_TRUE(input.Validate());
// Small translation tolerance -> all key maintained
{
RawAnimation output;
optimizer.setting.tolerance = .01f;
ASSERT_TRUE(optimizer(input, *skeleton, &output));
EXPECT_EQ(output.num_tracks(), 5);
const RawAnimation::JointTrack::Translations& translations =
output.tracks[2].translations;
ASSERT_EQ(translations.size(), 4u);
EXPECT_FLOAT_EQ(translations[0].time, 0.f);
EXPECT_FLOAT_EQ(translations[1].time, .1f);
EXPECT_FLOAT_EQ(translations[2].time, .2f);
EXPECT_FLOAT_EQ(translations[3].time, .3f);
}
// High translation tolerance -> all key interpolated
{
RawAnimation output;
optimizer.setting.tolerance = .1f;
ASSERT_TRUE(optimizer(input, *skeleton, &output));
EXPECT_EQ(output.num_tracks(), 5);
const RawAnimation::JointTrack::Translations& translations =
output.tracks[2].translations;
ASSERT_EQ(translations.size(), 2u);
EXPECT_FLOAT_EQ(translations[0].time, 0.f);
EXPECT_FLOAT_EQ(translations[1].time, .3f);
}
// Introduces a 10x scaling upstream that amplifies error
// Scaling on track 0
{
RawAnimation::ScaleKey key = {0.f, ozz::math::Float3(10.f, 0.f, 0.f)};
input.tracks[0].scales.push_back(key);
}
// High translation tolerance -> keys aren't interpolated because of scale
// effect.
{
RawAnimation output;
optimizer.setting.tolerance = .1f;
ASSERT_TRUE(optimizer(input, *skeleton, &output));
EXPECT_EQ(output.num_tracks(), 5);
const RawAnimation::JointTrack::Translations& translations =
output.tracks[2].translations;
ASSERT_EQ(translations.size(), 4u);
}
// Very high tolerance
{
RawAnimation output;
optimizer.setting.tolerance = 1.f;
ASSERT_TRUE(optimizer(input, *skeleton, &output));
EXPECT_EQ(output.num_tracks(), 5);
const RawAnimation::JointTrack::Translations& translations =
output.tracks[2].translations;
ASSERT_EQ(translations.size(), 2u);
}
// Introduces a -10x scaling upstream that amplifies error
// Scaling on track 0
{ input.tracks[0].scales[0].value = ozz::math::Float3(0.f, -10.f, 0.f); }
// High translation tolerance -> keys aren't interpolated because of scale
// effect.
{
RawAnimation output;
optimizer.setting.tolerance = .1f;
ASSERT_TRUE(optimizer(input, *skeleton, &output));
EXPECT_EQ(output.num_tracks(), 5);
const RawAnimation::JointTrack::Translations& translations =
output.tracks[2].translations;
ASSERT_EQ(translations.size(), 4u);
EXPECT_FLOAT_EQ(translations[0].time, 0.f);
EXPECT_FLOAT_EQ(translations[1].time, .1f);
EXPECT_FLOAT_EQ(translations[2].time, .2f);
EXPECT_FLOAT_EQ(translations[3].time, .3f);
}
// Very high tolerance
{
RawAnimation output;
optimizer.setting.tolerance = 1.f;
ASSERT_TRUE(optimizer(input, *skeleton, &output));
EXPECT_EQ(output.num_tracks(), 5);
const RawAnimation::JointTrack::Translations& translations =
output.tracks[2].translations;
ASSERT_EQ(translations.size(), 2u);
EXPECT_FLOAT_EQ(translations[0].time, 0.f);
EXPECT_FLOAT_EQ(translations[1].time, .3f);
}
// Compenstate scale on next joint
{
RawAnimation::ScaleKey key = {0.f, ozz::math::Float3(.1f, 0.f, 0.f)};
input.tracks[1].scales.push_back(key);
}
// High translation tolerance -> keys ar interpolated because of scale
// compensation.
{
RawAnimation output;
optimizer.setting.tolerance = 1.f;
ASSERT_TRUE(optimizer(input, *skeleton, &output));
EXPECT_EQ(output.num_tracks(), 5);
const RawAnimation::JointTrack::Translations& translations =
output.tracks[2].translations;
ASSERT_EQ(translations.size(), 2u);
}
// Remove scaling compensation
{ input.tracks[1].scales.clear(); }
// Introduces a .1x scaling upstream that amplifies error
// Scaling on track 0
{ input.tracks[0].scales[0].value = ozz::math::Float3(0.f, 0.f, .1f); }
// High translation tolerance -> keys aren't interpolated because of scale
// effect.
{
RawAnimation output;
optimizer.setting.tolerance = .001f;
ASSERT_TRUE(optimizer(input, *skeleton, &output));
EXPECT_EQ(output.num_tracks(), 5);
const RawAnimation::JointTrack::Translations& translations =
output.tracks[2].translations;
ASSERT_EQ(translations.size(), 4u);
EXPECT_FLOAT_EQ(translations[0].time, 0.f);
EXPECT_FLOAT_EQ(translations[1].time, .1f);
EXPECT_FLOAT_EQ(translations[2].time, .2f);
EXPECT_FLOAT_EQ(translations[3].time, .3f);
}
// Very high translation tolerance
{
RawAnimation output;
optimizer.setting.tolerance = .01f;
ASSERT_TRUE(optimizer(input, *skeleton, &output));
EXPECT_EQ(output.num_tracks(), 5);
const RawAnimation::JointTrack::Translations& translations =
output.tracks[2].translations;
ASSERT_EQ(translations.size(), 2u);
EXPECT_FLOAT_EQ(translations[0].time, 0.f);
EXPECT_FLOAT_EQ(translations[1].time, .3f);
}
// Remove scaling
{ input.tracks[0].scales.clear(); }
// Rotations on track 0.
{
RawAnimation::RotationKey key = {
0.f, ozz::math::Quaternion::FromEuler(0.f, 0.f, 0.f)};
input.tracks[0].rotations.push_back(key);
}
{ // Include error
const float angle_error = 2.5e-3f; // creates an arc of .1m at 40m.
RawAnimation::RotationKey key = {
.1f, ozz::math::Quaternion::FromEuler(ozz::math::kPi_4 + angle_error,
0.f, 0.f)};
input.tracks[0].rotations.push_back(key);
}
{
RawAnimation::RotationKey key = {
.2f, ozz::math::Quaternion::FromEuler(ozz::math::kPi_2, 0.f, 0.f)};
input.tracks[0].rotations.push_back(key);
}
// Big enough tolerance -> keys rejected
{
RawAnimation output;
optimizer.setting.tolerance = .3f;
optimizer.setting.distance = 40.f;
ASSERT_TRUE(optimizer(input, *skeleton, &output));
EXPECT_EQ(output.num_tracks(), 5);
const RawAnimation::JointTrack::Rotations& rotations =
output.tracks[0].rotations;
ASSERT_EQ(rotations.size(), 2u);
const RawAnimation::JointTrack::Translations& translations =
output.tracks[2].translations;
ASSERT_EQ(translations.size(), 2u);
}
// Small enough tolerance -> keys rejected
{
RawAnimation output;
optimizer.setting.tolerance = .05f;
optimizer.setting.distance = 40.f;
ASSERT_TRUE(optimizer(input, *skeleton, &output));
EXPECT_EQ(output.num_tracks(), 5);
const RawAnimation::JointTrack::Rotations& rotations =
output.tracks[0].rotations;
ASSERT_EQ(rotations.size(), 3u);
const RawAnimation::JointTrack::Translations& translations =
output.tracks[2].translations;
ASSERT_EQ(translations.size(), 4u);
}
// Back to default
optimizer.setting = AnimationOptimizer::Setting();
// Small translation tolerance -> all key maintained
{
RawAnimation output;
optimizer.setting.tolerance = .01f;
ASSERT_TRUE(optimizer(input, *skeleton, &output));
EXPECT_EQ(output.num_tracks(), 5);
const RawAnimation::JointTrack::Rotations& rotations =
output.tracks[0].rotations;
ASSERT_EQ(rotations.size(), 3u);
const RawAnimation::JointTrack::Translations& translations =
output.tracks[2].translations;
ASSERT_EQ(translations.size(), 4u);
}
// Introduces a .1x scaling upstream that lowers error
// Scaling on track 0
{
RawAnimation::ScaleKey key = {0.f, ozz::math::Float3(0.f, .1f, 0.f)};
input.tracks[1].scales.push_back(key);
}
// Small translation tolerance, but scaled down -> keys rejected
{
RawAnimation output;
optimizer.setting.tolerance = .011f;
ASSERT_TRUE(optimizer(input, *skeleton, &output));
EXPECT_EQ(output.num_tracks(), 5);
const RawAnimation::JointTrack::Rotations& rotations =
output.tracks[0].rotations;
ASSERT_EQ(rotations.size(), 2u);
const RawAnimation::JointTrack::Translations& translations =
output.tracks[2].translations;
ASSERT_EQ(translations.size(), 2u);
}
// More vertex distance -> keys are maintained (translation unaffected)
{
RawAnimation output;
optimizer.setting.tolerance = .01f;
optimizer.setting.distance = 1.f;
ASSERT_TRUE(optimizer(input, *skeleton, &output));
EXPECT_EQ(output.num_tracks(), 5);
const RawAnimation::JointTrack::Rotations& rotations =
output.tracks[0].rotations;
ASSERT_EQ(rotations.size(), 3u);
const RawAnimation::JointTrack::Translations& translations =
output.tracks[2].translations;
ASSERT_EQ(translations.size(), 2u);
}
// Remove scaling
{ input.tracks[2].scales.clear(); }
}
TEST(OptimizeOverride, AnimationOptimizer) {
// Prepares a skeleton.
RawSkeleton raw_skeleton;
raw_skeleton.roots.resize(1);
raw_skeleton.roots[0].children.resize(1);
raw_skeleton.roots[0].children[0].children.resize(1);
raw_skeleton.roots[0].children[0].children[0].children.resize(2);
SkeletonBuilder skeleton_builder;
ozz::unique_ptr<Skeleton> skeleton(skeleton_builder(raw_skeleton));
ASSERT_TRUE(skeleton);
// Disable non hierarchical optimizations
AnimationOptimizer optimizer;
const AnimationOptimizer::Setting loose_setting(1e-2f, // 1cm
1e-3f); // 1mm
// Disables vertex distance.
optimizer.setting.distance = 0.f;
RawAnimation input;
input.duration = 1.f;
input.tracks.resize(5);
// Translations on track 0.
{
RawAnimation::TranslationKey key = {0.f, ozz::math::Float3(.4f, 0.f, 0.f)};
input.tracks[0].translations.push_back(key);
}
// Rotations on track 0.
{
RawAnimation::RotationKey key = {
0.f, ozz::math::Quaternion::FromEuler(0.f, 0.f, 0.f)};
input.tracks[1].rotations.push_back(key);
}
{ // Includes an error that
const float angle_error = 1e-3f; // creates an arc of 1mm at 1m.
RawAnimation::RotationKey key = {
.1f, ozz::math::Quaternion::FromEuler(ozz::math::kPi_4 + angle_error,
0.f, 0.f)};
input.tracks[1].rotations.push_back(key);
}
{
RawAnimation::RotationKey key = {
.2f, ozz::math::Quaternion::FromEuler(ozz::math::kPi_2, 0.f, 0.f)};
input.tracks[1].rotations.push_back(key);
}
// Translations on track 1.
{
RawAnimation::TranslationKey key = {0.f, ozz::math::Float3(0.f, 0.f, 0.f)};
input.tracks[1].translations.push_back(key);
}
// Translations on track 2.
{
RawAnimation::TranslationKey key = {0.f, ozz::math::Float3(.05f, 0.f, 0.f)};
input.tracks[2].translations.push_back(key);
}
{
RawAnimation::TranslationKey key = {.1f, ozz::math::Float3(.06f, 0.f, 0.f)};
input.tracks[2].translations.push_back(key);
}
{ // Creates a variation.
const float trans_err = 5e-4f;
RawAnimation::TranslationKey key = {
.2f, ozz::math::Float3(.07f + trans_err, 0.f, 0.f)};
input.tracks[2].translations.push_back(key);
}
{
RawAnimation::TranslationKey key = {.3f, ozz::math::Float3(.08f, 0.f, 0.f)};
input.tracks[2].translations.push_back(key);
}
// Translations on track 3.
{
RawAnimation::TranslationKey key = {0.f, ozz::math::Float3(.16f, 0.f, 0.f)};
input.tracks[3].translations.push_back(key);
}
// Translations on track 4.
{
RawAnimation::TranslationKey key = {0.f, ozz::math::Float3(.32f, 0.f, 0.f)};
input.tracks[4].translations.push_back(key);
}
ASSERT_TRUE(input.Validate());
// Default global tolerances
{
RawAnimation output;
optimizer.setting = loose_setting;
ASSERT_TRUE(optimizer(input, *skeleton, &output));
ASSERT_EQ(output.num_tracks(), 5);
const RawAnimation::JointTrack::Rotations& rotations =
output.tracks[1].rotations;
ASSERT_EQ(rotations.size(), 2u);
EXPECT_FLOAT_EQ(rotations[0].time, 0.f);
EXPECT_FLOAT_EQ(rotations[1].time, .2f);
const RawAnimation::JointTrack::Translations& translations =
output.tracks[2].translations;
ASSERT_EQ(translations.size(), 2u);
EXPECT_FLOAT_EQ(translations[0].time, 0.f);
EXPECT_FLOAT_EQ(translations[1].time, .3f);
}
// Overriding root has no effect on its child, even with small tolerance.
{
RawAnimation output;
optimizer.setting = loose_setting;
const AnimationOptimizer::Setting joint_override(1e-6f, 1e6f);
optimizer.joints_setting_override[0] = joint_override;
ASSERT_TRUE(optimizer(input, *skeleton, &output));
ASSERT_EQ(output.num_tracks(), 5);
const RawAnimation::JointTrack::Rotations& rotations =
output.tracks[1].rotations;
EXPECT_EQ(rotations.size(), 2u);
const RawAnimation::JointTrack::Translations& translations =
output.tracks[2].translations;
EXPECT_EQ(translations.size(), 2u);
optimizer.joints_setting_override.clear();
}
// Overriding a joint has effect on itself.
{
RawAnimation output;
optimizer.setting = loose_setting;
const AnimationOptimizer::Setting joint_override(1e-3f, // 1mm
1e-2f); // 1cm
optimizer.joints_setting_override[1] = joint_override;
ASSERT_TRUE(optimizer(input, *skeleton, &output));
EXPECT_EQ(output.num_tracks(), 5);
const RawAnimation::JointTrack::Rotations& rotations =
output.tracks[1].rotations;
EXPECT_EQ(rotations.size(), 2u);
const RawAnimation::JointTrack::Translations& translations =
output.tracks[2].translations;
EXPECT_EQ(translations.size(), 2u);
optimizer.joints_setting_override.clear();
}
// Overriding leaf has effect up to the root though.
{
RawAnimation output;
optimizer.setting = loose_setting;
const AnimationOptimizer::Setting joint_override(1e-3f, // 1mm
10.f); // 10m
optimizer.joints_setting_override[2] = joint_override;
ASSERT_TRUE(optimizer(input, *skeleton, &output));
EXPECT_EQ(output.num_tracks(), 5);
const RawAnimation::JointTrack::Rotations& rotations =
output.tracks[1].rotations;
ASSERT_EQ(rotations.size(), 3u);
const RawAnimation::JointTrack::Translations& translations =
output.tracks[2].translations;
ASSERT_EQ(translations.size(), 2u);
optimizer.joints_setting_override.clear();
}
// Scale at root affects rotation and translation.
{
RawAnimation::ScaleKey key = {0.f, ozz::math::Float3(.1f, 2.f, .1f)};
input.tracks[0].scales.push_back(key);
RawAnimation output;
optimizer.setting = loose_setting;
const AnimationOptimizer::Setting joint_override(1.e-3f, // > 1mm
1.f); // 1m
optimizer.joints_setting_override[1] = joint_override;
optimizer.joints_setting_override[2] = joint_override;
ASSERT_TRUE(optimizer(input, *skeleton, &output));
EXPECT_EQ(output.num_tracks(), 5);
const RawAnimation::JointTrack::Rotations& rotations =
output.tracks[1].rotations;
EXPECT_EQ(rotations.size(), 3u);
const RawAnimation::JointTrack::Translations& translations =
output.tracks[2].translations;
EXPECT_EQ(translations.size(), 3u);
optimizer.joints_setting_override.clear();
input.tracks[0].scales.clear();
}
// Scale at leaf doesn't affect anything but the leaf.
{
RawAnimation::ScaleKey key = {0.f, ozz::math::Float3(.1f, 2.f, .1f)};
input.tracks[4].scales.push_back(key);
RawAnimation output;
optimizer.setting = loose_setting;
const AnimationOptimizer::Setting joint_override(1e-3f, // < 1mm
.5f); // .5m
optimizer.joints_setting_override[1] = joint_override;
ASSERT_TRUE(optimizer(input, *skeleton, &output));
EXPECT_EQ(output.num_tracks(), 5);
const RawAnimation::JointTrack::Rotations& rotations =
output.tracks[1].rotations;
EXPECT_EQ(rotations.size(), 2u);
const RawAnimation::JointTrack::Translations& translations =
output.tracks[2].translations;
EXPECT_EQ(translations.size(), 2u);
optimizer.joints_setting_override.clear();
input.tracks[4].scales.clear();
}
}
@@ -0,0 +1,169 @@
if(NOT ozz_build_fbx)
return()
endif()
# Creates a file with an invalid content.
file(WRITE "${ozz_temp_directory}/bad.fbx" "bad content")
# Run fbx2ozz generic failing tests
#----------------------------
add_test(NAME fbx2ozz_version COMMAND fbx2ozz "--version")
add_test(NAME fbx2ozz_bad_content COMMAND fbx2ozz "--file=${ozz_temp_directory}/bad.fbx" )
set_tests_properties(fbx2ozz_bad_content PROPERTIES PASS_REGULAR_EXPRESSION "Failed to import file \"${ozz_temp_directory}/bad.fbx\".")
# Run fbx2ozz skeleton failing tests
#----------------------------
add_test(NAME fbx2ozz_skel_no_skeleton COMMAND fbx2ozz "--file=${ozz_media_directory}/fbx/cube.fbx" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/fbx_should_not_exist.ozz\",\"import\":{\"enable\":true}},\"animations\":[]}}")
set_tests_properties(fbx2ozz_skel_no_skeleton PROPERTIES PASS_REGULAR_EXPRESSION "Failed to import skeleton.")
# Run fbx2ozz collada failing tests
#----------------------------
add_test(NAME fbx2ozz_skel_no_skeleton_dae COMMAND fbx2ozz "--file=${ozz_media_directory}/collada/cube.dae" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/fbx_should_not_exist.ozz\",\"import\":{\"enable\":true}},\"animations\":[]}}")
set_tests_properties(fbx2ozz_skel_no_skeleton_dae PROPERTIES PASS_REGULAR_EXPRESSION "Failed to import skeleton.")
# Ensures nothing was outputted.
add_test(NAME fbx2ozz_skel_output COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/fbx_should_not_exist.ozz" "${ozz_temp_directory}/fbx_should_not_exist_too.ozz")
set_tests_properties(fbx2ozz_skel_output PROPERTIES WILL_FAIL true)
set_tests_properties(fbx2ozz_skel_output PROPERTIES
DEPENDS "fbx2ozz_skel_no_skeleton
fbx2ozz_skel_no_skeleton_dae")
# Run fbx2ozz skeleton passing tests
#----------------------------
add_test(NAME fbx2ozz_skel_simple COMMAND fbx2ozz "--file=${ozz_media_directory}/fbx/pab/skeleton.fbx" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/fbx_skeleton.ozz\",\"import\":{\"enable\":true}},\"animations\":[]}}")
add_test(NAME fbx2ozz_skel_simple_raw COMMAND fbx2ozz "--file=${ozz_media_directory}/fbx/pab/skeleton.fbx" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/fbx_raw_skeleton.ozz\",\"import\":{\"enable\":true,\"raw\":true}},\"animations\":[]}}")
# Run fbx2ozz collada skeleton passing tests
#----------------------------
add_test(NAME fbx2ozz_skel_simple_dae_astro_max COMMAND fbx2ozz "--file=${ozz_media_directory}/collada/astro_max.dae" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/astro_max_skeleton.ozz\",\"import\":{\"enable\":true}},\"animations\":[]}}")
add_test(NAME fbx2ozz_skel_simple_dae_astro_maya COMMAND fbx2ozz "--file=${ozz_media_directory}/collada/astro_maya.dae" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/astro_maya_skeleton.ozz\",\"import\":{\"enable\":true}},\"animations\":[]}}")
add_test(NAME fbx2ozz_skel_simple_dae_seymour COMMAND fbx2ozz "--file=${ozz_media_directory}/collada/seymour.dae" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/seymour_skeleton.ozz\",\"import\":{\"enable\":true}},\"animations\":[]}}")
# Run fbx2ozz animation failing tests
#----------------------------
add_test(NAME fbx2ozz_anim_badcontent COMMAND fbx2ozz "--file=${ozz_temp_directory}/bad.fbx")
set_tests_properties(fbx2ozz_anim_badcontent PROPERTIES DEPENDS fbx2ozz_skel_simple)
set_tests_properties(fbx2ozz_anim_badcontent PROPERTIES PASS_REGULAR_EXPRESSION "FbxImporter initialization failed with error: Unexpected file type")
# Run fbx2ozz animation passing tests
#----------------------------
add_test(NAME fbx2ozz_anim_simple COMMAND fbx2ozz "--file=${ozz_media_directory}/fbx/pab/run.fbx" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/fbx_skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/fbx_animation_${CMAKE_CURRENT_LIST_LINE}.ozz\"}]}")
set_tests_properties(fbx2ozz_anim_simple PROPERTIES DEPENDS fbx2ozz_skel_simple)
add_test(NAME fbx2ozz_anim_simple_raw COMMAND fbx2ozz "--file=${ozz_media_directory}/fbx/pab/run.fbx" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/fbx_skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/fbx_animation_${CMAKE_CURRENT_LIST_LINE}.ozz\",\"raw\":true}]}")
set_tests_properties(fbx2ozz_anim_simple_raw PROPERTIES DEPENDS fbx2ozz_skel_simple)
add_test(NAME fbx2ozz_anim_simple_from_raw COMMAND fbx2ozz "--file=${ozz_media_directory}/fbx/pab/run.fbx" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/fbx_raw_skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/fbx_animation_${CMAKE_CURRENT_LIST_LINE}.ozz\"}]}")
set_tests_properties(fbx2ozz_anim_simple_from_raw PROPERTIES DEPENDS fbx2ozz_skel_simple_raw)
add_test(NAME fbx2ozz_anim_simple_raw_from_raw COMMAND fbx2ozz "--file=${ozz_media_directory}/fbx/pab/run.fbx" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/fbx_raw_skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/fbx_animation_${CMAKE_CURRENT_LIST_LINE}.ozz\",\"raw\":true}]}")
set_tests_properties(fbx2ozz_anim_simple_raw_from_raw PROPERTIES DEPENDS fbx2ozz_skel_simple_raw)
# Run fbx2ozz track passing tests
#----------------------------
configure_file("${CMAKE_CURRENT_LIST_DIR}/properties.json.cmake" "${ozz_temp_directory}/properties.json")
add_test(NAME fbx2ozz_anim_properties COMMAND fbx2ozz "--file=${ozz_media_directory}/fbx/properties.fbx" "--config_file=${ozz_temp_directory}/properties.json")
set_tests_properties(fbx2ozz_anim_properties PROPERTIES DEPENDS fbx2ozz_skel_properties)
# Ensures all tracks were outputted.
file(MAKE_DIRECTORY ${ozz_temp_directory}/fbx_properties_tracks_cp)
add_test(NAME fbx2ozz_anim_properties_output COMMAND ${CMAKE_COMMAND} -E copy
"${ozz_temp_directory}/fbx_properties_track1_animated-ozz_curve_bool.ozz"
"${ozz_temp_directory}/fbx_properties_track1_animated-ozz_curve_int.ozz"
"${ozz_temp_directory}/fbx_properties_track1_animated-ozz_curve_number.ozz"
"${ozz_temp_directory}/fbx_properties_track1_animated-ozz_step_bool.ozz"
"${ozz_temp_directory}/fbx_properties_track1_animated-ozz_step_enum.ozz"
"${ozz_temp_directory}/fbx_properties_track1_animated-ozz_step_int.ozz"
"${ozz_temp_directory}/fbx_properties_track1_animated-ozz_step_number.ozz"
"${ozz_temp_directory}/fbx_properties_track1_constant-ozz_bool.ozz"
"${ozz_temp_directory}/fbx_properties_track1_constant-ozz_enum.ozz"
"${ozz_temp_directory}/fbx_properties_track1_constant-ozz_int.ozz"
"${ozz_temp_directory}/fbx_properties_track1_constant-ozz_number.ozz"
"${ozz_temp_directory}/fbx_properties_track3_animated-ozz_curve_vector.ozz"
"${ozz_temp_directory}/fbx_properties_track3_constant-ozz_vector.ozz"
"${ozz_temp_directory}/fbx_properties_track3p_animated-ozz_curve_vector.ozz"
"${ozz_temp_directory}/fbx_properties_track3p_constant-ozz_vector.ozz"
"${ozz_temp_directory}/fbx_properties_track3v_animated-ozz_curve_vector.ozz"
"${ozz_temp_directory}/fbx_properties_track3v_constant-ozz_vector.ozz"
"${ozz_temp_directory}/fbx_properties_track4_animated-ozz_curve_color.ozz"
"${ozz_temp_directory}/fbx_properties_track4_constant-ozz_color.ozz"
"${ozz_temp_directory}/fbx_properties_tracks_cp/"
)
set_tests_properties(fbx2ozz_anim_properties_output PROPERTIES
DEPENDS fbx2ozz_anim_properties
"${ozz_media_directory}/fbx/properties.fbx"
"${ozz_temp_directory}/fbx_propreties_skeleton.ozz")
# Run fbx2ozz animation collada passing tests
#----------------------------
add_test(NAME fbx2ozz_anim_simple_dae_astro_max COMMAND fbx2ozz "--file=${ozz_media_directory}/collada/astro_max.dae" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/astro_max_skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/dae_animation_${CMAKE_CURRENT_LIST_LINE}.ozz\"}]}")
set_tests_properties(fbx2ozz_anim_simple_dae_astro_max PROPERTIES DEPENDS fbx2ozz_skel_simple_dae_astro_max)
add_test(NAME fbx2ozz_anim_simple_dae_astro_maya COMMAND fbx2ozz "--file=${ozz_media_directory}/collada/astro_maya.dae" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/astro_maya_skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/dae_animation_${CMAKE_CURRENT_LIST_LINE}.ozz\"}]}")
set_tests_properties(fbx2ozz_anim_simple_dae_astro_maya PROPERTIES DEPENDS fbx2ozz_skel_simple_dae_astro_maya)
add_test(NAME fbx2ozz_anim_simple_dae_seymour COMMAND fbx2ozz "--file=${ozz_media_directory}/collada/seymour.dae" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/seymour_skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{
\"filename\":\"${ozz_temp_directory}/dae_animation_${CMAKE_CURRENT_LIST_LINE}.ozz\"}]}")
set_tests_properties(fbx2ozz_anim_simple_dae_seymour PROPERTIES DEPENDS fbx2ozz_skel_simple_dae_seymour)
# Uses the sample playback to test other file format and special cases.
if(TARGET sample_playback)
add_custom_command(
DEPENDS $<$<AND:$<BOOL:${ozz_build_data}>,$<BOOL:${ozz_build_fbx}>>:fbx2ozz>
"${ozz_media_directory}/collada/pab/skeleton.dae"
"${ozz_media_directory}/collada/pab/skeleton_zup.dae"
"${ozz_media_directory}/collada/pab/walk.dae"
OUTPUT "${ozz_temp_directory}/pab_skeleton_dae.ozz"
"${ozz_temp_directory}/pab_skeleton_dae_zup.ozz"
"${ozz_temp_directory}/pab_walk_dae.ozz"
COMMAND fbx2ozz
"--file=${ozz_media_directory}/collada/pab/skeleton.dae"
"--config={\"skeleton\":{\"filename\": \"${ozz_temp_directory}/pab_skeleton_dae.ozz\",\"import\":{\"enable\":true}},\"animations\":[]}}"
COMMAND fbx2ozz
"--file=${ozz_media_directory}/collada/pab/skeleton_zup.dae"
"--config={\"skeleton\":{\"filename\": \"${ozz_temp_directory}/pab_skeleton_dae_zup.ozz\",\"import\":{\"enable\":true}},\"animations\":[]}}"
COMMAND fbx2ozz
"--file=${ozz_media_directory}/collada/pab/walk.dae"
"--config={\"skeleton\":{\"filename\": \"${ozz_temp_directory}/pab_skeleton_dae_zup.ozz\"},\"animations\": [{\"filename\":\"${ozz_temp_directory}/pab_walk_dae.ozz\"}]}"
VERBATIM)
add_custom_target(BUILD_DATA_FBX_TEST ALL DEPENDS
"${ozz_temp_directory}/pab_skeleton_dae.ozz"
"${ozz_temp_directory}/pab_skeleton_dae_zup.ozz"
"${ozz_temp_directory}/pab_walk_dae.ozz"
VERBATIM)
add_test(NAME sample_playback_dae_zup COMMAND sample_playback "--skeleton=${ozz_temp_directory}/pab_skeleton_dae_zup.ozz" "--animation=${ozz_temp_directory}/pab_walk_dae.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
add_test(NAME sample_playback_dae_seymour COMMAND sample_playback "--skeleton=${ozz_media_directory}/bin/seymour_skeleton.ozz" "--animation=${ozz_media_directory}/bin/seymour_animation.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
add_test(NAME sample_playback_dae_astro_max COMMAND sample_playback "--skeleton=${ozz_media_directory}/bin/astro_max_skeleton.ozz" "--animation=${ozz_media_directory}/bin/astro_max_animation.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
add_test(NAME sample_playback_dae_astro_maya COMMAND sample_playback "--skeleton=${ozz_media_directory}/bin/astro_maya_skeleton.ozz" "--animation=${ozz_media_directory}/bin/astro_maya_animation.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
add_test(NAME sample_playback_mixed_df COMMAND sample_playback "--skeleton=${ozz_temp_directory}/pab_skeleton_dae.ozz" "--animation=${ozz_media_directory}/bin/pab_walk.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
add_test(NAME sample_playback_mixed_fd COMMAND sample_playback "--skeleton=${ozz_media_directory}/bin/pab_skeleton.ozz" "--animation=${ozz_temp_directory}/pab_walk_dae.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
endif()
# ozz_animation_fbx fuse tests
set_source_files_properties(${PROJECT_BINARY_DIR}/src_fused/ozz_animation_fbx.cc PROPERTIES GENERATED 1)
add_executable(test_fuse_ozz_animation_fbx
fuse_ozz_animation_fbx_tests
${PROJECT_BINARY_DIR}/src_fused/ozz_animation_fbx.cc)
add_dependencies(test_fuse_ozz_animation_fbx BUILD_FUSE_ozz_animation_fbx)
target_include_directories(test_fuse_ozz_animation_fbx
PUBLIC ${FBX_INCLUDE_DIRS})
target_compile_options(test_fuse_ozz_animation_fbx
PUBLIC $<$<BOOL:${W_NULL_DEREFERENCE}>:-Wno-null-dereference>
PUBLIC $<$<BOOL:${W_PRAGMA_PACK}>:-Wno-pragma-pack>)
target_link_libraries(test_fuse_ozz_animation_fbx
debug ${FBX_LIBRARIES_DEBUG}
optimized ${FBX_LIBRARIES}
ozz_animation_offline)
set_target_properties(test_fuse_ozz_animation_fbx PROPERTIES FOLDER "ozz/tests/animation_offline")
add_test(NAME test_fuse_ozz_animation_fbx COMMAND test_fuse_ozz_animation_fbx)
@@ -0,0 +1,33 @@
//----------------------------------------------------------------------------//
// //
// 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"
int main(int, const char**) {
ozz::animation::offline::fbx::FbxManagerInstance instance;
return EXIT_SUCCESS;
}
@@ -0,0 +1,56 @@
{
"skeleton":
{
"filename":"${ozz_temp_directory}/fbx_propreties_skeleton.ozz"
},
"animations":
[
{
"filename":"${ozz_temp_directory}/fbx_properties_animation_${CMAKE_CURRENT_LIST_LINE}.ozz",
"tracks":
[
{
"properties":
[
{
"filename":"${ozz_temp_directory}/fbx_properties_track1_*.ozz",
"joint_name":"*",
"property_name":"ozz_*",
"type":"float1"
},
{
"filename":"${ozz_temp_directory}/fbx_properties_track2_*.ozz",
"joint_name":"*",
"property_name":"ozz_*",
"type":"float2"
},
{
"filename":"${ozz_temp_directory}/fbx_properties_track3_*.ozz",
"joint_name":"*",
"property_name":"ozz_*",
"type":"float3"
},
{
"filename":"${ozz_temp_directory}/fbx_properties_track3p_*.ozz",
"joint_name":"*",
"property_name":"ozz_*",
"type":"point"
},
{
"filename":"${ozz_temp_directory}/fbx_properties_track3v_*.ozz",
"joint_name":"*",
"property_name":"ozz_*",
"type":"vector"
},
{
"filename":"${ozz_temp_directory}/fbx_properties_track4_*.ozz",
"joint_name":"*",
"property_name":"ozz_*",
"type":"float4"
}
]
}
]
}
]
}
@@ -0,0 +1,78 @@
if(NOT ozz_build_gltf)
return()
endif()
# Creates a file with an invalid content.
file(WRITE "${ozz_temp_directory}/bad.unk" "bad content")
file(WRITE "${ozz_temp_directory}/bad.gltf" "bad content")
file(WRITE "${ozz_temp_directory}/bad.glb" "bad content")
# Run gltf2ozz generic failing tests
#----------------------------
add_test(NAME gltf2ozz_version COMMAND gltf2ozz "--version")
add_test(NAME gltf2ozz_bad_content COMMAND gltf2ozz "--file=${ozz_temp_directory}/bad.unk" )
set_tests_properties(gltf2ozz_bad_content PROPERTIES PASS_REGULAR_EXPRESSION "Failed to import file \"${ozz_temp_directory}/bad.unk\".")
add_test(NAME gltf2ozz_bad_content_gltf COMMAND gltf2ozz "--file=${ozz_temp_directory}/bad.gltf" )
set_tests_properties(gltf2ozz_bad_content_gltf PROPERTIES PASS_REGULAR_EXPRESSION "Failed to import file \"${ozz_temp_directory}/bad.gltf\".")
add_test(NAME gltf2ozz_bad_content_glb COMMAND gltf2ozz "--file=${ozz_temp_directory}/bad.glb" )
set_tests_properties(gltf2ozz_bad_content_glb PROPERTIES PASS_REGULAR_EXPRESSION "Failed to import file \"${ozz_temp_directory}/bad.glb\".")
# Run gltf2ozz skeleton failing tests
#----------------------------
add_test(NAME gltf2ozz_skel_no_animation COMMAND gltf2ozz "--file=${ozz_media_directory}/gltf/khronos/triangle.gltf" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/gltf_skeleton_${CMAKE_CURRENT_LIST_LINE}.ozz\",\"import\":{\"enable\":true}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/gltf_should_not_exist.ozz\",\"clip\":\"*\"}]}")
set_tests_properties(gltf2ozz_skel_no_animation PROPERTIES PASS_REGULAR_EXPRESSION "No animation found.")
add_test(NAME gltf2ozz_skel_no_matching_animation COMMAND gltf2ozz "--file=${ozz_media_directory}/gltf/khronos/interpolation_test.gltf" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/gltf_skeleton_${CMAKE_CURRENT_LIST_LINE}.ozz\",\"import\":{\"enable\":true}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/gltf_should_not_exist.ozz\",\"clip\":\"won_t_match\"}]}")
set_tests_properties(gltf2ozz_skel_no_matching_animation PROPERTIES PASS_REGULAR_EXPRESSION "No matching animation found")
# Ensures nothing unexpected was outputted
#-----------------------------------------
add_test(NAME gltf2ozz_skel_output COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/gltf_should_not_exist.ozz" "${ozz_temp_directory}/gltf_should_not_exist_too.ozz")
set_tests_properties(gltf2ozz_skel_output PROPERTIES WILL_FAIL true)
set_tests_properties(gltf2ozz_skel_output PROPERTIES
DEPENDS "gltf2ozz_skel_no_skeleton")
# Run gltf2ozz skeleton passing tests
#----------------------------
add_test(NAME gltf2ozz_skel_simple COMMAND gltf2ozz "--file=${ozz_media_directory}/gltf/khronos/interpolation_test.gltf" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/gltf_interpolation_test_skeleton.ozz\",\"import\":{\"enable\":true}}}")
add_test(NAME gltf2ozz_skel_box_animated COMMAND gltf2ozz "--file=${ozz_media_directory}/gltf/khronos/box_animated.gltf" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/gltf_box_animated_skeleton.ozz\",\"import\":{\"enable\":true}}}")
add_test(NAME gltf2ozz_skel_cesium COMMAND gltf2ozz "--file=${ozz_media_directory}/gltf/khronos/cesium_man.gltf" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/gltf_cesium_man_skeleton.ozz\",\"import\":{\"enable\":true}}}")
# Run gltf2ozz animation passing tests
#----------------------------
add_test(NAME gltf2ozz_animation_multiple COMMAND gltf2ozz "--file=${ozz_media_directory}/gltf/khronos/interpolation_test.gltf" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/gltf_interpolation_test_skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/gltf_interpolation_test_*.ozz\"}]}")
set_tests_properties(gltf2ozz_animation_multiple PROPERTIES DEPENDS gltf2ozz_skel_simple)
add_test(NAME gltf2ozz_box_animation COMMAND gltf2ozz "--file=${ozz_media_directory}/gltf/khronos/box_animated.gltf" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/gltf_box_animated_skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/gltf_box_animation.ozz\"}]}")
set_tests_properties(gltf2ozz_box_animation PROPERTIES DEPENDS gltf2ozz_skel_box_animated)
add_test(NAME gltf2ozz_cesium_animation COMMAND gltf2ozz "--file=${ozz_media_directory}/gltf/khronos/cesium_man.gltf" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/gltf_cesium_man_skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/gltf_cesium_man_animation.ozz\"}]}")
set_tests_properties(gltf2ozz_cesium_animation PROPERTIES DEPENDS gltf2ozz_skel_cesium)
# Uses the sample playback to test other file format and special cases.
if(TARGET sample_playback)
add_custom_command(
DEPENDS $<$<AND:$<BOOL:${ozz_build_data}>,$<BOOL:${ozz_build_gltf}>>:gltf2ozz>
"${ozz_media_directory}/gltf/sketchfab/ruby/scene.gltf"
OUTPUT "${ozz_temp_directory}/gltf_ruby_skeleton.ozz"
"${ozz_temp_directory}/gltf_ruby_animation.ozz"
COMMAND gltf2ozz
"--file=${ozz_media_directory}/gltf/sketchfab/ruby/scene.gltf"
"--config={\"skeleton\":{\"filename\": \"${ozz_temp_directory}/gltf_ruby_skeleton.ozz\",\"import\":{\"enable\":true}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/gltf_ruby_animation.ozz\"}]}"
VERBATIM)
add_custom_target(BUILD_DATA_GLTF_TEST ALL DEPENDS
"${ozz_temp_directory}/gltf_ruby_skeleton.ozz"
"${ozz_temp_directory}/gltf_ruby_animation.ozz"
VERBATIM)
add_test(NAME sample_playback_gltf_ruby COMMAND sample_playback "--skeleton=${ozz_temp_directory}/gltf_ruby_skeleton.ozz" "--animation=${ozz_temp_directory}/gltf_ruby_animation.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
endif()
@@ -0,0 +1,147 @@
//----------------------------------------------------------------------------//
// //
// 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 "gtest/gtest.h"
#include "ozz/base/io/archive.h"
#include "ozz/base/io/stream.h"
using ozz::animation::offline::RawAnimation;
TEST(Empty, RawAnimationSerialize) {
ozz::io::MemoryStream stream;
// Streams out.
ozz::io::OArchive o(&stream, ozz::GetNativeEndianness());
RawAnimation o_animation;
o << o_animation;
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive i(&stream);
RawAnimation i_animation;
i >> i_animation;
EXPECT_FLOAT_EQ(o_animation.duration, i_animation.duration);
EXPECT_EQ(o_animation.num_tracks(), i_animation.num_tracks());
}
TEST(Filled, RawAnimationSerialize) {
RawAnimation o_animation;
o_animation.duration = 46.f;
o_animation.tracks.resize(3);
const RawAnimation::TranslationKey t_key = {
0.f, ozz::math::Float3(46.f, 93.f, 99.f)};
o_animation.tracks[0].translations.push_back(t_key);
const RawAnimation::RotationKey r_key = {
46.f, ozz::math::Quaternion(0.f, 1.f, 0.f, 0.f)};
o_animation.tracks[1].rotations.push_back(r_key);
const RawAnimation::ScaleKey s_key = {1.f,
ozz::math::Float3(93.f, 46.f, 99.f)};
o_animation.tracks[2].scales.push_back(s_key);
EXPECT_TRUE(o_animation.Validate());
EXPECT_EQ(o_animation.num_tracks(), 3);
for (int e = 0; e < 2; ++e) {
ozz::Endianness endianess = e == 0 ? ozz::kBigEndian : ozz::kLittleEndian;
ozz::io::MemoryStream stream;
// Streams out.
ozz::io::OArchive o(&stream, endianess);
o << o_animation;
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive ia(&stream);
RawAnimation i_animation;
ia >> i_animation;
EXPECT_TRUE(i_animation.Validate());
EXPECT_FLOAT_EQ(o_animation.duration, i_animation.duration);
ASSERT_EQ(o_animation.num_tracks(), i_animation.num_tracks());
for (size_t i = 0; i < o_animation.tracks.size(); ++i) {
const RawAnimation::JointTrack& o_track = o_animation.tracks[i];
const RawAnimation::JointTrack& i_track = i_animation.tracks[i];
for (size_t j = 0; j < o_track.translations.size(); ++j) {
const RawAnimation::TranslationKey& o_key = o_track.translations[j];
const RawAnimation::TranslationKey& i_key = i_track.translations[j];
EXPECT_FLOAT_EQ(o_key.time, i_key.time);
EXPECT_TRUE(Compare(o_key.value, i_key.value, 0.f));
}
for (size_t j = 0; j < o_track.rotations.size(); ++j) {
const RawAnimation::RotationKey& o_key = o_track.rotations[j];
const RawAnimation::RotationKey& i_key = i_track.rotations[j];
EXPECT_FLOAT_EQ(o_key.time, i_key.time);
EXPECT_TRUE(Compare(o_key.value, i_key.value, .1f));
}
for (size_t j = 0; j < o_track.scales.size(); ++j) {
const RawAnimation::ScaleKey& o_key = o_track.scales[j];
const RawAnimation::ScaleKey& i_key = i_track.scales[j];
EXPECT_FLOAT_EQ(o_key.time, i_key.time);
EXPECT_TRUE(Compare(o_key.value, i_key.value, 0.f));
}
}
}
}
TEST(AlreadyInitialized, RawAnimationSerialize) {
RawAnimation o_animation;
o_animation.duration = 46.f;
o_animation.tracks.resize(1);
ozz::io::MemoryStream stream;
// Streams out.
ozz::io::OArchive o(&stream);
o << o_animation;
// Streams out a second time.
o_animation.duration = 93.f;
o_animation.tracks.resize(2);
o << o_animation;
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive i(&stream);
RawAnimation i_animation;
i >> i_animation;
EXPECT_FLOAT_EQ(i_animation.duration, 46.f);
ASSERT_EQ(i_animation.num_tracks(), 1);
// A second time
i >> i_animation;
EXPECT_FLOAT_EQ(i_animation.duration, 93.f);
ASSERT_EQ(i_animation.num_tracks(), 2);
}
@@ -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/animation/offline/raw_animation.h"
#include "gtest/gtest.h"
#include "ozz/base/io/archive.h"
#include "ozz/base/io/stream.h"
#include "ozz/base/log.h"
#include "ozz/options/options.h"
OZZ_OPTIONS_DECLARE_STRING(file, "Specifies input file", "", true)
OZZ_OPTIONS_DECLARE_INT(tracks, "Number of tracks", 0, true)
OZZ_OPTIONS_DECLARE_FLOAT(duration, "Duration", 0.f, true)
OZZ_OPTIONS_DECLARE_STRING(name, "Name", "", true)
int main(int _argc, char** _argv) {
// Parses arguments.
testing::InitGoogleTest(&_argc, _argv);
ozz::options::ParseResult parse_result = ozz::options::ParseCommandLine(
_argc, _argv, "1.0",
"Test RawAnimation archive versioning retrocompatibility");
if (parse_result != ozz::options::kSuccess) {
return parse_result == ozz::options::kExitSuccess ? EXIT_SUCCESS
: EXIT_FAILURE;
}
return RUN_ALL_TESTS();
}
TEST(Versioning, RawAnimationSerialize) {
// Open the file.
const char* filename = OPTIONS_file;
ozz::io::File file(filename, "rb");
ASSERT_TRUE(file.opened());
// Open archive and test object tag.
ozz::io::IArchive archive(&file);
ASSERT_TRUE(archive.TestTag<ozz::animation::offline::RawAnimation>());
// Read the object.
ozz::animation::offline::RawAnimation animation;
archive >> animation;
// More testing
EXPECT_EQ(animation.num_tracks(), OPTIONS_tracks);
EXPECT_FLOAT_EQ(animation.duration, OPTIONS_duration);
EXPECT_STREQ(animation.name.c_str(), OPTIONS_name.value());
}
@@ -0,0 +1,274 @@
//----------------------------------------------------------------------------//
// //
// 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 "gtest/gtest.h"
#include "ozz/base/gtest_helper.h"
#include "ozz/base/maths/gtest_math_helper.h"
#include "ozz/animation/offline/raw_animation.h"
using ozz::animation::offline::RawAnimation;
TEST(SamplingTrackEmpty, Utils) {
RawAnimation::JointTrack track;
ozz::math::Transform output;
ASSERT_TRUE(ozz::animation::offline::SampleTrack(track, 0.f, &output));
EXPECT_FLOAT3_EQ(output.translation, 0.f, 0.f, 0.f);
EXPECT_QUATERNION_EQ(output.rotation, 0.f, 0.f, 0.f, 1.f);
EXPECT_FLOAT3_EQ(output.scale, 1.f, 1.f, 1.f);
}
TEST(SamplingTrackInvalid, Utils) {
// Key order
{
RawAnimation::JointTrack track;
RawAnimation::TranslationKey t0 = {.9f, ozz::math::Float3(1.f, 2.f, 4.f)};
track.translations.push_back(t0);
RawAnimation::TranslationKey t1 = {.1f, ozz::math::Float3(2.f, 4.f, 8.f)};
track.translations.push_back(t1);
ozz::math::Transform output;
EXPECT_FALSE(ozz::animation::offline::SampleTrack(track, 0.f, &output));
}
// Negative time
{
RawAnimation::JointTrack track;
RawAnimation::TranslationKey t0 = {-1.f, ozz::math::Float3(1.f, 2.f, 4.f)};
track.translations.push_back(t0);
ozz::math::Transform output;
EXPECT_FALSE(ozz::animation::offline::SampleTrack(track, 0.f, &output));
}
}
TEST(SamplingTrack, Utils) {
RawAnimation::JointTrack track;
RawAnimation::TranslationKey t0 = {.1f, ozz::math::Float3(1.f, 2.f, 4.f)};
track.translations.push_back(t0);
RawAnimation::TranslationKey t1 = {.9f, ozz::math::Float3(2.f, 4.f, 8.f)};
track.translations.push_back(t1);
RawAnimation::RotationKey r0 = {
0.f, ozz::math::Quaternion(.70710677f, 0.f, 0.f, .70710677f)};
track.rotations.push_back(r0);
RawAnimation::RotationKey r1 = {
// /!\ Negated (other hemisphepre) quaternion
.5f, -ozz::math::Quaternion(0.f, .70710677f, 0.f, .70710677f)};
track.rotations.push_back(r1);
RawAnimation::RotationKey r2 = {
1.f, ozz::math::Quaternion(0.f, .70710677f, 0.f, .70710677f)};
track.rotations.push_back(r2);
RawAnimation::ScaleKey s0 = {.5f, ozz::math::Float3(-1.f, -2.f, -4.f)};
track.scales.push_back(s0);
ozz::math::Transform output;
// t = -.1
ASSERT_TRUE(ozz::animation::offline::SampleTrack(track, -.1f, &output));
EXPECT_FLOAT3_EQ(output.translation, 1.f, 2.f, 4.f);
EXPECT_QUATERNION_EQ(output.rotation, .70710677f, 0.f, 0.f, .70710677f);
EXPECT_FLOAT3_EQ(output.scale, -1.f, -2.f, -4.f);
// t = 0
ASSERT_TRUE(ozz::animation::offline::SampleTrack(track, 0.f, &output));
EXPECT_FLOAT3_EQ(output.translation, 1.f, 2.f, 4.f);
EXPECT_QUATERNION_EQ(output.rotation, .70710677f, 0.f, 0.f, .70710677f);
EXPECT_FLOAT3_EQ(output.scale, -1.f, -2.f, -4.f);
// t = .1
ASSERT_TRUE(ozz::animation::offline::SampleTrack(track, .1f, &output));
EXPECT_FLOAT3_EQ(output.translation, 1.f, 2.f, 4.f);
EXPECT_QUATERNION_EQ(output.rotation, .6172133f, .1543033f, 0.f, .7715167f);
EXPECT_FLOAT3_EQ(output.scale, -1.f, -2.f, -4.f);
// t = .4999999
ASSERT_TRUE(ozz::animation::offline::SampleTrack(track, .4999999f, &output));
EXPECT_FLOAT3_EQ(output.translation, 1.5f, 3.f, 6.f);
EXPECT_QUATERNION_EQ(output.rotation, 0.f, .70710677f, 0.f, .70710677f);
EXPECT_FLOAT3_EQ(output.scale, -1.f, -2.f, -4.f);
// t = .5
ASSERT_TRUE(ozz::animation::offline::SampleTrack(track, .5f, &output));
EXPECT_FLOAT3_EQ(output.translation, 1.5f, 3.f, 6.f);
EXPECT_QUATERNION_EQ(output.rotation, 0.f, .70710677f, 0.f, .70710677f);
EXPECT_FLOAT3_EQ(output.scale, -1.f, -2.f, -4.f);
// t = .75
ASSERT_TRUE(ozz::animation::offline::SampleTrack(track, .75f, &output));
// Fixed up based on dot with previous quaternion
EXPECT_QUATERNION_EQ(output.rotation, 0.f, -.70710677f, 0.f, -.70710677f);
EXPECT_FLOAT3_EQ(output.scale, -1.f, -2.f, -4.f);
// t= .9
ASSERT_TRUE(ozz::animation::offline::SampleTrack(track, .9f, &output));
EXPECT_FLOAT3_EQ(output.translation, 2.f, 4.f, 8.f);
EXPECT_QUATERNION_EQ(output.rotation, 0.f, -.70710677f, 0.f, -.70710677f);
EXPECT_FLOAT3_EQ(output.scale, -1.f, -2.f, -4.f);
// t= 1.
ASSERT_TRUE(ozz::animation::offline::SampleTrack(track, 1.f, &output));
EXPECT_FLOAT3_EQ(output.translation, 2.f, 4.f, 8.f);
EXPECT_QUATERNION_EQ(output.rotation, 0.f, .70710677f, 0.f, .70710677f);
EXPECT_FLOAT3_EQ(output.scale, -1.f, -2.f, -4.f);
// t= 1.1
ASSERT_TRUE(ozz::animation::offline::SampleTrack(track, 1.1f, &output));
EXPECT_FLOAT3_EQ(output.translation, 2.f, 4.f, 8.f);
EXPECT_QUATERNION_EQ(output.rotation, 0.f, .70710677f, 0.f, .70710677f);
EXPECT_FLOAT3_EQ(output.scale, -1.f, -2.f, -4.f);
}
TEST(SamplingAnimation, Utils) {
// Building an Animation with unsorted keys fails.
RawAnimation raw_animation;
raw_animation.duration = 2.f;
raw_animation.tracks.resize(2);
RawAnimation::TranslationKey a = {.2f, ozz::math::Float3(-1.f, 0.f, 0.f)};
raw_animation.tracks[0].translations.push_back(a);
RawAnimation::TranslationKey b = {0.f, ozz::math::Float3(2.f, 0.f, 0.f)};
raw_animation.tracks[1].translations.push_back(b);
RawAnimation::TranslationKey c = {0.2f, ozz::math::Float3(6.f, 0.f, 0.f)};
raw_animation.tracks[1].translations.push_back(c);
RawAnimation::TranslationKey d = {0.4f, ozz::math::Float3(8.f, 0.f, 0.f)};
raw_animation.tracks[1].translations.push_back(d);
ozz::math::Transform output[2];
// Too small
{
ozz::math::Transform small[1];
EXPECT_FALSE(
ozz::animation::offline::SampleAnimation(raw_animation, 0.f, small));
}
// Invalid, cos track are longer than duration
{
raw_animation.duration = .1f;
EXPECT_FALSE(
ozz::animation::offline::SampleAnimation(raw_animation, 0.f, output));
raw_animation.duration = 2.f;
}
ASSERT_TRUE(
ozz::animation::offline::SampleAnimation(raw_animation, -.1f, output));
EXPECT_FLOAT3_EQ(output[0].translation, -1.f, 0.f, 0.f);
EXPECT_QUATERNION_EQ(output[0].rotation, 0.f, 0.f, 0.f, 1.f);
EXPECT_FLOAT3_EQ(output[0].scale, 1.f, 1.f, 1.f);
EXPECT_FLOAT3_EQ(output[1].translation, 2.f, 0.f, 0.f);
EXPECT_QUATERNION_EQ(output[1].rotation, 0.f, 0.f, 0.f, 1.f);
EXPECT_FLOAT3_EQ(output[1].scale, 1.f, 1.f, 1.f);
ASSERT_TRUE(
ozz::animation::offline::SampleAnimation(raw_animation, 0.f, output));
EXPECT_FLOAT3_EQ(output[0].translation, -1.f, 0.f, 0.f);
EXPECT_FLOAT3_EQ(output[1].translation, 2.f, 0.f, 0.f);
ASSERT_TRUE(
ozz::animation::offline::SampleAnimation(raw_animation, .2f, output));
EXPECT_FLOAT3_EQ(output[0].translation, -1.f, 0.f, 0.f);
EXPECT_FLOAT3_EQ(output[1].translation, 6.f, 0.f, 0.f);
ASSERT_TRUE(
ozz::animation::offline::SampleAnimation(raw_animation, .3f, output));
EXPECT_FLOAT3_EQ(output[0].translation, -1.f, 0.f, 0.f);
EXPECT_FLOAT3_EQ(output[1].translation, 7.f, 0.f, 0.f);
ASSERT_TRUE(
ozz::animation::offline::SampleAnimation(raw_animation, .4f, output));
EXPECT_FLOAT3_EQ(output[0].translation, -1.f, 0.f, 0.f);
EXPECT_FLOAT3_EQ(output[1].translation, 8.f, 0.f, 0.f);
ASSERT_TRUE(
ozz::animation::offline::SampleAnimation(raw_animation, 2.f, output));
EXPECT_FLOAT3_EQ(output[0].translation, -1.f, 0.f, 0.f);
EXPECT_FLOAT3_EQ(output[1].translation, 8.f, 0.f, 0.f);
ASSERT_TRUE(
ozz::animation::offline::SampleAnimation(raw_animation, 3.f, output));
EXPECT_FLOAT3_EQ(output[0].translation, -1.f, 0.f, 0.f);
EXPECT_FLOAT3_EQ(output[1].translation, 8.f, 0.f, 0.f);
}
TEST(FixedRateSamplingTime, Utils) {
{ // From 0
ozz::animation::offline::FixedRateSamplingTime it(1.f, 30.f);
EXPECT_EQ(it.num_keys(), 31u);
EXPECT_EQ(it.time(0), 0.f);
EXPECT_FLOAT_EQ(it.time(1), 1.f / 30.f);
EXPECT_FLOAT_EQ(it.time(2), 2.f / 30.f);
EXPECT_FLOAT_EQ(it.time(29), 29.f / 30.f);
EXPECT_EQ(it.time(30), 1.f);
EXPECT_ASSERTION(it.time(31), "_key < num_keys");
}
{ // Offset
ozz::animation::offline::FixedRateSamplingTime it(3.f, 100.f);
EXPECT_EQ(it.num_keys(), 301u);
EXPECT_EQ(it.time(0), 0.f);
EXPECT_FLOAT_EQ(it.time(1), 1.f / 100.f);
EXPECT_FLOAT_EQ(it.time(2), 2.f / 100.f);
EXPECT_FLOAT_EQ(it.time(299), 299.f / 100.f);
EXPECT_EQ(it.time(300), 3.f);
}
{ // Ceil
ozz::animation::offline::FixedRateSamplingTime it(1.001f, 30.f);
EXPECT_EQ(it.num_keys(), 32u);
EXPECT_EQ(it.time(0), 0.f);
EXPECT_FLOAT_EQ(it.time(1), 1.f / 30.f);
EXPECT_FLOAT_EQ(it.time(2), 2.f / 30.f);
EXPECT_FLOAT_EQ(it.time(29), 29.f / 30.f);
EXPECT_FLOAT_EQ(it.time(30), 1.f);
EXPECT_EQ(it.time(31), 1.001f);
}
{ // Long
ozz::animation::offline::FixedRateSamplingTime it(1000.f, 30.f);
EXPECT_EQ(it.num_keys(), 30001u);
EXPECT_EQ(it.time(0), 0.f);
EXPECT_FLOAT_EQ(it.time(1), 1.f / 30.f);
EXPECT_FLOAT_EQ(it.time(2), 2.f / 30.f);
EXPECT_FLOAT_EQ(it.time(29999), 29999.f / 30.f);
EXPECT_EQ(it.time(30000), 1000.f);
}
}
@@ -0,0 +1,182 @@
//----------------------------------------------------------------------------//
// //
// 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 "gtest/gtest.h"
#include "ozz/base/io/archive.h"
#include "ozz/base/io/stream.h"
using ozz::animation::offline::RawSkeleton;
TEST(Empty, RawSkeletonSerialize) {
ozz::io::MemoryStream stream;
// Streams out.
ozz::io::OArchive o(&stream, ozz::GetNativeEndianness());
RawSkeleton o_skeleton;
o << o_skeleton;
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive i(&stream);
RawSkeleton i_skeleton;
i >> i_skeleton;
EXPECT_EQ(o_skeleton.num_joints(), i_skeleton.num_joints());
}
TEST(Filled, RawSkeletonSerialize) {
/* Builds output skeleton.
4 joints
*
|
root
/ \
j0 j1
|
j2
*/
RawSkeleton o_skeleton;
o_skeleton.roots.resize(1);
RawSkeleton::Joint& root = o_skeleton.roots[0];
root.name = "root";
root.transform = ozz::math::Transform::identity();
root.children.resize(2);
root.children[0].name = "j0";
root.children[0].transform = ozz::math::Transform::identity();
root.children[0].transform.translation.x = 46.f;
root.children[1].name = "j1";
root.children[1].transform = ozz::math::Transform::identity();
root.children[1].transform.scale.y = 99.f;
root.children[0].children.resize(1);
root.children[0].children[0].name = "j2";
root.children[0].children[0].transform = ozz::math::Transform::identity();
root.children[0].children[0].transform.rotation =
ozz::math::Quaternion(0.f, 0.f, 1.f, 0.f);
EXPECT_TRUE(o_skeleton.Validate());
EXPECT_EQ(o_skeleton.num_joints(), 4);
for (int e = 0; e < 2; ++e) {
ozz::Endianness endianess = e == 0 ? ozz::kBigEndian : ozz::kLittleEndian;
ozz::io::MemoryStream stream;
// Streams out.
ozz::io::OArchive o(&stream, endianess);
o << o_skeleton;
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive i(&stream);
RawSkeleton i_skeleton;
i >> i_skeleton;
EXPECT_TRUE(i_skeleton.Validate());
ASSERT_EQ(o_skeleton.num_joints(), i_skeleton.num_joints());
// Compares skeletons joint's name.
EXPECT_STREQ(o_skeleton.roots[0].name.c_str(),
i_skeleton.roots[0].name.c_str());
EXPECT_STREQ(o_skeleton.roots[0].children[0].name.c_str(),
i_skeleton.roots[0].children[0].name.c_str());
EXPECT_STREQ(o_skeleton.roots[0].children[0].children[0].name.c_str(),
i_skeleton.roots[0].children[0].children[0].name.c_str());
EXPECT_STREQ(o_skeleton.roots[0].children[1].name.c_str(),
i_skeleton.roots[0].children[1].name.c_str());
// Compares skeletons joint's transform.
EXPECT_TRUE(Compare(o_skeleton.roots[0].transform.translation,
i_skeleton.roots[0].transform.translation, 0.f));
EXPECT_TRUE(Compare(o_skeleton.roots[0].transform.rotation,
i_skeleton.roots[0].transform.rotation, 0.f));
EXPECT_TRUE(Compare(o_skeleton.roots[0].transform.scale,
i_skeleton.roots[0].transform.scale, 0.f));
EXPECT_TRUE(Compare(o_skeleton.roots[0].children[0].transform.translation,
i_skeleton.roots[0].children[0].transform.translation,
0.f));
EXPECT_TRUE(Compare(o_skeleton.roots[0].children[0].transform.rotation,
i_skeleton.roots[0].children[0].transform.rotation,
0.f));
EXPECT_TRUE(Compare(o_skeleton.roots[0].children[0].transform.scale,
i_skeleton.roots[0].children[0].transform.scale, 0.f));
EXPECT_TRUE(Compare(
o_skeleton.roots[0].children[0].children[0].transform.translation,
i_skeleton.roots[0].children[0].children[0].transform.translation,
0.f));
EXPECT_TRUE(Compare(
o_skeleton.roots[0].children[0].children[0].transform.rotation,
i_skeleton.roots[0].children[0].children[0].transform.rotation, 0.f));
EXPECT_TRUE(Compare(
o_skeleton.roots[0].children[0].children[0].transform.scale,
i_skeleton.roots[0].children[0].children[0].transform.scale, 0.f));
EXPECT_TRUE(Compare(o_skeleton.roots[0].children[1].transform.translation,
i_skeleton.roots[0].children[1].transform.translation,
0.f));
EXPECT_TRUE(Compare(o_skeleton.roots[0].children[1].transform.rotation,
i_skeleton.roots[0].children[1].transform.rotation,
0.f));
EXPECT_TRUE(Compare(o_skeleton.roots[0].children[1].transform.scale,
i_skeleton.roots[0].children[1].transform.scale, 0.f));
}
}
TEST(AlreadyInitialized, RawSkeletonSerialize) {
RawSkeleton o_skeleton;
o_skeleton.roots.resize(1);
ozz::io::MemoryStream stream;
// Streams out.
ozz::io::OArchive o(&stream);
o << o_skeleton;
// Streams out a second time.
o_skeleton.roots.resize(2);
o << o_skeleton;
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive i(&stream);
RawSkeleton i_skeleton;
i >> i_skeleton;
ASSERT_EQ(i_skeleton.num_joints(), 1);
// A second time
i >> i_skeleton;
ASSERT_EQ(i_skeleton.num_joints(), 2);
}
@@ -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/animation/offline/raw_skeleton.h"
#include "gtest/gtest.h"
#include "ozz/base/io/archive.h"
#include "ozz/base/io/stream.h"
#include "ozz/base/log.h"
#include "ozz/options/options.h"
OZZ_OPTIONS_DECLARE_STRING(file, "Specifies input file", "", true)
OZZ_OPTIONS_DECLARE_INT(joints, "Number of joints", 0, true)
OZZ_OPTIONS_DECLARE_STRING(root_name, "Name of the root joint", "", true)
int main(int _argc, char** _argv) {
// Parses arguments.
testing::InitGoogleTest(&_argc, _argv);
ozz::options::ParseResult parse_result = ozz::options::ParseCommandLine(
_argc, _argv, "1.0",
"Test RawSkeleton archive versioning retrocompatibility");
if (parse_result != ozz::options::kSuccess) {
return parse_result == ozz::options::kExitSuccess ? EXIT_SUCCESS
: EXIT_FAILURE;
}
return RUN_ALL_TESTS();
}
TEST(Versioning, RawSkeletonSerialize) {
// Open the file.
const char* filename = OPTIONS_file;
ozz::io::File file(filename, "rb");
ASSERT_TRUE(file.opened());
// Open archive and test object tag.
ozz::io::IArchive archive(&file);
ASSERT_TRUE(archive.TestTag<ozz::animation::offline::RawSkeleton>());
// Read the object.
ozz::animation::offline::RawSkeleton skeleton;
archive >> skeleton;
// More testing
EXPECT_EQ(skeleton.num_joints(), OPTIONS_joints);
if (skeleton.num_joints()) {
EXPECT_STREQ(skeleton.roots[0].name.c_str(), OPTIONS_root_name);
}
}
@@ -0,0 +1,309 @@
//----------------------------------------------------------------------------//
// //
// 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 "gtest/gtest.h"
#include "ozz/base/io/archive.h"
#include "ozz/base/io/stream.h"
#include "ozz/base/maths/gtest_math_helper.h"
using ozz::animation::offline::RawFloatTrack;
using ozz::animation::offline::RawFloat2Track;
using ozz::animation::offline::RawFloat3Track;
using ozz::animation::offline::RawFloat4Track;
using ozz::animation::offline::RawQuaternionTrack;
using ozz::animation::offline::RawTrackInterpolation;
TEST(Empty, RawTrackSerialize) {
ozz::io::MemoryStream stream;
// Streams out.
ozz::io::OArchive o(&stream, ozz::GetNativeEndianness());
RawFloatTrack o_track;
o << o_track;
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive i(&stream);
RawFloatTrack i_track;
i >> i_track;
EXPECT_EQ(o_track.keyframes.size(), i_track.keyframes.size());
EXPECT_STREQ(o_track.name.c_str(), i_track.name.c_str());
}
TEST(Filled, RawAnimationSerialize) {
RawFloatTrack o_track;
o_track.name = "test track";
const RawFloatTrack::Keyframe first_key = {RawTrackInterpolation::kLinear,
.5f, 46.f};
o_track.keyframes.push_back(first_key);
const RawFloatTrack::Keyframe second_key = {RawTrackInterpolation::kLinear,
.7f, 0.f};
o_track.keyframes.push_back(second_key);
EXPECT_TRUE(o_track.Validate());
EXPECT_EQ(o_track.keyframes.size(), 2u);
for (int e = 0; e < 2; ++e) {
ozz::Endianness endianess = e == 0 ? ozz::kBigEndian : ozz::kLittleEndian;
ozz::io::MemoryStream stream;
// Streams out.
ozz::io::OArchive o(&stream, endianess);
o << o_track;
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive ia(&stream);
RawFloatTrack i_track;
ia >> i_track;
EXPECT_TRUE(i_track.Validate());
ASSERT_EQ(o_track.keyframes.size(), i_track.keyframes.size());
EXPECT_STREQ(o_track.name.c_str(), i_track.name.c_str());
for (size_t i = 0; i < o_track.keyframes.size(); ++i) {
const RawFloatTrack::Keyframe& o_key = o_track.keyframes[i];
const RawFloatTrack::Keyframe& i_key = i_track.keyframes[i];
EXPECT_EQ(o_key.interpolation, i_key.interpolation);
EXPECT_FLOAT_EQ(o_key.ratio, i_key.ratio);
EXPECT_EQ(o_key.value, i_key.value);
}
}
}
TEST(AlreadyInitialized, RawAnimationSerialize) {
RawFloatTrack o_track;
ozz::io::MemoryStream stream;
// Streams out.
ozz::io::OArchive o(&stream);
o_track.name = "test track";
o << o_track;
// Streams out a second ratio.
o_track.keyframes.resize(2);
o_track.name = "test track 2";
o << o_track;
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive i(&stream);
RawFloatTrack i_track;
i >> i_track;
ASSERT_EQ(i_track.keyframes.size(), 0u);
// A second ratio
i >> i_track;
ASSERT_EQ(i_track.keyframes.size(), 2u);
EXPECT_STREQ(o_track.name.c_str(), i_track.name.c_str());
}
TEST(Float2, RawAnimationSerialize) {
RawFloat2Track o_track;
o_track.name = "test track";
const RawFloat2Track::Keyframe first_key = {
RawTrackInterpolation::kLinear, .5f, ozz::math::Float2(46.f, 99.f)};
o_track.keyframes.push_back(first_key);
const RawFloat2Track::Keyframe second_key = {
RawTrackInterpolation::kLinear, .7f, ozz::math::Float2(16.f, 93.f)};
o_track.keyframes.push_back(second_key);
EXPECT_TRUE(o_track.Validate());
EXPECT_EQ(o_track.keyframes.size(), 2u);
for (int e = 0; e < 2; ++e) {
ozz::Endianness endianess = e == 0 ? ozz::kBigEndian : ozz::kLittleEndian;
ozz::io::MemoryStream stream;
// Streams out.
ozz::io::OArchive o(&stream, endianess);
o << o_track;
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive ia(&stream);
RawFloat2Track i_track;
ia >> i_track;
EXPECT_TRUE(i_track.Validate());
ASSERT_EQ(o_track.keyframes.size(), i_track.keyframes.size());
EXPECT_STREQ(o_track.name.c_str(), i_track.name.c_str());
for (size_t i = 0; i < o_track.keyframes.size(); ++i) {
const RawFloat2Track::Keyframe& o_key = o_track.keyframes[i];
const RawFloat2Track::Keyframe& i_key = i_track.keyframes[i];
EXPECT_EQ(o_key.interpolation, i_key.interpolation);
EXPECT_FLOAT_EQ(o_key.ratio, i_key.ratio);
EXPECT_EQ(o_key.value, i_key.value);
}
}
}
TEST(Float3, RawAnimationSerialize) {
RawFloat3Track o_track;
const RawFloat3Track::Keyframe first_key = {
RawTrackInterpolation::kLinear, .5f, ozz::math::Float3(46.f, 99.f, 25.f)};
o_track.keyframes.push_back(first_key);
const RawFloat3Track::Keyframe second_key = {
RawTrackInterpolation::kLinear, .7f, ozz::math::Float3(16.f, 93.f, 4.f)};
o_track.keyframes.push_back(second_key);
EXPECT_TRUE(o_track.Validate());
EXPECT_EQ(o_track.keyframes.size(), 2u);
for (int e = 0; e < 2; ++e) {
ozz::Endianness endianess = e == 0 ? ozz::kBigEndian : ozz::kLittleEndian;
ozz::io::MemoryStream stream;
// Streams out.
ozz::io::OArchive o(&stream, endianess);
o << o_track;
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive ia(&stream);
RawFloat3Track i_track;
ia >> i_track;
EXPECT_TRUE(i_track.Validate());
ASSERT_EQ(o_track.keyframes.size(), i_track.keyframes.size());
for (size_t i = 0; i < o_track.keyframes.size(); ++i) {
const RawFloat3Track::Keyframe& o_key = o_track.keyframes[i];
const RawFloat3Track::Keyframe& i_key = i_track.keyframes[i];
EXPECT_EQ(o_key.interpolation, i_key.interpolation);
EXPECT_FLOAT_EQ(o_key.ratio, i_key.ratio);
EXPECT_EQ(o_key.value, i_key.value);
}
}
}
TEST(Float4, RawAnimationSerialize) {
RawFloat4Track o_track;
const RawFloat4Track::Keyframe first_key = {
RawTrackInterpolation::kLinear, .5f,
ozz::math::Float4(46.f, 99.f, 25.f, 5.f)};
o_track.keyframes.push_back(first_key);
const RawFloat4Track::Keyframe second_key = {
RawTrackInterpolation::kLinear, .7f,
ozz::math::Float4(16.f, 93.f, 4.f, 46.f)};
o_track.keyframes.push_back(second_key);
EXPECT_TRUE(o_track.Validate());
EXPECT_EQ(o_track.keyframes.size(), 2u);
for (int e = 0; e < 2; ++e) {
ozz::Endianness endianess = e == 0 ? ozz::kBigEndian : ozz::kLittleEndian;
ozz::io::MemoryStream stream;
// Streams out.
ozz::io::OArchive o(&stream, endianess);
o << o_track;
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive ia(&stream);
RawFloat4Track i_track;
ia >> i_track;
EXPECT_TRUE(i_track.Validate());
ASSERT_EQ(o_track.keyframes.size(), i_track.keyframes.size());
for (size_t i = 0; i < o_track.keyframes.size(); ++i) {
const RawFloat4Track::Keyframe& o_key = o_track.keyframes[i];
const RawFloat4Track::Keyframe& i_key = i_track.keyframes[i];
EXPECT_EQ(o_key.interpolation, i_key.interpolation);
EXPECT_FLOAT_EQ(o_key.ratio, i_key.ratio);
EXPECT_EQ(o_key.value, i_key.value);
}
}
}
TEST(Quaternion, RawAnimationSerialize) {
RawQuaternionTrack o_track;
const RawQuaternionTrack::Keyframe first_key = {
RawTrackInterpolation::kLinear, .5f,
ozz::math::Quaternion(0.f, .70710677f, 0.f, .70710677f)};
o_track.keyframes.push_back(first_key);
const RawQuaternionTrack::Keyframe second_key = {
RawTrackInterpolation::kLinear, .7f,
ozz::math::Quaternion(.6172133f, .1543033f, 0.f, .7715167f)};
o_track.keyframes.push_back(second_key);
EXPECT_TRUE(o_track.Validate());
EXPECT_EQ(o_track.keyframes.size(), 2u);
for (int e = 0; e < 2; ++e) {
ozz::Endianness endianess = e == 0 ? ozz::kBigEndian : ozz::kLittleEndian;
ozz::io::MemoryStream stream;
// Streams out.
ozz::io::OArchive o(&stream, endianess);
o << o_track;
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive ia(&stream);
RawQuaternionTrack i_track;
ia >> i_track;
EXPECT_TRUE(i_track.Validate());
ASSERT_EQ(o_track.keyframes.size(), i_track.keyframes.size());
for (size_t i = 0; i < o_track.keyframes.size(); ++i) {
const RawQuaternionTrack::Keyframe& o_key = o_track.keyframes[i];
const RawQuaternionTrack::Keyframe& i_key = i_track.keyframes[i];
EXPECT_EQ(o_key.interpolation, i_key.interpolation);
EXPECT_FLOAT_EQ(o_key.ratio, i_key.ratio);
EXPECT_EQ(o_key.value, i_key.value);
}
}
}
@@ -0,0 +1,709 @@
//----------------------------------------------------------------------------//
// //
// 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/offline/skeleton_builder.h"
#include <cstring>
#include "gtest/gtest.h"
#include "ozz/animation/runtime/skeleton.h"
#include "ozz/base/maths/simd_math.h"
#include "ozz/base/maths/soa_transform.h"
#include "ozz/base/memory/unique_ptr.h"
#include "ozz/base/maths/gtest_math_helper.h"
using ozz::animation::Skeleton;
using ozz::animation::offline::RawSkeleton;
using ozz::animation::offline::SkeletonBuilder;
TEST(Error, SkeletonBuilder) {
// Instantiates a builder objects with default parameters.
SkeletonBuilder builder;
// The default raw skeleton is valid. It has no joint.
{
RawSkeleton raw_skeleton;
EXPECT_TRUE(raw_skeleton.Validate());
EXPECT_EQ(raw_skeleton.num_joints(), 0);
ozz::unique_ptr<Skeleton> skeleton(builder(raw_skeleton));
ASSERT_TRUE(skeleton);
EXPECT_EQ(skeleton->num_joints(), 0);
}
}
namespace {
// Tester object that ensure order (depth-first) of joints iteration.
class RawSkeletonIterateDFTester {
public:
RawSkeletonIterateDFTester() : num_joint_(0) {}
void operator()(const RawSkeleton::Joint& _current,
const RawSkeleton::Joint* _parent) {
switch (num_joint_) {
case 0: {
EXPECT_TRUE(_current.name == "root" && _parent == nullptr);
break;
}
case 1: {
EXPECT_TRUE(_current.name == "j0" && _parent->name == "root");
break;
}
case 2: {
EXPECT_TRUE(_current.name == "j1" && _parent->name == "root");
break;
}
case 3: {
EXPECT_TRUE(_current.name == "j2" && _parent->name == "j1");
break;
}
case 4: {
EXPECT_TRUE(_current.name == "j3" && _parent->name == "j1");
break;
}
case 5: {
EXPECT_TRUE(_current.name == "j4" && _parent->name == "root");
break;
}
default: {
EXPECT_TRUE(false);
break;
}
}
++num_joint_;
}
private:
int num_joint_;
};
// Tester object that ensure order (breadth-first) of joints iteration.
class RawSkeletonIterateBFTester {
public:
RawSkeletonIterateBFTester() : num_joint_(0) {}
void operator()(const RawSkeleton::Joint& _current,
const RawSkeleton::Joint* _parent) {
switch (num_joint_) {
case 0: {
EXPECT_TRUE(_current.name == "root" && _parent == nullptr);
break;
}
case 1: {
EXPECT_TRUE(_current.name == "j0" && _parent->name == "root");
break;
}
case 2: {
EXPECT_TRUE(_current.name == "j1" && _parent->name == "root");
break;
}
case 3: {
EXPECT_TRUE(_current.name == "j4" && _parent->name == "root");
break;
}
case 4: {
EXPECT_TRUE(_current.name == "j2" && _parent->name == "j1");
break;
}
case 5: {
EXPECT_TRUE(_current.name == "j3" && _parent->name == "j1");
break;
}
default: {
EXPECT_TRUE(false);
break;
}
}
++num_joint_;
}
private:
int num_joint_;
};
} // namespace
TEST(Iterate, SkeletonBuilder) {
/*
5 joints
*
|
root
/ | \
j0 j1 j4
/ \
j2 j3
*/
RawSkeleton raw_skeleton;
raw_skeleton.roots.resize(1);
RawSkeleton::Joint& root = raw_skeleton.roots[0];
root.name = "root";
root.children.resize(3);
root.children[0].name = "j0";
root.children[1].name = "j1";
root.children[2].name = "j4";
root.children[1].children.resize(2);
root.children[1].children[0].name = "j2";
root.children[1].children[1].name = "j3";
EXPECT_TRUE(raw_skeleton.Validate());
EXPECT_EQ(raw_skeleton.num_joints(), 6);
IterateJointsDF(raw_skeleton, RawSkeletonIterateDFTester());
IterateJointsBF(raw_skeleton, RawSkeletonIterateBFTester());
}
TEST(Build, SkeletonBuilder) {
// Instantiates a builder objects with default parameters.
SkeletonBuilder builder;
// 1 joint: the root.
{
RawSkeleton raw_skeleton;
raw_skeleton.roots.resize(1);
RawSkeleton::Joint& root = raw_skeleton.roots[0];
root.name = "root";
EXPECT_TRUE(raw_skeleton.Validate());
EXPECT_EQ(raw_skeleton.num_joints(), 1);
ozz::unique_ptr<Skeleton> skeleton(builder(raw_skeleton));
ASSERT_TRUE(skeleton);
EXPECT_EQ(skeleton->num_joints(), 1);
EXPECT_EQ(skeleton->joint_parents()[0], Skeleton::kNoParent);
}
/*
2 joints
*
|
j0
|
j1
*/
{
RawSkeleton raw_skeleton;
raw_skeleton.roots.resize(1);
RawSkeleton::Joint& root = raw_skeleton.roots[0];
root.name = "j0";
root.children.resize(1);
root.children[0].name = "j1";
EXPECT_TRUE(raw_skeleton.Validate());
EXPECT_EQ(raw_skeleton.num_joints(), 2);
ozz::unique_ptr<Skeleton> skeleton(builder(raw_skeleton));
ASSERT_TRUE(skeleton);
EXPECT_EQ(skeleton->num_joints(), 2);
for (int i = 0; i < skeleton->num_joints(); ++i) {
const int parent_index = skeleton->joint_parents()[i];
if (std::strcmp(skeleton->joint_names()[i], "j0") == 0) {
EXPECT_EQ(parent_index, Skeleton::kNoParent);
} else if (std::strcmp(skeleton->joint_names()[i], "j1") == 0) {
EXPECT_TRUE(std::strcmp(skeleton->joint_names()[parent_index], "j0") ==
0);
} else {
EXPECT_TRUE(false);
}
}
}
/*
3 joints
*
|
j0
/ \
j1 j2
*/
{
RawSkeleton raw_skeleton;
raw_skeleton.roots.resize(1);
RawSkeleton::Joint& root = raw_skeleton.roots[0];
root.name = "j0";
root.children.resize(2);
root.children[0].name = "j1";
root.children[1].name = "j2";
EXPECT_TRUE(raw_skeleton.Validate());
EXPECT_EQ(raw_skeleton.num_joints(), 3);
ozz::unique_ptr<Skeleton> skeleton(builder(raw_skeleton));
ASSERT_TRUE(skeleton);
EXPECT_EQ(skeleton->num_joints(), 3);
for (int i = 0; i < skeleton->num_joints(); ++i) {
const int parent_index = skeleton->joint_parents()[i];
if (std::strcmp(skeleton->joint_names()[i], "j0") == 0) {
EXPECT_EQ(parent_index, Skeleton::kNoParent);
} else if (std::strcmp(skeleton->joint_names()[i], "j1") == 0) {
EXPECT_STREQ(skeleton->joint_names()[parent_index], "j0");
} else if (std::strcmp(skeleton->joint_names()[i], "j2") == 0) {
EXPECT_STREQ(skeleton->joint_names()[parent_index], "j0");
} else {
EXPECT_TRUE(false);
}
}
}
/*
4 joints
*
|
j0
/ \
j1 j3
|
j2
*/
{
RawSkeleton raw_skeleton;
raw_skeleton.roots.resize(1);
RawSkeleton::Joint& root = raw_skeleton.roots[0];
root.name = "j0";
root.children.resize(2);
root.children[0].name = "j1";
root.children[1].name = "j3";
root.children[0].children.resize(1);
root.children[0].children[0].name = "j2";
EXPECT_TRUE(raw_skeleton.Validate());
EXPECT_EQ(raw_skeleton.num_joints(), 4);
ozz::unique_ptr<Skeleton> skeleton(builder(raw_skeleton));
ASSERT_TRUE(skeleton);
EXPECT_EQ(skeleton->num_joints(), 4);
for (int i = 0; i < skeleton->num_joints(); ++i) {
const int parent_index = skeleton->joint_parents()[i];
if (std::strcmp(skeleton->joint_names()[i], "j0") == 0) {
EXPECT_EQ(parent_index, Skeleton::kNoParent);
} else if (std::strcmp(skeleton->joint_names()[i], "j1") == 0) {
EXPECT_STREQ(skeleton->joint_names()[parent_index], "j0");
} else if (std::strcmp(skeleton->joint_names()[i], "j2") == 0) {
EXPECT_STREQ(skeleton->joint_names()[parent_index], "j1");
} else if (std::strcmp(skeleton->joint_names()[i], "j3") == 0) {
EXPECT_STREQ(skeleton->joint_names()[parent_index], "j0");
} else {
EXPECT_TRUE(false);
}
}
}
/*
4 joints
*
|
j0
/ \
j1 j2
|
j3
*/
{
RawSkeleton raw_skeleton;
raw_skeleton.roots.resize(1);
RawSkeleton::Joint& root = raw_skeleton.roots[0];
root.name = "j0";
root.children.resize(2);
root.children[0].name = "j1";
root.children[1].name = "j2";
root.children[1].children.resize(1);
root.children[1].children[0].name = "j3";
EXPECT_TRUE(raw_skeleton.Validate());
EXPECT_EQ(raw_skeleton.num_joints(), 4);
ozz::unique_ptr<Skeleton> skeleton(builder(raw_skeleton));
ASSERT_TRUE(skeleton);
EXPECT_EQ(skeleton->num_joints(), 4);
for (int i = 0; i < skeleton->num_joints(); ++i) {
const int parent_index = skeleton->joint_parents()[i];
if (std::strcmp(skeleton->joint_names()[i], "j0") == 0) {
EXPECT_EQ(parent_index, Skeleton::kNoParent);
} else if (std::strcmp(skeleton->joint_names()[i], "j1") == 0) {
EXPECT_STREQ(skeleton->joint_names()[parent_index], "j0");
} else if (std::strcmp(skeleton->joint_names()[i], "j2") == 0) {
EXPECT_STREQ(skeleton->joint_names()[parent_index], "j0");
} else if (std::strcmp(skeleton->joint_names()[i], "j3") == 0) {
EXPECT_STREQ(skeleton->joint_names()[parent_index], "j2");
} else {
EXPECT_TRUE(false);
}
}
}
/*
5 joints
*
|
j0
/ \
j1 j2
/ \
j3 j4
*/
{
RawSkeleton raw_skeleton;
raw_skeleton.roots.resize(1);
RawSkeleton::Joint& root = raw_skeleton.roots[0];
root.name = "j0";
root.children.resize(2);
root.children[0].name = "j1";
root.children[1].name = "j2";
root.children[1].children.resize(2);
root.children[1].children[0].name = "j3";
root.children[1].children[1].name = "j4";
EXPECT_TRUE(raw_skeleton.Validate());
EXPECT_EQ(raw_skeleton.num_joints(), 5);
ozz::unique_ptr<Skeleton> skeleton(builder(raw_skeleton));
ASSERT_TRUE(skeleton);
EXPECT_EQ(skeleton->num_joints(), 5);
for (int i = 0; i < skeleton->num_joints(); ++i) {
const int parent_index = skeleton->joint_parents()[i];
if (std::strcmp(skeleton->joint_names()[i], "j0") == 0) {
EXPECT_EQ(parent_index, Skeleton::kNoParent);
} else if (std::strcmp(skeleton->joint_names()[i], "j1") == 0) {
EXPECT_STREQ(skeleton->joint_names()[parent_index], "j0");
} else if (std::strcmp(skeleton->joint_names()[i], "j2") == 0) {
EXPECT_STREQ(skeleton->joint_names()[parent_index], "j0");
} else if (std::strcmp(skeleton->joint_names()[i], "j3") == 0) {
EXPECT_STREQ(skeleton->joint_names()[parent_index], "j2");
} else if (std::strcmp(skeleton->joint_names()[i], "j4") == 0) {
EXPECT_STREQ(skeleton->joint_names()[parent_index], "j2");
} else {
EXPECT_TRUE(false);
}
}
}
/*
6 joints
*
|
j0
/ \
j1 j3
| / \
j2 j4 j5
*/
{
RawSkeleton raw_skeleton;
raw_skeleton.roots.resize(1);
RawSkeleton::Joint& root = raw_skeleton.roots[0];
root.name = "j0";
root.children.resize(2);
root.children[0].name = "j1";
root.children[1].name = "j3";
root.children[0].children.resize(1);
root.children[0].children[0].name = "j2";
root.children[1].children.resize(2);
root.children[1].children[0].name = "j4";
root.children[1].children[1].name = "j5";
EXPECT_TRUE(raw_skeleton.Validate());
EXPECT_EQ(raw_skeleton.num_joints(), 6);
ozz::unique_ptr<Skeleton> skeleton(builder(raw_skeleton));
ASSERT_TRUE(skeleton);
EXPECT_EQ(skeleton->num_joints(), 6);
for (int i = 0; i < skeleton->num_joints(); ++i) {
const int parent_index = skeleton->joint_parents()[i];
if (std::strcmp(skeleton->joint_names()[i], "j0") == 0) {
EXPECT_EQ(parent_index, Skeleton::kNoParent);
} else if (std::strcmp(skeleton->joint_names()[i], "j1") == 0) {
EXPECT_STREQ(skeleton->joint_names()[parent_index], "j0");
} else if (std::strcmp(skeleton->joint_names()[i], "j2") == 0) {
EXPECT_STREQ(skeleton->joint_names()[parent_index], "j1");
} else if (std::strcmp(skeleton->joint_names()[i], "j3") == 0) {
EXPECT_STREQ(skeleton->joint_names()[parent_index], "j0");
} else if (std::strcmp(skeleton->joint_names()[i], "j4") == 0) {
EXPECT_STREQ(skeleton->joint_names()[parent_index], "j3");
} else if (std::strcmp(skeleton->joint_names()[i], "j5") == 0) {
EXPECT_STREQ(skeleton->joint_names()[parent_index], "j3");
} else {
EXPECT_TRUE(false);
}
}
// Skeleton joins should be sorted "per parent" and maintain original
// children joint order.
EXPECT_EQ(skeleton->joint_parents()[0], Skeleton::kNoParent);
EXPECT_STREQ(skeleton->joint_names()[0], "j0");
EXPECT_EQ(skeleton->joint_parents()[1], 0);
EXPECT_STREQ(skeleton->joint_names()[1], "j1");
EXPECT_EQ(skeleton->joint_parents()[2], 1);
EXPECT_STREQ(skeleton->joint_names()[2], "j2");
EXPECT_EQ(skeleton->joint_parents()[3], 0);
EXPECT_STREQ(skeleton->joint_names()[3], "j3");
EXPECT_EQ(skeleton->joint_parents()[4], 3);
EXPECT_STREQ(skeleton->joint_names()[4], "j4");
EXPECT_EQ(skeleton->joint_parents()[5], 3);
EXPECT_STREQ(skeleton->joint_names()[5], "j5");
}
}
TEST(JointOrder, SkeletonBuilder) {
// Instantiates a builder objects with default parameters.
SkeletonBuilder builder;
/*
7 joints
*
|
j0
/ | \
j1 j3 j7
| / \
j2 j4 j5
|
j6
*/
RawSkeleton raw_skeleton;
raw_skeleton.roots.resize(1);
RawSkeleton::Joint& root = raw_skeleton.roots[0];
root.name = "j0";
root.children.resize(3);
root.children[0].name = "j1";
root.children[1].name = "j3";
root.children[2].name = "j7";
root.children[0].children.resize(1);
root.children[0].children[0].name = "j2";
root.children[1].children.resize(2);
root.children[1].children[0].name = "j4";
root.children[1].children[1].name = "j5";
root.children[1].children[1].children.resize(1);
root.children[1].children[1].children[0].name = "j6";
EXPECT_TRUE(raw_skeleton.Validate());
EXPECT_EQ(raw_skeleton.num_joints(), 8);
ozz::unique_ptr<Skeleton> skeleton(builder(raw_skeleton));
ASSERT_TRUE(skeleton);
EXPECT_EQ(skeleton->num_joints(), 8);
// Skeleton joints should be sorted "per parent" and maintain original
// children joint order.
EXPECT_EQ(skeleton->joint_parents()[0], Skeleton::kNoParent);
EXPECT_STREQ(skeleton->joint_names()[0], "j0");
EXPECT_EQ(skeleton->joint_parents()[1], 0);
EXPECT_STREQ(skeleton->joint_names()[1], "j1");
EXPECT_EQ(skeleton->joint_parents()[3], 0);
EXPECT_STREQ(skeleton->joint_names()[3], "j3");
EXPECT_EQ(skeleton->joint_parents()[7], 0);
EXPECT_STREQ(skeleton->joint_names()[7], "j7");
EXPECT_EQ(skeleton->joint_parents()[2], 1);
EXPECT_STREQ(skeleton->joint_names()[2], "j2");
EXPECT_EQ(skeleton->joint_parents()[4], 3);
EXPECT_STREQ(skeleton->joint_names()[4], "j4");
EXPECT_EQ(skeleton->joint_parents()[5], 3);
EXPECT_STREQ(skeleton->joint_names()[5], "j5");
EXPECT_EQ(skeleton->joint_parents()[6], 5);
EXPECT_STREQ(skeleton->joint_names()[6], "j6");
}
TEST(MultiRoots, SkeletonBuilder) {
// Instantiates a builder objects with default parameters.
SkeletonBuilder builder;
/*
6 joints (2 roots)
*
/ \
j0 j2
| | \
j1 j3 j5
|
j4
*/
RawSkeleton raw_skeleton;
raw_skeleton.roots.resize(2);
raw_skeleton.roots[0].name = "j0";
raw_skeleton.roots[0].children.resize(1);
raw_skeleton.roots[0].children[0].name = "j1";
raw_skeleton.roots[1].name = "j2";
raw_skeleton.roots[1].children.resize(2);
raw_skeleton.roots[1].children[0].name = "j3";
raw_skeleton.roots[1].children[1].name = "j5";
raw_skeleton.roots[1].children[0].children.resize(1);
raw_skeleton.roots[1].children[0].children[0].name = "j4";
EXPECT_TRUE(raw_skeleton.Validate());
EXPECT_EQ(raw_skeleton.num_joints(), 6);
ozz::unique_ptr<Skeleton> skeleton(builder(raw_skeleton));
ASSERT_TRUE(skeleton);
EXPECT_EQ(skeleton->num_joints(), 6);
for (int i = 0; i < skeleton->num_joints(); i++) {
const int parent_index = skeleton->joint_parents()[i];
if (std::strcmp(skeleton->joint_names()[i], "j0") == 0) {
EXPECT_EQ(parent_index, Skeleton::kNoParent);
} else if (std::strcmp(skeleton->joint_names()[i], "j1") == 0) {
EXPECT_STREQ(skeleton->joint_names()[parent_index], "j0");
} else if (std::strcmp(skeleton->joint_names()[i], "j2") == 0) {
EXPECT_EQ(parent_index, Skeleton::kNoParent);
} else if (std::strcmp(skeleton->joint_names()[i], "j3") == 0) {
EXPECT_STREQ(skeleton->joint_names()[parent_index], "j2");
} else if (std::strcmp(skeleton->joint_names()[i], "j4") == 0) {
EXPECT_STREQ(skeleton->joint_names()[parent_index], "j3");
} else if (std::strcmp(skeleton->joint_names()[i], "j5") == 0) {
EXPECT_STREQ(skeleton->joint_names()[parent_index], "j2");
} else {
EXPECT_TRUE(false);
}
}
}
TEST(BindPose, SkeletonBuilder) {
using ozz::math::Float3;
using ozz::math::Float4;
using ozz::math::Quaternion;
using ozz::math::Transform;
// Instantiates a builder objects with default parameters.
SkeletonBuilder builder;
/*
3 joints
*
|
j0
/ \
j1 j2
*/
RawSkeleton raw_skeleton;
raw_skeleton.roots.resize(1);
RawSkeleton::Joint& root = raw_skeleton.roots[0];
root.name = "j0";
root.transform = Transform::identity();
root.transform.translation = Float3(1.f, 2.f, 3.f);
root.transform.rotation = Quaternion(1.f, 0.f, 0.f, 0.f);
root.children.resize(2);
root.children[0].name = "j1";
root.children[0].transform = Transform::identity();
root.children[0].transform.rotation = Quaternion(0.f, 1.f, 0.f, 0.f);
root.children[0].transform.translation = Float3(4.f, 5.f, 6.f);
root.children[1].name = "j2";
root.children[1].transform = Transform::identity();
root.children[1].transform.translation = Float3(7.f, 8.f, 9.f);
root.children[1].transform.scale = Float3(-27.f, 46.f, 9.f);
EXPECT_TRUE(raw_skeleton.Validate());
EXPECT_EQ(raw_skeleton.num_joints(), 3);
ozz::unique_ptr<Skeleton> skeleton(builder(raw_skeleton));
ASSERT_TRUE(skeleton);
// Convert bind pose back to aos.
ozz::math::SimdFloat4 translations[4];
ozz::math::SimdFloat4 scales[4];
ozz::math::SimdFloat4 rotations[4];
const ozz::math::SoaTransform& bind_pose = skeleton->joint_bind_poses()[0];
ozz::math::Transpose3x4(&bind_pose.translation.x, translations);
ozz::math::Transpose4x4(&bind_pose.rotation.x, rotations);
ozz::math::Transpose3x4(&bind_pose.scale.x, scales);
for (int i = 0; i < skeleton->num_joints(); ++i) {
if (std::strcmp(skeleton->joint_names()[i], "j0") == 0) {
EXPECT_SIMDFLOAT_EQ(translations[i], 1.f, 2.f, 3.f, 0.f);
EXPECT_SIMDFLOAT_EQ(rotations[i], 1.f, 0.f, 0.f, 0.f);
EXPECT_SIMDFLOAT_EQ(scales[i], 1.f, 1.f, 1.f, 0.f);
} else if (std::strcmp(skeleton->joint_names()[i], "j1") == 0) {
EXPECT_SIMDFLOAT_EQ(translations[i], 4.f, 5.f, 6.f, 0.f);
EXPECT_SIMDFLOAT_EQ(rotations[i], 0.f, 1.f, 0.f, 0.f);
EXPECT_SIMDFLOAT_EQ(scales[i], 1.f, 1.f, 1.f, 0.f);
} else if (std::strcmp(skeleton->joint_names()[i], "j2") == 0) {
EXPECT_SIMDFLOAT_EQ(translations[i], 7.f, 8.f, 9.f, 0.f);
EXPECT_SIMDFLOAT_EQ(rotations[i], 0.f, 0.f, 0.f, 1.f);
EXPECT_SIMDFLOAT_EQ(scales[i], -27.f, 46.f, 9.f, 0.f);
} else {
EXPECT_TRUE(false);
}
}
// Unused joint from the SoA structure must be properly initialized
EXPECT_SIMDFLOAT_EQ(translations[3], 0.f, 0.f, 0.f, 0.f);
EXPECT_SIMDFLOAT_EQ(rotations[3], 0.f, 0.f, 0.f, 1.f);
EXPECT_SIMDFLOAT_EQ(scales[3], 1.f, 1.f, 1.f, 0.f);
}
TEST(MaxJoints, SkeletonBuilder) {
// Instantiates a builder objects with default parameters.
SkeletonBuilder builder;
{ // Inside the domain.
RawSkeleton raw_skeleton;
raw_skeleton.roots.resize(Skeleton::kMaxJoints);
EXPECT_TRUE(raw_skeleton.Validate());
EXPECT_EQ(raw_skeleton.num_joints(), Skeleton::kMaxJoints);
ozz::unique_ptr<Skeleton> skeleton(builder(raw_skeleton));
EXPECT_TRUE(skeleton);
}
{ // Outside of the domain.
RawSkeleton raw_skeleton;
raw_skeleton.roots.resize(Skeleton::kMaxJoints + 1);
EXPECT_FALSE(raw_skeleton.Validate());
EXPECT_EQ(raw_skeleton.num_joints(), Skeleton::kMaxJoints + 1);
EXPECT_TRUE(!builder(raw_skeleton));
}
}
@@ -0,0 +1,398 @@
if(NOT ozz_build_tools)
return()
endif()
add_executable(test2ozz
test2ozz.cc)
target_link_libraries(test2ozz
ozz_animation_tools
ozz_options)
set_target_properties(test2ozz
PROPERTIES FOLDER "ozz/tests/animation_offline")
# Creates content test files
file(WRITE "${ozz_temp_directory}/bad.content" "bad content")
file(WRITE "${ozz_temp_directory}/good.content0" "good content 0")
file(WRITE "${ozz_temp_directory}/good.content1" "good content 1")
file(WRITE "${ozz_temp_directory}/good.content2" "good content 2")
file(WRITE "${ozz_temp_directory}/good.content_renamed" "good content renamed")
file(WRITE "${ozz_temp_directory}/non_unique_names.content" "good content but not unique joint names")
# Creates config test files.
file(WRITE "${ozz_temp_directory}/valid_config.json" "{\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_valid_config.ozz\"}]}")
file(WRITE "${ozz_temp_directory}/no_output_config.json" "{\"skeleton\":{\"import\":{\"enable\":false}},\"animations\":[]}")
file(WRITE "${ozz_temp_directory}/invalid_config.json" "invalid json content")
# Run test2ozz generic failing tests
#----------------------------
add_test(NAME test2ozz_no_arg COMMAND test2ozz)
set_tests_properties(test2ozz_no_arg PROPERTIES PASS_REGULAR_EXPRESSION "Required option \"file\" is not specified.")
add_test(NAME test2ozz_bad_argument COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--bad")
set_tests_properties(test2ozz_bad_argument PROPERTIES PASS_REGULAR_EXPRESSION "Invalid command line argument:\"--bad\".")
add_test(NAME test2ozz_bad_endian COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--endian=fat")
set_tests_properties(test2ozz_bad_endian PROPERTIES PASS_REGULAR_EXPRESSION "Invalid endianness option \"fat\".")
add_test(NAME test2ozz_bad_log COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--log_level=loud")
set_tests_properties(test2ozz_bad_log PROPERTIES PASS_REGULAR_EXPRESSION "Invalid log level option \"loud\".")
add_test(NAME test2ozz_bad_content COMMAND test2ozz "--file=${ozz_temp_directory}/bad.content")
set_tests_properties(test2ozz_bad_content PROPERTIES PASS_REGULAR_EXPRESSION "Failed to import file \"${ozz_temp_directory}/bad.content\".")
add_test(NAME test2ozz_unexisting_test COMMAND test2ozz "--file=${ozz_temp_directory}/file_doesn_t_exist")
set_tests_properties(test2ozz_unexisting_test PROPERTIES PASS_REGULAR_EXPRESSION "File \"${ozz_temp_directory}/file_doesn_t_exist\" doesn't exist.")
add_test(NAME test2ozz_exclusive_config COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={}" "--config_file=${ozz_temp_directory}/valid_config.json")
set_tests_properties(test2ozz_exclusive_config PROPERTIES PASS_REGULAR_EXPRESSION "--config and --config_file are exclusive options.")
add_test(NAME test2ozz_unexisting_config_file COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config_file=${ozz_temp_directory}/unexisting_config.json")
set_tests_properties(test2ozz_unexisting_config_file PROPERTIES PASS_REGULAR_EXPRESSION "Failed to open config file: \"${ozz_temp_directory}/unexisting_config.json\".")
add_test(NAME test2ozz_invalid_config_file COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config_file=${ozz_temp_directory}/invalid_config.json")
set_tests_properties(test2ozz_invalid_config_file PROPERTIES PASS_REGULAR_EXPRESSION "Error while parsing configuration string")
add_test(NAME test2ozz_dump_config_bad_file COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config_dump_reference=${ozz_temp_directory}/unexisting/config.json")
set_tests_properties(test2ozz_dump_config_bad_file PROPERTIES PASS_REGULAR_EXPRESSION "Failed to open config file to dump" DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_bad_type COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"animations\":[{\"filename\":\"${ozz_temp_directory}/anim_should_not_exist.ozz\",\"sampling_rate\":\"1\"}]}")
set_tests_properties(test2ozz_bad_type PROPERTIES PASS_REGULAR_EXPRESSION "Invalid type \"UTF-8 string\" for json member \"root.animations\\\[0\\\].sampling_rate\". \"float\" expected." DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_bad_array COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"animations\":{\"sampling_rate\":true}}")
set_tests_properties(test2ozz_bad_array PROPERTIES PASS_REGULAR_EXPRESSION "Invalid type \"object\" for json member \"root.animations\". \"array\" expected." DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_bad_type_in_array COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"animations\":[{},{\"sampling_rate\":true}]}")
set_tests_properties(test2ozz_bad_type_in_array PROPERTIES PASS_REGULAR_EXPRESSION "Invalid type \"boolean\" for json member \"root.animations\\\[1\\\].sampling_rate\". \"float\" expected." DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_wrong_config_unexpected_member2 COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"animations\":[{},{\"foo\":true}]}")
set_tests_properties(test2ozz_wrong_config_unexpected_member2 PROPERTIES PASS_REGULAR_EXPRESSION "Invalid json member \"root.animations\\\[1\\\].foo\"." DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_wrong_optimize COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"animations\":[{\"filename\":\"${ozz_temp_directory}/anim_should_not_exist.ozz\",\"optimize\":99}]}")
set_tests_properties(test2ozz_wrong_optimize PROPERTIES PASS_REGULAR_EXPRESSION "Invalid type \"integer\" for json member \"root.animations\\\[0\\\].optimize\". \"boolean\" expected." DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_wrong_config_unexpected_member1 COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":\"${ozz_temp_directory}/skeleton.ozz\",\"filename\":\"${ozz_temp_directory}/anim_should_not_exist.ozz\"}")
set_tests_properties(test2ozz_wrong_config_unexpected_member1 PROPERTIES PASS_REGULAR_EXPRESSION "Invalid json member \"root.filename\"." DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_wrong_config_invalid_object COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"animations\":{\"filename\":\"${ozz_temp_directory}/anim_should_not_exist.ozz\"}}")
set_tests_properties(test2ozz_wrong_config_invalid_object PROPERTIES PASS_REGULAR_EXPRESSION "Invalid type \"object\" for json member \"root.animations\". \"array\" expected." DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_wrong_config_invalid_array COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"animations\":[{\"optimize\":[]}]}")
set_tests_properties(test2ozz_wrong_config_invalid_array PROPERTIES PASS_REGULAR_EXPRESSION "Invalid type \"array\" for json member \"root.animations\\\[0\\\].optimize\". \"boolean\" expected." DEPENDS test2ozz_skel_simple)
# Run test2ozz generic passing tests
#----------------------------
add_test(NAME test2ozz_version COMMAND test2ozz "--version")
set_tests_properties(test2ozz_version PROPERTIES PASS_REGULAR_EXPRESSION "version 2.0")
add_test(NAME test2ozz_log_verbose COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config_file=${ozz_temp_directory}/no_output_config.json" "--log_level=verbose")
set_tests_properties(test2ozz_log_verbose PROPERTIES PASS_REGULAR_EXPRESSION "Verbose log level activated.")
add_test(NAME test2ozz_native COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config_file=${ozz_temp_directory}/no_output_config.json" "--endian=native")
add_test(NAME test2ozz_little COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config_file=${ozz_temp_directory}/no_output_config.json" "--endian=little" "--log_level=verbose")
set_tests_properties(test2ozz_little PROPERTIES PASS_REGULAR_EXPRESSION "Little endian output binary format selected.")
add_test(NAME test2ozz_big COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config_file=${ozz_temp_directory}/no_output_config.json" "--endian=big" "--log_level=verbose")
set_tests_properties(test2ozz_big PROPERTIES PASS_REGULAR_EXPRESSION "Big endian output binary format selected.")
add_test(NAME test2ozz_log_config_verbose COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config_file=${ozz_temp_directory}/no_output_config.json" "--log_level=verbose")
set_tests_properties(test2ozz_log_config_verbose PROPERTIES PASS_REGULAR_EXPRESSION "// Animations to import.")
add_test(NAME test2ozz_dump_ref_config COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config_file=${ozz_temp_directory}/no_output_config.json" "--config_dump_reference=${ozz_temp_directory}/reference.json")
add_test(NAME test2ozz_dump_ref_config_output COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/reference.json" "${ozz_temp_directory}/reference_should_exist.json")
set_tests_properties(test2ozz_dump_ref_config_output PROPERTIES DEPENDS test2ozz_dump_ref_config)
file(MAKE_DIRECTORY ${ozz_temp_directory}/ref_config_relative)
add_test(NAME test2ozz_load_ref_config COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config_file=${ozz_temp_directory}/reference.json" WORKING_DIRECTORY "${ozz_temp_directory}/ref_config_relative")
set_tests_properties(test2ozz_load_ref_config PROPERTIES DEPENDS test2ozz_dump_ref_config)
file(MAKE_DIRECTORY ${ozz_temp_directory}/ref_config_relative/cp)
add_test(NAME test2ozz_load_ref_config_output COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/ref_config_relative/skeleton.ozz" "${ozz_temp_directory}/ref_config_relative/one.ozz" "${ozz_temp_directory}/ref_config_relative/joint0-property0.ozz" "${ozz_temp_directory}/ref_config_relative/joint0-property1.ozz" "${ozz_temp_directory}/ref_config_relative/joint1-property0.ozz" "${ozz_temp_directory}/ref_config_relative/joint1-property1.ozz" "${ozz_temp_directory}/ref_config_relative/cp/")
set_tests_properties(test2ozz_load_ref_config_output PROPERTIES DEPENDS test2ozz_load_ref_config)
file(MAKE_DIRECTORY ${ozz_temp_directory}/test2ozz_no_config)
add_test(NAME test2ozz_no_config COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" WORKING_DIRECTORY "${ozz_temp_directory}/test2ozz_no_config")
set_tests_properties(test2ozz_no_config PROPERTIES PASS_REGULAR_EXPRESSION "No configuration provided, using default configuration.")
file(MAKE_DIRECTORY ${ozz_temp_directory}/test2ozz_no_config/cp)
add_test(NAME test2ozz_no_config_output COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/test2ozz_no_config/skeleton.ozz" "${ozz_temp_directory}/test2ozz_no_config/one.ozz" "${ozz_temp_directory}/test2ozz_no_config/cp/")
set_tests_properties(test2ozz_no_config_output PROPERTIES DEPENDS test2ozz_no_config)
# Run test2ozz skeleton import failing tests
#----------------------
add_test(NAME test2ozz_skel_invalid_filename COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/invalid_path/skel_should_not_exist.ozz\",\"import\":{\"enable\":true}},\"animations\":[]}}")
set_tests_properties(test2ozz_skel_invalid_filename PROPERTIES PASS_REGULAR_EXPRESSION "Failed to open output file: \"${ozz_temp_directory}/invalid_path/skel_should_not_exist.ozz\".")
add_test(NAME test2ozz_skel_invalid_filename_raw COMMAND test2ozz "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/invalid_path/skel_should_not_exist.ozz\",\"import\":{\"enable\":true,\"raw\":true}},\"animations\":[]}}")
set_tests_properties(test2ozz_skel_invalid_filename_raw PROPERTIES WILL_FAIL true)
add_test(NAME test2ozz_skel_non_unique_names COMMAND test2ozz "--file=${ozz_temp_directory}/non_unique_names.content" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skel_should_not_exist.ozz\",\"import\":{\"enable\":true}},\"animations\":[]}}")
set_tests_properties(test2ozz_skel_non_unique_names PROPERTIES PASS_REGULAR_EXPRESSION "Skeleton contains at least one non-unique joint name \"jointx\", which is not supported.")
# Run test2ozz skeleton passing tests
#----------------------------
add_test(NAME test2ozz_skel_simple COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\"},\"animations\":[]}}")
add_test(NAME test2ozz_skel_simple_raw COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/raw_skeleton.ozz\",\"import\":{\"raw\":true}},\"animations\":[]}}")
add_test(NAME test2ozz_skel_no_import COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skel_should_not_exist.ozz\",\"import\":{\"enable\":false}},\"animations\":[]}}")
set_tests_properties(test2ozz_skel_no_import PROPERTIES PASS_REGULAR_EXPRESSION "Skeleton build disabled, import will be skipped")
add_test(NAME test2ozz_log_hierarchy COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton_${CMAKE_CURRENT_LIST_LINE}.ozz\"},\"animations\":[]}}" "--log_level=verbose")
set_tests_properties(test2ozz_log_hierarchy PROPERTIES PASS_REGULAR_EXPRESSION ".joint0 t")
# Ensures nothing was outputted.
add_test(NAME test2ozz_skel_output COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/skel_should_not_exist.ozz" "${ozz_temp_directory}/skel_should_not_exist_too.ozz")
set_tests_properties(test2ozz_skel_output PROPERTIES WILL_FAIL true)
set_tests_properties(test2ozz_skel_output PROPERTIES
DEPENDS "test2ozz_skel_no_import
test2ozz_skel_non_unique_names
test2ozz_skel_invalid_filename
test2ozz_skel_invalid_filename_raw")
# Run test2ozz animation import failing tests
#----------------------------
add_test(NAME test2ozz_anim_no_animation COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[]}")
set_tests_properties(test2ozz_anim_no_animation PROPERTIES PASS_REGULAR_EXPRESSION "Configuration contains no animation import definition, animations import will be skipped." DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_no_clip_name COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"clip\":\"\"}]}")
set_tests_properties(test2ozz_anim_no_clip_name PROPERTIES PASS_REGULAR_EXPRESSION "No clip name provided. Animation import will be skipped." DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_no_animation_output COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"\"}]}")
set_tests_properties(test2ozz_anim_no_animation_output PROPERTIES PASS_REGULAR_EXPRESSION "Failed to open output file: \"\"." DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_no_anim_test COMMAND test2ozz "--file=${ozz_temp_directory}/good.content0" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/anim_should_not_exist.ozz\"}]}")
set_tests_properties(test2ozz_anim_no_anim_test PROPERTIES PASS_REGULAR_EXPRESSION "No animation found." DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_bad_skeleton COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/bad.content\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/anim_should_not_exist.ozz\"}]}")
set_tests_properties(test2ozz_anim_bad_skeleton PROPERTIES PASS_REGULAR_EXPRESSION "Failed to read input skeleton from binary file" DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_unexisting_skeleton COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/unexisting.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/anim_should_not_exist.ozz\"}]}")
set_tests_properties(test2ozz_anim_unexisting_skeleton PROPERTIES PASS_REGULAR_EXPRESSION "Failed to open input skeleton ozz binary file: \"${ozz_temp_directory}/unexisting.ozz\"")
add_test(NAME test2ozz_anim_invalid_filename COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/invalid_path/anim_should_not_exist.ozz\"}]}")
set_tests_properties(test2ozz_anim_invalid_filename PROPERTIES PASS_REGULAR_EXPRESSION "Failed to open output file: \"${ozz_temp_directory}/invalid_path/anim_should_not_exist.ozz\"." DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_no_match COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"clip\":\"won_t_match\",\"filename\":\"${ozz_temp_directory}/invalid_path/anim_should_not_exist.ozz\"}]}")
set_tests_properties(test2ozz_anim_no_match PROPERTIES PASS_REGULAR_EXPRESSION "No matching animation found for \"won_t_match\"." DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_additive_wrong_ref COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_${CMAKE_CURRENT_LIST_LINE}.ozz\",\"additive\":true,\"additive_reference\":\"anim\"}]}")
set_tests_properties(test2ozz_anim_additive_wrong_ref PROPERTIES PASS_REGULAR_EXPRESSION "Invalid additive reference pose \"anim\"." DEPENDS test2ozz_skel_simple)
# Run test2ozz track import failing tests
#----------------------------
add_test(NAME test2ozz_anim_track_bad_joint_name COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_${CMAKE_CURRENT_LIST_LINE}.ozz\",\"tracks\":[{\"properties\":[{\"joint_name\":\"joint3\",\"property_name\":\"property2\",\"filename\":\"${ozz_temp_directory}/anim_should_not_exist.ozz\"}]}]}]}")
set_tests_properties(test2ozz_anim_track_bad_joint_name PROPERTIES PASS_REGULAR_EXPRESSION "No joint found for track import definition \"joint3\"" DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_track_bad_ppt_name COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_${CMAKE_CURRENT_LIST_LINE}.ozz\",\"tracks\":[{\"properties\":[{\"joint_name\":\"joint2\",\"property_name\":\"propertyx\",\"filename\":\"${ozz_temp_directory}/anim_should_not_exist.ozz\"}]}]}]}")
set_tests_properties(test2ozz_anim_track_bad_ppt_name PROPERTIES PASS_REGULAR_EXPRESSION "No property found for track import definition \"joint2:propertyx\"" DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_track_bad_type COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_${CMAKE_CURRENT_LIST_LINE}.ozz\",\"tracks\":[{\"properties\":[{\"joint_name\":\"joint1\",\"property_name\":\"property2\",\"type\":\"float3\",\"filename\":\"${ozz_temp_directory}/anim_should_not_exist.ozz\"}]}]}]}")
set_tests_properties(test2ozz_anim_track_bad_type PROPERTIES PASS_REGULAR_EXPRESSION "Incompatible type \"float3\" for matching property \"joint1:property2\" of type \"float2\"" DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_track_invalid_type COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_${CMAKE_CURRENT_LIST_LINE}.ozz\",\"tracks\":[{\"properties\":[{\"joint_name\":\"joint1\",\"property_name\":\"property2\",\"type\":\"1\",\"filename\":\"${ozz_temp_directory}/anim_should_not_exist.ozz\"}]}]}]}")
set_tests_properties(test2ozz_anim_track_invalid_type PROPERTIES PASS_REGULAR_EXPRESSION "Invalid value \"1\" for import track type property." DEPENDS test2ozz_skel_simple)
# Ensures nothing was outputted.
add_test(NAME test2ozz_anim_output COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/anim_should_not_exist.ozz" "${ozz_temp_directory}/anim_should_not_exist_too.ozz")
set_tests_properties(test2ozz_anim_output PROPERTIES WILL_FAIL true)
set_tests_properties(test2ozz_anim_output PROPERTIES
DEPENDS "test2ozz_anim_output
test2ozz_anim_missing_test
test2ozz_anim_bad_test
test2ozz_anim_bad_skeleton
test2ozz_anim_unexisting_skeleton
test2ozz_anim_invalid_filename
test2ozz_anim_bad_log_level
test2ozz_anim_track_bad_joint_name
test2ozz_anim_track_bad_ppt_name
test2ozz_anim_track_bad_type
test2ozz_anim_track_invalid_type")
# Run test2ozz animation import passing tests
#----------------------------
add_test(NAME test2ozz_anim_simple COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_simple.ozz\"}]}")
set_tests_properties(test2ozz_anim_simple PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_simple_output COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/animation_simple.ozz" "${ozz_temp_directory}/animation_simple_should_exist.ozz")
set_tests_properties(test2ozz_anim_simple_output PROPERTIES DEPENDS test2ozz_anim_simple)
add_test(NAME test2ozz_anim_simple_renamed COMMAND test2ozz "--file=${ozz_temp_directory}/good.content_renamed" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/*.ozz\"}]}")
set_tests_properties(test2ozz_anim_simple_renamed PROPERTIES PASS_REGULAR_EXPRESSION "in order to be used as a valid filename" DEPENDS test2ozz_skel_simple)
file(MAKE_DIRECTORY ${ozz_temp_directory}/anim_simple_relative)
add_test(NAME test2ozz_anim_simple_relative COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"animation.ozz\"}]}" WORKING_DIRECTORY "${ozz_temp_directory}/anim_simple_relative")
set_tests_properties(test2ozz_anim_simple_relative PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_simple_relative_output COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/anim_simple_relative/animation.ozz" "${ozz_temp_directory}/anim_simple_relative/animation_should_exist.ozz")
set_tests_properties(test2ozz_anim_simple_relative_output PROPERTIES DEPENDS test2ozz_anim_simple_relative)
add_test(NAME test2ozz_anim_simple_config_file COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config_file=${ozz_temp_directory}/valid_config.json")
set_tests_properties(test2ozz_anim_simple_config_file PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_optimize COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_${CMAKE_CURRENT_LIST_LINE}.ozz\",\"optimize\":true}]}")
set_tests_properties(test2ozz_anim_optimize PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_optimize_tol COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_${CMAKE_CURRENT_LIST_LINE}.ozz\",\"optimize\":true,\"optimization_settings\":{\"tolerance\":0.002,\"distance\":0.002}}]}")
set_tests_properties(test2ozz_anim_optimize_tol PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_optimize_joints_tol COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_${CMAKE_CURRENT_LIST_LINE}.ozz\",\"optimize\":true,\"optimization_settings\":{\"override\":[{\"name\":\"joint?\",\"tolerance\":0.002,\"distance\":0.002}]}}]}")
set_tests_properties(test2ozz_anim_optimize_joints_tol PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_optimize_joints_tol_verbose COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_${CMAKE_CURRENT_LIST_LINE}.ozz\",\"optimize\":true,\"optimization_settings\":{\"override\":[{\"name\":\"joint?\",\"tolerance\":0.002,\"distance\":0.002}]}}]}" "--log_level=verbose")
set_tests_properties(test2ozz_anim_optimize_joints_tol_verbose PROPERTIES PASS_REGULAR_EXPRESSION "Found joint \"joint2\" matching pattern" DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_optimize_joints_not_found_tol COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_${CMAKE_CURRENT_LIST_LINE}.ozz\",\"optimize\":true,\"optimization_settings\":{\"override\":[{\"name\":\"joint_won_t_find\",\"tolerance\":0.002,\"distance\":0.002}]}}]}")
set_tests_properties(test2ozz_anim_optimize_joints_not_found_tol PROPERTIES PASS_REGULAR_EXPRESSION "No joint found for optimization setting" DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_optimize_joints_redundant_tol COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_${CMAKE_CURRENT_LIST_LINE}.ozz\",\"optimize\":true,\"optimization_settings\":{\"override\":[{\"name\":\"joint1\"},{\"name\":\"joint?\"}]}}]}")
set_tests_properties(test2ozz_anim_optimize_joints_redundant_tol PROPERTIES PASS_REGULAR_EXPRESSION "Redundant optimization setting for pattern" DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_no_opt COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_${CMAKE_CURRENT_LIST_LINE}.ozz\"}]},\"optimize\":false}]}")
set_tests_properties(test2ozz_anim_no_opt PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_simple_raw COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/raw_animation_${CMAKE_CURRENT_LIST_LINE}.ozz\",\"raw\":false}]}")
set_tests_properties(test2ozz_anim_simple_raw PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_simple_from_raw COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/raw_skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_${CMAKE_CURRENT_LIST_LINE}.ozz\"}]}")
set_tests_properties(test2ozz_anim_simple_from_raw PROPERTIES DEPENDS test2ozz_skel_simple_raw)
add_test(NAME test2ozz_anim_simple_raw_from_raw COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/raw_skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_${CMAKE_CURRENT_LIST_LINE}.ozz\",\"raw\":false}]}")
set_tests_properties(test2ozz_anim_simple_raw_from_raw PROPERTIES DEPENDS test2ozz_skel_simple_raw)
add_test(NAME test2ozz_anim_additive COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_${CMAKE_CURRENT_LIST_LINE}.ozz\",\"additive\":true}]}")
set_tests_properties(test2ozz_anim_additive PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_additive_raw COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_${CMAKE_CURRENT_LIST_LINE}.ozz\",\"raw\":false,\"additive\":true}]}")
set_tests_properties(test2ozz_anim_additive_raw PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_additive_ref_anim COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_${CMAKE_CURRENT_LIST_LINE}.ozz\",\"additive\":true,\"additive_reference\":\"animation\"}]}")
set_tests_properties(test2ozz_anim_additive_ref_anim PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_additive_ref_skel COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_${CMAKE_CURRENT_LIST_LINE}.ozz\",\"additive\":true,\"additive_reference\":\"skeleton\"}]}")
set_tests_properties(test2ozz_anim_additive_ref_skel PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_native COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_native_${CMAKE_CURRENT_LIST_LINE}.ozz\"}]}" "--endian=native")
set_tests_properties(test2ozz_anim_native PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_little COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_little_${CMAKE_CURRENT_LIST_LINE}.ozz\"}]}" "--endian=little")
set_tests_properties(test2ozz_anim_little PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_big COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_big_${CMAKE_CURRENT_LIST_LINE}.ozz\"}]}" "--endian=big")
set_tests_properties(test2ozz_anim_big PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_unmatch_skeleton COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_big_${CMAKE_CURRENT_LIST_LINE}.ozz\"}]}" "--endian=big")
set_tests_properties(test2ozz_anim_unmatch_skeleton PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_sampling_rate COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_${CMAKE_CURRENT_LIST_LINE}.ozz\",\"sampling_rate\":10.}]}")
set_tests_properties(test2ozz_anim_sampling_rate PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_sampling_rate_int COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_${CMAKE_CURRENT_LIST_LINE}.ozz\",\"sampling_rate\":1}]}")
set_tests_properties(test2ozz_anim_sampling_rate_int PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_sampling_rate_0 COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_${CMAKE_CURRENT_LIST_LINE}.ozz\",\"sampling_rate\":0}]}")
set_tests_properties(test2ozz_anim_sampling_rate_0 PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_sampling_rate_neg COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_${CMAKE_CURRENT_LIST_LINE}.ozz\",\"sampling_rate\":-1}]}")
set_tests_properties(test2ozz_anim_sampling_rate_neg PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_log_verbose COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_${CMAKE_CURRENT_LIST_LINE}.ozz\"}]}" "--log_level=verbose")
set_tests_properties(test2ozz_anim_log_verbose PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_single1_wild COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_single1_wild_*.ozz\"}]}")
set_tests_properties(test2ozz_anim_single1_wild PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_single1_wild_output1 COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/animation_single1_wild_one.ozz" "${ozz_temp_directory}/animation_single1_wild_one_should_exist.ozz")
set_tests_properties(test2ozz_anim_single1_wild_output1 PROPERTIES DEPENDS test2ozz_anim_single1_wild)
add_test(NAME test2ozz_anim_multi1 COMMAND test2ozz "--file=${ozz_temp_directory}/good.content2" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_multi2_*.ozz\"}]}")
set_tests_properties(test2ozz_anim_multi1 PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_multi2_output1 COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/animation_multi2_one.ozz" "${ozz_temp_directory}/animation_multi2_one_should_exist.ozz")
set_tests_properties(test2ozz_anim_multi2_output1 PROPERTIES DEPENDS test2ozz_anim_multi1)
add_test(NAME test2ozz_anim_multi2_output2 COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/animation_multi2_TWO.ozz" "${ozz_temp_directory}/animation_multi1_TWO_should_exist.ozz")
set_tests_properties(test2ozz_anim_multi2_output2 PROPERTIES DEPENDS test2ozz_anim_multi1)
add_test(NAME test2ozz_anim_multi2_single COMMAND test2ozz "--file=${ozz_temp_directory}/good.content2" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"clip\":\"T?O\",\"filename\":\"${ozz_temp_directory}/animation_multi2_single.ozz\"}]}")
set_tests_properties(test2ozz_anim_multi2_single PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_multi2_single_output1 COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/animation_multi2_single.ozz" "${ozz_temp_directory}/animation_multi2_single_should_exist.ozz")
set_tests_properties(test2ozz_anim_multi2_single_output1 PROPERTIES DEPENDS test2ozz_anim_multi2_single)
add_test(NAME test2ozz_anim_multi2_multi COMMAND test2ozz "--file=${ozz_temp_directory}/good.content2" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"clip\":\"one\",\"filename\":\"${ozz_temp_directory}/animation_multi2_multi_one.ozz\"},{\"clip\":\"TWO\",\"filename\":\"${ozz_temp_directory}/animation_multi2_multi_TWO.ozz\"}]}")
set_tests_properties(test2ozz_anim_multi2_multi PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_multi2_multi_output1 COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/animation_multi2_multi_one.ozz" "${ozz_temp_directory}/animation_multi2_multi_one_should_exist.ozz")
set_tests_properties(test2ozz_anim_multi2_multi_output1 PROPERTIES DEPENDS test2ozz_anim_multi2_multi)
add_test(NAME test2ozz_anim_multi2_multi_output2 COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/animation_multi2_multi_TWO.ozz" "${ozz_temp_directory}/animation_multi2_multi_TWO_should_exist.ozz")
set_tests_properties(test2ozz_anim_multi2_multi_output2 PROPERTIES DEPENDS test2ozz_anim_multi2_multi)
# Run test2ozz tracks import passing tests
#----------------------------
add_test(NAME test2ozz_anim_track_simple COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_track_simple_*.ozz\",\"tracks\":[{\"properties\":[{\"joint_name\":\"joint0\",\"property_name\":\"property0\",\"filename\":\"${ozz_temp_directory}/track_simple.ozz\"}]}]}]}")
set_tests_properties(test2ozz_anim_track_simple PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_track_simple_output COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/track_simple.ozz" "${ozz_temp_directory}/track_simple_should_exist.ozz")
set_tests_properties(test2ozz_anim_track_simple_output PROPERTIES DEPENDS test2ozz_anim_track_simple)
add_test(NAME test2ozz_anim_track_simple_output_anim COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/animation_track_simple_one.ozz" "${ozz_temp_directory}/animation_track_simple_one_should_exist.ozz")
set_tests_properties(test2ozz_anim_track_simple_output_anim PROPERTIES DEPENDS test2ozz_anim_track_simple)
add_test(NAME test2ozz_anim_track_simple_raw COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_track_simple_raw_*.ozz\",\"tracks\":[{\"properties\":[{\"joint_name\":\"joint0\",\"property_name\":\"property0\",\"filename\":\"${ozz_temp_directory}/track_simple_raw.ozz\",\"raw\":true}]}]}]}")
set_tests_properties(test2ozz_anim_track_simple_raw PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_track_simple_raw_output COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/track_simple_raw.ozz" "${ozz_temp_directory}/track_simple_raw_should_exist.ozz")
set_tests_properties(test2ozz_anim_track_simple_raw_output PROPERTIES DEPENDS test2ozz_anim_track_simple_raw)
add_test(NAME test2ozz_anim_track_simple_raw_output_anim COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/animation_track_simple_raw_one.ozz" "${ozz_temp_directory}/animation_track_simple_raw_one_should_exist.ozz")
set_tests_properties(test2ozz_anim_track_simple_raw_output_anim PROPERTIES DEPENDS test2ozz_anim_track_simple_raw)
add_test(NAME test2ozz_anim_track_simple_no_opt COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_track_simple_no_opt_*.ozz\",\"tracks\":[{\"properties\":[{\"joint_name\":\"joint0\",\"property_name\":\"property0\",\"filename\":\"${ozz_temp_directory}/track_simple_no_opt.ozz\",\"optimize\":false}]}]}]}")
set_tests_properties(test2ozz_anim_track_simple_no_opt PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_track_simple_no_opt_output COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/track_simple_no_opt.ozz" "${ozz_temp_directory}/track_simple_no_opt_should_exist.ozz")
set_tests_properties(test2ozz_anim_track_simple_no_opt_output PROPERTIES DEPENDS test2ozz_anim_track_simple_no_opt)
add_test(NAME test2ozz_anim_track_simple_no_opt_output_anim COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/animation_track_simple_no_opt_one.ozz" "${ozz_temp_directory}/animation_track_simple_no_opt_one_should_exist.ozz")
set_tests_properties(test2ozz_anim_track_simple_no_opt_output_anim PROPERTIES DEPENDS test2ozz_anim_track_simple_no_opt)
add_test(NAME test2ozz_anim_track_simple_no_opt_raw COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_track_simple_no_opt_raw_*.ozz\",\"tracks\":[{\"properties\":[{\"joint_name\":\"joint0\",\"property_name\":\"property0\",\"filename\":\"${ozz_temp_directory}/track_simple_no_opt_raw.ozz\",\"optimize\":false,\"raw\":true}]}]}]}")
set_tests_properties(test2ozz_anim_track_simple_no_opt_raw PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_track_simple_no_opt_raw_output COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/track_simple_no_opt_raw.ozz" "${ozz_temp_directory}/track_simple_no_opt_raw_should_exist.ozz")
set_tests_properties(test2ozz_anim_track_simple_no_opt_raw_output PROPERTIES DEPENDS test2ozz_anim_track_simple_no_opt_raw)
add_test(NAME test2ozz_anim_track_simple_no_opt_raw_output_anim COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/animation_track_simple_no_opt_raw_one.ozz" "${ozz_temp_directory}/animation_track_simple_no_opt_raw_one_should_exist.ozz")
set_tests_properties(test2ozz_anim_track_simple_no_opt_raw_output_anim PROPERTIES DEPENDS test2ozz_anim_track_simple_no_opt_raw)
add_test(NAME test2ozz_anim_track2_simple COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_track2_simple_*.ozz\",\"tracks\":[{\"properties\":[{\"joint_name\":\"joint0\",\"property_name\":\"property2\",\"type\":\"float2\",\"filename\":\"${ozz_temp_directory}/track2_simple.ozz\"}]}]}]}")
set_tests_properties(test2ozz_anim_track2_simple PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_track2_simple_output COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/track2_simple.ozz" "${ozz_temp_directory}/track2_simple_should_exist.ozz")
set_tests_properties(test2ozz_anim_track2_simple_output PROPERTIES DEPENDS test2ozz_anim_track2_simple)
add_test(NAME test2ozz_anim_track2_simple_output_anim COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/animation_track2_simple_one.ozz" "${ozz_temp_directory}/animation_track2_simple_one_should_exist.ozz")
set_tests_properties(test2ozz_anim_track2_simple_output_anim PROPERTIES DEPENDS test2ozz_anim_track2_simple)
add_test(NAME test2ozz_anim_track_wild_joint COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton.ozz\",\"import\":{\"enable\":false}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_track_wild_joint_*.ozz\",\"tracks\":[{\"properties\":[{\"joint_name\":\"joint?\",\"property_name\":\"property0\",\"filename\":\"${ozz_temp_directory}/track_wild_*.ozz\"}]}]}]}")
set_tests_properties(test2ozz_anim_track_wild_joint PROPERTIES DEPENDS test2ozz_skel_simple)
add_test(NAME test2ozz_anim_track_wild_joint_output0
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/track_wild_joint0-property0.ozz" "${ozz_temp_directory}/track_wild_joint0-property0_should_exist.ozz")
set_tests_properties(test2ozz_anim_track_wild_joint_output0 PROPERTIES DEPENDS test2ozz_anim_track_wild_joint)
add_test(NAME test2ozz_anim_track_wild_joint_output1
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/track_wild_joint1-property0.ozz" "${ozz_temp_directory}/track_wild_joint1-property0_should_exist.ozz")
set_tests_properties(test2ozz_anim_track_wild_joint_output1 PROPERTIES DEPENDS test2ozz_anim_track_wild_joint)
# Run test2ozz both skeleton and animation import passing tests
#----------------------------
add_test(NAME test2ozz_skel_anim_simple COMMAND test2ozz "--file=${ozz_temp_directory}/good.content1" "--config={\"skeleton\":{\"filename\":\"${ozz_temp_directory}/skeleton_skel_anim.ozz\",\"import\":{\"enable\":true}},\"animations\":[{\"filename\":\"${ozz_temp_directory}/animation_skel_anim_simple.ozz\"}]}")
# Fused sources tests
#----------------------------
# ozz_animation_tools fuse tests
set_source_files_properties(${PROJECT_BINARY_DIR}/src_fused/ozz_animation_tools.cc PROPERTIES GENERATED 1)
add_executable(test_fuse_ozz_animation_tools
test2ozz.cc
${PROJECT_BINARY_DIR}/src_fused/ozz_animation_tools.cc)
add_dependencies(test_fuse_ozz_animation_tools BUILD_FUSE_ozz_animation_tools)
target_link_libraries(test_fuse_ozz_animation_tools
ozz_animation_offline
ozz_options
json
gtest)
set_target_properties(test_fuse_ozz_animation_tools PROPERTIES FOLDER "ozz/tests/animation_offline")
add_test(NAME test_fuse_ozz_animation_tools_no_arg COMMAND test_fuse_ozz_animation_tools)
set_tests_properties(test_fuse_ozz_animation_tools_no_arg PROPERTIES PASS_REGULAR_EXPRESSION "Required option \"file\" is not specified.")
@@ -0,0 +1,319 @@
//----------------------------------------------------------------------------//
// //
// 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 <string.h>
#include "ozz/animation/offline/tools/import2ozz.h"
#include "ozz/animation/runtime/skeleton.h"
#include "ozz/base/io/stream.h"
#include "ozz/base/memory/unique_ptr.h"
class TestConverter : public ozz::animation::offline::OzzImporter {
public:
TestConverter() {}
~TestConverter() {}
private:
virtual bool Load(const char* _filename) {
file_ = ozz::make_unique<ozz::io::File>(_filename, "rb");
if (!file_->opened()) {
file_.reset(nullptr);
return false;
}
const char good_content[] = "good content";
char buffer[256];
bool valid =
file_->Read(buffer, sizeof(buffer)) >= sizeof(good_content) - 1 &&
memcmp(buffer, good_content, sizeof(good_content) - 1) == 0;
file_->Seek(0, ozz::io::File::kSet);
return valid;
}
virtual bool Import(ozz::animation::offline::RawSkeleton* _skeleton,
const NodeType& _types) {
(void)_types;
if (file_ && file_->opened()) {
char buffer[256];
{
file_->Seek(0, ozz::io::File::kSet);
const char content[] = "good content 1";
if (file_->Read(buffer, sizeof(buffer)) >= sizeof(content) - 1 &&
memcmp(buffer, content, sizeof(content) - 1) == 0) {
_skeleton->roots.resize(1);
ozz::animation::offline::RawSkeleton::Joint& root =
_skeleton->roots[0];
root.name = "root";
root.children.resize(3);
ozz::animation::offline::RawSkeleton::Joint& joint0 =
root.children[0];
joint0.name = "joint0";
ozz::animation::offline::RawSkeleton::Joint& joint1 =
root.children[1];
joint1.name = "joint1";
ozz::animation::offline::RawSkeleton::Joint& joint2 =
root.children[2];
joint2.name = "joint2";
return true;
}
}
{
file_->Seek(0, ozz::io::File::kSet);
const char content[] = "good content renamed";
if (file_->Read(buffer, sizeof(buffer)) >= sizeof(content) - 1 &&
memcmp(buffer, content, sizeof(content) - 1) == 0) {
_skeleton->roots.resize(1);
ozz::animation::offline::RawSkeleton::Joint& root =
_skeleton->roots[0];
root.name = "root";
return true;
}
}
{
file_->Seek(0, ozz::io::File::kSet);
const char content[] =
"good content but not unique joint names";
if (file_->Read(buffer, sizeof(buffer)) >= sizeof(content) - 1 &&
memcmp(buffer, content, sizeof(content) - 1) == 0) {
_skeleton->roots.resize(1);
ozz::animation::offline::RawSkeleton::Joint& root =
_skeleton->roots[0];
root.name = "jointx";
root.children.resize(3);
ozz::animation::offline::RawSkeleton::Joint& joint0 =
root.children[0];
joint0.name = "joint0";
ozz::animation::offline::RawSkeleton::Joint& joint1 =
root.children[1];
joint1.name = "joint1";
ozz::animation::offline::RawSkeleton::Joint& joint2 =
root.children[2];
joint2.name = "jointx";
return true;
}
}
}
return false;
}
virtual AnimationNames GetAnimationNames() {
AnimationNames names;
if (file_ && file_->opened()) {
char buffer[256];
{
file_->Seek(0, ozz::io::File::kSet);
const char content[] = "good content 0";
if (file_->Read(buffer, sizeof(buffer)) >= sizeof(content) - 1 &&
memcmp(buffer, content, sizeof(content) - 1) == 0) {
return names; // No animations
}
}
{
file_->Seek(0, ozz::io::File::kSet);
const char content[] = "good content 1";
if (file_->Read(buffer, sizeof(buffer)) >= sizeof(content) - 1 &&
memcmp(buffer, content, sizeof(content) - 1) == 0) {
names.push_back("one");
return names;
}
}
{
file_->Seek(0, ozz::io::File::kSet);
const char content[] = "good content renamed";
if (file_->Read(buffer, sizeof(buffer)) >= sizeof(content) - 1 &&
memcmp(buffer, content, sizeof(content) - 1) == 0) {
names.push_back("renamed?");
return names;
}
}
// Handles more than one animation per file.
{
file_->Seek(0, ozz::io::File::kSet);
const char content[] = "good content 2";
if (file_->Read(buffer, sizeof(buffer)) >= sizeof(content) - 1 &&
memcmp(buffer, content, sizeof(content) - 1) == 0) {
names.push_back("one");
names.push_back("TWO");
return names;
}
}
}
return names;
}
virtual bool Import(const char* _animation_name,
const ozz::animation::Skeleton& _skeleton,
float _sampling_rate,
ozz::animation::offline::RawAnimation* _animation) {
(void)_sampling_rate;
(void)_skeleton;
if (file_ && file_->opened()) {
char buffer[256];
{ // Handles a single animation per file.
file_->Seek(0, ozz::io::File::kSet);
const char content[] = "good content 1";
if (file_->Read(buffer, sizeof(buffer)) >= sizeof(content) - 1 &&
memcmp(buffer, content, sizeof(content) - 1) == 0) {
if (strcmp(_animation_name, "one") != 0) {
return false;
}
_animation->tracks.resize(_skeleton.num_joints());
return true;
}
}
{ // Handles a single animation per file that needs renaming.
file_->Seek(0, ozz::io::File::kSet);
const char content[] = "good content renamed";
if (file_->Read(buffer, sizeof(buffer)) >= sizeof(content) - 1 &&
memcmp(buffer, content, sizeof(content) - 1) == 0) {
if (strcmp(_animation_name, "renamed?") != 0) {
return false;
}
_animation->tracks.resize(_skeleton.num_joints());
return true;
}
}
{ // Handles more than one animation per file.
file_->Seek(0, ozz::io::File::kSet);
const char content[] = "good content 2";
if (file_->Read(buffer, sizeof(buffer)) >= sizeof(content) - 1 &&
memcmp(buffer, content, sizeof(content) - 1) == 0) {
if (strcmp(_animation_name, "one") != 0 &&
strcmp(_animation_name, "TWO") != 0) {
return false;
}
_animation->tracks.resize(_skeleton.num_joints());
return true;
}
}
}
return false;
}
virtual NodeProperties GetNodeProperties(const char* _node_name) {
NodeProperties ppts;
bool found =
strcmp(_node_name, "joint0") == 0 || strcmp(_node_name, "joint1") == 0;
if (found) {
const NodeProperty ppt0 = {"property0", NodeProperty::kFloat1};
ppts.push_back(ppt0);
const NodeProperty ppt1 = {"property1", NodeProperty::kFloat1};
ppts.push_back(ppt1);
const NodeProperty ppt2 = {"property2", NodeProperty::kFloat2};
ppts.push_back(ppt2);
const NodeProperty ppt3 = {"property3", NodeProperty::kFloat3};
ppts.push_back(ppt3);
}
return ppts;
}
virtual bool Import(const char* _animation_name, const char* _node_name,
const char* _track_name, NodeProperty::Type _track_type,
float _sampling_rate,
ozz::animation::offline::RawFloatTrack* _track) {
(void)_animation_name;
(void)_track_type;
(void)_sampling_rate;
(void)_track;
// joint2 doesn't have the property
bool found = (strcmp(_node_name, "joint0") == 0 ||
strcmp(_node_name, "joint1") == 0) &&
(strcmp(_track_name, "property0") == 0 ||
strcmp(_track_name, "property1") == 0);
return found;
}
virtual bool Import(const char* _animation_name, const char* _node_name,
const char* _track_name, NodeProperty::Type _track_type,
float _sampling_rate,
ozz::animation::offline::RawFloat2Track* _track) {
(void)_animation_name;
(void)_track_type;
(void)_sampling_rate;
(void)_track;
// joint2 doesn't have the property
bool found = (strcmp(_node_name, "joint0") == 0 ||
strcmp(_node_name, "joint1") == 0) &&
strcmp(_track_name, "property2") == 0;
return found;
}
virtual bool Import(const char* _animation_name, const char* _node_name,
const char* _track_name, NodeProperty::Type _track_type,
float _sampling_rate,
ozz::animation::offline::RawFloat3Track* _track) {
(void)_animation_name;
(void)_track_type;
(void)_sampling_rate;
(void)_track;
// joint2 doesn't have the property
bool found = (strcmp(_node_name, "joint0") == 0 ||
strcmp(_node_name, "joint1") == 0) &&
strcmp(_track_name, "property3") == 0;
return found;
}
virtual bool Import(const char* _animation_name, const char* _node_name,
const char* _track_name, NodeProperty::Type _track_type,
float _sampling_rate,
ozz::animation::offline::RawFloat4Track* _track) {
(void)_animation_name;
(void)_track_type;
(void)_sampling_rate;
(void)_track;
// joint2 doesn't have the property
bool found = (strcmp(_node_name, "joint0") == 0 ||
strcmp(_node_name, "joint1") == 0) &&
strcmp(_track_name, "property4") == 0;
return found;
}
ozz::unique_ptr<ozz::io::File> file_;
};
int main(int _argc, const char** _argv) {
TestConverter converter;
return converter(_argc, _argv);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,402 @@
//----------------------------------------------------------------------------//
// //
// 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 "gtest/gtest.h"
#include "ozz/base/maths/gtest_math_helper.h"
#include "ozz/animation/offline/raw_track.h"
#include "ozz/animation/offline/track_builder.h"
using ozz::animation::offline::RawFloat2Track;
using ozz::animation::offline::RawFloat3Track;
using ozz::animation::offline::RawFloat4Track;
using ozz::animation::offline::RawFloatTrack;
using ozz::animation::offline::RawQuaternionTrack;
using ozz::animation::offline::RawTrackInterpolation;
using ozz::animation::offline::TrackOptimizer;
TEST(Error, TrackOptimizer) {
TrackOptimizer optimizer;
{ // nullptr output.
RawFloatTrack input;
EXPECT_TRUE(input.Validate());
// Builds animation
EXPECT_FALSE(optimizer(input, nullptr));
}
{ // Invalid input animation.
RawFloatTrack input;
input.keyframes.resize(1);
input.keyframes[0].ratio = 99.f;
EXPECT_FALSE(input.Validate());
// Builds animation
RawFloatTrack output;
output.keyframes.resize(1);
EXPECT_FALSE(optimizer(input, &output));
EXPECT_EQ(output.keyframes.size(), 0u);
}
}
TEST(Name, FloatTrackOptimizer) {
// Step keys aren't optimized.
TrackOptimizer optimizer;
RawFloatTrack raw_float_track;
raw_float_track.name = "FloatTrackOptimizer test";
RawFloatTrack output;
ASSERT_TRUE(optimizer(raw_float_track, &output));
EXPECT_STREQ(raw_float_track.name.c_str(), output.name.c_str());
}
TEST(OptimizeSteps, TrackOptimizer) {
// Step keys aren't optimized.
TrackOptimizer optimizer;
RawFloatTrack raw_float_track;
const RawFloatTrack::Keyframe key0 = {RawTrackInterpolation::kStep, .5f,
46.f};
raw_float_track.keyframes.push_back(key0);
const RawFloatTrack::Keyframe key1 = {RawTrackInterpolation::kStep, .7f, 0.f};
raw_float_track.keyframes.push_back(key1);
const RawFloatTrack::Keyframe key2 = {RawTrackInterpolation::kStep, .8f,
1e-9f};
raw_float_track.keyframes.push_back(key2);
RawFloatTrack output;
ASSERT_TRUE(optimizer(raw_float_track, &output));
EXPECT_EQ(output.keyframes.size(), 3u);
EXPECT_EQ(output.keyframes[0].interpolation, key0.interpolation);
EXPECT_FLOAT_EQ(output.keyframes[0].ratio, key0.ratio);
EXPECT_FLOAT_EQ(output.keyframes[0].value, key0.value);
EXPECT_EQ(output.keyframes[1].interpolation, key1.interpolation);
EXPECT_FLOAT_EQ(output.keyframes[1].ratio, key1.ratio);
EXPECT_FLOAT_EQ(output.keyframes[1].value, key1.value);
EXPECT_EQ(output.keyframes[2].interpolation, key2.interpolation);
EXPECT_FLOAT_EQ(output.keyframes[2].ratio, key2.ratio);
EXPECT_FLOAT_EQ(output.keyframes[2].value, key2.value);
}
TEST(OptimizeInterpolate, TrackOptimizer) {
// Step keys aren't optimized.
TrackOptimizer optimizer;
RawFloatTrack raw_float_track;
const RawFloatTrack::Keyframe key0 = {RawTrackInterpolation::kLinear, 0.f,
69.f};
raw_float_track.keyframes.push_back(key0);
const RawFloatTrack::Keyframe key1 = {RawTrackInterpolation::kLinear, .25f,
46.f};
raw_float_track.keyframes.push_back(key1);
const RawFloatTrack::Keyframe key2 = {RawTrackInterpolation::kLinear, .5f,
23.f};
raw_float_track.keyframes.push_back(key2);
const RawFloatTrack::Keyframe key3 = {RawTrackInterpolation::kLinear,
.500001f, 23.000001f};
raw_float_track.keyframes.push_back(key3);
const RawFloatTrack::Keyframe key4 = {RawTrackInterpolation::kLinear, .75f,
0.f};
raw_float_track.keyframes.push_back(key4);
const RawFloatTrack::Keyframe key5 = {RawTrackInterpolation::kLinear, .8f,
1e-12f};
raw_float_track.keyframes.push_back(key5);
const RawFloatTrack::Keyframe key6 = {RawTrackInterpolation::kLinear, 1.f,
-1e-12f};
raw_float_track.keyframes.push_back(key6);
{
RawFloatTrack output;
optimizer.tolerance = 1e-3f;
ASSERT_TRUE(optimizer(raw_float_track, &output));
EXPECT_EQ(output.keyframes.size(), 2u);
EXPECT_EQ(output.keyframes[0].interpolation, key0.interpolation);
EXPECT_FLOAT_EQ(output.keyframes[0].ratio, key0.ratio);
EXPECT_FLOAT_EQ(output.keyframes[0].value, key0.value);
EXPECT_EQ(output.keyframes[1].interpolation, key4.interpolation);
EXPECT_FLOAT_EQ(output.keyframes[1].ratio, key4.ratio);
EXPECT_FLOAT_EQ(output.keyframes[1].value, key4.value);
}
{
RawFloatTrack output;
optimizer.tolerance = 1e-9f;
ASSERT_TRUE(optimizer(raw_float_track, &output));
EXPECT_EQ(output.keyframes.size(), 4u);
EXPECT_EQ(output.keyframes[0].interpolation, key0.interpolation);
EXPECT_FLOAT_EQ(output.keyframes[0].ratio, key0.ratio);
EXPECT_FLOAT_EQ(output.keyframes[0].value, key0.value);
EXPECT_EQ(output.keyframes[1].interpolation, key2.interpolation);
EXPECT_FLOAT_EQ(output.keyframes[1].ratio, key2.ratio);
EXPECT_FLOAT_EQ(output.keyframes[1].value, key2.value);
EXPECT_EQ(output.keyframes[2].interpolation, key3.interpolation);
EXPECT_FLOAT_EQ(output.keyframes[2].ratio, key3.ratio);
EXPECT_FLOAT_EQ(output.keyframes[2].value, key3.value);
EXPECT_EQ(output.keyframes[3].interpolation, key4.interpolation);
EXPECT_FLOAT_EQ(output.keyframes[3].ratio, key4.ratio);
EXPECT_FLOAT_EQ(output.keyframes[3].value, key4.value);
}
}
TEST(float, TrackOptimizer) {
TrackOptimizer optimizer;
RawFloatTrack raw_track;
const RawFloatTrack::Keyframe key0 = {RawTrackInterpolation::kLinear, 0.f,
6.9f};
raw_track.keyframes.push_back(key0);
const RawFloatTrack::Keyframe key1 = {RawTrackInterpolation::kLinear, .25f,
4.6f};
raw_track.keyframes.push_back(key1);
const RawFloatTrack::Keyframe key2 = {RawTrackInterpolation::kLinear, .5f,
2.3f};
raw_track.keyframes.push_back(key2);
const RawFloatTrack::Keyframe key3 = {RawTrackInterpolation::kLinear,
.500001f, 2.300001f};
raw_track.keyframes.push_back(key3);
const RawFloatTrack::Keyframe key4 = {RawTrackInterpolation::kLinear, .75f,
0.f};
raw_track.keyframes.push_back(key4);
const RawFloatTrack::Keyframe key5 = {RawTrackInterpolation::kLinear, .8f,
1e-12f};
raw_track.keyframes.push_back(key5);
const RawFloatTrack::Keyframe key6 = {RawTrackInterpolation::kLinear, 1.f,
-1e-12f};
raw_track.keyframes.push_back(key6);
RawFloatTrack output;
ASSERT_TRUE(optimizer(raw_track, &output));
EXPECT_EQ(output.keyframes.size(), 2u);
EXPECT_EQ(output.keyframes[0].interpolation, key0.interpolation);
EXPECT_FLOAT_EQ(output.keyframes[0].ratio, key0.ratio);
EXPECT_FLOAT_EQ(output.keyframes[0].value, key0.value);
EXPECT_EQ(output.keyframes[1].interpolation, key4.interpolation);
EXPECT_FLOAT_EQ(output.keyframes[1].ratio, key4.ratio);
EXPECT_FLOAT_EQ(output.keyframes[1].value, key4.value);
}
TEST(Float2, TrackOptimizer) {
TrackOptimizer optimizer;
RawFloat2Track raw_track;
const RawFloat2Track::Keyframe key0 = {RawTrackInterpolation::kLinear, 0.f,
ozz::math::Float2(6.9f, 0.f)};
raw_track.keyframes.push_back(key0);
const RawFloat2Track::Keyframe key1 = {RawTrackInterpolation::kLinear, .25f,
ozz::math::Float2(4.6f, 0.f)};
raw_track.keyframes.push_back(key1);
const RawFloat2Track::Keyframe key2 = {RawTrackInterpolation::kLinear, .5f,
ozz::math::Float2(2.3f, 0.f)};
raw_track.keyframes.push_back(key2);
const RawFloat2Track::Keyframe key3 = {RawTrackInterpolation::kLinear,
.500001f,
ozz::math::Float2(2.3000001f, 0.f)};
raw_track.keyframes.push_back(key3);
const RawFloat2Track::Keyframe key4 = {RawTrackInterpolation::kLinear, .75f,
ozz::math::Float2(0.f, 0.f)};
raw_track.keyframes.push_back(key4);
const RawFloat2Track::Keyframe key5 = {RawTrackInterpolation::kLinear, .8f,
ozz::math::Float2(0.f, 1e-12f)};
raw_track.keyframes.push_back(key5);
const RawFloat2Track::Keyframe key6 = {RawTrackInterpolation::kLinear, 1.f,
ozz::math::Float2(-1e-12f, 0.f)};
raw_track.keyframes.push_back(key6);
RawFloat2Track output;
ASSERT_TRUE(optimizer(raw_track, &output));
EXPECT_EQ(output.keyframes.size(), 2u);
EXPECT_EQ(output.keyframes[0].interpolation, key0.interpolation);
EXPECT_FLOAT_EQ(output.keyframes[0].ratio, key0.ratio);
EXPECT_FLOAT2_EQ(output.keyframes[0].value, key0.value.x, key0.value.y);
EXPECT_EQ(output.keyframes[1].interpolation, key4.interpolation);
EXPECT_FLOAT_EQ(output.keyframes[1].ratio, key4.ratio);
EXPECT_FLOAT2_EQ(output.keyframes[1].value, key4.value.x, key4.value.y);
}
TEST(Float3, TrackOptimizer) {
TrackOptimizer optimizer;
RawFloat3Track raw_track;
const RawFloat3Track::Keyframe key0 = {RawTrackInterpolation::kLinear, 0.f,
ozz::math::Float3(6.9f, 0.f, 0.f)};
raw_track.keyframes.push_back(key0);
const RawFloat3Track::Keyframe key1 = {RawTrackInterpolation::kLinear, .25f,
ozz::math::Float3(4.6f, 0.f, 0.f)};
raw_track.keyframes.push_back(key1);
const RawFloat3Track::Keyframe key2 = {RawTrackInterpolation::kLinear, .5f,
ozz::math::Float3(2.3f, 0.f, 0.f)};
raw_track.keyframes.push_back(key2);
const RawFloat3Track::Keyframe key3 = {
RawTrackInterpolation::kLinear, .500001f,
ozz::math::Float3(2.3000001f, 0.f, 0.f)};
raw_track.keyframes.push_back(key3);
const RawFloat3Track::Keyframe key4 = {RawTrackInterpolation::kLinear, .75f,
ozz::math::Float3(0.f, 0.f, 0.f)};
raw_track.keyframes.push_back(key4);
const RawFloat3Track::Keyframe key5 = {RawTrackInterpolation::kLinear, .8f,
ozz::math::Float3(0.f, 0.f, 1e-12f)};
raw_track.keyframes.push_back(key5);
const RawFloat3Track::Keyframe key6 = {RawTrackInterpolation::kLinear, 1.f,
ozz::math::Float3(0.f, -1e-12f, 0.f)};
raw_track.keyframes.push_back(key6);
RawFloat3Track output;
ASSERT_TRUE(optimizer(raw_track, &output));
EXPECT_EQ(output.keyframes.size(), 2u);
EXPECT_EQ(output.keyframes[0].interpolation, key0.interpolation);
EXPECT_FLOAT_EQ(output.keyframes[0].ratio, key0.ratio);
EXPECT_FLOAT3_EQ(output.keyframes[0].value, key0.value.x, key0.value.y,
key0.value.z);
EXPECT_EQ(output.keyframes[1].interpolation, key4.interpolation);
EXPECT_FLOAT_EQ(output.keyframes[1].ratio, key4.ratio);
EXPECT_FLOAT3_EQ(output.keyframes[1].value, key4.value.x, key4.value.y,
key4.value.z);
}
TEST(Float4, TrackOptimizer) {
TrackOptimizer optimizer;
RawFloat4Track raw_track;
const RawFloat4Track::Keyframe key0 = {
RawTrackInterpolation::kLinear, 0.f,
ozz::math::Float4(6.9f, 0.f, 0.f, 0.f)};
raw_track.keyframes.push_back(key0);
const RawFloat4Track::Keyframe key1 = {
RawTrackInterpolation::kLinear, .25f,
ozz::math::Float4(4.6f, 0.f, 0.f, 0.f)};
raw_track.keyframes.push_back(key1);
const RawFloat4Track::Keyframe key2 = {
RawTrackInterpolation::kLinear, .5f,
ozz::math::Float4(2.3f, 0.f, 0.f, 0.f)};
raw_track.keyframes.push_back(key2);
const RawFloat4Track::Keyframe key3 = {
RawTrackInterpolation::kLinear, .500001f,
ozz::math::Float4(2.3000001f, 0.f, 0.f, 0.f)};
raw_track.keyframes.push_back(key3);
const RawFloat4Track::Keyframe key4 = {RawTrackInterpolation::kLinear, .75f,
ozz::math::Float4(0.f, 0.f, 0.f, 0.f)};
raw_track.keyframes.push_back(key4);
const RawFloat4Track::Keyframe key5 = {
RawTrackInterpolation::kLinear, .8f,
ozz::math::Float4(0.f, 0.f, 0.f, 1e-12f)};
raw_track.keyframes.push_back(key5);
const RawFloat4Track::Keyframe key6 = {
RawTrackInterpolation::kLinear, 1.f,
ozz::math::Float4(0.f, 0.f, 0.f, -1e-12f)};
raw_track.keyframes.push_back(key6);
RawFloat4Track output;
ASSERT_TRUE(optimizer(raw_track, &output));
EXPECT_EQ(output.keyframes.size(), 2u);
EXPECT_EQ(output.keyframes[0].interpolation, key0.interpolation);
EXPECT_FLOAT_EQ(output.keyframes[0].ratio, key0.ratio);
EXPECT_FLOAT4_EQ(output.keyframes[0].value, key0.value.x, key0.value.y,
key0.value.z, key0.value.w);
EXPECT_EQ(output.keyframes[1].interpolation, key4.interpolation);
EXPECT_FLOAT_EQ(output.keyframes[1].ratio, key4.ratio);
EXPECT_FLOAT4_EQ(output.keyframes[1].value, key4.value.x, key4.value.y,
key4.value.z, key0.value.w);
}
TEST(Quaternion, TrackOptimizer) {
TrackOptimizer optimizer;
RawQuaternionTrack raw_track;
const RawQuaternionTrack::Keyframe key0 = {
RawTrackInterpolation::kLinear, 0.f,
ozz::math::Quaternion(.70710677f, 0.f, 0.f, .70710677f)};
raw_track.keyframes.push_back(key0);
const RawQuaternionTrack::Keyframe key1 = {
RawTrackInterpolation::kLinear, .1f,
ozz::math::Quaternion(.6172133f, .1543033f, 0.f, .7715167f)}; // NLerp
raw_track.keyframes.push_back(key1);
const RawQuaternionTrack::Keyframe key2 = {
RawTrackInterpolation::kLinear, .5f,
ozz::math::Quaternion(0.f, .70710677f, 0.f, .70710677f)};
raw_track.keyframes.push_back(key2);
const RawQuaternionTrack::Keyframe key3 = {
RawTrackInterpolation::kLinear, .500001f,
ozz::math::Quaternion(0.f, .70710676f, 0.f, .70710678f)};
raw_track.keyframes.push_back(key3);
const RawQuaternionTrack::Keyframe key4 = {
RawTrackInterpolation::kLinear, .75f,
ozz::math::Quaternion(0.f, .70710677f, 0.f, .70710677f)};
raw_track.keyframes.push_back(key4);
const RawQuaternionTrack::Keyframe key5 = {
RawTrackInterpolation::kLinear, .8f,
ozz::math::Quaternion(-0.f, -0.70710677f, -0.f, -.70710677f)};
raw_track.keyframes.push_back(key5);
const RawQuaternionTrack::Keyframe key6 = {
RawTrackInterpolation::kLinear, 1.f,
ozz::math::Quaternion(0.f, .70710677f, 0.f, .70710677f)};
raw_track.keyframes.push_back(key6);
RawQuaternionTrack output;
ASSERT_TRUE(optimizer(raw_track, &output));
EXPECT_EQ(output.keyframes.size(), 2u);
EXPECT_EQ(output.keyframes[0].interpolation, key0.interpolation);
EXPECT_FLOAT_EQ(output.keyframes[0].ratio, key0.ratio);
EXPECT_QUATERNION_EQ(output.keyframes[0].value, key0.value.x, key0.value.y,
key0.value.z, key0.value.w);
EXPECT_EQ(output.keyframes[1].interpolation, key2.interpolation);
EXPECT_FLOAT_EQ(output.keyframes[1].ratio, key2.ratio);
EXPECT_QUATERNION_EQ(output.keyframes[1].value, key2.value.x, key2.value.y,
key2.value.z, key2.value.w);
}
@@ -0,0 +1,155 @@
# sampling_job_tests
add_executable(test_sampling_job
sampling_job_tests.cc)
target_link_libraries(test_sampling_job
ozz_animation_offline
gtest)
set_target_properties(test_sampling_job PROPERTIES FOLDER "ozz/tests/animation")
add_test(NAME test_sampling_job COMMAND test_sampling_job)
# blending_job_tests
add_executable(test_blending_job
blending_job_tests.cc)
target_link_libraries(test_blending_job
ozz_animation_offline
gtest)
set_target_properties(test_blending_job PROPERTIES FOLDER "ozz/tests/animation")
add_test(NAME test_blending_job COMMAND test_blending_job)
# local_to_model_job_tests
add_executable(test_local_to_model_job
local_to_model_job_tests.cc)
target_link_libraries(test_local_to_model_job
ozz_animation_offline
gtest)
set_target_properties(test_local_to_model_job PROPERTIES FOLDER "ozz/tests/animation")
add_test(NAME test_local_to_model_job COMMAND test_local_to_model_job)
add_executable(test_animation_archive
animation_archive_tests.cc)
target_link_libraries(test_animation_archive
ozz_animation_offline
gtest)
set_target_properties(test_animation_archive PROPERTIES FOLDER "ozz/tests/animation")
add_test(NAME test_animation_archive COMMAND test_animation_archive)
add_executable(test_animation_archive_versioning
animation_archive_versioning_tests.cc)
target_link_libraries(test_animation_archive_versioning
ozz_animation
ozz_options
gtest)
set_target_properties(test_animation_archive_versioning PROPERTIES FOLDER "ozz/tests/animation")
add_test(NAME test_animation_archive_versioning_le COMMAND test_animation_archive_versioning "--file=${ozz_media_directory}/bin/versioning/animation_v6_le.ozz" "--tracks=67" "--duration=.66666667" "--name=run")
add_test(NAME test_animation_archive_versioning_be COMMAND test_animation_archive_versioning "--file=${ozz_media_directory}/bin/versioning/animation_v6_be.ozz" "--tracks=67" "--duration=.66666667" "--name=run")
# Previous versions.
add_test(NAME test_animation_archive_versioning_le_older5 COMMAND test_animation_archive_versioning "--file=${ozz_media_directory}/bin/versioning/animation_v5_le.ozz" "--tracks=67" "--duration=.66666667" "--name=")
set_tests_properties(test_animation_archive_versioning_le_older5 PROPERTIES WILL_FAIL true)
add_test(NAME test_animation_archive_versioning_le_older4 COMMAND test_animation_archive_versioning "--file=${ozz_media_directory}/bin/versioning/animation_v4_le.ozz" "--tracks=67" "--duration=.66666667" "--name=")
set_tests_properties(test_animation_archive_versioning_le_older4 PROPERTIES WILL_FAIL true)
add_test(NAME test_animation_archive_versioning_le_older3 COMMAND test_animation_archive_versioning "--file=${ozz_media_directory}/bin/versioning/animation_v3_le.ozz" "--tracks=67" "--duration=.66666667" "--name=")
set_tests_properties(test_animation_archive_versioning_le_older3 PROPERTIES WILL_FAIL true)
add_test(NAME test_animation_archive_versioning_le_older2 COMMAND test_animation_archive_versioning "--file=${ozz_media_directory}/bin/versioning/animation_v2_le.ozz" "--tracks=67" "--duration=1.33333302" "--name=")
set_tests_properties(test_animation_archive_versioning_le_older2 PROPERTIES WILL_FAIL true)
add_test(NAME test_animation_archive_versioning_le_older1 COMMAND test_animation_archive_versioning "--file=${ozz_media_directory}/bin/versioning/animation_v1_le.ozz" "--tracks=67" "--duration=.66666698" "--name=")
set_tests_properties(test_animation_archive_versioning_le_older1 PROPERTIES WILL_FAIL true)
add_executable(test_skeleton_archive
skeleton_archive_tests.cc)
target_link_libraries(test_skeleton_archive
ozz_animation_offline
gtest)
set_target_properties(test_skeleton_archive PROPERTIES FOLDER "ozz/tests/animation")
add_test(NAME test_skeleton_archive COMMAND test_skeleton_archive)
add_executable(test_skeleton_archive_versioning
skeleton_archive_versioning_tests.cc)
target_link_libraries(test_skeleton_archive_versioning
ozz_animation
ozz_options
gtest)
set_target_properties(test_skeleton_archive_versioning PROPERTIES FOLDER "ozz/tests/animation")
# Previous skeleton versions.
add_test(NAME test_skeleton_archive_versioning_le_older1 COMMAND test_skeleton_archive_versioning "--file=${ozz_media_directory}/bin/versioning/skeleton_v1_le.ozz" "--joints=67" "--root_name=Hips")
set_tests_properties(test_skeleton_archive_versioning_le_older1 PROPERTIES WILL_FAIL true)
# Current skeleton version.
add_test(NAME test_skeleton_archive_versioning_le COMMAND test_skeleton_archive_versioning "--file=${ozz_media_directory}/bin/versioning/skeleton_v2_le.ozz" "--joints=67" "--root_name=Hips")
add_test(NAME test_skeleton_archive_versioning_be COMMAND test_skeleton_archive_versioning "--file=${ozz_media_directory}/bin/versioning/skeleton_v2_be.ozz" "--joints=67" "--root_name=Hips")
add_executable(test_skeleton_utils
skeleton_utils_tests.cc)
target_link_libraries(test_skeleton_utils
ozz_animation_offline
gtest)
set_target_properties(test_skeleton_utils PROPERTIES FOLDER "ozz/tests/animation")
add_test(NAME test_skeleton_utils COMMAND test_skeleton_utils)
add_executable(test_animation_utils
animation_utils_tests.cc)
target_link_libraries(test_animation_utils
ozz_animation_offline
gtest)
set_target_properties(test_animation_utils PROPERTIES FOLDER "ozz/tests/animation")
add_test(NAME test_animation_utils COMMAND test_skeleton_utils)
# track_sampling_job_tests
add_executable(test_track_sampling_job
track_sampling_job_tests.cc)
target_link_libraries(test_track_sampling_job
ozz_animation_offline
ozz_animation
ozz_base
gtest)
set_target_properties(test_track_sampling_job PROPERTIES FOLDER "ozz/tests/animation")
add_test(NAME test_track_sampling_job COMMAND test_track_sampling_job)
# test_track_triggering_job
add_executable(test_track_triggering_job
track_triggering_job_tests.cc
track_triggering_job_trait_tests.cc)
target_link_libraries(test_track_triggering_job
ozz_animation_offline
ozz_animation
ozz_base
gtest)
set_target_properties(test_track_triggering_job PROPERTIES FOLDER "ozz/tests/animation")
add_test(NAME test_track_triggering_job COMMAND test_track_triggering_job)
add_executable(test_track_archive
track_archive_tests.cc)
target_link_libraries(test_track_archive
ozz_animation_offline
gtest)
set_target_properties(test_track_archive PROPERTIES FOLDER "ozz/tests/animation")
add_test(NAME test_track_archive COMMAND test_track_archive)
add_executable(test_ik_aim_job
ik_aim_job_tests.cc)
target_link_libraries(test_ik_aim_job
ozz_animation
gtest)
set_target_properties(test_ik_aim_job PROPERTIES FOLDER "ozz/tests/animation")
add_test(NAME test_ik_aim_job COMMAND test_ik_aim_job)
add_executable(test_ik_two_bone_job
ik_two_bone_job_tests.cc)
target_link_libraries(test_ik_two_bone_job
ozz_animation
gtest)
set_target_properties(test_ik_two_bone_job PROPERTIES FOLDER "ozz/tests/animation")
add_test(NAME test_ik_two_bone_job COMMAND test_ik_two_bone_job)
# ozz_animation fuse tests
set_source_files_properties(${PROJECT_BINARY_DIR}/src_fused/ozz_animation.cc PROPERTIES GENERATED 1)
add_executable(test_fuse_animation
sampling_job_tests.cc
${PROJECT_BINARY_DIR}/src_fused/ozz_animation.cc)
add_dependencies(test_fuse_animation BUILD_FUSE_ozz_animation)
target_link_libraries(test_fuse_animation
ozz_animation_offline
gtest)
add_test(NAME test_fuse_animation COMMAND test_fuse_animation)
set_target_properties(test_fuse_animation PROPERTIES FOLDER "ozz/tests/animation")
@@ -0,0 +1,185 @@
//----------------------------------------------------------------------------//
// //
// 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 "gtest/gtest.h"
#include "ozz/base/maths/gtest_math_helper.h"
#include "ozz/base/io/archive.h"
#include "ozz/base/io/stream.h"
#include "ozz/base/memory/unique_ptr.h"
#include "ozz/base/maths/soa_transform.h"
#include "ozz/animation/runtime/sampling_job.h"
#include "ozz/animation/offline/animation_builder.h"
#include "ozz/animation/offline/raw_animation.h"
using ozz::animation::Animation;
using ozz::animation::offline::RawAnimation;
using ozz::animation::offline::AnimationBuilder;
TEST(Empty, AnimationSerialize) {
ozz::io::MemoryStream stream;
// Streams out.
ozz::io::OArchive o(&stream, ozz::GetNativeEndianness());
Animation o_animation;
o << o_animation;
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive i(&stream);
Animation i_animation;
i >> i_animation;
EXPECT_EQ(o_animation.num_tracks(), i_animation.num_tracks());
}
TEST(Filled, AnimationSerialize) {
// Builds a valid animation.
ozz::unique_ptr<Animation> o_animation;
{
RawAnimation raw_animation;
raw_animation.duration = 1.f;
raw_animation.tracks.resize(1);
RawAnimation::TranslationKey t_key0 = {0.f,
ozz::math::Float3(93.f, 58.f, 46.f)};
raw_animation.tracks[0].translations.push_back(t_key0);
RawAnimation::TranslationKey t_key1 = {.9f,
ozz::math::Float3(46.f, 58.f, 93.f)};
raw_animation.tracks[0].translations.push_back(t_key1);
RawAnimation::RotationKey r_key = {
0.7f, ozz::math::Quaternion(0.f, 1.f, 0.f, 0.f)};
raw_animation.tracks[0].rotations.push_back(r_key);
RawAnimation::ScaleKey s_key = {0.1f, ozz::math::Float3(99.f, 26.f, 14.f)};
raw_animation.tracks[0].scales.push_back(s_key);
AnimationBuilder builder;
o_animation = builder(raw_animation);
ASSERT_TRUE(o_animation);
}
for (int e = 0; e < 2; ++e) {
ozz::Endianness endianess = e == 0 ? ozz::kBigEndian : ozz::kLittleEndian;
ozz::io::MemoryStream stream;
// Streams out.
ozz::io::OArchive o(&stream, endianess);
o << *o_animation;
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive i(&stream);
Animation i_animation;
i >> i_animation;
ASSERT_FLOAT_EQ(o_animation->duration(), i_animation.duration());
ASSERT_EQ(o_animation->num_tracks(), i_animation.num_tracks());
EXPECT_EQ(o_animation->size(), i_animation.size());
// Needs to sample to test the animation.
ozz::animation::SamplingJob job;
ozz::animation::SamplingCache cache(1);
ozz::math::SoaTransform output[1];
job.animation = o_animation.get();
job.cache = &cache;
job.output = output;
// Samples and compares the two animations
{ // Samples at t = 0
job.ratio = 0.f;
job.Run();
EXPECT_SOAFLOAT3_EQ_EST(output[0].translation, 93.f, 0.f, 0.f, 0.f, 58.f,
0.f, 0.f, 0.f, 46.f, 0.f, 0.f, 0.f);
EXPECT_SOAQUATERNION_EQ_EST(output[0].rotation, 0.f, 0.f, 0.f, 0.f, 1.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f,
1.f, 1.f);
EXPECT_SOAFLOAT3_EQ_EST(output[0].scale, 99.f, 1.f, 1.f, 1.f, 26.f, 1.f,
1.f, 1.f, 14.f, 1.f, 1.f, 1.f);
}
{ // Samples at t = 1
job.ratio = 1.f;
job.Run();
EXPECT_SOAFLOAT3_EQ_EST(output[0].translation, 46.f, 0.f, 0.f, 0.f, 58.f,
0.f, 0.f, 0.f, 93.f, 0.f, 0.f, 0.f);
EXPECT_SOAQUATERNION_EQ_EST(output[0].rotation, 0.f, 0.f, 0.f, 0.f, 1.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f,
1.f, 1.f);
EXPECT_SOAFLOAT3_EQ_EST(output[0].scale, 99.f, 1.f, 1.f, 1.f, 26.f, 1.f,
1.f, 1.f, 14.f, 1.f, 1.f, 1.f);
}
}
}
TEST(AlreadyInitialized, AnimationSerialize) {
ozz::io::MemoryStream stream;
{
ozz::io::OArchive o(&stream);
RawAnimation raw_animation;
raw_animation.duration = 1.f;
raw_animation.tracks.resize(1);
AnimationBuilder builder;
ozz::unique_ptr<Animation> o_animation(builder(raw_animation));
ASSERT_TRUE(o_animation);
o << *o_animation;
raw_animation.duration = 2.f;
raw_animation.tracks.resize(2);
o_animation = builder(raw_animation);
ASSERT_TRUE(o_animation);
o << *o_animation;
}
{
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive i(&stream);
// Reads and check the first animation.
Animation i_animation;
i >> i_animation;
EXPECT_FLOAT_EQ(i_animation.duration(), 1.f);
EXPECT_EQ(i_animation.num_tracks(), 1);
// Reuse the animation a second time.
i >> i_animation;
EXPECT_FLOAT_EQ(i_animation.duration(), 2.f);
ASSERT_EQ(i_animation.num_tracks(), 2);
}
}
@@ -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/animation/runtime/animation.h"
#include "gtest/gtest.h"
#include "ozz/base/io/archive.h"
#include "ozz/base/io/stream.h"
#include "ozz/base/log.h"
#include "ozz/options/options.h"
OZZ_OPTIONS_DECLARE_STRING(file, "Specifies input file", "", true)
OZZ_OPTIONS_DECLARE_INT(tracks, "Number of tracks", 0, true)
OZZ_OPTIONS_DECLARE_FLOAT(duration, "Duration", 0.f, true)
OZZ_OPTIONS_DECLARE_STRING(name, "Name", "", true)
int main(int _argc, char** _argv) {
// Parses arguments.
testing::InitGoogleTest(&_argc, _argv);
ozz::options::ParseResult parse_result = ozz::options::ParseCommandLine(
_argc, _argv, "1.0",
"Test Animation archive versioning retrocompatibility");
if (parse_result != ozz::options::kSuccess) {
return parse_result == ozz::options::kExitSuccess ? EXIT_SUCCESS
: EXIT_FAILURE;
}
return RUN_ALL_TESTS();
}
TEST(Versioning, AnimationSerialize) {
// Open the file.
const char* filename = OPTIONS_file;
ozz::io::File file(filename, "rb");
ASSERT_TRUE(file.opened());
// Open archive and test object tag.
ozz::io::IArchive archive(&file);
ASSERT_TRUE(archive.TestTag<ozz::animation::Animation>());
// Read the object.
ozz::animation::Animation animation;
archive >> animation;
// More testing
EXPECT_EQ(animation.num_tracks(), OPTIONS_tracks);
EXPECT_FLOAT_EQ(animation.duration(), OPTIONS_duration);
EXPECT_STREQ(animation.name(), OPTIONS_name);
}
@@ -0,0 +1,85 @@
//----------------------------------------------------------------------------//
// //
// 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"
#include "gtest/gtest.h"
#include "ozz/base/maths/gtest_math_helper.h"
#include "ozz/base/memory/unique_ptr.h"
#include "ozz/animation/offline/animation_builder.h"
#include "ozz/animation/offline/raw_animation.h"
using ozz::animation::Animation;
using ozz::animation::offline::RawAnimation;
using ozz::animation::offline::AnimationBuilder;
TEST(CountKeyframes, AnimationUtils) {
// Builds a valid animation.
ozz::unique_ptr<Animation> animation;
{
RawAnimation raw_animation;
raw_animation.duration = 1.f;
raw_animation.tracks.resize(2);
RawAnimation::TranslationKey t_key0 = {0.f,
ozz::math::Float3(93.f, 58.f, 46.f)};
raw_animation.tracks[0].translations.push_back(t_key0);
RawAnimation::TranslationKey t_key1 = {.9f,
ozz::math::Float3(46.f, 58.f, 93.f)};
raw_animation.tracks[0].translations.push_back(t_key1);
RawAnimation::TranslationKey t_key2 = {1.f,
ozz::math::Float3(46.f, 58.f, 99.f)};
raw_animation.tracks[0].translations.push_back(t_key2);
RawAnimation::RotationKey r_key = {
0.7f, ozz::math::Quaternion(0.f, 1.f, 0.f, 0.f)};
raw_animation.tracks[0].rotations.push_back(r_key);
RawAnimation::ScaleKey s_key = {0.1f, ozz::math::Float3(99.f, 26.f, 14.f)};
raw_animation.tracks[1].scales.push_back(s_key);
AnimationBuilder builder;
animation = builder(raw_animation);
ASSERT_TRUE(animation);
}
// 4 more tracks than expected due to SoA
EXPECT_EQ(ozz::animation::CountTranslationKeyframes(*animation, -1), 9);
EXPECT_EQ(ozz::animation::CountTranslationKeyframes(*animation, 0), 3);
EXPECT_EQ(ozz::animation::CountTranslationKeyframes(*animation, 1), 2);
EXPECT_EQ(ozz::animation::CountRotationKeyframes(*animation, -1), 8);
EXPECT_EQ(ozz::animation::CountRotationKeyframes(*animation, 0), 2);
EXPECT_EQ(ozz::animation::CountRotationKeyframes(*animation, 1), 2);
EXPECT_EQ(ozz::animation::CountScaleKeyframes(*animation, -1), 8);
EXPECT_EQ(ozz::animation::CountScaleKeyframes(*animation, 0), 2);
EXPECT_EQ(ozz::animation::CountScaleKeyframes(*animation, 1), 2);
}
@@ -0,0 +1,871 @@
//----------------------------------------------------------------------------//
// //
// 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 "gtest/gtest.h"
#include "ozz/animation/runtime/blending_job.h"
#include "ozz/base/maths/gtest_math_helper.h"
#include "ozz/base/maths/soa_transform.h"
using ozz::animation::BlendingJob;
TEST(JobValidity, BlendingJob) {
const ozz::math::SoaTransform identity = ozz::math::SoaTransform::identity();
const ozz::math::SimdFloat4 zero = ozz::math::simd_float4::zero();
BlendingJob::Layer layers[2];
const ozz::math::SoaTransform bind_poses[3] = {identity, identity, identity};
const ozz::math::SoaTransform input_transforms[3] = {identity, identity,
identity};
ozz::math::SoaTransform output_transforms[3] = {identity, identity, identity};
ozz::math::SimdFloat4 joint_weights[3] = {zero, zero, zero};
layers[0].transform = input_transforms;
layers[1].transform = {input_transforms, input_transforms + 2};
{ // Empty/default job.
BlendingJob job;
EXPECT_FALSE(job.Validate());
EXPECT_FALSE(job.Run());
}
{ // Invalid output.
BlendingJob job;
job.layers = {layers, layers + 2};
job.bind_pose = {bind_poses, bind_poses + 2};
EXPECT_FALSE(job.Validate());
EXPECT_FALSE(job.Run());
}
{ // Layers are optional.
BlendingJob job;
job.bind_pose = {bind_poses, bind_poses + 2};
job.output = {output_transforms, output_transforms + 2};
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
}
{ // Invalid bind pose.
BlendingJob job;
job.layers = {layers, layers + 2};
job.output = {output_transforms, output_transforms + 2};
EXPECT_FALSE(job.Validate());
EXPECT_FALSE(job.Run());
}
{ // Invalid layer input range, too small.
BlendingJob::Layer invalid_layers[2];
invalid_layers[0].transform = {input_transforms, input_transforms + 1};
invalid_layers[1].transform = {input_transforms, input_transforms + 2};
BlendingJob job;
job.layers = invalid_layers;
job.bind_pose = {bind_poses, bind_poses + 2};
job.output = {output_transforms, output_transforms + 2};
EXPECT_FALSE(job.Validate());
EXPECT_FALSE(job.Run());
}
{ // Invalid output range, smaller output.
BlendingJob job;
job.layers = {layers, layers + 2};
job.bind_pose = {bind_poses, bind_poses + 2};
job.output = {output_transforms, output_transforms + 1};
EXPECT_FALSE(job.Validate());
EXPECT_FALSE(job.Run());
}
{ // Invalid smaller input.
BlendingJob job;
job.layers = {layers, layers + 2};
job.bind_pose = {bind_poses, bind_poses + 3};
job.output = {output_transforms, output_transforms + 3};
EXPECT_FALSE(job.Validate());
EXPECT_FALSE(job.Run());
}
{ // Invalid threshold.
BlendingJob job;
job.threshold = 0.f;
job.layers = {layers, layers + 2};
job.bind_pose = {bind_poses, bind_poses + 2};
job.output = {output_transforms, output_transforms + 2};
EXPECT_FALSE(job.Validate());
EXPECT_FALSE(job.Run());
}
{ // Invalid joint weights range.
layers[0].joint_weights = {joint_weights, joint_weights + 1};
BlendingJob job;
job.layers = {layers, layers + 2};
job.bind_pose = {bind_poses, bind_poses + 2};
job.output = {output_transforms, output_transforms + 2};
EXPECT_FALSE(job.Validate());
EXPECT_FALSE(job.Run());
}
{ // Valid job.
layers[0].joint_weights = {nullptr, nullptr};
BlendingJob job;
job.layers = {layers, layers + 2};
job.bind_pose = {bind_poses, bind_poses + 2};
job.output = {output_transforms, output_transforms + 2};
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
}
{ // Valid joint weights range.
layers[0].joint_weights = {joint_weights, joint_weights + 2};
BlendingJob job;
job.layers = {layers, layers + 2};
job.bind_pose = {bind_poses, bind_poses + 2};
job.output = {output_transforms, output_transforms + 2};
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
}
{ // Valid job, bigger output.
layers[0].joint_weights = {joint_weights, joint_weights + 2};
BlendingJob job;
job.layers = {layers, layers + 2};
job.bind_pose = {bind_poses, bind_poses + 2};
job.output = {output_transforms, output_transforms + 3};
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
}
{ // Valid no layers.
BlendingJob job;
job.bind_pose = {bind_poses, bind_poses + 2};
job.output = {output_transforms, output_transforms + 2};
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
}
}
TEST(JobValidityAdditive, BlendingJob) {
const ozz::math::SoaTransform identity = ozz::math::SoaTransform::identity();
const ozz::math::SimdFloat4 zero = ozz::math::simd_float4::zero();
BlendingJob::Layer layers[2];
BlendingJob::Layer additive_layers[2];
const ozz::math::SoaTransform bind_poses[3] = {identity, identity, identity};
const ozz::math::SoaTransform input_transforms[3] = {identity, identity,
identity};
ozz::math::SoaTransform output_transforms[3] = {identity, identity, identity};
ozz::math::SimdFloat4 joint_weights[3] = {zero, zero, zero};
layers[0].transform = input_transforms;
layers[1].transform = input_transforms;
additive_layers[0].transform = input_transforms;
additive_layers[1].transform = input_transforms;
{ // Valid additive job, no normal blending.
BlendingJob job;
job.additive_layers = additive_layers;
job.bind_pose = bind_poses;
job.output = output_transforms;
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
}
{ // Valid additive job, with normal blending also.
BlendingJob job;
job.layers = layers;
job.additive_layers = additive_layers;
job.bind_pose = bind_poses;
job.output = output_transforms;
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
}
{ // Invalid layer input range, too small.
BlendingJob::Layer invalid_layers[2];
invalid_layers[0].transform = {input_transforms, input_transforms + 1};
invalid_layers[1].transform = {input_transforms, input_transforms + 2};
BlendingJob job;
job.layers = layers;
job.additive_layers = invalid_layers;
job.bind_pose = {bind_poses, bind_poses + 2};
job.output = {output_transforms, output_transforms + 2};
EXPECT_FALSE(job.Validate());
EXPECT_FALSE(job.Run());
}
{ // Valid additive job, with per-joint weights.
layers[0].joint_weights = {joint_weights, joint_weights + 2};
BlendingJob job;
job.additive_layers = additive_layers;
job.bind_pose = {bind_poses, bind_poses + 2};
job.output = {output_transforms, output_transforms + 2};
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
}
}
TEST(Empty, BlendingJob) {
const ozz::math::SoaTransform identity = ozz::math::SoaTransform::identity();
// Initialize bind pose.
ozz::math::SoaTransform bind_poses[2] = {identity, identity};
bind_poses[0].translation = ozz::math::SoaFloat3::Load(
ozz::math::simd_float4::Load(0.f, 1.f, 2.f, 3.f),
ozz::math::simd_float4::Load(4.f, 5.f, 6.f, 7.f),
ozz::math::simd_float4::Load(8.f, 9.f, 10.f, 11.f));
bind_poses[0].scale = ozz::math::SoaFloat3::Load(
ozz::math::simd_float4::Load(0.f, 10.f, 20.f, 30.f),
ozz::math::simd_float4::Load(40.f, 50.f, 60.f, 70.f),
ozz::math::simd_float4::Load(80.f, 90.f, 100.f, 110.f));
bind_poses[1].translation = bind_poses[0].translation *
ozz::math::simd_float4::Load(2.f, 2.f, 2.f, 2.f);
bind_poses[1].scale =
bind_poses[0].scale * ozz::math::simd_float4::Load(2.f, 2.f, 2.f, 2.f);
BlendingJob job;
job.bind_pose = bind_poses;
ozz::math::SoaTransform output_transforms[2];
job.output = output_transforms;
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 0.f, 1.f, 2.f, 3.f, 4.f,
5.f, 6.f, 7.f, 8.f, 9.f, 10.f, 11.f);
EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 0.f, 10.f, 20.f, 30.f, 40.f,
50.f, 60.f, 70.f, 80.f, 90.f, 100.f, 110.f);
EXPECT_SOAFLOAT3_EQ(output_transforms[1].translation, 0.f, 2.f, 4.f, 6.f, 8.f,
10.f, 12.f, 14.f, 16.f, 18.f, 20.f, 22.f);
EXPECT_SOAFLOAT3_EQ(output_transforms[1].scale, 0.f, 20.f, 40.f, 60.f, 80.f,
100.f, 120.f, 140.f, 160.f, 180.f, 200.f, 220.f);
}
TEST(Weight, BlendingJob) {
const ozz::math::SoaTransform identity = ozz::math::SoaTransform::identity();
// Initialize inputs.
ozz::math::SoaTransform input_transforms[2][2] = {{identity, identity},
{identity, identity}};
input_transforms[0][0].translation = ozz::math::SoaFloat3::Load(
ozz::math::simd_float4::Load(0.f, 1.f, 2.f, 3.f),
ozz::math::simd_float4::Load(4.f, 5.f, 6.f, 7.f),
ozz::math::simd_float4::Load(8.f, 9.f, 10.f, 11.f));
input_transforms[0][1].translation = ozz::math::SoaFloat3::Load(
ozz::math::simd_float4::Load(12.f, 13.f, 14.f, 15.f),
ozz::math::simd_float4::Load(16.f, 17.f, 18.f, 19.f),
ozz::math::simd_float4::Load(20.f, 21.f, 22.f, 23.f));
input_transforms[1][0].translation = -input_transforms[0][0].translation;
input_transforms[1][1].translation = -input_transforms[0][1].translation;
// Initialize bind pose.
ozz::math::SoaTransform bind_poses[2] = {identity, identity};
bind_poses[0].scale = ozz::math::SoaFloat3::Load(
ozz::math::simd_float4::Load(0.f, 1.f, 2.f, 3.f),
ozz::math::simd_float4::Load(4.f, 5.f, 6.f, 7.f),
ozz::math::simd_float4::Load(8.f, 9.f, 10.f, 11.f));
bind_poses[1].scale =
bind_poses[0].scale * ozz::math::simd_float4::Load(2.f, 2.f, 2.f, 2.f);
{
BlendingJob::Layer layers[2];
layers[0].transform = input_transforms[0];
layers[1].transform = input_transforms[1];
ozz::math::SoaTransform output_transforms[2];
BlendingJob job;
job.layers = layers;
job.bind_pose = bind_poses;
job.output = output_transforms;
// Weight 0 (a bit less must give the same result) for the first layer,
// 1 for the second.
layers[0].weight = -.07f;
layers[1].weight = 1.f;
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, -0.f, -1.f, -2.f,
-3.f, -4.f, -5.f, -6.f, -7.f, -8.f, -9.f, -10.f, -11.f);
EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);
EXPECT_SOAFLOAT3_EQ(output_transforms[1].translation, -12.f, -13.f, -14.f,
-15.f, -16.f, -17.f, -18.f, -19.f, -20.f, -21.f, -22.f,
-23.f);
EXPECT_SOAFLOAT3_EQ(output_transforms[1].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);
// Weight 1 for the first layer, 0 for the second.
layers[0].weight = 1.f;
layers[1].weight = 1e-27f; // Very low weight value.
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 0.f, 1.f, 2.f, 3.f,
4.f, 5.f, 6.f, 7.f, 8.f, 9.f, 10.f, 11.f);
EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);
EXPECT_SOAFLOAT3_EQ(output_transforms[1].translation, 12.f, 13.f, 14.f,
15.f, 16.f, 17.f, 18.f, 19.f, 20.f, 21.f, 22.f, 23.f);
EXPECT_SOAFLOAT3_EQ(output_transforms[1].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);
// Weight .5 for both layers.
layers[0].weight = .5f;
layers[1].weight = .5f;
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 0.f, 0.f, 0.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f);
EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);
EXPECT_SOAFLOAT3_EQ(output_transforms[1].translation, 0.f, 0.f, 0.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f);
EXPECT_SOAFLOAT3_EQ(output_transforms[1].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);
}
}
TEST(JointWeights, BlendingJob) {
const ozz::math::SoaTransform identity = ozz::math::SoaTransform::identity();
// Initialize inputs.
ozz::math::SoaTransform input_transforms[2][2] = {{identity, identity},
{identity, identity}};
input_transforms[0][0].translation = ozz::math::SoaFloat3::Load(
ozz::math::simd_float4::Load(0.f, 1.f, 2.f, 3.f),
ozz::math::simd_float4::Load(4.f, 5.f, 6.f, 7.f),
ozz::math::simd_float4::Load(8.f, 9.f, 10.f, 11.f));
input_transforms[0][1].translation = ozz::math::SoaFloat3::Load(
ozz::math::simd_float4::Load(12.f, 13.f, 14.f, 15.f),
ozz::math::simd_float4::Load(16.f, 17.f, 18.f, 19.f),
ozz::math::simd_float4::Load(20.f, 21.f, 22.f, 23.f));
input_transforms[1][0].translation = -input_transforms[0][0].translation;
input_transforms[1][1].translation = -input_transforms[0][1].translation;
ozz::math::SimdFloat4 joint_weights[2][2] = {
{ozz::math::simd_float4::Load(1.f, 1.f, 0.f, 0.f),
ozz::math::simd_float4::Load(1.f, 0.f, 1.f, 1.f)},
{ozz::math::simd_float4::Load(1.f, 1.f, 1.f, 0.f),
ozz::math::simd_float4::Load(0.f, 1.f, 1.f, 1.f)}};
// Initialize bind pose.
ozz::math::SoaTransform bind_poses[2] = {identity, identity};
bind_poses[0].translation = ozz::math::SoaFloat3::Load(
ozz::math::simd_float4::Load(10.f, 11.f, 12.f, 13.f),
ozz::math::simd_float4::Load(14.f, 15.f, 16.f, 17.f),
ozz::math::simd_float4::Load(18.f, 19.f, 20.f, 21.f));
bind_poses[0].scale = ozz::math::SoaFloat3::Load(
ozz::math::simd_float4::Load(0.f, 1.f, 2.f, 3.f),
ozz::math::simd_float4::Load(4.f, 5.f, 6.f, 7.f),
ozz::math::simd_float4::Load(8.f, 9.f, 10.f, 11.f));
bind_poses[1].scale =
bind_poses[0].scale * ozz::math::simd_float4::Load(2.f, 2.f, 2.f, 2.f);
BlendingJob::Layer layers[2];
layers[0].transform = input_transforms[0];
layers[0].joint_weights = joint_weights[0];
layers[1].transform = input_transforms[1];
layers[1].joint_weights = joint_weights[1];
{ // Weight .5 for both layers.
ozz::math::SoaTransform output_transforms[3];
BlendingJob job;
job.layers = layers;
job.bind_pose = bind_poses;
job.output = output_transforms;
layers[0].weight = .5f;
layers[1].weight = .5f;
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 0.f, 0.f, -2.f, 13.f,
0.f, 0.f, -6.f, 17.f, 0.f, 0.f, -10.f, 21.f);
EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 1.f, 1.f, 1.f, 3.f, 1.f,
1.f, 1.f, 7.f, 1.f, 1.f, 1.f, 11.f);
EXPECT_SOAFLOAT3_EQ(output_transforms[1].translation, 12.f, -13.f, 0.f, 0.f,
16.f, -17.f, 0.f, 0.f, 20.f, -21.f, 0.f, 0.f);
EXPECT_SOAFLOAT3_EQ(output_transforms[1].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);
}
{ // Null weight for the first layer.
ozz::math::SoaTransform output_transforms[2];
BlendingJob job;
job.layers = layers;
job.bind_pose = bind_poses;
job.output = output_transforms;
layers[0].weight = 0.f;
layers[1].weight = 1.f;
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, -0.f, -1.f, -2.f,
13.f, -4.f, -5.f, -6.f, 17.f, -8.f, -9.f, -10.f, 21.f);
EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 1.f, 1.f, 1.f, 3.f, 1.f,
1.f, 1.f, 7.f, 1.f, 1.f, 1.f, 11.f);
EXPECT_SOAFLOAT3_EQ(output_transforms[1].translation, 0.f, -13.f, -14.f,
-15.f, 0.f, -17.f, -18.f, -19.f, 0.f, -21.f, -22.f,
-23.f);
EXPECT_SOAFLOAT3_EQ(output_transforms[1].scale, 0.f, 1.f, 1.f, 1.f, 8.f,
1.f, 1.f, 1.f, 16.f, 1.f, 1.f, 1.f);
}
}
TEST(Normalize, BlendingJob) {
const ozz::math::SoaTransform identity = ozz::math::SoaTransform::identity();
// Initialize inputs.
ozz::math::SoaTransform input_transforms[2][1] = {{identity}, {identity}};
// Initialize bind pose.
ozz::math::SoaTransform bind_poses[1] = {identity};
bind_poses[0].scale = ozz::math::SoaFloat3::Load(
ozz::math::simd_float4::Load(0.f, 1.f, 2.f, 3.f),
ozz::math::simd_float4::Load(4.f, 5.f, 6.f, 7.f),
ozz::math::simd_float4::Load(8.f, 9.f, 10.f, 11.f));
input_transforms[0][0].rotation = ozz::math::SoaQuaternion::Load(
ozz::math::simd_float4::Load(.70710677f, 0.f, 0.f, .382683432f),
ozz::math::simd_float4::Load(0.f, 0.f, .70710677f, 0.f),
ozz::math::simd_float4::Load(0.f, 0.f, 0.f, 0.f),
ozz::math::simd_float4::Load(.70710677f, 1.f, .70710677f, .9238795f));
input_transforms[1][0].rotation = ozz::math::SoaQuaternion::Load(
ozz::math::simd_float4::Load(0.f, .70710677f, -.70710677f, -.382683432f),
ozz::math::simd_float4::Load(0.f, 0.f, 0.f, 0.f),
ozz::math::simd_float4::Load(0.f, 0.f, -.70710677f, 0.f),
ozz::math::simd_float4::Load(1.f, .70710677f, 0.f, -.9238795f));
{ // Un-normalized weights < 1.f.
input_transforms[0][0].translation = ozz::math::SoaFloat3::Load(
ozz::math::simd_float4::Load(2.f, 3.f, 4.f, 5.f),
ozz::math::simd_float4::Load(6.f, 7.f, 8.f, 9.f),
ozz::math::simd_float4::Load(10.f, 11.f, 12.f, 13.f));
input_transforms[1][0].translation = ozz::math::SoaFloat3::Load(
ozz::math::simd_float4::Load(3.f, 4.f, 5.f, 6.f),
ozz::math::simd_float4::Load(7.f, 8.f, 9.f, 10.f),
ozz::math::simd_float4::Load(11.f, 12.f, 13.f, 14.f));
BlendingJob::Layer layers[2];
layers[0].weight = 0.2f;
layers[0].transform = input_transforms[0];
layers[1].weight = 0.3f;
layers[1].transform = input_transforms[1];
ozz::math::SoaTransform output_transforms[1];
BlendingJob job;
job.layers = layers;
job.bind_pose = bind_poses;
job.output = output_transforms;
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 2.6f, 3.6f, 4.6f,
5.6f, 6.6f, 7.6f, 8.6f, 9.6f, 10.6f, 11.6f, 12.6f,
13.6f);
EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, .30507791f,
.45761687f, -.58843851f, .38268352f, 0.f, 0.f,
.39229235f, 0.f, 0.f, 0.f, -.58843851f, 0.f,
.95224595f, .88906217f, .39229235f, .92387962f);
EXPECT_TRUE(
ozz::math::AreAllTrue(IsNormalizedEst(output_transforms[0].rotation)));
EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);
}
{ // Un-normalized weights > 1.f.
input_transforms[0][0].translation = ozz::math::SoaFloat3::Load(
ozz::math::simd_float4::Load(5.f, 10.f, 15.f, 20.f),
ozz::math::simd_float4::Load(25.f, 30.f, 35.f, 40.f),
ozz::math::simd_float4::Load(45.f, 50.f, 55.f, 60.f));
input_transforms[1][0].translation = ozz::math::SoaFloat3::Load(
ozz::math::simd_float4::Load(10.f, 15.f, 20.f, 25.f),
ozz::math::simd_float4::Load(30.f, 35.f, 40.f, 45.f),
ozz::math::simd_float4::Load(50.f, 55.f, 60.f, 65.f));
BlendingJob::Layer layers[2];
layers[0].weight = 2.f;
layers[0].transform = input_transforms[0];
layers[1].weight = 3.f;
layers[1].transform = input_transforms[1];
ozz::math::SoaTransform output_transforms[1];
BlendingJob job;
job.layers = layers;
job.bind_pose = bind_poses;
job.output = output_transforms;
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 8.f, 13.f, 18.f, 23.f,
28.f, 33.f, 38.f, 43.f, 48.f, 53.f, 58.f, 63.f);
EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, .30507791f,
.45761687f, -.58843851f, .38268352f, 0.f, 0.f,
.39229235f, 0.f, 0.f, 0.f, -.58843851f, 0.f,
.95224595f, .88906217f, .39229235f, .92387962f);
EXPECT_TRUE(
ozz::math::AreAllTrue(IsNormalizedEst(output_transforms[0].rotation)));
EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);
}
{ // Un-normalized weights > 1.f, with per-joint weights.
input_transforms[0][0].translation = ozz::math::SoaFloat3::Load(
ozz::math::simd_float4::Load(5.f, 10.f, 15.f, 20.f),
ozz::math::simd_float4::Load(25.f, 30.f, 35.f, 40.f),
ozz::math::simd_float4::Load(45.f, 50.f, 55.f, 60.f));
input_transforms[1][0].translation = ozz::math::SoaFloat3::Load(
ozz::math::simd_float4::Load(10.f, 15.f, 20.f, 25.f),
ozz::math::simd_float4::Load(30.f, 35.f, 40.f, 45.f),
ozz::math::simd_float4::Load(50.f, 55.f, 60.f, 65.f));
ozz::math::SimdFloat4 joint_weights[1] = {
ozz::math::simd_float4::Load(1.f, -1.f, 2.f, .1f)};
BlendingJob::Layer layers[2];
layers[0].weight = 2.f;
layers[0].transform = input_transforms[0];
layers[1].weight = 3.f;
layers[1].transform = input_transforms[1];
layers[1].joint_weights = joint_weights;
ozz::math::SoaTransform output_transforms[1];
BlendingJob job;
job.layers = layers;
job.bind_pose = bind_poses;
job.output = output_transforms;
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 8.f, 10.f,
150.f / 8.f, 47.5f / 2.3f, 28.f, 30.f, 310.f / 8.f,
93.5f / 2.3f, 48.f, 50.f, 470.f / 8.f, 139.5f / 2.3f);
EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);
}
}
TEST(Threshold, BlendingJob) {
const ozz::math::SoaTransform identity = ozz::math::SoaTransform::identity();
// Initialize inputs.
ozz::math::SoaTransform input_transforms[2][1] = {{identity}, {identity}};
// Initialize bind pose.
ozz::math::SoaTransform bind_poses[1] = {identity};
bind_poses[0].scale = ozz::math::SoaFloat3::Load(
ozz::math::simd_float4::Load(0.f, 1.f, 2.f, 3.f),
ozz::math::simd_float4::Load(4.f, 5.f, 6.f, 7.f),
ozz::math::simd_float4::Load(8.f, 9.f, 10.f, 11.f));
input_transforms[0][0].translation = ozz::math::SoaFloat3::Load(
ozz::math::simd_float4::Load(2.f, 3.f, 4.f, 5.f),
ozz::math::simd_float4::Load(6.f, 7.f, 8.f, 9.f),
ozz::math::simd_float4::Load(10.f, 11.f, 12.f, 13.f));
input_transforms[1][0].translation = ozz::math::SoaFloat3::Load(
ozz::math::simd_float4::Load(3.f, 4.f, 5.f, 6.f),
ozz::math::simd_float4::Load(7.f, 8.f, 9.f, 10.f),
ozz::math::simd_float4::Load(11.f, 12.f, 13.f, 14.f));
{ // Threshold is not reached.
BlendingJob::Layer layers[2];
layers[0].weight = 0.04f;
layers[0].transform = input_transforms[0];
layers[1].weight = 0.06f;
layers[1].transform = input_transforms[1];
ozz::math::SoaTransform output_transforms[1];
BlendingJob job;
job.threshold = .1f;
job.layers = layers;
job.bind_pose = bind_poses;
job.output = output_transforms;
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 2.6f, 3.6f, 4.6f,
5.6f, 6.6f, 7.6f, 8.6f, 9.6f, 10.6f, 11.6f, 12.6f,
13.6f);
EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, 0.f, 0.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
1.f, 1.f, 1.f, 1.f);
EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);
}
{ // Threshold is reached at 100%.
BlendingJob::Layer layers[2];
layers[0].weight = 1e-27f;
layers[0].transform = input_transforms[0];
layers[1].weight = 0.f;
layers[1].transform = input_transforms[1];
ozz::math::SoaTransform output_transforms[1];
BlendingJob job;
job.threshold = .1f;
job.layers = layers;
job.bind_pose = bind_poses;
job.output = output_transforms;
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 0.f, 0.f, 0.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f);
EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, 0.f, 0.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
1.f, 1.f, 1.f, 1.f);
EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 0.f, 1.f, 2.f, 3.f, 4.f,
5.f, 6.f, 7.f, 8.f, 9.f, 10.f, 11.f);
}
}
TEST(AdditiveWeight, BlendingJob) {
const ozz::math::SoaTransform identity = ozz::math::SoaTransform::identity();
// Initialize inputs.
ozz::math::SoaTransform input_transforms[2][1] = {{identity}, {identity}};
input_transforms[0][0].translation = ozz::math::SoaFloat3::Load(
ozz::math::simd_float4::Load(0.f, 1.f, 2.f, 3.f),
ozz::math::simd_float4::Load(4.f, 5.f, 6.f, 7.f),
ozz::math::simd_float4::Load(8.f, 9.f, 10.f, 11.f));
input_transforms[0][0].rotation = ozz::math::SoaQuaternion::Load(
ozz::math::simd_float4::Load(.70710677f, 0.f, 0.f, .382683432f),
ozz::math::simd_float4::Load(0.f, 0.f, .70710677f, 0.f),
ozz::math::simd_float4::Load(0.f, 0.f, 0.f, 0.f),
ozz::math::simd_float4::Load(.70710677f, 1.f, -.70710677f, .9238795f));
input_transforms[0][0].scale = ozz::math::SoaFloat3::Load(
ozz::math::simd_float4::Load(12.f, 13.f, 14.f, 15.f),
ozz::math::simd_float4::Load(16.f, 17.f, 18.f, 19.f),
ozz::math::simd_float4::Load(20.f, 21.f, 22.f, 23.f));
input_transforms[1][0].translation = -input_transforms[0][0].translation;
input_transforms[1][0].rotation = Conjugate(input_transforms[0][0].rotation);
input_transforms[1][0].scale = -input_transforms[0][0].scale;
// Initialize bind pose.
ozz::math::SoaTransform bind_poses[1] = {identity};
{
BlendingJob::Layer layers[1];
layers[0].transform = input_transforms[0];
ozz::math::SoaTransform output_transforms[1];
BlendingJob job;
job.additive_layers = layers;
job.bind_pose = bind_poses;
job.output = output_transforms;
// No weight for the 1st layer.
layers[0].weight = 0.f;
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 0.f, 0.f, 0.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f);
EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, 0.f, 0.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
1.f, 1.f, 1.f, 1.f);
EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);
// .5 weight for the 1st layer.
layers[0].weight = .5f;
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 0.f, .5f, 1.f, 1.5f,
2.f, 2.5f, 3.f, 3.5f, 4.f, 4.5f, 5.f, 5.5f);
EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, .3826834f, 0.f,
0.f, .19509032f, 0.f, 0.f, -.3826834f, 0.f, 0.f,
0.f, 0.f, 0.f, .9238795f, 1.f, .9238795f,
.98078528f);
EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 6.5f, 7.f, 7.5f, 8.f, 8.5f,
9.f, 9.5f, 10.f, 10.5f, 11.f, 11.5f, 12.f);
// Full weight for the 1st layer.
layers[0].weight = 1.f;
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 0.f, 1.f, 2.f, 3.f,
4.f, 5.f, 6.f, 7.f, 8.f, 9.f, 10.f, 11.f);
EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, .70710677f, 0.f,
0.f, .382683432f, 0.f, 0.f, -.70710677f, 0.f,
0.f, 0.f, 0.f, 0.f, .70710677f, 1.f, .70710677f,
.9238795f);
EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 12.f, 13.f, 14.f, 15.f,
16.f, 17.f, 18.f, 19.f, 20.f, 21.f, 22.f, 23.f);
}
{
BlendingJob::Layer layers[2];
layers[0].transform = input_transforms[0];
layers[1].transform = input_transforms[1];
ozz::math::SoaTransform output_transforms[1];
BlendingJob job;
job.additive_layers = layers;
job.bind_pose = bind_poses;
job.output = output_transforms;
// No weight for the 1st layer.
layers[0].weight = 0.f;
layers[1].weight = 1.f;
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, -0.f, -1.f, -2.f,
-3.f, -4.f, -5.f, -6.f, -7.f, -8.f, -9.f, -10.f, -11.f);
EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, -.70710677f,
-0.f, -0.f, -.382683432f, -0.f, -0.f,
.70710677f, -0.f, -0.f, -0.f, -0.f, -0.f,
.70710677f, 1.f, .70710677f, .9238795f);
EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, -12.f, -13.f, -14.f, -15.f,
-16.f, -17.f, -18.f, -19.f, -20.f, -21.f, -22.f, -23.f);
// Full weight for the both layer.
layers[0].weight = 1.f;
layers[1].weight = 1.f;
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 0.f, 0.f, 0.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f);
EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, 0.f, 0.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
1.f, 1.f, 1.f, 1.f);
EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, -144.f, -169.f, -196.f,
-225.f, -256.f, -289.f, -324.f, -361.f, -400.f, -441.f,
-484.f, -529.f);
// Subtract second layer.
layers[0].weight = .5f;
layers[1].transform = input_transforms[0];
layers[1].weight = -.5f;
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 0.f, 0.f, 0.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f);
EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, 0.f, 0.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
1.f, 1.f, 1.f, 1.f);
EXPECT_SOAFLOAT3_EQ_EST(output_transforms[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);
}
}
TEST(AdditiveJointWeight, BlendingJob) {
const ozz::math::SoaTransform identity = ozz::math::SoaTransform::identity();
// Initialize inputs.
ozz::math::SoaTransform input_transforms[1] = {identity};
input_transforms[0].translation = ozz::math::SoaFloat3::Load(
ozz::math::simd_float4::Load(0.f, 1.f, 2.f, 3.f),
ozz::math::simd_float4::Load(4.f, 5.f, 6.f, 7.f),
ozz::math::simd_float4::Load(8.f, 9.f, 10.f, 11.f));
input_transforms[0].rotation = ozz::math::SoaQuaternion::Load(
ozz::math::simd_float4::Load(.70710677f, 0.f, 0.f, .382683432f),
ozz::math::simd_float4::Load(0.f, 0.f, .70710677f, 0.f),
ozz::math::simd_float4::Load(0.f, 0.f, 0.f, 0.f),
ozz::math::simd_float4::Load(.70710677f, 1.f, -.70710677f, .9238795f));
input_transforms[0].scale = ozz::math::SoaFloat3::Load(
ozz::math::simd_float4::Load(12.f, 13.f, 14.f, 15.f),
ozz::math::simd_float4::Load(16.f, 17.f, 18.f, 19.f),
ozz::math::simd_float4::Load(20.f, 21.f, 22.f, 23.f));
ozz::math::SimdFloat4 joint_weights[1] = {
ozz::math::simd_float4::Load(1.f, .5f, 0.f, -1.f)};
// Initialize bind pose.
ozz::math::SoaTransform bind_poses[1] = {identity};
{
BlendingJob::Layer layers[1];
layers[0].transform = input_transforms;
layers[0].joint_weights = joint_weights;
ozz::math::SoaTransform output_transforms[1];
BlendingJob job;
job.additive_layers = layers;
job.bind_pose = bind_poses;
job.output = output_transforms;
// No weight for the 1st layer.
layers[0].weight = 0.f;
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 0.f, 0.f, 0.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f);
EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, 0.f, 0.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
1.f, 1.f, 1.f, 1.f);
EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);
// .5 weight for the 1st layer.
layers[0].weight = .5f;
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 0.f, .25f, 0.f, 0.f,
2.f, 1.25f, 0.f, 0.f, 4.f, 2.25f, 0.f, 0.f);
EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, .3826834f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
0.f, .9238795f, 1.f, 1.f, 1.f);
EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 6.5f, 4.f, 1.f, 1.f, 8.5f,
5.f, 1.f, 1.f, 10.5f, 6.f, 1.f, 1.f);
// Full weight for the 1st layer.
layers[0].weight = 1.f;
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 0.f, .5f, 0.f, 0.f,
4.f, 2.5f, 0.f, 0.f, 8.f, 4.5f, 0.f, 0.f);
EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, .70710677f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
0.f, .70710677f, 1.f, 1.f, 1.f);
EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 12.f, 7.f, 1.f, 1.f, 16.f,
9.f, 1.f, 1.f, 20.f, 11.f, 1.f, 1.f);
// Subtract layer.
layers[0].weight = -1.f;
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, -0.f, -.5f, 0.f, 0.f,
-4.f, -2.5f, 0.f, 0.f, -8.f, -4.5f, 0.f, 0.f);
EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, -.70710677f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
0.f, .70710677f, 1.f, 1.f, 1.f);
EXPECT_SOAFLOAT3_EQ_EST(output_transforms[0].scale, 1.f / 12.f, 1.f / 7.f,
1.f, 1.f, 1.f / 16.f, 1.f / 9.f, 1.f, 1.f,
1.f / 20.f, 1.f / 11.f, 1.f, 1.f);
}
}
@@ -0,0 +1,626 @@
//----------------------------------------------------------------------------//
// //
// 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 "ozz/base/maths/quaternion.h"
#include "ozz/base/maths/simd_math.h"
#include "ozz/base/maths/simd_quaternion.h"
#include "gtest/gtest.h"
#include "ozz/base/maths/gtest_math_helper.h"
TEST(JobValidity, IKAimJob) {
const ozz::math::Float4x4 joint = ozz::math::Float4x4::identity();
ozz::math::SimdQuaternion quat;
{ // Default is invalid
ozz::animation::IKAimJob job;
EXPECT_FALSE(job.Validate());
}
{ // Invalid joint matrix
ozz::animation::IKAimJob job;
job.joint = &joint;
EXPECT_FALSE(job.Validate());
}
{ // Invalid output
ozz::animation::IKAimJob job;
job.joint_correction = &quat;
EXPECT_FALSE(job.Validate());
}
{ // Invalid non normalized forward vector.
ozz::animation::IKAimJob job;
job.forward = ozz::math::simd_float4::Load(.5f, 0.f, 0.f, 0.f);
EXPECT_FALSE(job.Validate());
}
{ // Valid
ozz::animation::IKAimJob job;
job.joint = &joint;
job.joint_correction = &quat;
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
}
}
TEST(Correction, IKAimJob) {
ozz::math::SimdQuaternion quat;
ozz::animation::IKAimJob job;
job.joint_correction = &quat;
// Test will be executed with different root transformations
const ozz::math::Float4x4 parents[] = {
ozz::math::Float4x4::identity(), // No root transformation
ozz::math::Float4x4::Translation(ozz::math::simd_float4::y_axis()), // Up
ozz::math::Float4x4::FromEuler(ozz::math::simd_float4::Load(
ozz::math::kPi / 3.f, 0.f, 0.f, 0.f)), // Rotated
ozz::math::Float4x4::Scaling(ozz::math::simd_float4::Load(
2.f, 2.f, 2.f, 0.f)), // Uniformly scaled
ozz::math::Float4x4::Scaling(ozz::math::simd_float4::Load(
1.f, 2.f, 1.f, 0.f)), // Non-uniformly scaled
ozz::math::Float4x4::Scaling(
ozz::math::simd_float4::Load(-3.f, -3.f, -3.f, 0.f)) // Mirrored
};
for (size_t i = 0; i < OZZ_ARRAY_SIZE(parents); ++i) {
const ozz::math::Float4x4& parent = parents[i];
job.joint = &parent;
// These are in joint local-space
job.forward = ozz::math::simd_float4::x_axis();
job.up = ozz::math::simd_float4::y_axis();
// Pole vector is in model space
job.pole_vector = TransformVector(parent, ozz::math::simd_float4::y_axis());
{ // x
job.target = TransformPoint(parent, ozz::math::simd_float4::x_axis());
EXPECT_TRUE(job.Run());
EXPECT_SIMDQUATERNION_EQ_TOL(quat, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
{ // -x
job.target = TransformPoint(parent, -ozz::math::simd_float4::x_axis());
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion y_Pi = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::y_axis(), ozz::math::kPi);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, y_Pi.x, y_Pi.y, y_Pi.z, y_Pi.w, 2e-3f);
}
{ // z
job.target = TransformPoint(parent, ozz::math::simd_float4::z_axis());
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion y_mPi_2 =
ozz::math::Quaternion::FromAxisAngle(ozz::math::Float3::y_axis(),
-ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, y_mPi_2.x, y_mPi_2.y, y_mPi_2.z,
y_mPi_2.w, 2e-3f);
}
{ // -z
job.target = TransformPoint(parent, -ozz::math::simd_float4::z_axis());
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion y_Pi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::y_axis(), ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, y_Pi_2.x, y_Pi_2.y, y_Pi_2.z, y_Pi_2.w,
2e-3f);
}
{ // 45 up y
job.target = TransformPoint(
parent, ozz::math::simd_float4::Load(1.f, 1.f, 0.f, 0.f));
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion z_Pi_4 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), ozz::math::kPi_4);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, z_Pi_4.x, z_Pi_4.y, z_Pi_4.z, z_Pi_4.w,
2e-3f);
}
{ // 45 up y, further
job.target = TransformPoint(
parent, ozz::math::simd_float4::Load(2.f, 2.f, 0.f, 0.f));
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion z_Pi_4 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), ozz::math::kPi_4);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, z_Pi_4.x, z_Pi_4.y, z_Pi_4.z, z_Pi_4.w,
2e-3f);
}
}
}
TEST(Forward, IKAimJob) {
ozz::animation::IKAimJob job;
ozz::math::SimdQuaternion quat;
job.joint_correction = &quat;
const ozz::math::Float4x4 joint = ozz::math::Float4x4::identity();
job.joint = &joint;
job.target = ozz::math::simd_float4::x_axis();
job.up = ozz::math::simd_float4::y_axis();
job.pole_vector = ozz::math::simd_float4::y_axis();
{ // forward x
job.forward = ozz::math::simd_float4::x_axis();
EXPECT_TRUE(job.Run());
EXPECT_SIMDQUATERNION_EQ_TOL(quat, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
{ // forward -x
job.forward = -ozz::math::simd_float4::x_axis();
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion y_Pi = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::y_axis(), -ozz::math::kPi);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, y_Pi.x, y_Pi.y, y_Pi.z, y_Pi.w, 2e-3f);
}
{ // forward z
job.forward = ozz::math::simd_float4::z_axis();
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion y_Pi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::y_axis(), ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, y_Pi_2.x, y_Pi_2.y, y_Pi_2.z, y_Pi_2.w,
2e-3f);
}
}
TEST(Up, IKAimJob) {
ozz::animation::IKAimJob job;
ozz::math::SimdQuaternion quat;
job.joint_correction = &quat;
const ozz::math::Float4x4 joint = ozz::math::Float4x4::identity();
job.joint = &joint;
job.target = ozz::math::simd_float4::x_axis();
job.forward = ozz::math::simd_float4::x_axis();
job.up = ozz::math::simd_float4::y_axis();
job.pole_vector = ozz::math::simd_float4::y_axis();
{ // up y
job.up = ozz::math::simd_float4::y_axis();
EXPECT_TRUE(job.Run());
EXPECT_SIMDQUATERNION_EQ_TOL(quat, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
{ // up -y
job.up = -ozz::math::simd_float4::y_axis();
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion x_Pi = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::x_axis(), ozz::math::kPi);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, x_Pi.x, x_Pi.y, x_Pi.z, x_Pi.w, 2e-3f);
}
{ // up z
job.up = ozz::math::simd_float4::z_axis();
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion x_mPi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::x_axis(), -ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, x_mPi_2.x, x_mPi_2.y, x_mPi_2.z,
x_mPi_2.w, 2e-3f);
}
{ // up 2*z
job.up =
ozz::math::simd_float4::z_axis() * ozz::math::simd_float4::Load1(2.f);
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion x_mPi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::x_axis(), -ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, x_mPi_2.x, x_mPi_2.y, x_mPi_2.z,
x_mPi_2.w, 2e-3f);
}
{ // up very small z
job.up =
ozz::math::simd_float4::z_axis() * ozz::math::simd_float4::Load1(1e-9f);
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion x_mPi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::x_axis(), -ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, x_mPi_2.x, x_mPi_2.y, x_mPi_2.z,
x_mPi_2.w, 2e-3f);
}
{ // up is zero
job.up = ozz::math::simd_float4::zero();
EXPECT_TRUE(job.Run());
EXPECT_SIMDQUATERNION_EQ_TOL(quat, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
}
TEST(Pole, IKAimJob) {
ozz::animation::IKAimJob job;
ozz::math::SimdQuaternion quat;
job.joint_correction = &quat;
const ozz::math::Float4x4 joint = ozz::math::Float4x4::identity();
job.joint = &joint;
job.target = ozz::math::simd_float4::x_axis();
job.forward = ozz::math::simd_float4::x_axis();
job.up = ozz::math::simd_float4::y_axis();
{ // Pole y
job.pole_vector = ozz::math::simd_float4::y_axis();
EXPECT_TRUE(job.Run());
EXPECT_SIMDQUATERNION_EQ_TOL(quat, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
{ // Pole -y
job.pole_vector = -ozz::math::simd_float4::y_axis();
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion x_Pi = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::x_axis(), ozz::math::kPi);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, x_Pi.x, x_Pi.y, x_Pi.z, x_Pi.w, 2e-3f);
}
{ // Pole z
job.pole_vector = ozz::math::simd_float4::z_axis();
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion x_Pi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::x_axis(), ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, x_Pi_2.x, x_Pi_2.y, x_Pi_2.z, x_Pi_2.w,
2e-3f);
}
{ // Pole 2*z
job.pole_vector =
ozz::math::simd_float4::z_axis() * ozz::math::simd_float4::Load1(2.f);
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion x_Pi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::x_axis(), ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, x_Pi_2.x, x_Pi_2.y, x_Pi_2.z, x_Pi_2.w,
2e-3f);
}
{ // Pole very small z
job.pole_vector =
ozz::math::simd_float4::z_axis() * ozz::math::simd_float4::Load1(1e-9f);
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion x_Pi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::x_axis(), ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, x_Pi_2.x, x_Pi_2.y, x_Pi_2.z, x_Pi_2.w,
2e-3f);
}
}
TEST(Offset, IKAimJob) {
ozz::animation::IKAimJob job;
ozz::math::SimdQuaternion quat;
job.joint_correction = &quat;
const ozz::math::Float4x4 joint = ozz::math::Float4x4::identity();
job.joint = &joint;
bool reached;
job.reached = &reached;
job.target = ozz::math::simd_float4::x_axis();
job.forward = ozz::math::simd_float4::x_axis();
job.up = ozz::math::simd_float4::y_axis();
job.pole_vector = ozz::math::simd_float4::y_axis();
{ // No offset
reached = false;
job.offset = ozz::math::simd_float4::zero();
EXPECT_TRUE(job.Run());
EXPECT_SIMDQUATERNION_EQ_TOL(quat, 0.f, 0.f, 0.f, 1.f, 2e-3f);
EXPECT_TRUE(reached);
}
{ // Offset inside target sphere
reached = false;
job.offset =
ozz::math::simd_float4::Load(0.f, ozz::math::kSqrt2_2, 0.f, 0.f);
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion z_Pi_4 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), -ozz::math::kPi_4);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, z_Pi_4.x, z_Pi_4.y, z_Pi_4.z, z_Pi_4.w,
2e-3f);
EXPECT_TRUE(reached);
}
{ // Offset inside target sphere
reached = false;
job.offset = ozz::math::simd_float4::Load(.5f, .5f, 0.f, 0.f);
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion z_Pi_6 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), -ozz::math::kPi / 6.f);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, z_Pi_6.x, z_Pi_6.y, z_Pi_6.z, z_Pi_6.w,
2e-3f);
EXPECT_TRUE(reached);
}
{ // Offset inside target sphere
reached = false;
job.offset = ozz::math::simd_float4::Load(-.5f, .5f, 0.f, 0.f);
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion z_Pi_6 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), -ozz::math::kPi / 6.f);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, z_Pi_6.x, z_Pi_6.y, z_Pi_6.z, z_Pi_6.w,
2e-3f);
EXPECT_TRUE(reached);
}
{ // Offset inside target sphere
reached = false;
job.offset = ozz::math::simd_float4::Load(.5f, 0.f, .5f, 0.f);
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion y_Pi_6 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::y_axis(), ozz::math::kPi / 6.f);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, y_Pi_6.x, y_Pi_6.y, y_Pi_6.z, y_Pi_6.w,
2e-3f);
EXPECT_TRUE(reached);
}
{ // Offset on target sphere
reached = false;
job.offset = ozz::math::simd_float4::Load(0.f, 1.f, 0.f, 0.f);
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion z_Pi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), -ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, z_Pi_2.x, z_Pi_2.y, z_Pi_2.z, z_Pi_2.w,
2e-3f);
EXPECT_TRUE(reached);
}
{ // Offset outside of target sphere, unreachable
reached = true;
job.offset = ozz::math::simd_float4::Load(0.f, 2.f, 0.f, 0.f);
EXPECT_TRUE(job.Run());
EXPECT_SIMDQUATERNION_EQ_TOL(quat, 0.f, 0.f, 0.f, 1.f, 2e-3f);
EXPECT_FALSE(reached);
}
const ozz::math::Float4x4 translated_joint =
ozz::math::Float4x4::Translation(ozz::math::simd_float4::y_axis());
job.joint = &translated_joint;
{ // Offset inside of target sphere, unreachable
reached = false;
job.offset = ozz::math::simd_float4::y_axis();
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion z_Pi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), -ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, z_Pi_2.x, z_Pi_2.y, z_Pi_2.z, z_Pi_2.w,
2e-3f);
EXPECT_TRUE(reached);
}
}
TEST(Twist, IKAimJob) {
ozz::animation::IKAimJob job;
ozz::math::SimdQuaternion quat;
job.joint_correction = &quat;
const ozz::math::Float4x4 joint = ozz::math::Float4x4::identity();
job.joint = &joint;
job.target = ozz::math::simd_float4::x_axis();
job.forward = ozz::math::simd_float4::x_axis();
job.up = ozz::math::simd_float4::y_axis();
{ // Pole y, twist 0
job.pole_vector = ozz::math::simd_float4::y_axis();
job.twist_angle = 0.f;
EXPECT_TRUE(job.Run());
EXPECT_SIMDQUATERNION_EQ_TOL(quat, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
{ // Pole y, twist pi
job.pole_vector = ozz::math::simd_float4::y_axis();
job.twist_angle = ozz::math::kPi;
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion x_Pi = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::x_axis(), -ozz::math::kPi);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, x_Pi.x, x_Pi.y, x_Pi.z, x_Pi.w, 2e-3f);
}
{ // Pole y, twist -pi
job.pole_vector = ozz::math::simd_float4::y_axis();
job.twist_angle = -ozz::math::kPi;
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion x_mPi = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::x_axis(), -ozz::math::kPi);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, x_mPi.x, x_mPi.y, x_mPi.z, x_mPi.w,
2e-3f);
}
{ // Pole y, twist pi/2
job.pole_vector = ozz::math::simd_float4::y_axis();
job.twist_angle = ozz::math::kPi_2;
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion x_Pi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::x_axis(), ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, x_Pi_2.x, x_Pi_2.y, x_Pi_2.z, x_Pi_2.w,
2e-3f);
}
{ // Pole z, twist pi/2
job.pole_vector = ozz::math::simd_float4::z_axis();
job.twist_angle = ozz::math::kPi_2;
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion x_Pi = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::x_axis(), ozz::math::kPi);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, x_Pi.x, x_Pi.y, x_Pi.z, x_Pi.w, 2e-3f);
}
}
TEST(AlignedTargetUp, IKAimJob) {
ozz::animation::IKAimJob job;
ozz::math::SimdQuaternion quat;
job.joint_correction = &quat;
const ozz::math::Float4x4 joint = ozz::math::Float4x4::identity();
job.joint = &joint;
job.forward = ozz::math::simd_float4::x_axis();
job.pole_vector = ozz::math::simd_float4::y_axis();
{ // Not aligned
job.target = ozz::math::simd_float4::x_axis();
job.up = ozz::math::simd_float4::y_axis();
EXPECT_TRUE(job.Run());
EXPECT_SIMDQUATERNION_EQ_TOL(quat, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
{ // Aligned y
job.target = ozz::math::simd_float4::y_axis();
job.up = ozz::math::simd_float4::y_axis();
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion z_Pi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, z_Pi_2.x, z_Pi_2.y, z_Pi_2.z, z_Pi_2.w,
2e-3f);
}
{ // Aligned 2*y
job.target =
ozz::math::simd_float4::y_axis() * ozz::math::simd_float4::Load1(2.f);
job.up = ozz::math::simd_float4::y_axis();
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion z_Pi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, z_Pi_2.x, z_Pi_2.y, z_Pi_2.z, z_Pi_2.w,
2e-3f);
}
{ // Aligned -2*y
job.target =
ozz::math::simd_float4::y_axis() * ozz::math::simd_float4::Load1(-2.f);
job.up = ozz::math::simd_float4::y_axis();
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion z_mPi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), -ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, z_mPi_2.x, z_mPi_2.y, z_mPi_2.z,
z_mPi_2.w, 2e-3f);
}
}
TEST(AlignedTargetPole, IKAimJob) {
ozz::animation::IKAimJob job;
ozz::math::SimdQuaternion quat;
job.joint_correction = &quat;
const ozz::math::Float4x4 joint = ozz::math::Float4x4::identity();
job.joint = &joint;
job.forward = ozz::math::simd_float4::x_axis();
job.up = ozz::math::simd_float4::y_axis();
{ // Not aligned
job.target = ozz::math::simd_float4::x_axis();
job.pole_vector = ozz::math::simd_float4::y_axis();
EXPECT_TRUE(job.Run());
EXPECT_SIMDQUATERNION_EQ_TOL(quat, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
{ // Aligned y
job.target = ozz::math::simd_float4::y_axis();
job.pole_vector = ozz::math::simd_float4::y_axis();
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion z_Pi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, z_Pi_2.x, z_Pi_2.y, z_Pi_2.z, z_Pi_2.w,
2e-3f);
}
}
TEST(TargetTooClose, IKAimJob) {
ozz::animation::IKAimJob job;
ozz::math::SimdQuaternion quat;
job.joint_correction = &quat;
const ozz::math::Float4x4 joint = ozz::math::Float4x4::identity();
job.joint = &joint;
job.target = ozz::math::simd_float4::zero();
job.forward = ozz::math::simd_float4::x_axis();
job.up = ozz::math::simd_float4::y_axis();
job.pole_vector = ozz::math::simd_float4::y_axis();
EXPECT_TRUE(job.Run());
EXPECT_SIMDQUATERNION_EQ_TOL(quat, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
TEST(Weight, IKAimJob) {
ozz::animation::IKAimJob job;
ozz::math::SimdQuaternion quat;
job.joint_correction = &quat;
const ozz::math::Float4x4 joint = ozz::math::Float4x4::identity();
job.joint = &joint;
job.target = ozz::math::simd_float4::z_axis();
job.forward = ozz::math::simd_float4::x_axis();
job.up = ozz::math::simd_float4::y_axis();
job.pole_vector = ozz::math::simd_float4::y_axis();
{ // Full weight
job.weight = 1.f;
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion y_mPi2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::y_axis(), -ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, y_mPi2.x, y_mPi2.y, y_mPi2.z, y_mPi2.w,
2e-3f);
}
{ // > 1
job.weight = 2.f;
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion y_mPi2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::y_axis(), -ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, y_mPi2.x, y_mPi2.y, y_mPi2.z, y_mPi2.w,
2e-3f);
}
{ // Half weight
job.weight = .5f;
EXPECT_TRUE(job.Run());
const ozz::math::Quaternion y_mPi4 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::y_axis(), -ozz::math::kPi_4);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, y_mPi4.x, y_mPi4.y, y_mPi4.z, y_mPi4.w,
2e-3f);
}
{ // Zero weight
job.weight = 0.f;
EXPECT_TRUE(job.Run());
EXPECT_SIMDQUATERNION_EQ_TOL(quat, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
{ // < 0
job.weight = -.5f;
EXPECT_TRUE(job.Run());
EXPECT_SIMDQUATERNION_EQ_TOL(quat, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
}
TEST(ZeroScale, IKAimJob) {
ozz::animation::IKAimJob job;
ozz::math::SimdQuaternion quat;
job.joint_correction = &quat;
const ozz::math::Float4x4 joint =
ozz::math::Float4x4::Scaling(ozz::math::simd_float4::zero());
job.joint = &joint;
EXPECT_TRUE(job.Run());
EXPECT_SIMDQUATERNION_EQ_TOL(quat, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
@@ -0,0 +1,976 @@
//----------------------------------------------------------------------------//
// //
// 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 "ozz/base/maths/quaternion.h"
#include "ozz/base/maths/simd_math.h"
#include "ozz/base/maths/simd_quaternion.h"
#include "gtest/gtest.h"
#include "ozz/base/maths/gtest_math_helper.h"
// Implement helper macro that verify that target was reached once ik job is
// executed.
#define EXPECT_REACHED(_job) \
\
do { \
SCOPED_TRACE(""); \
_ExpectReached(_job, true); \
} while (void(0), 0)
#define EXPECT_NOT_REACHED(_job) \
\
do { \
SCOPED_TRACE(""); \
_ExpectReached(_job, false); \
} while (void(0), 0)
void _ExpectReached(const ozz::animation::IKTwoBoneJob& _job, bool _reachable) {
// Computes local transforms
const ozz::math::Float4x4 mid_local =
Invert(*_job.start_joint) * *_job.mid_joint;
const ozz::math::Float4x4 end_local =
Invert(*_job.mid_joint) * *_job.end_joint;
// Rebuild corrected model transforms
const ozz::math::Float4x4 start_correction =
ozz::math::Float4x4::FromQuaternion(_job.start_joint_correction->xyzw);
const ozz::math::Float4x4 start_corrected =
*_job.start_joint * start_correction;
const ozz::math::Float4x4 mid_correction =
ozz::math::Float4x4::FromQuaternion(_job.mid_joint_correction->xyzw);
const ozz::math::Float4x4 mid_corrected =
start_corrected * mid_local * mid_correction;
const ozz::math::Float4x4 end_corrected = mid_corrected * end_local;
const ozz::math::SimdFloat4 diff =
ozz::math::Length3(end_corrected.cols[3] - _job.target);
EXPECT_EQ(ozz::math::GetX(diff) < 1e-2f, _reachable);
EXPECT_TRUE(_job.reached == nullptr || *_job.reached == _reachable);
}
TEST(JobValidity, IKTwoBoneJob) {
// Setup initial pose
const ozz::math::Float4x4 start = ozz::math::Float4x4::identity();
const ozz::math::Float4x4 mid = ozz::math::Float4x4::FromAffine(
ozz::math::simd_float4::y_axis(),
ozz::math::SimdQuaternion::FromAxisAngle(
ozz::math::simd_float4::z_axis(),
ozz::math::simd_float4::Load1(ozz::math::kPi_2))
.xyzw,
ozz::math::simd_float4::one());
const ozz::math::Float4x4 end = ozz::math::Float4x4::Translation(
ozz::math::simd_float4::x_axis() + ozz::math::simd_float4::y_axis());
{ // Default is invalid
ozz::animation::IKTwoBoneJob job;
EXPECT_FALSE(job.Validate());
}
{ // Missing start joint matrix
ozz::animation::IKTwoBoneJob job;
job.mid_joint = &mid;
job.end_joint = &end;
ozz::math::SimdQuaternion quat;
job.start_joint_correction = &quat;
job.mid_joint_correction = &quat;
EXPECT_FALSE(job.Validate());
}
{ // Missing mid joint matrix
ozz::animation::IKTwoBoneJob job;
job.start_joint = &start;
job.end_joint = &end;
ozz::math::SimdQuaternion quat;
job.start_joint_correction = &quat;
job.mid_joint_correction = &quat;
EXPECT_FALSE(job.Validate());
}
{ // Missing end joint matrix
ozz::animation::IKTwoBoneJob job;
job.start_joint = &start;
job.mid_joint = &mid;
ozz::math::SimdQuaternion quat;
job.start_joint_correction = &quat;
job.mid_joint_correction = &quat;
EXPECT_FALSE(job.Validate());
}
{ // Missing start joint output quaternion
ozz::animation::IKTwoBoneJob job;
job.start_joint = &start;
job.mid_joint = &mid;
job.end_joint = &end;
ozz::math::SimdQuaternion quat;
job.mid_joint_correction = &quat;
EXPECT_FALSE(job.Validate());
}
{ // Missing mid joint output quaternion
ozz::animation::IKTwoBoneJob job;
job.start_joint = &start;
job.mid_joint = &mid;
job.end_joint = &end;
ozz::math::SimdQuaternion quat;
job.start_joint_correction = &quat;
EXPECT_FALSE(job.Validate());
}
{ // Unnormalized mid axis
ozz::animation::IKTwoBoneJob job;
job.mid_axis =
ozz::math::simd_float4::Load(0.f, .70710678f, 0.f, .70710678f);
job.start_joint = &start;
job.mid_joint = &mid;
job.end_joint = &end;
ozz::math::SimdQuaternion quat;
job.start_joint_correction = &quat;
job.mid_joint_correction = &quat;
EXPECT_FALSE(job.Validate());
}
{ // Valid
ozz::animation::IKTwoBoneJob job;
job.start_joint = &start;
job.mid_joint = &mid;
job.end_joint = &end;
ozz::math::SimdQuaternion quat;
job.start_joint_correction = &quat;
job.mid_joint_correction = &quat;
EXPECT_TRUE(job.Validate());
}
}
TEST(StartJointCorrection, IKTwoBoneJob) {
// Setup initial pose
const ozz::math::Float4x4 base_start = ozz::math::Float4x4::identity();
const ozz::math::Float4x4 base_mid = ozz::math::Float4x4::FromAffine(
ozz::math::simd_float4::y_axis(),
ozz::math::SimdQuaternion::FromAxisAngle(
ozz::math::simd_float4::z_axis(),
ozz::math::simd_float4::Load1(ozz::math::kPi_2))
.xyzw,
ozz::math::simd_float4::one());
const ozz::math::Float4x4 base_end = ozz::math::Float4x4::Translation(
ozz::math::simd_float4::x_axis() + ozz::math::simd_float4::y_axis());
const ozz::math::SimdFloat4 mid_axis =
ozz::math::Cross3(base_start.cols[3] - base_mid.cols[3],
base_end.cols[3] - base_mid.cols[3]);
// Test will be executed with different root transformations
const ozz::math::Float4x4 parents[] = {
ozz::math::Float4x4::identity(), // No root transformation
ozz::math::Float4x4::Translation(ozz::math::simd_float4::y_axis()), // Up
ozz::math::Float4x4::FromEuler(ozz::math::simd_float4::Load(
ozz::math::kPi / 3.f, 0.f, 0.f, 0.f)), // Rotated
ozz::math::Float4x4::Scaling(ozz::math::simd_float4::Load(
2.f, 2.f, 2.f, 0.f)), // Uniformly scaled
ozz::math::Float4x4::Scaling(ozz::math::simd_float4::Load(
1.f, 2.f, 1.f, 0.f)), // Non-uniformly scaled
ozz::math::Float4x4::Scaling(
ozz::math::simd_float4::Load(-3.f, -3.f, -3.f, 0.f)) // Mirrored
};
for (size_t i = 0; i < OZZ_ARRAY_SIZE(parents); ++i) {
const ozz::math::Float4x4& parent = parents[i];
const ozz::math::Float4x4 start = parent * base_start;
const ozz::math::Float4x4 mid = parent * base_mid;
const ozz::math::Float4x4 end = parent * base_end;
// Prepares job.
ozz::animation::IKTwoBoneJob job;
job.pole_vector = TransformVector(parent, ozz::math::simd_float4::y_axis());
job.mid_axis = mid_axis;
job.start_joint = &start;
job.mid_joint = &mid;
job.end_joint = &end;
ozz::math::SimdQuaternion qstart;
job.start_joint_correction = &qstart;
ozz::math::SimdQuaternion qmid;
job.mid_joint_correction = &qmid;
bool reached;
job.reached = &reached;
ASSERT_TRUE(job.Validate());
{ // No correction expected
job.target = TransformPoint(
parent, ozz::math::simd_float4::Load(1.f, 1.f, 0.f, 0.f));
ASSERT_TRUE(job.Run());
EXPECT_REACHED(job);
EXPECT_SIMDQUATERNION_EQ_TOL(qstart, 0.f, 0.f, 0.f, 1.f, 2e-3f);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
{ // 90
job.target = TransformPoint(
parent, ozz::math::simd_float4::Load(0.f, 1.f, 1.f, 0.f));
ASSERT_TRUE(job.Run());
EXPECT_REACHED(job);
const ozz::math::Quaternion y_mPi_2 =
ozz::math::Quaternion::FromAxisAngle(ozz::math::Float3::y_axis(),
-ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(qstart, y_mPi_2.x, y_mPi_2.y, y_mPi_2.z,
y_mPi_2.w, 2e-3f);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
{ // 180 behind
job.target = TransformPoint(
parent, ozz::math::simd_float4::Load(-1.f, 1.f, 0.f, 0.f));
ASSERT_TRUE(job.Run());
EXPECT_REACHED(job);
const ozz::math::Quaternion y_kPi = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::y_axis(), ozz::math::kPi);
EXPECT_SIMDQUATERNION_EQ_TOL(qstart, y_kPi.x, y_kPi.y, y_kPi.z, y_kPi.w,
2e-3f);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
{ // 270
job.target = TransformPoint(
parent, ozz::math::simd_float4::Load(0.f, 1.f, -1.f, 0.f));
ASSERT_TRUE(job.Run());
EXPECT_REACHED(job);
const ozz::math::Quaternion y_kPi_2 =
ozz::math::Quaternion::FromAxisAngle(ozz::math::Float3::y_axis(),
ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(qstart, y_kPi_2.x, y_kPi_2.y, y_kPi_2.z,
y_kPi_2.w, 2e-3f);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
}
}
TEST(Pole, IKTwoBoneJob) {
// Setup initial pose
const ozz::math::Float4x4 start = ozz::math::Float4x4::identity();
const ozz::math::Float4x4 mid = ozz::math::Float4x4::FromAffine(
ozz::math::simd_float4::y_axis(),
ozz::math::SimdQuaternion::FromAxisAngle(
ozz::math::simd_float4::z_axis(),
ozz::math::simd_float4::Load1(ozz::math::kPi_2))
.xyzw,
ozz::math::simd_float4::one());
const ozz::math::Float4x4 end = ozz::math::Float4x4::Translation(
ozz::math::simd_float4::x_axis() + ozz::math::simd_float4::y_axis());
const ozz::math::SimdFloat4 mid_axis =
ozz::math::Cross3(start.cols[3] - mid.cols[3], end.cols[3] - mid.cols[3]);
// Prepares job.
ozz::animation::IKTwoBoneJob job;
job.start_joint = &start;
job.mid_joint = &mid;
job.end_joint = &end;
job.mid_axis = mid_axis;
ozz::math::SimdQuaternion qstart;
job.start_joint_correction = &qstart;
ozz::math::SimdQuaternion qmid;
job.mid_joint_correction = &qmid;
bool reached;
job.reached = &reached;
ASSERT_TRUE(job.Validate());
// Pole Y
{
job.pole_vector = ozz::math::simd_float4::y_axis();
job.target = ozz::math::simd_float4::Load(1.f, 1.f, 0.f, 0.f);
ASSERT_TRUE(job.Run());
EXPECT_REACHED(job);
EXPECT_SIMDQUATERNION_EQ_TOL(qstart, 0.f, 0.f, 0.f, 1.f, 2e-3f);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
// Pole Z
{
job.pole_vector = ozz::math::simd_float4::z_axis();
job.target = ozz::math::simd_float4::Load(1.f, 0.f, 1.f, 0.f);
ASSERT_TRUE(job.Run());
EXPECT_REACHED(job);
const ozz::math::Quaternion x_kPi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::x_axis(), ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(qstart, x_kPi_2.x, x_kPi_2.y, x_kPi_2.z,
x_kPi_2.w, 2e-3f);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
// Pole -Z
{
job.pole_vector = -ozz::math::simd_float4::z_axis();
job.target = ozz::math::simd_float4::Load(1.f, 0.f, -1.f, 0.f);
ASSERT_TRUE(job.Run());
EXPECT_REACHED(job);
const ozz::math::Quaternion x_mPi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::x_axis(), -ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(qstart, x_mPi_2.x, x_mPi_2.y, x_mPi_2.z,
x_mPi_2.w, 2e-3f);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
// Pole X
{
job.pole_vector = ozz::math::simd_float4::x_axis();
job.target = ozz::math::simd_float4::Load(1.f, -1.f, 0.f, 0.f);
ASSERT_TRUE(job.Run());
EXPECT_REACHED(job);
const ozz::math::Quaternion z_mPi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), -ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(qstart, z_mPi_2.x, z_mPi_2.y, z_mPi_2.z,
z_mPi_2.w, 2e-3f);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
// Pole -X
{
job.pole_vector = -ozz::math::simd_float4::x_axis();
job.target = ozz::math::simd_float4::Load(-1.f, 1.f, 0.f, 0.f);
ASSERT_TRUE(job.Run());
EXPECT_REACHED(job);
const ozz::math::Quaternion z_Pi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(qstart, z_Pi_2.x, z_Pi_2.y, z_Pi_2.z, z_Pi_2.w,
2e-3f);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
}
TEST(ZeroScale, IKTwoBoneJob) {
// Setup initial pose
const ozz::math::Float4x4 start =
ozz::math::Float4x4::Scaling(ozz::math::simd_float4::zero());
const ozz::math::Float4x4 mid = start;
const ozz::math::Float4x4 end = start;
// Prepares job.
ozz::animation::IKTwoBoneJob job;
job.start_joint = &start;
job.mid_joint = &mid;
job.end_joint = &end;
ozz::math::SimdQuaternion qstart;
job.start_joint_correction = &qstart;
ozz::math::SimdQuaternion qmid;
job.mid_joint_correction = &qmid;
ASSERT_TRUE(job.Validate());
ASSERT_TRUE(job.Run());
EXPECT_SIMDQUATERNION_EQ_TOL(qstart, 0.f, 0.f, 0.f, 1.f, 2e-3f);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
TEST(Soften, IKTwoBoneJob) {
// Setup initial pose
const ozz::math::Float4x4 start = ozz::math::Float4x4::identity();
const ozz::math::Float4x4 mid = ozz::math::Float4x4::FromAffine(
ozz::math::simd_float4::y_axis(),
ozz::math::SimdQuaternion::FromAxisAngle(
ozz::math::simd_float4::z_axis(),
ozz::math::simd_float4::Load1(ozz::math::kPi_2))
.xyzw,
ozz::math::simd_float4::one());
const ozz::math::Float4x4 end = ozz::math::Float4x4::Translation(
ozz::math::simd_float4::x_axis() + ozz::math::simd_float4::y_axis());
const ozz::math::SimdFloat4 mid_axis =
ozz::math::Cross3(start.cols[3] - mid.cols[3], end.cols[3] - mid.cols[3]);
// Prepares job.
ozz::animation::IKTwoBoneJob job;
job.start_joint = &start;
job.mid_joint = &mid;
job.end_joint = &end;
job.mid_axis = mid_axis;
ozz::math::SimdQuaternion qstart;
job.start_joint_correction = &qstart;
ozz::math::SimdQuaternion qmid;
job.mid_joint_correction = &qmid;
bool reached;
job.reached = &reached;
ASSERT_TRUE(job.Validate());
// Reachable
{
job.pole_vector = ozz::math::simd_float4::y_axis();
job.target = ozz::math::simd_float4::Load(2.f, 0.f, 0.f, 0.f);
job.soften = 1.f;
ASSERT_TRUE(job.Run());
EXPECT_REACHED(job);
const ozz::math::Quaternion z_mPi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), -ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(qstart, z_mPi_2.x, z_mPi_2.y, z_mPi_2.z,
z_mPi_2.w, 2e-3f);
const ozz::math::Quaternion z_Pi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, z_Pi_2.x, z_Pi_2.y, z_Pi_2.z, z_Pi_2.w,
2e-3f);
}
// Reachable, softened
{
job.pole_vector = ozz::math::simd_float4::y_axis();
job.target = ozz::math::simd_float4::Load(2.f * .5f, 0.f, 0.f, 0.f);
job.soften = .5f;
ASSERT_TRUE(job.Run());
EXPECT_REACHED(job);
}
// Reachable, softened
{
job.pole_vector = ozz::math::simd_float4::y_axis();
job.target = ozz::math::simd_float4::Load(2.f * .4f, 0.f, 0.f, 0.f);
job.soften = .5f;
ASSERT_TRUE(job.Run());
EXPECT_REACHED(job);
}
// Not reachable, softened
{
job.pole_vector = ozz::math::simd_float4::y_axis();
job.target = ozz::math::simd_float4::Load(2.f * .6f, 0.f, 0.f, 0.f);
job.soften = .5f;
ASSERT_TRUE(job.Run());
EXPECT_NOT_REACHED(job);
}
// Not reachable, softened at max
{
job.pole_vector = ozz::math::simd_float4::y_axis();
job.target = ozz::math::simd_float4::Load(2.f * .6f, 0.f, 0.f, 0.f);
job.soften = 0.f;
ASSERT_TRUE(job.Run());
EXPECT_NOT_REACHED(job);
}
// Not reachable, softened
{
job.pole_vector = ozz::math::simd_float4::y_axis();
job.target = ozz::math::simd_float4::Load(2.f, 0.f, 0.f, 0.f);
job.soften = .5f;
ASSERT_TRUE(job.Run());
EXPECT_NOT_REACHED(job);
}
// Not reachable, a bit too far
{
job.pole_vector = ozz::math::simd_float4::y_axis();
job.target = ozz::math::simd_float4::Load(3.f, 0.f, 0.f, 0.f);
job.soften = 1.f;
ASSERT_TRUE(job.Run());
EXPECT_NOT_REACHED(job);
}
}
TEST(Twist, IKTwoBoneJob) {
// Setup initial pose
const ozz::math::Float4x4 start = ozz::math::Float4x4::identity();
const ozz::math::Float4x4 mid = ozz::math::Float4x4::FromAffine(
ozz::math::simd_float4::y_axis(),
ozz::math::SimdQuaternion::FromAxisAngle(
ozz::math::simd_float4::z_axis(),
ozz::math::simd_float4::Load1(ozz::math::kPi_2))
.xyzw,
ozz::math::simd_float4::one());
const ozz::math::Float4x4 end = ozz::math::Float4x4::Translation(
ozz::math::simd_float4::x_axis() + ozz::math::simd_float4::y_axis());
const ozz::math::SimdFloat4 mid_axis =
ozz::math::Cross3(start.cols[3] - mid.cols[3], end.cols[3] - mid.cols[3]);
// Prepares job.
ozz::animation::IKTwoBoneJob job;
job.pole_vector = ozz::math::simd_float4::y_axis();
job.target = ozz::math::simd_float4::Load(1.f, 1.f, 0.f, 0.f);
job.start_joint = &start;
job.mid_joint = &mid;
job.end_joint = &end;
job.mid_axis = mid_axis;
ozz::math::SimdQuaternion qstart;
job.start_joint_correction = &qstart;
ozz::math::SimdQuaternion qmid;
job.mid_joint_correction = &qmid;
bool reached;
job.reached = &reached;
ASSERT_TRUE(job.Validate());
// Twist angle 0
{
job.twist_angle = 0.f;
ASSERT_TRUE(job.Run());
EXPECT_REACHED(job);
EXPECT_SIMDQUATERNION_EQ_TOL(qstart, 0.f, 0.f, 0.f, 1.f, 2e-3f);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
// Twist angle pi / 2
{
job.twist_angle = ozz::math::kPi_2;
ASSERT_TRUE(job.Run());
EXPECT_REACHED(job);
const ozz::math::Quaternion h_Pi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3(.70710678f, .70710678f, 0.f), ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(qstart, h_Pi_2.x, h_Pi_2.y, h_Pi_2.z, h_Pi_2.w,
2e-3f);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
// Twist angle pi
{
job.twist_angle = ozz::math::kPi;
ASSERT_TRUE(job.Run());
EXPECT_REACHED(job);
const ozz::math::Quaternion h_Pi = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3(.70710678f, .70710678f, 0.f), -ozz::math::kPi);
EXPECT_SIMDQUATERNION_EQ_TOL(qstart, h_Pi.x, h_Pi.y, h_Pi.z, h_Pi.w, 2e-3f);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
// Twist angle 2pi
{
job.twist_angle = ozz::math::kPi * 2.f;
ASSERT_TRUE(job.Run());
EXPECT_REACHED(job);
EXPECT_SIMDQUATERNION_EQ_TOL(qstart, 0.f, 0.f, 0.f, 1.f, 2e-3f);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
}
TEST(Weight, IKTwoBoneJob) {
// Setup initial pose
const ozz::math::Float4x4 start = ozz::math::Float4x4::identity();
const ozz::math::Float4x4 mid = ozz::math::Float4x4::FromAffine(
ozz::math::simd_float4::y_axis(),
ozz::math::SimdQuaternion::FromAxisAngle(
ozz::math::simd_float4::z_axis(),
ozz::math::simd_float4::Load1(ozz::math::kPi_2))
.xyzw,
ozz::math::simd_float4::one());
const ozz::math::Float4x4 end = ozz::math::Float4x4::Translation(
ozz::math::simd_float4::x_axis() + ozz::math::simd_float4::y_axis());
const ozz::math::SimdFloat4 mid_axis =
ozz::math::Cross3(start.cols[3] - mid.cols[3], end.cols[3] - mid.cols[3]);
// Prepares job.
ozz::animation::IKTwoBoneJob job;
job.start_joint = &start;
job.mid_joint = &mid;
job.end_joint = &end;
job.mid_axis = mid_axis;
ozz::math::SimdQuaternion qstart;
job.start_joint_correction = &qstart;
ozz::math::SimdQuaternion qmid;
job.mid_joint_correction = &qmid;
bool reached;
job.reached = &reached;
ASSERT_TRUE(job.Validate());
// Maximum weight
{
job.pole_vector = ozz::math::simd_float4::y_axis();
job.target = ozz::math::simd_float4::Load(2.f, 0.f, 0.f, 0.f);
job.weight = 1.f;
ASSERT_TRUE(job.Run());
EXPECT_REACHED(job);
const ozz::math::Quaternion z_mPi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), -ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(qstart, z_mPi_2.x, z_mPi_2.y, z_mPi_2.z,
z_mPi_2.w, 2e-3f);
const ozz::math::Quaternion z_Pi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, z_Pi_2.x, z_Pi_2.y, z_Pi_2.z, z_Pi_2.w,
2e-3f);
}
// Weight > 1 is clamped
{
job.pole_vector = ozz::math::simd_float4::y_axis();
job.target = ozz::math::simd_float4::Load(2.f, 0.f, 0.f, 0.f);
job.weight = 1.1f;
ASSERT_TRUE(job.Run());
EXPECT_REACHED(job);
const ozz::math::Quaternion z_mPi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), -ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(qstart, z_mPi_2.x, z_mPi_2.y, z_mPi_2.z,
z_mPi_2.w, 2e-3f);
const ozz::math::Quaternion z_Pi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, z_Pi_2.x, z_Pi_2.y, z_Pi_2.z, z_Pi_2.w,
2e-3f);
}
// 0 weight
{
job.pole_vector = ozz::math::simd_float4::y_axis();
job.target = ozz::math::simd_float4::Load(2.f, 0.f, 0.f, 0.f);
job.weight = 0.f;
ASSERT_TRUE(job.Run());
EXPECT_NOT_REACHED(job);
EXPECT_SIMDQUATERNION_EQ_EST(qstart, 0.f, 0.f, 0.f, 1.f);
EXPECT_SIMDQUATERNION_EQ_EST(qmid, 0.f, 0.f, 0.f, 1.f);
}
// Weight < 0 is clamped
{
job.pole_vector = ozz::math::simd_float4::y_axis();
job.target = ozz::math::simd_float4::Load(2.f, 0.f, 0.f, 0.f);
job.weight = -.1f;
ASSERT_TRUE(job.Run());
EXPECT_NOT_REACHED(job);
EXPECT_SIMDQUATERNION_EQ_EST(qstart, 0.f, 0.f, 0.f, 1.f);
EXPECT_SIMDQUATERNION_EQ_EST(qmid, 0.f, 0.f, 0.f, 1.f);
}
// .5 weight
{
job.pole_vector = ozz::math::simd_float4::y_axis();
job.target = ozz::math::simd_float4::Load(2.f, 0.f, 0.f, 0.f);
job.weight = .5f;
ASSERT_TRUE(job.Run());
EXPECT_NOT_REACHED(job);
const ozz::math::Quaternion z_mPi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), -ozz::math::kPi_2 * job.weight);
EXPECT_SIMDQUATERNION_EQ_TOL(qstart, z_mPi_2.x, z_mPi_2.y, z_mPi_2.z,
z_mPi_2.w, 2e-3f);
const ozz::math::Quaternion z_Pi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), ozz::math::kPi_2 * job.weight);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, z_Pi_2.x, z_Pi_2.y, z_Pi_2.z, z_Pi_2.w,
2e-3f);
}
}
TEST(PoleTargetAlignment, IKTwoBoneJob) {
// Setup initial pose
const ozz::math::Float4x4 start = ozz::math::Float4x4::identity();
const ozz::math::Float4x4 mid = ozz::math::Float4x4::FromAffine(
ozz::math::simd_float4::y_axis(),
ozz::math::SimdQuaternion::FromAxisAngle(
ozz::math::simd_float4::z_axis(),
ozz::math::simd_float4::Load1(ozz::math::kPi_2))
.xyzw,
ozz::math::simd_float4::one());
const ozz::math::Float4x4 end = ozz::math::Float4x4::Translation(
ozz::math::simd_float4::x_axis() + ozz::math::simd_float4::y_axis());
const ozz::math::SimdFloat4 mid_axis =
ozz::math::Cross3(start.cols[3] - mid.cols[3], end.cols[3] - mid.cols[3]);
// Prepares job.
ozz::animation::IKTwoBoneJob job;
job.start_joint = &start;
job.mid_joint = &mid;
job.end_joint = &end;
job.mid_axis = mid_axis;
ozz::math::SimdQuaternion qstart;
job.start_joint_correction = &qstart;
ozz::math::SimdQuaternion qmid;
job.mid_joint_correction = &qmid;
bool reached;
job.reached = &reached;
ASSERT_TRUE(job.Validate());
{ // Reachable, undefined qstart
job.pole_vector = ozz::math::simd_float4::y_axis();
job.target = ozz::math::simd_float4::Load(0.f, ozz::math::kSqrt2, 0.f, 0.f);
ASSERT_TRUE(job.Run());
EXPECT_REACHED(job);
// qstart is undefined, many solutions in this case
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
{ // Reachable, defined qstart
job.pole_vector = ozz::math::simd_float4::y_axis();
job.target =
ozz::math::simd_float4::Load(.001f, ozz::math::kSqrt2, 0.f, 0.f);
ASSERT_TRUE(job.Run());
EXPECT_REACHED(job);
const ozz::math::Quaternion z_Pi_4 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), ozz::math::kPi_4);
EXPECT_SIMDQUATERNION_EQ_TOL(qstart, z_Pi_4.x, z_Pi_4.y, z_Pi_4.z, z_Pi_4.w,
2e-3f);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
{ // Full extent, undefined qstart, end not reached
job.pole_vector = ozz::math::simd_float4::y_axis();
job.target = ozz::math::simd_float4::Load(0.f, 3.f, 0.f, 0.f);
ASSERT_TRUE(job.Run());
// qstart is undefined, many solutions in this case
const ozz::math::Quaternion z_Pi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, z_Pi_2.x, z_Pi_2.y, z_Pi_2.z, z_Pi_2.w,
2e-3f);
}
}
TEST(MidAxis, IKTwoBoneJob) {
// Setup initial pose
const ozz::math::Float4x4 start = ozz::math::Float4x4::identity();
const ozz::math::Float4x4 mid = ozz::math::Float4x4::FromAffine(
ozz::math::simd_float4::y_axis(),
ozz::math::SimdQuaternion::FromAxisAngle(
ozz::math::simd_float4::z_axis(),
ozz::math::simd_float4::Load1(ozz::math::kPi_2))
.xyzw,
ozz::math::simd_float4::one());
const ozz::math::Float4x4 end = ozz::math::Float4x4::Translation(
ozz::math::simd_float4::x_axis() + ozz::math::simd_float4::y_axis());
const ozz::math::SimdFloat4 mid_axis =
ozz::math::Cross3(start.cols[3] - mid.cols[3], end.cols[3] - mid.cols[3]);
// Prepares job.
ozz::animation::IKTwoBoneJob job;
job.pole_vector = ozz::math::simd_float4::y_axis();
job.target = ozz::math::simd_float4::Load(1.f, 1.f, 0.f, 0.f);
job.start_joint = &start;
job.mid_joint = &mid;
job.end_joint = &end;
ozz::math::SimdQuaternion qstart;
job.start_joint_correction = &qstart;
ozz::math::SimdQuaternion qmid;
job.mid_joint_correction = &qmid;
bool reached;
job.reached = &reached;
ASSERT_TRUE(job.Validate());
// Positive mid_axis
{
job.mid_axis = mid_axis;
job.target = ozz::math::simd_float4::Load(1.f, 1.f, 0.f, 0.f);
ASSERT_TRUE(job.Run());
EXPECT_REACHED(job);
EXPECT_SIMDQUATERNION_EQ_TOL(qstart, 0.f, 0.f, 0.f, 1.f, 2e-3f);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
// Negative mid_axis
{
job.mid_axis = -mid_axis;
job.target = ozz::math::simd_float4::Load(1.f, 1.f, 0.f, 0.f);
ASSERT_TRUE(job.Run());
EXPECT_REACHED(job);
const ozz::math::Quaternion y_Pi = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::y_axis(), ozz::math::kPi);
EXPECT_SIMDQUATERNION_EQ_TOL(qstart, y_Pi.x, y_Pi.y, y_Pi.z, y_Pi.w, 2e-3f);
const ozz::math::Quaternion z_Pi = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), ozz::math::kPi);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, z_Pi.x, z_Pi.y, z_Pi.z, z_Pi.w, 2e-3f);
}
// Aligned joints
{
// Replaces "end" joint matrix to align the 3 joints.
const ozz::math::Float4x4 aligned_end = ozz::math::Float4x4::Translation(
ozz::math::simd_float4::Load(0.f, 2.f, 0.f, 0.f));
job.end_joint = &aligned_end;
job.mid_axis = mid_axis;
job.target = ozz::math::simd_float4::Load(1.f, 1.f, 0.f, 0.f);
ASSERT_TRUE(job.Run());
EXPECT_REACHED(job);
// Restore end joint matrix
job.end_joint = &end;
// Start rotates 180 on y, to allow Mid to turn positively on z axis.
EXPECT_SIMDQUATERNION_EQ_TOL(qstart, 0.f, 0.f, 0.f, 1.f, 2e-3f);
const ozz::math::Quaternion z_mPi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), -ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, z_mPi_2.x, z_mPi_2.y, z_mPi_2.z,
z_mPi_2.w, 2e-3f);
}
}
TEST(AlignedJointsAndTarget, IKTwoBoneJob) {
// Setup initial pose
const ozz::math::Float4x4 start = ozz::math::Float4x4::identity();
const ozz::math::Float4x4 mid =
ozz::math::Float4x4::Translation(ozz::math::simd_float4::x_axis());
const ozz::math::Float4x4 end = ozz::math::Float4x4::Translation(
ozz::math::simd_float4::x_axis() + ozz::math::simd_float4::x_axis());
const ozz::math::SimdFloat4 mid_axis = ozz::math::simd_float4::z_axis();
// Prepares job.
ozz::animation::IKTwoBoneJob job;
job.pole_vector = ozz::math::simd_float4::y_axis();
job.mid_axis = mid_axis;
job.start_joint = &start;
job.mid_joint = &mid;
job.end_joint = &end;
ozz::math::SimdQuaternion qstart;
job.start_joint_correction = &qstart;
ozz::math::SimdQuaternion qmid;
job.mid_joint_correction = &qmid;
bool reached;
job.reached = &reached;
ASSERT_TRUE(job.Validate());
// Aligned and reachable
{
job.target = ozz::math::simd_float4::Load(2.f, 0.f, 0.f, 0.f);
ASSERT_TRUE(job.Run());
EXPECT_REACHED(job);
// Start rotates 180 on y, to allow Mid to turn positively on z axis.
EXPECT_SIMDQUATERNION_EQ_TOL(qstart, 0.f, 0.f, 0.f, 1.f, 2e-3f);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
// Aligned and unreachable
{
job.target = ozz::math::simd_float4::Load(3.f, 0.f, 0.f, 0.f);
ASSERT_TRUE(job.Run());
EXPECT_NOT_REACHED(job);
// Start rotates 180 on y, to allow Mid to turn positively on z axis.
EXPECT_SIMDQUATERNION_EQ_TOL(qstart, 0.f, 0.f, 0.f, 1.f, 2e-3f);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, 0.f, 0.f, 0.f, 1.f, 2e-3f);
}
}
TEST(ZeroLengthStartTarget, IKTwoBoneJob) {
// Setup initial pose
const ozz::math::Float4x4 start = ozz::math::Float4x4::identity();
const ozz::math::Float4x4 mid = ozz::math::Float4x4::FromAffine(
ozz::math::simd_float4::y_axis(),
ozz::math::SimdQuaternion::FromAxisAngle(
ozz::math::simd_float4::z_axis(),
ozz::math::simd_float4::Load1(ozz::math::kPi_2))
.xyzw,
ozz::math::simd_float4::one());
const ozz::math::Float4x4 end = ozz::math::Float4x4::Translation(
ozz::math::simd_float4::x_axis() + ozz::math::simd_float4::y_axis());
// Prepares job.
ozz::animation::IKTwoBoneJob job;
job.pole_vector = ozz::math::simd_float4::y_axis();
job.target = start.cols[3]; // 0 length from start to target
job.start_joint = &start;
job.mid_joint = &mid;
job.end_joint = &end;
ozz::math::SimdQuaternion qstart;
job.start_joint_correction = &qstart;
ozz::math::SimdQuaternion qmid;
job.mid_joint_correction = &qmid;
bool reached;
job.reached = &reached;
ASSERT_TRUE(job.Validate());
ASSERT_TRUE(job.Run());
EXPECT_SIMDQUATERNION_EQ_TOL(qstart, 0.f, 0.f, 0.f, 1.f, 2e-3f);
// Mid joint is bent -90 degrees to reach start.
const ozz::math::Quaternion z_mPi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::z_axis(), -ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(qmid, z_mPi_2.x, z_mPi_2.y, z_mPi_2.z, z_mPi_2.w,
2e-3f);
}
TEST(ZeroLengthBoneChain, IKTwoBoneJob) {
// Setup initial pose
const ozz::math::Float4x4 start = ozz::math::Float4x4::identity();
const ozz::math::Float4x4 mid = ozz::math::Float4x4::identity();
const ozz::math::Float4x4 end = ozz::math::Float4x4::identity();
// Prepares job.
ozz::animation::IKTwoBoneJob job;
job.pole_vector = ozz::math::simd_float4::y_axis();
job.target = ozz::math::simd_float4::x_axis();
job.start_joint = &start;
job.mid_joint = &mid;
job.end_joint = &end;
ozz::math::SimdQuaternion qstart;
job.start_joint_correction = &qstart;
ozz::math::SimdQuaternion qmid;
job.mid_joint_correction = &qmid;
ASSERT_TRUE(job.Validate());
// Just expecting it's not crashing
ASSERT_TRUE(job.Run());
EXPECT_NOT_REACHED(job);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,640 @@
//----------------------------------------------------------------------------//
// //
// 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 "gtest/gtest.h"
#include "ozz/animation/offline/animation_builder.h"
#include "ozz/animation/offline/raw_animation.h"
#include "ozz/animation/runtime/animation.h"
#include "ozz/animation/runtime/sampling_job.h"
#include "ozz/base/maths/gtest_math_helper.h"
#include "ozz/base/maths/soa_transform.h"
#include "ozz/base/memory/unique_ptr.h"
using ozz::animation::Animation;
using ozz::animation::SamplingCache;
using ozz::animation::SamplingJob;
using ozz::animation::offline::AnimationBuilder;
using ozz::animation::offline::RawAnimation;
TEST(JobValidity, SamplingJob) {
RawAnimation raw_animation;
raw_animation.duration = 1.f;
raw_animation.tracks.resize(1);
AnimationBuilder builder;
ozz::unique_ptr<Animation> animation(builder(raw_animation));
ASSERT_TRUE(animation);
// Allocates cache.
SamplingCache cache(1);
{ // Empty/default job
SamplingJob job;
EXPECT_FALSE(job.Validate());
EXPECT_FALSE(job.Run());
}
{ // Invalid output
SamplingJob job;
job.animation = animation.get();
job.cache = &cache;
EXPECT_FALSE(job.Validate());
EXPECT_FALSE(job.Run());
}
{ // Invalid animation.
ozz::math::SoaTransform output[1];
SamplingJob job;
job.cache = &cache;
job.output = output;
EXPECT_FALSE(job.Validate());
EXPECT_FALSE(job.Run());
}
{ // Invalid cache.
ozz::math::SoaTransform output[1];
SamplingJob job;
job.animation = animation.get();
job.output = output;
EXPECT_FALSE(job.Validate());
EXPECT_FALSE(job.Run());
}
{ // Invalid cache size.
SamplingCache zero_cache(0);
ozz::math::SoaTransform output[1];
SamplingJob job;
job.animation = animation.get();
job.cache = &zero_cache;
job.output = output;
EXPECT_FALSE(job.Validate());
EXPECT_FALSE(job.Run());
}
{ // Invalid job with smaller output.
ozz::math::SoaTransform* output = nullptr;
SamplingJob job;
job.ratio =
2155.f; // Any time ratio can be set, it's clamped in unit interval.
job.animation = animation.get();
job.cache = &cache;
job.output = ozz::span<ozz::math::SoaTransform>(output, size_t(0));
EXPECT_FALSE(job.Validate());
EXPECT_FALSE(job.Run());
}
{ // Valid job.
ozz::math::SoaTransform output[1];
SamplingJob job;
job.ratio = 2155.f; // Any time can be set.
job.animation = animation.get();
job.cache = &cache;
job.output = output;
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
}
{ // Valid job with bigger cache.
SamplingCache big_cache(2);
ozz::math::SoaTransform output[1];
SamplingJob job;
job.ratio = 2155.f; // Any time can be set.
job.animation = animation.get();
job.cache = &big_cache;
job.output = output;
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
}
{ // Valid job with bigger output.
ozz::math::SoaTransform output[2];
SamplingJob job;
job.ratio = 2155.f; // Any time can be set.
job.animation = animation.get();
job.cache = &cache;
job.output = output;
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
}
{ // Default animation.
ozz::math::SoaTransform output[1];
Animation default_animation;
SamplingJob job;
job.animation = &default_animation;
job.cache = &cache;
job.output = output;
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
}
}
TEST(Sampling, SamplingJob) {
// Instantiates a builder objects with default parameters.
AnimationBuilder builder;
// Building an Animation with unsorted keys fails.
RawAnimation raw_animation;
raw_animation.duration = 1.f;
raw_animation.tracks.resize(4);
SamplingCache cache(4);
// Raw animation inputs.
// 0 1
// -----------------------
// 0 - | A |
// 1 - | |
// 2 - B C D E F
// 3 - | G H |
// Final animation.
// 0 1
// -----------------------
// 0 - A-1 4
// 1 - 1 5
// 2 - B2 C6 D8 E10 F11
// 3 - 3 G7 H9 12
struct {
float sample_time;
float trans[12];
} result[] = {
{-.2f, {-1.f, 0.f, 2.f, 7.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f}},
{.0f, {-1.f, 0.f, 2.f, 7.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f}},
{.0000001f,
{-1.f, 0.f, 2.f, 7.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f}},
{.1f, {-1.f, 0.f, 4.f, 7.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f}},
{.2f, {-1.f, 0.f, 6.f, 7.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f}},
{.3f, {-1.f, 0.f, 7.f, 7.5, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f}},
{.4f, {-1.f, 0.f, 8.f, 8.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f}},
{.3999999f,
{-1.f, 0.f, 8.f, 8.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f}},
{.4000001f,
{-1.f, 0.f, 8.f, 8.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f}},
{.5f, {-1.f, 0.f, 9.f, 8.5f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f}},
{.6f, {-1.f, 0.f, 10.f, 9.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f}},
{.9999999f,
{-1.f, 0.f, 11.f, 9.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f}},
{1.f, {-1.f, 0.f, 11.f, 9.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f}},
{1.000001f,
{-1.f, 0.f, 11.f, 9.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f}},
{.5f, {-1.f, 0.f, 9.f, 8.5f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f}},
{.9999999f,
{-1.f, 0.f, 11.f, 9.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f}},
{.0000001f,
{-1.f, 0.f, 2.f, 7.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f}}};
RawAnimation::TranslationKey a = {.2f, ozz::math::Float3(-1.f, 0.f, 0.f)};
raw_animation.tracks[0].translations.push_back(a);
RawAnimation::TranslationKey b = {0.f, ozz::math::Float3(2.f, 0.f, 0.f)};
raw_animation.tracks[2].translations.push_back(b);
RawAnimation::TranslationKey c = {0.2f, ozz::math::Float3(6.f, 0.f, 0.f)};
raw_animation.tracks[2].translations.push_back(c);
RawAnimation::TranslationKey d = {0.4f, ozz::math::Float3(8.f, 0.f, 0.f)};
raw_animation.tracks[2].translations.push_back(d);
RawAnimation::TranslationKey e = {0.6f, ozz::math::Float3(10.f, 0.f, 0.f)};
raw_animation.tracks[2].translations.push_back(e);
RawAnimation::TranslationKey f = {1.f, ozz::math::Float3(11.f, 0.f, 0.f)};
raw_animation.tracks[2].translations.push_back(f);
RawAnimation::TranslationKey g = {0.2f, ozz::math::Float3(7.f, 0.f, 0.f)};
raw_animation.tracks[3].translations.push_back(g);
RawAnimation::TranslationKey h = {0.6f, ozz::math::Float3(9.f, 0.f, 0.f)};
raw_animation.tracks[3].translations.push_back(h);
// Builds animation
ozz::unique_ptr<Animation> animation(builder(raw_animation));
ASSERT_TRUE(animation);
ozz::math::SoaTransform output[1];
SamplingJob job;
job.animation = animation.get();
job.cache = &cache;
job.output = output;
for (size_t i = 0; i < OZZ_ARRAY_SIZE(result); ++i) {
memset(output, 0xde, sizeof(output));
job.ratio = result[i].sample_time / animation->duration();
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ_EST(
output[0].translation, result[i].trans[0], result[i].trans[1],
result[i].trans[2], result[i].trans[3], result[i].trans[4],
result[i].trans[5], result[i].trans[6], result[i].trans[7],
result[i].trans[8], result[i].trans[9], result[i].trans[10],
result[i].trans[11]);
EXPECT_SOAQUATERNION_EQ_EST(output[0].rotation, 0.f, 0.f, 0.f, 0.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f, 1.f,
1.f, 1.f);
EXPECT_SOAFLOAT3_EQ_EST(output[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f,
1.f, 1.f, 1.f, 1.f, 1.f);
}
}
TEST(SamplingNoTrack, SamplingJob) {
RawAnimation raw_animation;
raw_animation.duration = 46.f;
SamplingCache cache(1);
AnimationBuilder builder;
ozz::unique_ptr<Animation> animation(builder(raw_animation));
ASSERT_TRUE(animation);
ozz::math::SoaTransform test_output[1];
ozz::math::SoaTransform output[1];
memset(test_output, 0xde, sizeof(test_output));
memset(output, 0xde, sizeof(output));
SamplingJob job;
job.ratio = 0.f;
job.animation = animation.get();
job.cache = &cache;
job.output = output;
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
// Tests output.
EXPECT_EQ(memcmp(test_output, output, sizeof(output)), 0);
}
TEST(Sampling1Track0Key, SamplingJob) {
RawAnimation raw_animation;
raw_animation.duration = 46.f;
raw_animation.tracks.resize(1); // Adds a joint.
SamplingCache cache(1);
AnimationBuilder builder;
ozz::unique_ptr<Animation> animation(builder(raw_animation));
ASSERT_TRUE(animation);
ozz::math::SoaTransform output[1];
SamplingJob job;
job.animation = animation.get();
job.cache = &cache;
job.output = output;
for (float t = -.2f; t < 1.2f; t += .1f) {
memset(output, 0xde, sizeof(output));
job.ratio = t;
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ_EST(output[0].translation, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f);
EXPECT_SOAQUATERNION_EQ_EST(output[0].rotation, 0.f, 0.f, 0.f, 0.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f, 1.f,
1.f, 1.f);
EXPECT_SOAFLOAT3_EQ_EST(output[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f,
1.f, 1.f, 1.f, 1.f, 1.f);
}
}
TEST(Sampling1Track1Key, SamplingJob) {
RawAnimation raw_animation;
raw_animation.duration = 46.f;
raw_animation.tracks.resize(1); // Adds a joint.
SamplingCache cache(1);
const RawAnimation::TranslationKey tkey = {.3f,
ozz::math::Float3(1.f, -1.f, 5.f)};
raw_animation.tracks[0].translations.push_back(tkey); // Adds a key.
AnimationBuilder builder;
ozz::unique_ptr<Animation> animation(builder(raw_animation));
ASSERT_TRUE(animation);
ozz::math::SoaTransform output[1];
SamplingJob job;
job.animation = animation.get();
job.cache = &cache;
job.output = output;
for (float t = -.2f; t < 1.2f; t += .1f) {
memset(output, 0xde, sizeof(output));
job.ratio = t;
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ_EST(output[0].translation, 1.f, 0.f, 0.f, 0.f, -1.f,
0.f, 0.f, 0.f, 5.f, 0.f, 0.f, 0.f);
EXPECT_SOAQUATERNION_EQ_EST(output[0].rotation, 0.f, 0.f, 0.f, 0.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f, 1.f,
1.f, 1.f);
EXPECT_SOAFLOAT3_EQ_EST(output[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f,
1.f, 1.f, 1.f, 1.f, 1.f);
}
}
TEST(Sampling1Track2Keys, SamplingJob) {
RawAnimation raw_animation;
raw_animation.duration = 46.f;
raw_animation.tracks.resize(1); // Adds a joint.
SamplingCache cache(1);
const RawAnimation::TranslationKey tkey0 = {.5f,
ozz::math::Float3(1.f, 2.f, 4.f)};
raw_animation.tracks[0].translations.push_back(tkey0); // Adds a key.
const RawAnimation::TranslationKey tkey1 = {.8f,
ozz::math::Float3(2.f, 4.f, 8.f)};
raw_animation.tracks[0].translations.push_back(tkey1); // Adds a key.
AnimationBuilder builder;
ozz::unique_ptr<Animation> animation(builder(raw_animation));
ASSERT_TRUE(animation);
ozz::math::SoaTransform output[1];
memset(output, 0xde, sizeof(output));
SamplingJob job;
job.animation = animation.get();
job.cache = &cache;
job.output = output;
// Samples at t = 0.
job.ratio = 0.f;
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ_EST(output[0].translation, 1.f, 0.f, 0.f, 0.f, 2.f, 0.f,
0.f, 0.f, 4.f, 0.f, 0.f, 0.f);
EXPECT_SOAQUATERNION_EQ_EST(output[0].rotation, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f, 1.f, 1.f, 1.f);
EXPECT_SOAFLOAT3_EQ_EST(output[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f,
1.f, 1.f, 1.f, 1.f, 1.f);
// Samples at t = tkey0.
job.ratio = tkey0.time / animation->duration();
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ_EST(output[0].translation, 1.f, 0.f, 0.f, 0.f, 2.f, 0.f,
0.f, 0.f, 4.f, 0.f, 0.f, 0.f);
EXPECT_SOAQUATERNION_EQ_EST(output[0].rotation, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f, 1.f, 1.f, 1.f);
EXPECT_SOAFLOAT3_EQ_EST(output[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f,
1.f, 1.f, 1.f, 1.f, 1.f);
// Samples at t = tkey1.
job.ratio = tkey1.time / animation->duration();
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ_EST(output[0].translation, 2.f, 0.f, 0.f, 0.f, 4.f, 0.f,
0.f, 0.f, 8.f, 0.f, 0.f, 0.f);
EXPECT_SOAQUATERNION_EQ_EST(output[0].rotation, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f, 1.f, 1.f, 1.f);
EXPECT_SOAFLOAT3_EQ_EST(output[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f,
1.f, 1.f, 1.f, 1.f, 1.f);
// Samples at t = end.
job.ratio = 1.f;
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ_EST(output[0].translation, 2.f, 0.f, 0.f, 0.f, 4.f, 0.f,
0.f, 0.f, 8.f, 0.f, 0.f, 0.f);
EXPECT_SOAQUATERNION_EQ_EST(output[0].rotation, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f, 1.f, 1.f, 1.f);
EXPECT_SOAFLOAT3_EQ_EST(output[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f,
1.f, 1.f, 1.f, 1.f, 1.f);
// Samples at tkey0.time < t < tkey1.time.
job.ratio = (tkey0.time / animation->duration() +
tkey1.time / animation->duration()) /
2.f;
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ_EST(output[0].translation, 1.5f, 0.f, 0.f, 0.f, 3.f, 0.f,
0.f, 0.f, 6.f, 0.f, 0.f, 0.f);
EXPECT_SOAQUATERNION_EQ_EST(output[0].rotation, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f, 1.f, 1.f, 1.f);
EXPECT_SOAFLOAT3_EQ_EST(output[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f,
1.f, 1.f, 1.f, 1.f, 1.f);
}
TEST(Sampling4Track2Keys, SamplingJob) {
RawAnimation raw_animation;
raw_animation.duration = 1.f;
raw_animation.tracks.resize(4); // Adds a joint.
SamplingCache cache(1);
const RawAnimation::TranslationKey tkey00 = {
.5f, ozz::math::Float3(1.f, 2.f, 4.f)};
raw_animation.tracks[0].translations.push_back(tkey00); // Adds a key.
const RawAnimation::TranslationKey tkey01 = {
.8f, ozz::math::Float3(2.f, 4.f, 8.f)};
raw_animation.tracks[0].translations.push_back(tkey01); // Adds a key.
// This quaternion will be negated as the builder ensures that the first key
// is in identity quaternion hemisphere.
const RawAnimation::RotationKey rkey10 = {
0.f, ozz::math::Quaternion(0.f, 0.f, 0.f, -1.f)};
raw_animation.tracks[1].rotations.push_back(rkey10); // Adds a key.
const RawAnimation::RotationKey rkey11 = {
1.f, ozz::math::Quaternion(0.f, 1.f, 0.f, 0.f)};
raw_animation.tracks[1].rotations.push_back(rkey11); // Adds a key.
const RawAnimation::ScaleKey skey20 = {.5f, ozz::math::Float3(0.f, 0.f, 0.f)};
raw_animation.tracks[2].scales.push_back(skey20); // Adds a key.
const RawAnimation::ScaleKey skey21 = {.8f,
ozz::math::Float3(-1.f, -1.f, -1.f)};
raw_animation.tracks[2].scales.push_back(skey21); // Adds a key.
const RawAnimation::TranslationKey tkey30 = {
0.f, ozz::math::Float3(-1.f, -2.f, -4.f)};
raw_animation.tracks[3].translations.push_back(tkey30); // Adds a key.
const RawAnimation::TranslationKey tkey31 = {
1.f, ozz::math::Float3(-2.f, -4.f, -8.f)};
raw_animation.tracks[3].translations.push_back(tkey31); // Adds a key.
AnimationBuilder builder;
ozz::unique_ptr<Animation> animation(builder(raw_animation));
ASSERT_TRUE(animation);
ozz::math::SoaTransform output[1];
memset(output, 0xde, sizeof(output));
SamplingJob job;
job.animation = animation.get();
job.cache = &cache;
job.output = output;
// Samples at t = 0.
job.ratio = 0.f;
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ_EST(output[0].translation, 1.f, 0.f, 0.f, -1.f, 2.f, 0.f,
0.f, -2.f, 4.f, 0.f, 0.f, -4.f);
EXPECT_SOAQUATERNION_EQ_EST(output[0].rotation, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f, 1.f, 1.f, 1.f);
EXPECT_SOAFLOAT3_EQ_EST(output[0].scale, 1.f, 1.f, 0.f, 1.f, 1.f, 1.f, 0.f,
1.f, 1.f, 1.f, 0.f, 1.f);
// Samples at t = tkey00.
job.ratio = tkey00.time / animation->duration();
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ_EST(output[0].translation, 1.f, 0.f, 0.f, -1.5f, 2.f, 0.f,
0.f, -3.f, 4.f, 0.f, 0.f, -6.f);
EXPECT_SOAQUATERNION_EQ_EST(output[0].rotation, 0.f, 0.f, 0.f, 0.f, 0.f,
.7071067f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f,
.7071067f, 1.f, 1.f);
EXPECT_SOAFLOAT3_EQ_EST(output[0].scale, 1.f, 1.f, 0.f, 1.f, 1.f, 1.f, 0.f,
1.f, 1.f, 1.f, 0.f, 1.f);
// Samples at t = end.
job.ratio = 1.f;
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ_EST(output[0].translation, 2.f, 0.f, 0.f, -2.f, 4.f, 0.f,
0.f, -4.f, 8.f, 0.f, 0.f, -8.f);
EXPECT_SOAQUATERNION_EQ_EST(output[0].rotation, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 1.f, 1.f);
EXPECT_SOAFLOAT3_EQ_EST(output[0].scale, 1.f, 1.f, -1.f, 1.f, 1.f, 1.f, -1.f,
1.f, 1.f, 1.f, -1.f, 1.f);
}
TEST(Cache, SamplingJob) {
RawAnimation raw_animation;
raw_animation.duration = 46.f;
raw_animation.tracks.resize(1); // Adds a joint.
const RawAnimation::TranslationKey empty_key = {
0.f, RawAnimation::TranslationKey::identity()};
raw_animation.tracks[0].translations.push_back(empty_key);
SamplingCache cache(1);
ozz::unique_ptr<Animation> animations[2];
{
const RawAnimation::TranslationKey tkey = {
.3f, ozz::math::Float3(1.f, -1.f, 5.f)};
raw_animation.tracks[0].translations[0] = tkey;
AnimationBuilder builder;
animations[0] = builder(raw_animation);
ASSERT_TRUE(animations[0]);
}
{
const RawAnimation::TranslationKey tkey = {
.3f, ozz::math::Float3(-1.f, 1.f, -5.f)};
raw_animation.tracks[0].translations[0] = tkey;
AnimationBuilder builder;
animations[1] = builder(raw_animation);
ASSERT_TRUE(animations[1]);
}
ozz::math::SoaTransform output[1];
SamplingJob job;
job.animation = animations[0].get();
job.cache = &cache;
job.ratio = 0.f;
job.output = output;
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ_EST(output[0].translation, 1.f, 0.f, 0.f, 0.f, -1.f, 0.f,
0.f, 0.f, 5.f, 0.f, 0.f, 0.f);
EXPECT_SOAQUATERNION_EQ_EST(output[0].rotation, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f, 1.f, 1.f, 1.f);
EXPECT_SOAFLOAT3_EQ_EST(output[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f,
1.f, 1.f, 1.f, 1.f, 1.f);
// Re-uses cache.
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ_EST(output[0].translation, 1.f, 0.f, 0.f, 0.f, -1.f, 0.f,
0.f, 0.f, 5.f, 0.f, 0.f, 0.f);
// Invalidates cache.
cache.Invalidate();
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ_EST(output[0].translation, 1.f, 0.f, 0.f, 0.f, -1.f, 0.f,
0.f, 0.f, 5.f, 0.f, 0.f, 0.f);
// Changes animation.
job.animation = animations[1].get();
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ_EST(output[0].translation, -1.f, 0.f, 0.f, 0.f, 1.f, 0.f,
0.f, 0.f, -5.f, 0.f, 0.f, 0.f);
EXPECT_SOAQUATERNION_EQ_EST(output[0].rotation, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f, 1.f, 1.f, 1.f);
EXPECT_SOAFLOAT3_EQ_EST(output[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f,
1.f, 1.f, 1.f, 1.f, 1.f);
// Invalidates and changes animation.
job.animation = animations[1].get();
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
EXPECT_SOAFLOAT3_EQ_EST(output[0].translation, -1.f, 0.f, 0.f, 0.f, 1.f, 0.f,
0.f, 0.f, -5.f, 0.f, 0.f, 0.f);
}
TEST(CacheResize, SamplingJob) {
RawAnimation raw_animation;
raw_animation.duration = 46.f;
raw_animation.tracks.resize(7);
AnimationBuilder builder;
ozz::unique_ptr<Animation> animation(builder(raw_animation));
ASSERT_TRUE(animation);
// Empty cache by default
SamplingCache cache;
ozz::math::SoaTransform output[7];
SamplingJob job;
job.animation = animation.get();
job.cache = &cache;
job.ratio = 0.f;
job.output = output;
// Cache is too small
EXPECT_FALSE(job.Validate());
// Cache is ok.
cache.Resize(7);
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
// Cache is too small
cache.Resize(1);
EXPECT_FALSE(job.Validate());
}
@@ -0,0 +1,179 @@
//----------------------------------------------------------------------------//
// //
// 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 "gtest/gtest.h"
#include "ozz/animation/offline/raw_skeleton.h"
#include "ozz/animation/offline/skeleton_builder.h"
#include "ozz/base/io/archive.h"
#include "ozz/base/io/stream.h"
#include "ozz/base/maths/soa_transform.h"
#include "ozz/base/memory/unique_ptr.h"
using ozz::animation::Skeleton;
using ozz::animation::offline::RawSkeleton;
using ozz::animation::offline::SkeletonBuilder;
TEST(Empty, SkeletonSerialize) {
ozz::io::MemoryStream stream;
// Streams out.
ozz::io::OArchive o(&stream, ozz::GetNativeEndianness());
Skeleton o_skeleton;
o << o_skeleton;
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive i(&stream);
Skeleton i_skeleton;
i >> i_skeleton;
EXPECT_EQ(o_skeleton.num_joints(), i_skeleton.num_joints());
}
TEST(Filled, SkeletonSerialize) {
ozz::unique_ptr<Skeleton> o_skeleton;
/* Builds output skeleton.
3 joints
*
|
root
/ \
j0 j1
*/
{
RawSkeleton raw_skeleton;
raw_skeleton.roots.resize(1);
RawSkeleton::Joint& root = raw_skeleton.roots[0];
root.name = "root";
root.children.resize(2);
root.children[0].name = "j0";
root.children[1].name = "j1";
EXPECT_TRUE(raw_skeleton.Validate());
EXPECT_EQ(raw_skeleton.num_joints(), 3);
SkeletonBuilder builder;
o_skeleton = builder(raw_skeleton);
ASSERT_TRUE(o_skeleton);
}
for (int e = 0; e < 2; ++e) {
ozz::Endianness endianess = e == 0 ? ozz::kBigEndian : ozz::kLittleEndian;
ozz::io::MemoryStream stream;
// Streams out.
ozz::io::OArchive o(&stream, endianess);
o << *o_skeleton;
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive ia(&stream);
Skeleton i_skeleton;
ia >> i_skeleton;
// Compares skeletons.
EXPECT_EQ(o_skeleton->num_joints(), i_skeleton.num_joints());
for (int i = 0; i < i_skeleton.num_joints(); ++i) {
EXPECT_EQ(i_skeleton.joint_parents()[i],
o_skeleton->joint_parents()[i]);
EXPECT_STREQ(i_skeleton.joint_names()[i], o_skeleton->joint_names()[i]);
}
for (int i = 0; i < (i_skeleton.num_joints() + 3) / 4; ++i) {
EXPECT_TRUE(ozz::math::AreAllTrue(
i_skeleton.joint_bind_poses()[i].translation ==
o_skeleton->joint_bind_poses()[i].translation));
EXPECT_TRUE(ozz::math::AreAllTrue(
i_skeleton.joint_bind_poses()[i].rotation ==
o_skeleton->joint_bind_poses()[i].rotation));
EXPECT_TRUE(
ozz::math::AreAllTrue(i_skeleton.joint_bind_poses()[i].scale ==
o_skeleton->joint_bind_poses()[i].scale));
}
}
}
TEST(AlreadyInitialized, SkeletonSerialize) {
ozz::unique_ptr<Skeleton> o_skeleton[2];
/* Builds output skeleton.
3 joints
*
|
root
/ \
j0 j1
*/
{
RawSkeleton raw_skeleton;
raw_skeleton.roots.resize(1);
raw_skeleton.roots[0].name = "root0";
SkeletonBuilder builder;
o_skeleton[0] = builder(raw_skeleton);
ASSERT_TRUE(o_skeleton[0]);
raw_skeleton.roots.resize(2);
raw_skeleton.roots[1].name = "root1";
o_skeleton[1] = builder(raw_skeleton);
ASSERT_TRUE(o_skeleton[1]);
}
{
// Streams out.
ozz::io::MemoryStream stream;
ozz::io::OArchive o(&stream);
o << *o_skeleton[0];
o << *o_skeleton[1];
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive i(&stream);
// Reads and check the first skeleton.
Skeleton i_skeleton;
i >> i_skeleton;
ASSERT_EQ(o_skeleton[0]->num_joints(), 1);
EXPECT_STREQ(i_skeleton.joint_names()[0], o_skeleton[0]->joint_names()[0]);
// Reuse the skeleton a second time.
i >> i_skeleton;
ASSERT_EQ(o_skeleton[1]->num_joints(), 2);
EXPECT_STREQ(i_skeleton.joint_names()[0], o_skeleton[1]->joint_names()[0]);
EXPECT_STREQ(i_skeleton.joint_names()[1], o_skeleton[1]->joint_names()[1]);
}
}
@@ -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/animation/runtime/skeleton.h"
#include "gtest/gtest.h"
#include "ozz/base/io/archive.h"
#include "ozz/base/io/stream.h"
#include "ozz/base/log.h"
#include "ozz/options/options.h"
OZZ_OPTIONS_DECLARE_STRING(file, "Specifies input file", "", true)
OZZ_OPTIONS_DECLARE_INT(joints, "Number of joints", 0, true)
OZZ_OPTIONS_DECLARE_STRING(root_name, "Name of the root joint", "", true)
int main(int _argc, char** _argv) {
// Parses arguments.
testing::InitGoogleTest(&_argc, _argv);
ozz::options::ParseResult parse_result = ozz::options::ParseCommandLine(
_argc, _argv, "1.0",
"Test Skeleton archive versioning retrocompatibility");
if (parse_result != ozz::options::kSuccess) {
return parse_result == ozz::options::kExitSuccess ? EXIT_SUCCESS
: EXIT_FAILURE;
}
return RUN_ALL_TESTS();
}
TEST(Versioning, SkeletonSerialize) {
// Open the file.
const char* filename = OPTIONS_file;
ozz::io::File file(filename, "rb");
ASSERT_TRUE(file.opened());
// Open archive and test object tag.
ozz::io::IArchive archive(&file);
ASSERT_TRUE(archive.TestTag<ozz::animation::Skeleton>());
// Read the object.
ozz::animation::Skeleton skeleton;
archive >> skeleton;
// More testing
EXPECT_EQ(skeleton.num_joints(), OPTIONS_joints);
if (skeleton.num_joints()) {
EXPECT_STREQ(skeleton.joint_names()[0], OPTIONS_root_name);
}
}
@@ -0,0 +1,417 @@
//----------------------------------------------------------------------------//
// //
// 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/offline/skeleton_builder.h"
#include <algorithm>
#include <cstring>
#include "gtest/gtest.h"
#include "ozz/base/gtest_helper.h"
#include "ozz/base/maths/gtest_math_helper.h"
#include "ozz/animation/runtime/skeleton.h"
#include "ozz/animation/runtime/skeleton_utils.h"
#include "ozz/base/memory/unique_ptr.h"
using ozz::animation::Skeleton;
using ozz::animation::offline::RawSkeleton;
using ozz::animation::offline::SkeletonBuilder;
TEST(JointBindPose, SkeletonUtils) {
// Instantiates a builder objects with default parameters.
SkeletonBuilder builder;
RawSkeleton raw_skeleton;
raw_skeleton.roots.resize(1);
RawSkeleton::Joint& r = raw_skeleton.roots[0];
r.name = "r0";
r.transform.translation = ozz::math::Float3::x_axis();
r.transform.rotation = ozz::math::Quaternion::identity();
r.transform.scale = ozz::math::Float3::zero();
r.children.resize(2);
RawSkeleton::Joint& c0 = r.children[0];
c0.name = "j0";
c0.transform.translation = ozz::math::Float3::y_axis();
c0.transform.rotation = -ozz::math::Quaternion::identity();
c0.transform.scale = -ozz::math::Float3::one();
RawSkeleton::Joint& c1 = r.children[1];
c1.name = "j1";
c1.transform.translation = ozz::math::Float3::z_axis();
c1.transform.rotation = Conjugate(ozz::math::Quaternion::identity());
c1.transform.scale = ozz::math::Float3::one();
EXPECT_TRUE(raw_skeleton.Validate());
EXPECT_EQ(raw_skeleton.num_joints(), 3);
ozz::unique_ptr<Skeleton> skeleton(builder(raw_skeleton));
ASSERT_TRUE(skeleton);
EXPECT_EQ(skeleton->num_joints(), 3);
// Out of range.
EXPECT_ASSERTION(GetJointLocalBindPose(*skeleton, 3),
"Joint index out of range.");
const ozz::math::Transform bind_pose0 = GetJointLocalBindPose(*skeleton, 0);
EXPECT_FLOAT3_EQ(bind_pose0.translation, 1.f, 0.f, 0.f);
EXPECT_QUATERNION_EQ(bind_pose0.rotation, 0.f, 0.f, 0.f, 1.f);
EXPECT_FLOAT3_EQ(bind_pose0.scale, 0.f, 0.f, 0.f);
const ozz::math::Transform bind_pose1 = GetJointLocalBindPose(*skeleton, 1);
EXPECT_FLOAT3_EQ(bind_pose1.translation, 0.f, 1.f, 0.f);
EXPECT_QUATERNION_EQ(bind_pose1.rotation, 0.f, 0.f, 0.f, -1.f);
EXPECT_FLOAT3_EQ(bind_pose1.scale, -1.f, -1.f, -1.f);
const ozz::math::Transform bind_pose2 = GetJointLocalBindPose(*skeleton, 2);
EXPECT_FLOAT3_EQ(bind_pose2.translation, 0.f, 0.f, 1.f);
EXPECT_QUATERNION_EQ(bind_pose2.rotation, -0.f, -0.f, -0.f, 1.f);
EXPECT_FLOAT3_EQ(bind_pose2.scale, 1.f, 1.f, 1.f);
}
/* Definition of the skeleton used by the tests.
10 joints, 2 roots
*
/ \
j0 j8
/ \ \
j1 j4 j9
| / \
j2 j5 j6
| |
j3 j7
*/
namespace {
class IterateDFFailTester {
public:
void operator()(int, int) { ASSERT_TRUE(false); }
};
class IterateDFTester {
public:
IterateDFTester(const ozz::animation::Skeleton* _skeleton, int _start)
: skeleton_(_skeleton), start_(_start), num_iterations_(0) {}
void operator()(int _current, int _parent) {
const int joint = start_ + num_iterations_;
EXPECT_EQ(joint, _current);
EXPECT_EQ(skeleton_->joint_parents()[joint], _parent);
++num_iterations_;
}
int num_iterations() const { return num_iterations_; }
private:
// Iterated skeleton.
const ozz::animation::Skeleton* skeleton_;
// First joint to explore.
int start_;
// Number of iterations completed.
int num_iterations_;
};
} // namespace
TEST(InterateDF, SkeletonUtils) {
// Instantiates a builder objects with default parameters.
SkeletonBuilder builder;
RawSkeleton raw_skeleton;
raw_skeleton.roots.resize(2);
RawSkeleton::Joint& j0 = raw_skeleton.roots[0];
j0.name = "j0";
RawSkeleton::Joint& j8 = raw_skeleton.roots[1];
j8.name = "j8";
j0.children.resize(2);
j0.children[0].name = "j1";
j0.children[1].name = "j4";
j0.children[0].children.resize(1);
j0.children[0].children[0].name = "j2";
j0.children[0].children[0].children.resize(1);
j0.children[0].children[0].children[0].name = "j3";
j0.children[1].children.resize(2);
j0.children[1].children[0].name = "j5";
j0.children[1].children[1].name = "j6";
j0.children[1].children[1].children.resize(1);
j0.children[1].children[1].children[0].name = "j7";
j8.children.resize(1);
j8.children[0].name = "j9";
EXPECT_TRUE(raw_skeleton.Validate());
EXPECT_EQ(raw_skeleton.num_joints(), 10);
ozz::unique_ptr<Skeleton> skeleton(builder(raw_skeleton));
ASSERT_TRUE(skeleton);
EXPECT_EQ(skeleton->num_joints(), 10);
{
IterateDFTester fct =
IterateJointsDF(*skeleton, IterateDFTester(skeleton.get(), 0), -12);
EXPECT_EQ(fct.num_iterations(), 10);
}
{
IterateDFTester fct =
IterateJointsDF(*skeleton, IterateDFTester(skeleton.get(), 0));
EXPECT_EQ(fct.num_iterations(), 10);
}
{
IterateDFTester fct = IterateJointsDF(
*skeleton, IterateDFTester(skeleton.get(), 0), Skeleton::kNoParent);
EXPECT_EQ(fct.num_iterations(), 10);
}
{
IterateDFTester fct =
IterateJointsDF(*skeleton, IterateDFTester(skeleton.get(), 0), 0);
EXPECT_EQ(fct.num_iterations(), 8);
}
{
IterateDFTester fct =
IterateJointsDF(*skeleton, IterateDFTester(skeleton.get(), 1), 1);
EXPECT_EQ(fct.num_iterations(), 3);
}
{
IterateDFTester fct =
IterateJointsDF(*skeleton, IterateDFTester(skeleton.get(), 2), 2);
EXPECT_EQ(fct.num_iterations(), 2);
}
{
IterateDFTester fct =
IterateJointsDF(*skeleton, IterateDFTester(skeleton.get(), 3), 3);
EXPECT_EQ(fct.num_iterations(), 1);
}
{
IterateDFTester fct =
IterateJointsDF(*skeleton, IterateDFTester(skeleton.get(), 4), 4);
EXPECT_EQ(fct.num_iterations(), 4);
}
{
IterateDFTester fct =
IterateJointsDF(*skeleton, IterateDFTester(skeleton.get(), 5), 5);
EXPECT_EQ(fct.num_iterations(), 1);
}
{
IterateDFTester fct =
IterateJointsDF(*skeleton, IterateDFTester(skeleton.get(), 6), 6);
EXPECT_EQ(fct.num_iterations(), 2);
}
{
IterateDFTester fct =
IterateJointsDF(*skeleton, IterateDFTester(skeleton.get(), 7), 7);
EXPECT_EQ(fct.num_iterations(), 1);
}
{
IterateDFTester fct =
IterateJointsDF(*skeleton, IterateDFTester(skeleton.get(), 8), 8);
EXPECT_EQ(fct.num_iterations(), 2);
}
{
IterateDFTester fct =
IterateJointsDF(*skeleton, IterateDFTester(skeleton.get(), 9), 9);
EXPECT_EQ(fct.num_iterations(), 1);
}
IterateJointsDF(*skeleton, IterateDFFailTester(), 10);
IterateJointsDF(*skeleton, IterateDFFailTester(), 99);
}
TEST(InterateDFEmpty, SkeletonUtils) {
ozz::animation::Skeleton empty;
IterateJointsDF(empty, IterateDFFailTester(),
ozz::animation::Skeleton::kNoParent);
IterateJointsDF(empty, IterateDFFailTester(), 0);
}
namespace {
class IterateDFReverseTester {
public:
IterateDFReverseTester(const ozz::animation::Skeleton* _skeleton)
: skeleton_(_skeleton), num_iterations_(0) {}
void operator()(int _current, int _parent) {
if (num_iterations_ == 0) {
EXPECT_TRUE(ozz::animation::IsLeaf(*skeleton_, _current));
}
// A joint is traversed once.
ozz::vector<int>::const_iterator itc =
std::find(processed_joints_.begin(), processed_joints_.end(), _current);
EXPECT_TRUE(itc == processed_joints_.end());
// A parent can't be traversed before a child.
ozz::vector<int>::const_iterator itp =
std::find(processed_joints_.begin(), processed_joints_.end(), _parent);
EXPECT_TRUE(itp == processed_joints_.end());
// joint processed
processed_joints_.push_back(_current);
// Validates parent id.
EXPECT_EQ(skeleton_->joint_parents()[_current], _parent);
++num_iterations_;
}
int num_iterations() const { return num_iterations_; }
private:
// Iterated skeleton.
const ozz::animation::Skeleton* skeleton_;
// Number of iterations completed.
int num_iterations_;
// Already processed joints
ozz::vector<int> processed_joints_;
};
} // namespace
TEST(InterateDFReverse, SkeletonUtils) {
// Instantiates a builder objects with default parameters.
SkeletonBuilder builder;
RawSkeleton raw_skeleton;
raw_skeleton.roots.resize(2);
RawSkeleton::Joint& j0 = raw_skeleton.roots[0];
j0.name = "j0";
RawSkeleton::Joint& j8 = raw_skeleton.roots[1];
j8.name = "j8";
j0.children.resize(2);
j0.children[0].name = "j1";
j0.children[1].name = "j4";
j0.children[0].children.resize(1);
j0.children[0].children[0].name = "j2";
j0.children[0].children[0].children.resize(1);
j0.children[0].children[0].children[0].name = "j3";
j0.children[1].children.resize(2);
j0.children[1].children[0].name = "j5";
j0.children[1].children[1].name = "j6";
j0.children[1].children[1].children.resize(1);
j0.children[1].children[1].children[0].name = "j7";
j8.children.resize(1);
j8.children[0].name = "j9";
EXPECT_TRUE(raw_skeleton.Validate());
EXPECT_EQ(raw_skeleton.num_joints(), 10);
ozz::unique_ptr<Skeleton> skeleton(builder(raw_skeleton));
ASSERT_TRUE(skeleton);
EXPECT_EQ(skeleton->num_joints(), 10);
{
IterateDFReverseTester fct = IterateJointsDFReverse(
*skeleton, IterateDFReverseTester(skeleton.get()));
EXPECT_EQ(fct.num_iterations(), 10);
}
}
/* Definition of the skeleton used by the tests.
10 joints, 2 roots
*
/ \
j0 j8
/ \ \
j1 j4 j9
| / \
j2 j5 j6
| |
j3 j7
*/
TEST(IsLeaf, SkeletonUtils) {
// Instantiates a builder objects with default parameters.
SkeletonBuilder builder;
RawSkeleton raw_skeleton;
raw_skeleton.roots.resize(2);
RawSkeleton::Joint& j0 = raw_skeleton.roots[0];
j0.name = "j0";
RawSkeleton::Joint& j8 = raw_skeleton.roots[1];
j8.name = "j8";
j0.children.resize(2);
j0.children[0].name = "j1";
j0.children[1].name = "j4";
j0.children[0].children.resize(1);
j0.children[0].children[0].name = "j2";
j0.children[0].children[0].children.resize(1);
j0.children[0].children[0].children[0].name = "j3";
j0.children[1].children.resize(2);
j0.children[1].children[0].name = "j5";
j0.children[1].children[1].name = "j6";
j0.children[1].children[1].children.resize(1);
j0.children[1].children[1].children[0].name = "j7";
j8.children.resize(1);
j8.children[0].name = "j9";
EXPECT_TRUE(raw_skeleton.Validate());
EXPECT_EQ(raw_skeleton.num_joints(), 10);
ozz::unique_ptr<Skeleton> skeleton(builder(raw_skeleton));
ASSERT_TRUE(skeleton);
EXPECT_EQ(skeleton->num_joints(), 10);
// Out of bound
EXPECT_ASSERTION(IsLeaf(*skeleton, 10), "_joint index out of range");
EXPECT_ASSERTION(IsLeaf(*skeleton, 93), "_joint index out of range");
EXPECT_ASSERTION(IsLeaf(*skeleton, ozz::animation::Skeleton::kNoParent),
"_joint index out of range");
EXPECT_FALSE(IsLeaf(*skeleton, 0));
EXPECT_FALSE(IsLeaf(*skeleton, 1));
EXPECT_FALSE(IsLeaf(*skeleton, 2));
EXPECT_TRUE(IsLeaf(*skeleton, 3));
EXPECT_FALSE(IsLeaf(*skeleton, 4));
EXPECT_TRUE(IsLeaf(*skeleton, 5));
EXPECT_FALSE(IsLeaf(*skeleton, 6));
EXPECT_TRUE(IsLeaf(*skeleton, 7));
EXPECT_FALSE(IsLeaf(*skeleton, 8));
EXPECT_TRUE(IsLeaf(*skeleton, 9));
}
@@ -0,0 +1,454 @@
//----------------------------------------------------------------------------//
// //
// 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 "gtest/gtest.h"
#include "ozz/base/maths/gtest_math_helper.h"
#include "ozz/base/io/archive.h"
#include "ozz/base/io/stream.h"
#include "ozz/base/memory/unique_ptr.h"
#include "ozz/animation/runtime/track_sampling_job.h"
#include "ozz/animation/offline/raw_track.h"
#include "ozz/animation/offline/track_builder.h"
using ozz::animation::FloatTrack;
using ozz::animation::FloatTrackSamplingJob;
using ozz::animation::Float2Track;
using ozz::animation::Float2TrackSamplingJob;
using ozz::animation::Float3Track;
using ozz::animation::Float3TrackSamplingJob;
using ozz::animation::Float4Track;
using ozz::animation::Float4TrackSamplingJob;
using ozz::animation::QuaternionTrack;
using ozz::animation::QuaternionTrackSamplingJob;
using ozz::animation::offline::RawFloatTrack;
using ozz::animation::offline::RawFloat2Track;
using ozz::animation::offline::RawFloat3Track;
using ozz::animation::offline::RawFloat4Track;
using ozz::animation::offline::RawQuaternionTrack;
using ozz::animation::offline::RawTrackInterpolation;
using ozz::animation::offline::TrackBuilder;
TEST(Empty, TrackSerialize) {
ozz::io::MemoryStream stream;
// Streams out.
ozz::io::OArchive o(&stream, ozz::GetNativeEndianness());
FloatTrack o_track;
o << o_track;
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive i(&stream);
FloatTrack i_track;
i >> i_track;
EXPECT_EQ(o_track.size(), i_track.size());
}
TEST(Name, TrackSerialize) {
ozz::io::MemoryStream stream;
// Instantiates a builder objects with default parameters.
TrackBuilder builder;
{ // No name
RawFloatTrack raw_float_track;
ozz::unique_ptr<FloatTrack> o_track(builder(raw_float_track));
ASSERT_TRUE(o_track);
// Streams out.
{
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::OArchive o(&stream, ozz::GetNativeEndianness());
o << *o_track;
}
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive i(&stream);
FloatTrack i_track;
i >> i_track;
EXPECT_STREQ(o_track->name(), i_track.name());
}
{ // A name
RawFloatTrack raw_float_track;
raw_float_track.name = "test name";
ozz::unique_ptr<FloatTrack> o_track(builder(raw_float_track));
ASSERT_TRUE(o_track);
// Streams out.
{
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::OArchive o(&stream, ozz::GetNativeEndianness());
o << *o_track;
}
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive i(&stream);
FloatTrack i_track;
i >> i_track;
EXPECT_STREQ(o_track->name(), i_track.name());
}
}
TEST(FilledFloat, TrackSerialize) {
// Builds a valid animation.
ozz::unique_ptr<FloatTrack> o_track;
{
TrackBuilder builder;
RawFloatTrack raw_float_track;
const RawFloatTrack::Keyframe key0 = {RawTrackInterpolation::kLinear, 0.f,
0.f};
raw_float_track.keyframes.push_back(key0);
const RawFloatTrack::Keyframe key1 = {RawTrackInterpolation::kStep, .5f,
46.f};
raw_float_track.keyframes.push_back(key1);
const RawFloatTrack::Keyframe key2 = {RawTrackInterpolation::kLinear, .7f,
0.f};
raw_float_track.keyframes.push_back(key2);
// Builds track
o_track = builder(raw_float_track);
ASSERT_TRUE(o_track);
}
for (int e = 0; e < 2; ++e) {
ozz::Endianness endianess = e == 0 ? ozz::kBigEndian : ozz::kLittleEndian;
ozz::io::MemoryStream stream;
// Streams out.
ozz::io::OArchive o(&stream, endianess);
o << *o_track;
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive i(&stream);
FloatTrack i_track;
i >> i_track;
EXPECT_EQ(o_track->size(), i_track.size());
// Samples and compares the two animations
FloatTrackSamplingJob sampling;
sampling.track = &i_track;
float result;
sampling.result = &result;
sampling.ratio = 0.f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT_EQ(result, 0.f);
sampling.ratio = .5f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT_EQ(result, 46.f);
sampling.ratio = 1.f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT_EQ(result, 0.f);
}
}
TEST(FilledFloat2, TrackSerialize) {
TrackBuilder builder;
RawFloat2Track raw_float2_track;
const RawFloat2Track::Keyframe key0 = {RawTrackInterpolation::kLinear, 0.f,
ozz::math::Float2(0.f, 26.f)};
raw_float2_track.keyframes.push_back(key0);
const RawFloat2Track::Keyframe key1 = {RawTrackInterpolation::kStep, .5f,
ozz::math::Float2(46.f, 0.f)};
raw_float2_track.keyframes.push_back(key1);
const RawFloat2Track::Keyframe key2 = {RawTrackInterpolation::kLinear, .7f,
ozz::math::Float2(0.f, 5.f)};
raw_float2_track.keyframes.push_back(key2);
// Builds track
ozz::unique_ptr<Float2Track> o_track(builder(raw_float2_track));
ASSERT_TRUE(o_track);
ozz::io::MemoryStream stream;
// Streams out.
ozz::io::OArchive o(&stream);
o << *o_track;
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive i(&stream);
Float2Track i_track;
i >> i_track;
EXPECT_EQ(o_track->size(), i_track.size());
// Samples and compares the two animations
Float2TrackSamplingJob sampling;
sampling.track = &i_track;
ozz::math::Float2 result;
sampling.result = &result;
sampling.ratio = 0.f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT2_EQ(result, 0.f, 26.f);
sampling.ratio = .5f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT2_EQ(result, 46.f, 0.f);
sampling.ratio = 1.f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT2_EQ(result, 0.f, 5.f);
}
TEST(FilledFloat3, TrackSerialize) {
TrackBuilder builder;
RawFloat3Track raw_float3_track;
const RawFloat3Track::Keyframe key0 = {RawTrackInterpolation::kLinear, 0.f,
ozz::math::Float3(0.f, 26.f, 93.f)};
raw_float3_track.keyframes.push_back(key0);
const RawFloat3Track::Keyframe key1 = {RawTrackInterpolation::kStep, .5f,
ozz::math::Float3(46.f, 0.f, 25.f)};
raw_float3_track.keyframes.push_back(key1);
const RawFloat3Track::Keyframe key2 = {RawTrackInterpolation::kLinear, .7f,
ozz::math::Float3(0.f, 5.f, 0.f)};
raw_float3_track.keyframes.push_back(key2);
// Builds track
ozz::unique_ptr<Float3Track> o_track(builder(raw_float3_track));
ASSERT_TRUE(o_track);
ozz::io::MemoryStream stream;
// Streams out.
ozz::io::OArchive o(&stream);
o << *o_track;
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive i(&stream);
Float3Track i_track;
i >> i_track;
EXPECT_EQ(o_track->size(), i_track.size());
// Samples and compares the two animations
Float3TrackSamplingJob sampling;
sampling.track = &i_track;
ozz::math::Float3 result;
sampling.result = &result;
sampling.ratio = 0.f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT3_EQ(result, 0.f, 26.f, 93.f);
sampling.ratio = .5f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT3_EQ(result, 46.f, 0.f, 25.f);
sampling.ratio = 1.f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT3_EQ(result, 0.f, 5.f, 0.f);
}
TEST(FilledFloat4, TrackSerialize) {
TrackBuilder builder;
RawFloat4Track raw_float4_track;
const RawFloat4Track::Keyframe key0 = {
RawTrackInterpolation::kLinear, 0.f,
ozz::math::Float4(0.f, 26.f, 93.f, 5.f)};
raw_float4_track.keyframes.push_back(key0);
const RawFloat4Track::Keyframe key1 = {
RawTrackInterpolation::kStep, .5f,
ozz::math::Float4(46.f, 0.f, 25.f, 25.f)};
raw_float4_track.keyframes.push_back(key1);
const RawFloat4Track::Keyframe key2 = {RawTrackInterpolation::kLinear, .7f,
ozz::math::Float4(0.f, 5.f, 0.f, 0.f)};
raw_float4_track.keyframes.push_back(key2);
// Builds track
ozz::unique_ptr<Float4Track> o_track(builder(raw_float4_track));
ASSERT_TRUE(o_track);
ozz::io::MemoryStream stream;
// Streams out.
ozz::io::OArchive o(&stream);
o << *o_track;
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive i(&stream);
Float4Track i_track;
i >> i_track;
EXPECT_EQ(o_track->size(), i_track.size());
// Samples and compares the two animations
Float4TrackSamplingJob sampling;
sampling.track = &i_track;
ozz::math::Float4 result;
sampling.result = &result;
sampling.ratio = 0.f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT4_EQ(result, 0.f, 26.f, 93.f, 5.f);
sampling.ratio = .5f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT4_EQ(result, 46.f, 0.f, 25.f, 25.f);
sampling.ratio = 1.f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT4_EQ(result, 0.f, 5.f, 0.f, 0.f);
}
TEST(FilledQuaternion, TrackSerialize) {
TrackBuilder builder;
RawQuaternionTrack raw_quat_track;
const RawQuaternionTrack::Keyframe key0 = {
RawTrackInterpolation::kLinear, 0.f,
ozz::math::Quaternion(0.f, .70710677f, 0.f, .70710677f)};
raw_quat_track.keyframes.push_back(key0);
const RawQuaternionTrack::Keyframe key1 = {
RawTrackInterpolation::kStep, .5f,
ozz::math::Quaternion(.61721331f, .15430345f, 0.f, .77151674f)};
raw_quat_track.keyframes.push_back(key1);
const RawQuaternionTrack::Keyframe key2 = {
RawTrackInterpolation::kLinear, .7f,
ozz::math::Quaternion(1.f, 0.f, 0.f, 0.f)};
raw_quat_track.keyframes.push_back(key2);
// Builds track
ozz::unique_ptr<QuaternionTrack> o_track(builder(raw_quat_track));
ASSERT_TRUE(o_track);
ozz::io::MemoryStream stream;
// Streams out.
ozz::io::OArchive o(&stream);
o << *o_track;
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive i(&stream);
QuaternionTrack i_track;
i >> i_track;
EXPECT_EQ(o_track->size(), i_track.size());
// Samples and compares the two animations
QuaternionTrackSamplingJob sampling;
sampling.track = &i_track;
ozz::math::Quaternion result;
sampling.result = &result;
sampling.ratio = 0.f;
ASSERT_TRUE(sampling.Run());
EXPECT_QUATERNION_EQ(result, 0.f, .70710677f, 0.f, .70710677f);
sampling.ratio = .5f;
ASSERT_TRUE(sampling.Run());
EXPECT_QUATERNION_EQ(result, .61721331f, .15430345f, 0.f, .77151674f);
sampling.ratio = 1.f;
ASSERT_TRUE(sampling.Run());
EXPECT_QUATERNION_EQ(result, 1.f, 0.f, 0.f, 0.f);
}
TEST(AlreadyInitialized, TrackSerialize) {
ozz::io::MemoryStream stream;
{
ozz::io::OArchive o(&stream);
TrackBuilder builder;
RawFloatTrack raw_float_track;
const RawFloatTrack::Keyframe key0 = {RawTrackInterpolation::kLinear, 0.f,
0.f};
raw_float_track.keyframes.push_back(key0);
const RawFloatTrack::Keyframe key1 = {RawTrackInterpolation::kStep, .5f,
46.f};
raw_float_track.keyframes.push_back(key1);
const RawFloatTrack::Keyframe key2 = {RawTrackInterpolation::kLinear, .7f,
0.f};
raw_float_track.keyframes.push_back(key2);
// Builds track
ozz::unique_ptr<FloatTrack> o_track(builder(raw_float_track));
ASSERT_TRUE(o_track);
o << *o_track;
// Builds 2nd track
const RawFloatTrack::Keyframe key3 = {RawTrackInterpolation::kStep, .9f,
46.f};
raw_float_track.keyframes.push_back(key3);
o_track = builder(raw_float_track);
ASSERT_TRUE(o_track);
o << *o_track;
}
{
// Streams in.
stream.Seek(0, ozz::io::Stream::kSet);
ozz::io::IArchive i(&stream);
// Reads and check the first animation.
FloatTrack i_track;
i >> i_track;
const size_t size = i_track.size();
// Reuse the animation a second ratio.
i >> i_track;
ASSERT_TRUE(i_track.size() > size);
}
}
@@ -0,0 +1,467 @@
//----------------------------------------------------------------------------//
// //
// 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 "gtest/gtest.h"
#include "ozz/base/maths/gtest_math_helper.h"
#include "ozz/base/memory/unique_ptr.h"
#include "ozz/animation/offline/raw_track.h"
#include "ozz/animation/offline/track_builder.h"
#include "ozz/animation/runtime/track.h"
using ozz::animation::FloatTrack;
using ozz::animation::Float2Track;
using ozz::animation::Float3Track;
using ozz::animation::Float4Track;
using ozz::animation::QuaternionTrack;
using ozz::animation::FloatTrackSamplingJob;
using ozz::animation::offline::RawFloatTrack;
using ozz::animation::offline::TrackBuilder;
using ozz::animation::offline::RawTrackInterpolation;
TEST(JobValidity, TrackSamplingJob) {
// Instantiates a builder objects with default parameters.
TrackBuilder builder;
// Building default RawFloatTrack succeeds.
RawFloatTrack raw_float_track;
EXPECT_TRUE(raw_float_track.Validate());
// Builds track
ozz::unique_ptr<FloatTrack> track(builder(raw_float_track));
ASSERT_TRUE(track);
{ // Empty/default job
FloatTrackSamplingJob job;
EXPECT_FALSE(job.Validate());
EXPECT_FALSE(job.Run());
}
{ // Invalid output
FloatTrackSamplingJob job;
job.track = track.get();
EXPECT_FALSE(job.Validate());
EXPECT_FALSE(job.Run());
}
{ // Invalid track.
FloatTrackSamplingJob job;
float result;
job.result = &result;
EXPECT_FALSE(job.Validate());
EXPECT_FALSE(job.Run());
}
{ // Valid
FloatTrackSamplingJob job;
job.track = track.get();
float result;
job.result = &result;
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
}
}
TEST(Default, TrackSamplingJob) {
FloatTrack default_track;
FloatTrackSamplingJob job;
job.track = &default_track;
float result = 1.f;
job.result = &result;
EXPECT_TRUE(job.Validate());
EXPECT_TRUE(job.Run());
EXPECT_FLOAT_EQ(result, 0.f);
}
TEST(Bounds, TrackSamplingJob) {
TrackBuilder builder;
float result;
RawFloatTrack raw_float_track;
const RawFloatTrack::Keyframe key0 = {RawTrackInterpolation::kLinear, 0.f,
0.f};
raw_float_track.keyframes.push_back(key0);
const RawFloatTrack::Keyframe key1 = {RawTrackInterpolation::kStep, .5f,
46.f};
raw_float_track.keyframes.push_back(key1);
const RawFloatTrack::Keyframe key2 = {RawTrackInterpolation::kLinear, .7f,
0.f};
raw_float_track.keyframes.push_back(key2);
// Builds track
ozz::unique_ptr<FloatTrack> track(builder(raw_float_track));
ASSERT_TRUE(track);
// Samples to verify build output.
FloatTrackSamplingJob sampling;
sampling.track = track.get();
sampling.result = &result;
sampling.ratio = 0.f - 1e-7f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT_EQ(result, 0.f);
sampling.ratio = 0.f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT_EQ(result, 0.f);
sampling.ratio = .5f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT_EQ(result, 46.f);
sampling.ratio = 1.f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT_EQ(result, 0.f);
sampling.ratio = 1.f + 1e-7f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT_EQ(result, 0.f);
sampling.ratio = 1.5f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT_EQ(result, 0.f);
}
TEST(Float, TrackSamplingJob) {
TrackBuilder builder;
float result;
ozz::animation::offline::RawFloatTrack raw_track;
const ozz::animation::offline::RawFloatTrack::Keyframe key0 = {
RawTrackInterpolation::kLinear, 0.f, 0.f};
raw_track.keyframes.push_back(key0);
const ozz::animation::offline::RawFloatTrack::Keyframe key1 = {
RawTrackInterpolation::kStep, .5f, 4.6f};
raw_track.keyframes.push_back(key1);
const ozz::animation::offline::RawFloatTrack::Keyframe key2 = {
RawTrackInterpolation::kLinear, .7f, 9.2f};
raw_track.keyframes.push_back(key2);
const ozz::animation::offline::RawFloatTrack::Keyframe key3 = {
RawTrackInterpolation::kLinear, .9f, 0.f};
raw_track.keyframes.push_back(key3);
// Builds track
ozz::unique_ptr<FloatTrack> track(builder(raw_track));
ASSERT_TRUE(track);
// Samples to verify build output.
ozz::animation::FloatTrackSamplingJob sampling;
sampling.track = track.get();
sampling.result = &result;
sampling.ratio = 0.f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT_EQ(result, 0.f);
sampling.ratio = .25f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT_EQ(result, 2.3f);
sampling.ratio = .5f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT_EQ(result, 4.6f);
sampling.ratio = .6f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT_EQ(result, 4.6f);
sampling.ratio = .7f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT_EQ(result, 9.2f);
sampling.ratio = .8f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT_EQ(result, 4.6f);
sampling.ratio = .9f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT_EQ(result, 0.f);
sampling.ratio = 1.f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT_EQ(result, 0.f);
}
TEST(Float2, TrackSamplingJob) {
TrackBuilder builder;
ozz::math::Float2 result;
ozz::animation::offline::RawFloat2Track raw_track;
const ozz::animation::offline::RawFloat2Track::Keyframe key0 = {
RawTrackInterpolation::kLinear, 0.f, ozz::math::Float2(0.f, 0.f)};
raw_track.keyframes.push_back(key0);
const ozz::animation::offline::RawFloat2Track::Keyframe key1 = {
RawTrackInterpolation::kStep, .5f, ozz::math::Float2(2.3f, 4.6f)};
raw_track.keyframes.push_back(key1);
const ozz::animation::offline::RawFloat2Track::Keyframe key2 = {
RawTrackInterpolation::kLinear, .7f, ozz::math::Float2(4.6f, 9.2f)};
raw_track.keyframes.push_back(key2);
const ozz::animation::offline::RawFloat2Track::Keyframe key3 = {
RawTrackInterpolation::kLinear, .9f, ozz::math::Float2(0.f, 0.f)};
raw_track.keyframes.push_back(key3);
// Builds track
ozz::unique_ptr<Float2Track> track(builder(raw_track));
ASSERT_TRUE(track);
// Samples to verify build output.
ozz::animation::Float2TrackSamplingJob sampling;
sampling.track = track.get();
sampling.result = &result;
sampling.ratio = 0.f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT2_EQ(result, 0.f, 0.f);
sampling.ratio = .25f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT2_EQ(result, 1.15f, 2.3f);
sampling.ratio = .5f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT2_EQ(result, 2.3f, 4.6f);
sampling.ratio = .6f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT2_EQ(result, 2.3f, 4.6f);
sampling.ratio = .7f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT2_EQ(result, 4.6f, 9.2f);
sampling.ratio = .8f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT2_EQ(result, 2.3f, 4.6f);
sampling.ratio = .9f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT2_EQ(result, 0.f, 0.f);
sampling.ratio = 1.f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT2_EQ(result, 0.f, 0.f);
}
TEST(Float3, TrackSamplingJob) {
TrackBuilder builder;
ozz::math::Float3 result;
ozz::animation::offline::RawFloat3Track raw_track;
const ozz::animation::offline::RawFloat3Track::Keyframe key0 = {
RawTrackInterpolation::kLinear, 0.f, ozz::math::Float3(0.f, 0.f, 0.f)};
raw_track.keyframes.push_back(key0);
const ozz::animation::offline::RawFloat3Track::Keyframe key1 = {
RawTrackInterpolation::kStep, .5f, ozz::math::Float3(0.f, 2.3f, 4.6f)};
raw_track.keyframes.push_back(key1);
const ozz::animation::offline::RawFloat3Track::Keyframe key2 = {
RawTrackInterpolation::kLinear, .7f, ozz::math::Float3(0.f, 4.6f, 9.2f)};
raw_track.keyframes.push_back(key2);
const ozz::animation::offline::RawFloat3Track::Keyframe key3 = {
RawTrackInterpolation::kLinear, .9f, ozz::math::Float3(0.f, 0.f, 0.f)};
raw_track.keyframes.push_back(key3);
// Builds track
ozz::unique_ptr<Float3Track> track(builder(raw_track));
ASSERT_TRUE(track);
// Samples to verify build output.
ozz::animation::Float3TrackSamplingJob sampling;
sampling.track = track.get();
sampling.result = &result;
sampling.ratio = 0.f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT3_EQ(result, 0.f, 0.f, 0.f);
sampling.ratio = .25f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT3_EQ(result, 0.f, 1.15f, 2.3f);
sampling.ratio = .5f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT3_EQ(result, 0.f, 2.3f, 4.6f);
sampling.ratio = .6f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT3_EQ(result, 0.f, 2.3f, 4.6f);
sampling.ratio = .7f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT3_EQ(result, 0.f, 4.6f, 9.2f);
sampling.ratio = .8f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT3_EQ(result, 0.f, 2.3f, 4.6f);
sampling.ratio = .9f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT3_EQ(result, 0.f, 0.f, 0.f);
sampling.ratio = 1.f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT3_EQ(result, 0.f, 0.f, 0.f);
}
TEST(Float4, TrackSamplingJob) {
TrackBuilder builder;
ozz::math::Float4 result;
ozz::animation::offline::RawFloat4Track raw_track;
const ozz::animation::offline::RawFloat4Track::Keyframe key0 = {
RawTrackInterpolation::kLinear, 0.f,
ozz::math::Float4(0.f, 0.f, 0.f, 0.f)};
raw_track.keyframes.push_back(key0);
const ozz::animation::offline::RawFloat4Track::Keyframe key1 = {
RawTrackInterpolation::kStep, .5f,
ozz::math::Float4(0.f, 2.3f, 0.f, 4.6f)};
raw_track.keyframes.push_back(key1);
const ozz::animation::offline::RawFloat4Track::Keyframe key2 = {
RawTrackInterpolation::kLinear, .7f,
ozz::math::Float4(0.f, 4.6f, 0.f, 9.2f)};
raw_track.keyframes.push_back(key2);
const ozz::animation::offline::RawFloat4Track::Keyframe key3 = {
RawTrackInterpolation::kLinear, .9f,
ozz::math::Float4(0.f, 0.f, 0.f, 0.f)};
raw_track.keyframes.push_back(key3);
// Builds track
ozz::unique_ptr<Float4Track> track(builder(raw_track));
ASSERT_TRUE(track);
// Samples to verify build output.
ozz::animation::Float4TrackSamplingJob sampling;
sampling.track = track.get();
sampling.result = &result;
sampling.ratio = 0.f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT4_EQ(result, 0.f, 0.f, 0.f, 0.f);
sampling.ratio = .25f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT4_EQ(result, 0.f, 1.15f, 0.f, 2.3f);
sampling.ratio = .5f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT4_EQ(result, 0.f, 2.3f, 0.f, 4.6f);
sampling.ratio = .6f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT4_EQ(result, 0.f, 2.3f, 0.f, 4.6f);
sampling.ratio = .7f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT4_EQ(result, 0.f, 4.6f, 0.f, 9.2f);
sampling.ratio = .8f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT4_EQ(result, 0.f, 2.3f, 0.f, 4.6f);
sampling.ratio = .9f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT4_EQ(result, 0.f, 0.f, 0.f, 0.f);
sampling.ratio = 1.f;
ASSERT_TRUE(sampling.Run());
EXPECT_FLOAT4_EQ(result, 0.f, 0.f, 0.f, 0.f);
}
TEST(Quaternion, TrackSamplingJob) {
TrackBuilder builder;
ozz::math::Quaternion result;
ozz::animation::offline::RawQuaternionTrack raw_track;
const ozz::animation::offline::RawQuaternionTrack::Keyframe key0 = {
RawTrackInterpolation::kLinear, 0.f,
ozz::math::Quaternion(.70710677f, 0.f, 0.f, .70710677f)};
raw_track.keyframes.push_back(key0);
const ozz::animation::offline::RawQuaternionTrack::Keyframe key1 = {
RawTrackInterpolation::kStep, .5f,
ozz::math::Quaternion(0.f, .70710677f, 0.f, .70710677f)};
raw_track.keyframes.push_back(key1);
const ozz::animation::offline::RawQuaternionTrack::Keyframe key2 = {
RawTrackInterpolation::kLinear, .7f,
ozz::math::Quaternion(.70710677f, 0.f, 0.f, .70710677f)};
raw_track.keyframes.push_back(key2);
const ozz::animation::offline::RawQuaternionTrack::Keyframe key3 = {
RawTrackInterpolation::kLinear, .9f, ozz::math::Quaternion::identity()};
raw_track.keyframes.push_back(key3);
// Builds track
ozz::unique_ptr<QuaternionTrack> track(builder(raw_track));
ASSERT_TRUE(track);
// Samples to verify build output.
ozz::animation::QuaternionTrackSamplingJob sampling;
sampling.track = track.get();
sampling.result = &result;
sampling.ratio = 0.f;
ASSERT_TRUE(sampling.Run());
EXPECT_QUATERNION_EQ(result, .70710677f, 0.f, 0.f, .70710677f);
sampling.ratio = .1f;
ASSERT_TRUE(sampling.Run());
EXPECT_QUATERNION_EQ(result, .61721331f, .15430345f, 0.f, .77151674f);
sampling.ratio = .4999999f;
ASSERT_TRUE(sampling.Run());
EXPECT_QUATERNION_EQ(result, 0.f, .70710677f, 0.f, .70710677f);
sampling.ratio = .5f;
ASSERT_TRUE(sampling.Run());
EXPECT_QUATERNION_EQ(result, 0.f, .70710677f, 0.f, .70710677f);
sampling.ratio = .6f;
ASSERT_TRUE(sampling.Run());
EXPECT_QUATERNION_EQ(result, 0.f, .70710677f, 0.f, .70710677f);
sampling.ratio = .7f;
ASSERT_TRUE(sampling.Run());
EXPECT_QUATERNION_EQ(result, .70710677f, 0.f, 0.f, .70710677f);
sampling.ratio = .8f;
ASSERT_TRUE(sampling.Run());
EXPECT_QUATERNION_EQ(result, .38268333f, 0.f, 0.f, .92387962f);
sampling.ratio = .9f;
ASSERT_TRUE(sampling.Run());
EXPECT_QUATERNION_EQ(result, 0.f, 0.f, 0.f, 1.f);
sampling.ratio = 1.f;
ASSERT_TRUE(sampling.Run());
EXPECT_QUATERNION_EQ(result, 0.f, 0.f, 0.f, 1.f);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,110 @@
//----------------------------------------------------------------------------//
// //
// 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_trait.h"
#include "gtest/gtest.h"
#include "ozz/base/gtest_helper.h"
#include "ozz/base/maths/gtest_math_helper.h"
#include "ozz/base/memory/unique_ptr.h"
#include "ozz/animation/offline/raw_track.h"
#include "ozz/animation/offline/track_builder.h"
#include "ozz/animation/runtime/track.h"
#include <algorithm>
using ozz::animation::FloatTrack;
using ozz::animation::TrackTriggeringJob;
using ozz::animation::offline::RawFloatTrack;
using ozz::animation::offline::RawTrackInterpolation;
using ozz::animation::offline::TrackBuilder;
namespace {
bool IsRising(const TrackTriggeringJob::Edge& _edge) { return _edge.rising; }
} // namespace
TEST(Algorithm, TrackEdgeTriggerJob) {
TrackBuilder builder;
ozz::animation::offline::RawFloatTrack raw_track;
// Keyframe values oscillate in range [0,2].
const ozz::animation::offline::RawFloatTrack::Keyframe key0 = {
RawTrackInterpolation::kStep, 0.f, 0.f};
raw_track.keyframes.push_back(key0);
const ozz::animation::offline::RawFloatTrack::Keyframe key1 = {
RawTrackInterpolation::kStep, .5f, 2.f};
raw_track.keyframes.push_back(key1);
const ozz::animation::offline::RawFloatTrack::Keyframe key2 = {
RawTrackInterpolation::kStep, 1.f, 0.f};
raw_track.keyframes.push_back(key2);
// Builds track
ozz::unique_ptr<FloatTrack> track(builder(raw_track));
ASSERT_TRUE(track);
TrackTriggeringJob job;
job.track = track.get();
job.threshold = 1.f;
job.from = 0.f;
job.to = 2.f;
TrackTriggeringJob::Iterator iterator;
job.iterator = &iterator;
ASSERT_TRUE(job.Run());
{ // copy
ozz::vector<TrackTriggeringJob::Edge> edges;
std::copy(iterator, job.end(), std::back_inserter(edges));
EXPECT_EQ(edges.size(), 4u);
}
{ // count
ozz::vector<TrackTriggeringJob::Edge> edges;
std::iterator_traits<TrackTriggeringJob::Iterator>::difference_type count =
std::count_if(iterator, job.end(), IsRising);
EXPECT_EQ(count, 2);
std::iterator_traits<TrackTriggeringJob::Iterator>::difference_type
count_end = std::count_if(job.end(), job.end(), IsRising);
EXPECT_EQ(count_end, 0);
}
{ // for_each
ozz::vector<TrackTriggeringJob::Edge> edges;
std::for_each(iterator, job.end(), IsRising);
}
{ // find_if
ozz::vector<TrackTriggeringJob::Edge> edges;
TrackTriggeringJob::Iterator it_if =
std::find_if(iterator, job.end(), IsRising);
EXPECT_TRUE(it_if->rising);
}
}