Initial commit

This commit is contained in:
Martin Felis
2021-11-11 21:22:24 +01:00
commit b78045ffe7
812 changed files with 421882 additions and 0 deletions
@@ -0,0 +1,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);
}
}