Compare commits

..

No commits in common. "ccb9bc4e9b795ba6d82b458b5809fce7bec91c49" and "1ef53d6486d68e533bfdad8d5f020969a66f5261" have entirely different histories.

46 changed files with 1014 additions and 1949 deletions

View File

@ -1,19 +1,3 @@
Release version 0.14.3
----------------------
* Build pipeline
- Adds vs2022 compiler support for fbxsdk (#170)
Release version 0.14.2
----------------------
* Library
- Transitions away from sprintf to the more secure snprintf.
- #147 Works around gcc 11 error stringop-overflow which emits false positives for ozz math serialization.
* Build pipeline
- Updates CI compiler versions.
Release version 0.14.1 Release version 0.14.1
---------------------- ----------------------

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.24) cmake_minimum_required (VERSION 3.3)
# Defines the project's name # Defines the project's name
project(ozz) project(ozz)
@ -9,7 +9,7 @@ get_directory_property(is_sub_project PARENT_DIRECTORY)
# Current version # Current version
set(OZZ_VERSION_MAJOR 0) set(OZZ_VERSION_MAJOR 0)
set(OZZ_VERSION_MINOR 14) set(OZZ_VERSION_MINOR 14)
set(OZZ_VERSION_PATCH 3) set(OZZ_VERSION_PATCH 1)
set(OZZ_VERSION ${OZZ_VERSION_MAJOR}.${OZZ_VERSION_MINOR}.${OZZ_VERSION_PATCH}) set(OZZ_VERSION ${OZZ_VERSION_MAJOR}.${OZZ_VERSION_MINOR}.${OZZ_VERSION_PATCH})
# Add project build options # Add project build options
@ -30,7 +30,6 @@ if(WIN32 AND BUILD_SHARED_LIBS AND NOT ozz_build_msvc_rt_dll)
message("Forcing ozz_build_msvc_rt_dll to ON as ozz is being built as dll (BUILD_SHARED_LIBS is ON).") message("Forcing ozz_build_msvc_rt_dll to ON as ozz is being built as dll (BUILD_SHARED_LIBS is ON).")
set(ozz_build_msvc_rt_dll ON) set(ozz_build_msvc_rt_dll ON)
endif() endif()
if(is_sub_project) if(is_sub_project)
set(ozz_build_msvc_rt_dll ${ozz_build_msvc_rt_dll} PARENT_SCOPE) set(ozz_build_msvc_rt_dll ${ozz_build_msvc_rt_dll} PARENT_SCOPE)
endif() endif()
@ -52,6 +51,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/build-utils/cm
# Detects Fbx SDK, required to build Fbx pipeline. # Detects Fbx SDK, required to build Fbx pipeline.
if(ozz_build_tools AND ozz_build_fbx) if(ozz_build_tools AND ozz_build_fbx)
# Select a msvc runtime compatible with ozz_build_msvc_rt_dll # Select a msvc runtime compatible with ozz_build_msvc_rt_dll
set(FBX_SHARED ${BUILD_SHARED_LIBS}) set(FBX_SHARED ${BUILD_SHARED_LIBS})
set(FBX_MSVC_RT_DLL ${ozz_build_msvc_rt_dll}) set(FBX_MSVC_RT_DLL ${ozz_build_msvc_rt_dll})
@ -69,7 +69,6 @@ else()
# Disables fbx if tools are disabled # Disables fbx if tools are disabled
set(ozz_build_fbx OFF) set(ozz_build_fbx OFF)
endif() endif()
if(is_sub_project) if(is_sub_project)
set(ozz_build_fbx ${ozz_build_fbx} PARENT_SCOPE) set(ozz_build_fbx ${ozz_build_fbx} PARENT_SCOPE)
endif() endif()
@ -79,7 +78,6 @@ if(ozz_build_tools AND ozz_build_gltf)
else() else()
set(ozz_build_gltf OFF) set(ozz_build_gltf OFF)
endif() endif()
if(is_sub_project) if(is_sub_project)
set(ozz_build_gltf ${ozz_build_gltf} PARENT_SCOPE) set(ozz_build_gltf ${ozz_build_gltf} PARENT_SCOPE)
endif() endif()
@ -133,4 +131,4 @@ install(FILES
${PROJECT_SOURCE_DIR}/CHANGES.md ${PROJECT_SOURCE_DIR}/CHANGES.md
${PROJECT_SOURCE_DIR}/LICENSE.md ${PROJECT_SOURCE_DIR}/LICENSE.md
${PROJECT_SOURCE_DIR}/README.md ${PROJECT_SOURCE_DIR}/README.md
DESTINATION "share/doc/ozz-animation/") DESTINATION ./)

View File

@ -18,7 +18,7 @@ Documentation and samples are available from [ozz-animation website](http://guil
Supported platforms Supported platforms
------------------- -------------------
Ozz is tested on Linux, Mac OS and Windows, for x86, x86-64 and ARM architectures. The run-time code (ozz_base, ozz_animation, ozz_geometry) depends only on c++11, on the C and the C++ standard libraries, and has no OS specific code. Portability to any other platform shouldn't be an issue. Ozz is tested on Linux, Mac OS and Windows, for x86, x86-64 and ARM architectures. The run-time code (ozz_base, ozz_animation, ozz_geometry) depends only on c++11, the standard CRT and has no OS specific code, portability to any other platform shouldn't be an issue.
Samples, tools and tests depend on external libraries (glfw, tinygltf, Fbx SDK, jsoncpp, gtest, ...), which could limit portability. Samples, tools and tests depend on external libraries (glfw, tinygltf, Fbx SDK, jsoncpp, gtest, ...), which could limit portability.

View File

@ -33,7 +33,6 @@ set(cxx_all_flags
if(NOT CMAKE_CXX_STANDARD) if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD 11)
endif() endif()
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_EXTENSIONS OFF)
@ -42,15 +41,15 @@ if(ozz_build_simd_ref)
add_compile_definitions(OZZ_BUILD_SIMD_REF) add_compile_definitions(OZZ_BUILD_SIMD_REF)
endif() endif()
# Disables crt secure warnings
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
#-------------------------------------- #--------------------------------------
# Modify default MSVC compilation flags # Modify default MSVC compilation flags
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
#--------------------------- #---------------------------
# For the common build flags # For the common build flags
# Disables crt secure warnings
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
# Adds support for multiple processes builds # Adds support for multiple processes builds
add_compile_options(/MP) add_compile_options(/MP)
@ -61,15 +60,18 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
add_compile_options(/WX) add_compile_options(/WX)
# Select whether to use the DLL version or the static library version of the Visual C++ runtime library. # Select whether to use the DLL version or the static library version of the Visual C++ runtime library.
foreach(flag ${cxx_all_flags})
if (ozz_build_msvc_rt_dll) if (ozz_build_msvc_rt_dll)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL") string(REGEX REPLACE "/MT" "/MD" ${flag} "${${flag}}")
else() else()
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>") string(REGEX REPLACE "/MD" "/MT" ${flag} "${${flag}}")
endif() endif()
endforeach()
#-------------------------------------- #--------------------------------------
# else consider the compiler as GCC compatible (inc clang) # else consider the compiler as GCC compatible (inc clang)
else() else()
# Set the warning level to Wall # Set the warning level to Wall
add_compile_options(-Wall) add_compile_options(-Wall)
@ -81,20 +83,18 @@ else()
# ignored-attributes reports issue when using _m128 as template argument # ignored-attributes reports issue when using _m128 as template argument
check_cxx_compiler_flag("-Wignored-attributes" W_IGNORED_ATTRIBUTES) check_cxx_compiler_flag("-Wignored-attributes" W_IGNORED_ATTRIBUTES)
if(W_IGNORED_ATTRIBUTES) if(W_IGNORED_ATTRIBUTES)
add_compile_options(-Wno-ignored-attributes) add_compile_options(-Wno-ignored-attributes)
endif() endif()
# Disables c98 retrocompatibility warnings # Disables c98 retrocompatibility warnings
check_cxx_compiler_flag("-Wc++98-compat-pedantic" W_98_COMPAT_PEDANTIC) check_cxx_compiler_flag("-Wc++98-compat-pedantic" W_98_COMPAT_PEDANTIC)
if(W_98_COMPAT_PEDANTIC) if(W_98_COMPAT_PEDANTIC)
add_compile_options(-Wno-c++98-compat-pedantic) add_compile_options(-Wno-c++98-compat-pedantic)
endif() endif()
# Check some options availibity for the targetted compiler # Check some options availibity for the targetted compiler
check_cxx_compiler_flag("-Wunused-result" W_UNUSED_RESULT)
check_cxx_compiler_flag("-Wnull-dereference" W_NULL_DEREFERENCE) check_cxx_compiler_flag("-Wnull-dereference" W_NULL_DEREFERENCE)
check_cxx_compiler_flag("-Wpragma-pack" W_PRAGMA_PACK) check_cxx_compiler_flag("-Wpragma-pack" W_PRAGMA_PACK)
@ -108,6 +108,7 @@ else()
# set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS "-msse2") # set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS "-msse2")
#endif() #endif()
endif() endif()
endif() endif()
#--------------------- #---------------------
@ -115,7 +116,6 @@ endif()
message(STATUS "---------------------------------------------------------") message(STATUS "---------------------------------------------------------")
message(STATUS "Default build type is: ${CMAKE_BUILD_TYPE}") message(STATUS "Default build type is: ${CMAKE_BUILD_TYPE}")
message(STATUS "The following compilation flags will be used:") message(STATUS "The following compilation flags will be used:")
foreach(flag ${cxx_all_flags}) foreach(flag ${cxx_all_flags})
message(${flag} " ${${flag}}") message(${flag} " ${${flag}}")
endforeach() endforeach()
@ -124,7 +124,6 @@ message(STATUS "---------------------------------------------------------")
get_directory_property(DirectoryCompileOptions DIRECTORY ${PROJECT_SOURCE_DIR} COMPILE_OPTIONS) get_directory_property(DirectoryCompileOptions DIRECTORY ${PROJECT_SOURCE_DIR} COMPILE_OPTIONS)
message(STATUS "Directory Compile Options:") message(STATUS "Directory Compile Options:")
foreach(opt ${DirectoryCompileOptions}) foreach(opt ${DirectoryCompileOptions})
message(STATUS ${opt}) message(STATUS ${opt})
endforeach() endforeach()
@ -133,7 +132,6 @@ message(STATUS "---------------------------------------------------------")
get_directory_property(DirectoryCompileDefinitions DIRECTORY ${PROJECT_SOURCE_DIR} COMPILE_DEFINITIONS) get_directory_property(DirectoryCompileDefinitions DIRECTORY ${PROJECT_SOURCE_DIR} COMPILE_DEFINITIONS)
message(STATUS "Directory Compile Definitions:") message(STATUS "Directory Compile Definitions:")
foreach(def ${DirectoryCompileDefinitions}) foreach(def ${DirectoryCompileDefinitions})
message(STATUS ${def}) message(STATUS ${def})
endforeach() endforeach()

View File

@ -67,9 +67,7 @@ function(FindFbxLibrariesGeneric _FBX_ROOT_DIR _OUT_FBX_LIBRARIES _OUT_FBX_LIBRA
# Figures out matching compiler/os directory. # Figures out matching compiler/os directory.
if("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xMSVC") if("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xMSVC")
if(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19.30) if(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19.20)
set(FBX_CP_PATH "vs2022")
elseif(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19.20)
set(FBX_CP_PATH "vs2019") set(FBX_CP_PATH "vs2019")
elseif(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19.10) elseif(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19.10)
set(FBX_CP_PATH "vs2017") set(FBX_CP_PATH "vs2017")

View File

@ -57,6 +57,8 @@ if(UNIX AND APPLE)
lib/cocoa/cocoa_joystick.m lib/cocoa/cocoa_joystick.m
lib/cocoa/cocoa_time.m lib/cocoa/cocoa_time.m
lib/cocoa/cocoa_window.m) lib/cocoa/cocoa_window.m)
# Treats .m files as C files.
set_source_files_properties(${specific_objc_file_list} PROPERTIES LANGUAGE C)
# Disables warnings in glfw. # Disables warnings in glfw.
set_source_files_properties(${specific_objc_file_list} PROPERTIES COMPILE_FLAGS set_source_files_properties(${specific_objc_file_list} PROPERTIES COMPILE_FLAGS

View File

@ -3887,7 +3887,7 @@ std::string valueToString(double value, bool useSpecialFloats, unsigned int prec
int len = -1; int len = -1;
char formatString[6]; char formatString[6];
snprintf(formatString, sizeof(formatString), "%%.%dg", precision); sprintf(formatString, "%%.%dg", precision);
// Print into the buffer. We need not request the alternative representation // Print into the buffer. We need not request the alternative representation
// that always has a decimal point because JSON doesn't distingish the // that always has a decimal point because JSON doesn't distingish the

View File

@ -66,11 +66,12 @@ template <typename _Ty, size_t _size = sizeof(_Ty)>
struct EndianSwapper; struct EndianSwapper;
// Internal macro used to swap two bytes. // Internal macro used to swap two bytes.
OZZ_INLINE void _in_place_byte_swap(byte& _a, byte& _b) { #define OZZ_BYTE_SWAP(_a, _b) \
_a = _a ^ _b; do { \
_b = _a ^ _b; const ozz::byte temp = (_a); \
_a = _a ^ _b; (_a) = (_b); \
} (_b) = temp; \
} while (0)
// EndianSwapper specialization for 1 byte types. // EndianSwapper specialization for 1 byte types.
template <typename _Ty> template <typename _Ty>
@ -85,12 +86,12 @@ struct EndianSwapper<_Ty, 2> {
OZZ_INLINE static void Swap(_Ty* _ty, size_t _count) { OZZ_INLINE static void Swap(_Ty* _ty, size_t _count) {
byte* alias = reinterpret_cast<byte*>(_ty); byte* alias = reinterpret_cast<byte*>(_ty);
for (size_t i = 0; i < _count * 2; i += 2) { for (size_t i = 0; i < _count * 2; i += 2) {
_in_place_byte_swap(alias[i + 0], alias[i + 1]); OZZ_BYTE_SWAP(alias[i + 0], alias[i + 1]);
} }
} }
OZZ_INLINE static _Ty Swap(_Ty _ty) { // Pass by copy to swap _ty in-place. OZZ_INLINE static _Ty Swap(_Ty _ty) { // Pass by copy to swap _ty in-place.
byte* alias = reinterpret_cast<byte*>(&_ty); byte* alias = reinterpret_cast<byte*>(&_ty);
_in_place_byte_swap(alias[0], alias[1]); OZZ_BYTE_SWAP(alias[0], alias[1]);
return _ty; return _ty;
} }
}; };
@ -101,14 +102,14 @@ struct EndianSwapper<_Ty, 4> {
OZZ_INLINE static void Swap(_Ty* _ty, size_t _count) { OZZ_INLINE static void Swap(_Ty* _ty, size_t _count) {
byte* alias = reinterpret_cast<byte*>(_ty); byte* alias = reinterpret_cast<byte*>(_ty);
for (size_t i = 0; i < _count * 4; i += 4) { for (size_t i = 0; i < _count * 4; i += 4) {
_in_place_byte_swap(alias[i + 0], alias[i + 3]); OZZ_BYTE_SWAP(alias[i + 0], alias[i + 3]);
_in_place_byte_swap(alias[i + 1], alias[i + 2]); OZZ_BYTE_SWAP(alias[i + 1], alias[i + 2]);
} }
} }
OZZ_INLINE static _Ty Swap(_Ty _ty) { // Pass by copy to swap _ty in-place. OZZ_INLINE static _Ty Swap(_Ty _ty) { // Pass by copy to swap _ty in-place.
byte* alias = reinterpret_cast<byte*>(&_ty); byte* alias = reinterpret_cast<byte*>(&_ty);
_in_place_byte_swap(alias[0], alias[3]); OZZ_BYTE_SWAP(alias[0], alias[3]);
_in_place_byte_swap(alias[1], alias[2]); OZZ_BYTE_SWAP(alias[1], alias[2]);
return _ty; return _ty;
} }
}; };
@ -119,22 +120,25 @@ struct EndianSwapper<_Ty, 8> {
OZZ_INLINE static void Swap(_Ty* _ty, size_t _count) { OZZ_INLINE static void Swap(_Ty* _ty, size_t _count) {
byte* alias = reinterpret_cast<byte*>(_ty); byte* alias = reinterpret_cast<byte*>(_ty);
for (size_t i = 0; i < _count * 8; i += 8) { for (size_t i = 0; i < _count * 8; i += 8) {
_in_place_byte_swap(alias[i + 0], alias[i + 7]); OZZ_BYTE_SWAP(alias[i + 0], alias[i + 7]);
_in_place_byte_swap(alias[i + 1], alias[i + 6]); OZZ_BYTE_SWAP(alias[i + 1], alias[i + 6]);
_in_place_byte_swap(alias[i + 2], alias[i + 5]); OZZ_BYTE_SWAP(alias[i + 2], alias[i + 5]);
_in_place_byte_swap(alias[i + 3], alias[i + 4]); OZZ_BYTE_SWAP(alias[i + 3], alias[i + 4]);
} }
} }
OZZ_INLINE static _Ty Swap(_Ty _ty) { // Pass by copy to swap _ty in-place. OZZ_INLINE static _Ty Swap(_Ty _ty) { // Pass by copy to swap _ty in-place.
byte* alias = reinterpret_cast<byte*>(&_ty); byte* alias = reinterpret_cast<byte*>(&_ty);
_in_place_byte_swap(alias[0], alias[7]); OZZ_BYTE_SWAP(alias[0], alias[7]);
_in_place_byte_swap(alias[1], alias[6]); OZZ_BYTE_SWAP(alias[1], alias[6]);
_in_place_byte_swap(alias[2], alias[5]); OZZ_BYTE_SWAP(alias[2], alias[5]);
_in_place_byte_swap(alias[3], alias[4]); OZZ_BYTE_SWAP(alias[3], alias[4]);
return _ty; return _ty;
} }
}; };
// OZZ_BYTE_SWAP is not useful anymore.
#undef OZZ_BYTE_SWAP
// Helper function that swaps _count elements of the array _ty in place. // Helper function that swaps _count elements of the array _ty in place.
template <typename _Ty> template <typename _Ty>
OZZ_INLINE void EndianSwap(_Ty* _ty, size_t _count) { OZZ_INLINE void EndianSwap(_Ty* _ty, size_t _count) {

View File

@ -250,7 +250,7 @@ class AdditiveBlendSampleApplication : public ozz::sample::Application {
ozz::sample::ImGui::OpenClose oc(_im_gui, "Blending parameters", &open); ozz::sample::ImGui::OpenClose oc(_im_gui, "Blending parameters", &open);
if (open) { if (open) {
_im_gui->DoLabel("Main layer:"); _im_gui->DoLabel("Main layer:");
std::snprintf(label, sizeof(label), "Layer weight: %.2f", base_weight_); std::sprintf(label, "Layer weight: %.2f", base_weight_);
_im_gui->DoSlider(label, 0.f, 1.f, &base_weight_, 1.f); _im_gui->DoSlider(label, 0.f, 1.f, &base_weight_, 1.f);
_im_gui->DoLabel("Additive layer:"); _im_gui->DoLabel("Additive layer:");
@ -259,7 +259,7 @@ class AdditiveBlendSampleApplication : public ozz::sample::Application {
std::memcpy(weights.data(), additive_weigths_, std::memcpy(weights.data(), additive_weigths_,
sizeof(additive_weigths_)); sizeof(additive_weigths_));
std::snprintf(label, sizeof(label), "Weights\nCurl: %.2f\nSplay: %.2f", std::sprintf(label, "Weights\nCurl: %.2f\nSplay: %.2f",
additive_weigths_[kCurl], additive_weigths_[kSplay]); additive_weigths_[kCurl], additive_weigths_[kSplay]);
if (_im_gui->DoSlider2D(label, {{0.f, 0.f}}, {{1.f, 1.f}}, &weights)) { if (_im_gui->DoSlider2D(label, {{0.f, 0.f}}, {{1.f, 1.f}}, &weights)) {
auto_animate_weights_ = false; // User interacted. auto_animate_weights_ = false; // User interacted.

View File

@ -161,16 +161,16 @@ class AttachSampleApplication : public ozz::sample::Application {
if (open && skeleton_.num_joints() != 0) { if (open && skeleton_.num_joints() != 0) {
_im_gui->DoLabel("Select joint:"); _im_gui->DoLabel("Select joint:");
char label[64]; char label[64];
std::snprintf(label, sizeof(label), "%s (%d)", std::sprintf(label, "%s (%d)", skeleton_.joint_names()[attachment_],
skeleton_.joint_names()[attachment_], attachment_); attachment_);
_im_gui->DoSlider(label, 0, skeleton_.num_joints() - 1, &attachment_); _im_gui->DoSlider(label, 0, skeleton_.num_joints() - 1, &attachment_);
_im_gui->DoLabel("Attachment offset:"); _im_gui->DoLabel("Attachment offset:");
std::snprintf(label, sizeof(label), "x: %02f", offset_.x); sprintf(label, "x: %02f", offset_.x);
_im_gui->DoSlider(label, -1.f, 1.f, &offset_.x); _im_gui->DoSlider(label, -1.f, 1.f, &offset_.x);
std::snprintf(label, sizeof(label), "y: %02f", offset_.y); sprintf(label, "y: %02f", offset_.y);
_im_gui->DoSlider(label, -1.f, 1.f, &offset_.y); _im_gui->DoSlider(label, -1.f, 1.f, &offset_.y);
std::snprintf(label, sizeof(label), "z: %02f", offset_.z); sprintf(label, "z: %02f", offset_.z);
_im_gui->DoSlider(label, -1.f, 1.f, &offset_.z); _im_gui->DoSlider(label, -1.f, 1.f, &offset_.z);
} }
} }

View File

@ -58,7 +58,7 @@ OZZ_OPTIONS_DECLARE_STRING(animation2,
// Third animation archive can be specified as an option. // Third animation archive can be specified as an option.
OZZ_OPTIONS_DECLARE_STRING(animation3, OZZ_OPTIONS_DECLARE_STRING(animation3,
"Path to the third animation (ozz archive format).", "Path to the second animation (ozz archive format).",
"media/animation3.ozz", false) "media/animation3.ozz", false)
class BlendSampleApplication : public ozz::sample::Application { class BlendSampleApplication : public ozz::sample::Application {
@ -242,16 +242,16 @@ class BlendSampleApplication : public ozz::sample::Application {
} }
char label[64]; char label[64];
std::snprintf(label, sizeof(label), "Blend ratio: %.2f", blend_ratio_); std::sprintf(label, "Blend ratio: %.2f", blend_ratio_);
_im_gui->DoSlider(label, 0.f, 1.f, &blend_ratio_, 1.f, !manual_); _im_gui->DoSlider(label, 0.f, 1.f, &blend_ratio_, 1.f, !manual_);
for (int i = 0; i < kNumLayers; ++i) { for (int i = 0; i < kNumLayers; ++i) {
Sampler& sampler = samplers_[i]; Sampler& sampler = samplers_[i];
std::snprintf(label, sizeof(label), "Weight %d: %.2f", i, sampler.weight); std::sprintf(label, "Weight %d: %.2f", i, sampler.weight);
_im_gui->DoSlider(label, 0.f, 1.f, &sampler.weight, 1.f, manual_); _im_gui->DoSlider(label, 0.f, 1.f, &sampler.weight, 1.f, manual_);
} }
std::snprintf(label, sizeof(label), "Threshold: %.2f", threshold_); std::sprintf(label, "Threshold: %.2f", threshold_);
_im_gui->DoSlider(label, .01f, 1.f, &threshold_); _im_gui->DoSlider(label, .01f, 1.f, &threshold_);
} }
} }

View File

@ -608,7 +608,7 @@ class FootIKSampleApplication : public ozz::sample::Application {
virtual void OnDestroy() {} virtual void OnDestroy() {}
virtual bool OnGui(ozz::sample::ImGui* _im_gui) { virtual bool OnGui(ozz::sample::ImGui* _im_gui) {
char label[32]; char txt[32];
// Main options // Main options
{ {
@ -636,12 +636,12 @@ class FootIKSampleApplication : public ozz::sample::Application {
static bool opened = true; static bool opened = true;
ozz::sample::ImGui::OpenClose oc(_im_gui, "IK settings", &opened); ozz::sample::ImGui::OpenClose oc(_im_gui, "IK settings", &opened);
if (opened) { if (opened) {
snprintf(label, sizeof(label), "Foot height %.2g", foot_heigh_); sprintf(txt, "Foot height %.2g", foot_heigh_);
_im_gui->DoSlider(label, 0.f, .3f, &foot_heigh_); _im_gui->DoSlider(txt, 0.f, .3f, &foot_heigh_);
snprintf(label, sizeof(label), "Weight %.2g", weight_); sprintf(txt, "Weight %.2g", weight_);
_im_gui->DoSlider(label, 0.f, 1.f, &weight_); _im_gui->DoSlider(txt, 0.f, 1.f, &weight_);
snprintf(label, sizeof(label), "Soften %.2g", soften_); sprintf(txt, "Soften %.2g", soften_);
_im_gui->DoSlider(label, 0.f, 1.f, &soften_, 1.f, two_bone_ik_); _im_gui->DoSlider(txt, 0.f, 1.f, &soften_, 1.f, two_bone_ik_);
} }
} }
@ -652,19 +652,19 @@ class FootIKSampleApplication : public ozz::sample::Application {
bool moved = false; bool moved = false;
// Translation // Translation
_im_gui->DoLabel("Translation"); _im_gui->DoLabel("Translation");
snprintf(label, sizeof(label), "x %.2g", root_translation_.x); sprintf(txt, "x %.2g", root_translation_.x);
moved |= _im_gui->DoSlider(label, -10.f, 10.f, &root_translation_.x); moved |= _im_gui->DoSlider(txt, -10.f, 10.f, &root_translation_.x);
snprintf(label, sizeof(label), "y %.2g", root_translation_.y); sprintf(txt, "y %.2g", root_translation_.y);
moved |= _im_gui->DoSlider(label, 0.f, 5.f, &root_translation_.y, 1.f, moved |= _im_gui->DoSlider(txt, 0.f, 5.f, &root_translation_.y, 1.f,
!auto_character_height_); !auto_character_height_);
snprintf(label, sizeof(label), "z %.2g", root_translation_.z); sprintf(txt, "z %.2g", root_translation_.z);
moved |= _im_gui->DoSlider(label, -10.f, 10.f, &root_translation_.z); moved |= _im_gui->DoSlider(txt, -10.f, 10.f, &root_translation_.z);
// Rotation (in euler form) // Rotation (in euler form)
_im_gui->DoLabel("Rotation"); _im_gui->DoLabel("Rotation");
snprintf(label, sizeof(label), "yaw %.3g", root_yaw_ * ozz::math::kRadianToDegree); sprintf(txt, "yaw %.3g", root_yaw_ * ozz::math::kRadianToDegree);
moved |= moved |=
_im_gui->DoSlider(label, -ozz::math::kPi, ozz::math::kPi, &root_yaw_); _im_gui->DoSlider(txt, -ozz::math::kPi, ozz::math::kPi, &root_yaw_);
// Character position shouldn't be changed after the update. In this // Character position shouldn't be changed after the update. In this
// case, because UI is updated after "game" update, we need to recompute // case, because UI is updated after "game" update, we need to recompute

View File

@ -528,32 +528,31 @@ bool Application::Gui() {
} }
bool Application::FrameworkGui() { bool Application::FrameworkGui() {
char label[64];
// Downcast to public imgui. // Downcast to public imgui.
ImGui* im_gui = im_gui_.get(); ImGui* im_gui = im_gui_.get();
{ // Render statistics { // Render statistics
static bool open = true; static bool open = true;
ImGui::OpenClose stat_oc(im_gui, "Statistics", &open); ImGui::OpenClose stat_oc(im_gui, "Statistics", &open);
if (open) { if (open) {
char szLabel[64];
{ // FPS { // FPS
Record::Statistics statistics = fps_->GetStatistics(); Record::Statistics statistics = fps_->GetStatistics();
std::snprintf(label, sizeof(label), "FPS: %.0f", std::sprintf(szLabel, "FPS: %.0f",
statistics.mean == 0.f ? 0.f : 1000.f / statistics.mean); statistics.mean == 0.f ? 0.f : 1000.f / statistics.mean);
static bool fps_open = false; static bool fps_open = false;
ImGui::OpenClose stats(im_gui, label, &fps_open); ImGui::OpenClose stats(im_gui, szLabel, &fps_open);
if (fps_open) { if (fps_open) {
std::snprintf(label, sizeof(label), "Frame: %.2f ms", std::sprintf(szLabel, "Frame: %.2f ms", statistics.mean);
statistics.mean); im_gui->DoGraph(szLabel, 0.f, statistics.max, statistics.latest,
im_gui->DoGraph(label, 0.f, statistics.max, statistics.latest,
fps_->cursor(), fps_->record_begin(), fps_->cursor(), fps_->record_begin(),
fps_->record_end()); fps_->record_end());
} }
} }
{ // Update time { // Update time
Record::Statistics statistics = update_time_->GetStatistics(); Record::Statistics statistics = update_time_->GetStatistics();
std::snprintf(label, sizeof(label), "Update: %.2f ms", statistics.mean); std::sprintf(szLabel, "Update: %.2f ms", statistics.mean);
static bool update_open = true; // This is the most relevant for ozz. static bool update_open = true; // This is the most relevant for ozz.
ImGui::OpenClose stats(im_gui, label, &update_open); ImGui::OpenClose stats(im_gui, szLabel, &update_open);
if (update_open) { if (update_open) {
im_gui->DoGraph(nullptr, 0.f, statistics.max, statistics.latest, im_gui->DoGraph(nullptr, 0.f, statistics.max, statistics.latest,
update_time_->cursor(), update_time_->record_begin(), update_time_->cursor(), update_time_->record_begin(),
@ -562,9 +561,9 @@ bool Application::FrameworkGui() {
} }
{ // Render time { // Render time
Record::Statistics statistics = render_time_->GetStatistics(); Record::Statistics statistics = render_time_->GetStatistics();
std::snprintf(label, sizeof(label), "Render: %.2f ms", statistics.mean); std::sprintf(szLabel, "Render: %.2f ms", statistics.mean);
static bool render_open = false; static bool render_open = false;
ImGui::OpenClose stats(im_gui, label, &render_open); ImGui::OpenClose stats(im_gui, szLabel, &render_open);
if (render_open) { if (render_open) {
im_gui->DoGraph(nullptr, 0.f, statistics.max, statistics.latest, im_gui->DoGraph(nullptr, 0.f, statistics.max, statistics.latest,
render_time_->cursor(), render_time_->record_begin(), render_time_->cursor(), render_time_->record_begin(),
@ -581,15 +580,18 @@ bool Application::FrameworkGui() {
im_gui->DoButton("Freeze", true, &freeze_); im_gui->DoButton("Freeze", true, &freeze_);
im_gui->DoCheckBox("Fix update rate", &fix_update_rate, true); im_gui->DoCheckBox("Fix update rate", &fix_update_rate, true);
if (!fix_update_rate) { if (!fix_update_rate) {
std::snprintf(label, sizeof(label), "Time factor: %.2f", time_factor_); char sz_factor[64];
im_gui->DoSlider(label, -5.f, 5.f, &time_factor_); std::sprintf(sz_factor, "Time factor: %.2f", time_factor_);
im_gui->DoSlider(sz_factor, -5.f, 5.f, &time_factor_);
if (im_gui->DoButton("Reset time factor", time_factor_ != 1.f)) { if (im_gui->DoButton("Reset time factor", time_factor_ != 1.f)) {
time_factor_ = 1.f; time_factor_ = 1.f;
} }
} else { } else {
std::snprintf(label, sizeof(label), "Update rate: %.0f fps", char sz_fixed_update_rate[64];
std::sprintf(sz_fixed_update_rate, "Update rate: %.0f fps",
fixed_update_rate); fixed_update_rate);
im_gui->DoSlider(label, 1.f, 200.f, &fixed_update_rate, .5f, true); im_gui->DoSlider(sz_fixed_update_rate, 1.f, 200.f, &fixed_update_rate,
.5f, true);
if (im_gui->DoButton("Reset update rate", fixed_update_rate != 60.f)) { if (im_gui->DoButton("Reset update rate", fixed_update_rate != 60.f)) {
fixed_update_rate = 60.f; fixed_update_rate = 60.f;
} }
@ -613,9 +615,10 @@ bool Application::FrameworkGui() {
} }
// Vertical sync & swap interval // Vertical sync & swap interval
bool changed = im_gui->DoCheckBox("Vertical sync", &vertical_sync_); bool changed = im_gui->DoCheckBox("Vertical sync", &vertical_sync_);
std::snprintf(label, sizeof(label), "Swap interval: %d", swap_interval_); char szLabel[64];
std::sprintf(szLabel, "Swap interval: %d", swap_interval_);
changed |= changed |=
im_gui->DoSlider(label, 1, 4, &swap_interval_, 1.f, vertical_sync_); im_gui->DoSlider(szLabel, 1, 4, &swap_interval_, 1.f, vertical_sync_);
if (changed) { if (changed) {
glfwSwapInterval(vertical_sync_ ? swap_interval_ : 0); glfwSwapInterval(vertical_sync_ ? swap_interval_ : 0);
} }
@ -637,9 +640,10 @@ bool Application::FrameworkGui() {
} }
} }
std::snprintf(label, sizeof(label), "Resolution: %dx%d", resolution_.width, char szResolution[64];
std::sprintf(szResolution, "Resolution: %dx%d", resolution_.width,
resolution_.height); resolution_.height);
if (im_gui->DoSlider(label, 0, kNumPresets - 1, &preset_lookup)) { if (im_gui->DoSlider(szResolution, 0, kNumPresets - 1, &preset_lookup)) {
// Resolution changed. // Resolution changed.
resolution_ = resolution_presets[preset_lookup]; resolution_ = resolution_presets[preset_lookup];
glfwSetWindowSize(resolution_.width, resolution_.height); glfwSetWindowSize(resolution_.width, resolution_.height);

View File

@ -149,7 +149,7 @@ class Application {
enum LoopStatus { enum LoopStatus {
kContinue, // Can continue with next loop. kContinue, // Can continue with next loop.
kBreak, // Should stop looping (ex: exit). kBreak, // Should stop looping (ex: exit).
kBreakFailure, // Should stop looping because something went wrong. kBreakFailure, // // Should stop looping beacause something went wrong.
}; };
LoopStatus OneLoop(int _loops); LoopStatus OneLoop(int _loops);

View File

@ -100,7 +100,7 @@ bool FormatFloat(float _value, char* _string, const char* _string_end) {
if (!_string || _string_end - _string < 8 + precision + 1) { if (!_string || _string_end - _string < 8 + precision + 1) {
return false; return false;
} }
std::snprintf(_string, _string_end - _string, "%.2g\n", _value); std::sprintf(_string, "%.2g\n", _value);
// Removes unnecessary '0' digits in the exponent. // Removes unnecessary '0' digits in the exponent.
char* exponent = strchr(_string, 'e'); char* exponent = strchr(_string, 'e');

View File

@ -160,12 +160,12 @@ bool Shooter::Process() {
GL(BindBuffer(GL_PIXEL_PACK_BUFFER, shot.pbo)); GL(BindBuffer(GL_PIXEL_PACK_BUFFER, shot.pbo));
const void* pixels = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY); const void* pixels = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
if (pixels) { if (pixels) {
char filename[16]; char name[16];
std::snprintf(filename, sizeof(filename), "%06d.tga", shot_number_++); sprintf(name, "%06d.tga", shot_number_++);
ozz::sample::image::WriteTGA( ozz::sample::image::WriteTGA(name, shot.width, shot.height, image_format_,
filename, shot.width, shot.height, image_format_, reinterpret_cast<const uint8_t*>(pixels),
reinterpret_cast<const uint8_t*>(pixels), false); false);
GL(UnmapBuffer(GL_PIXEL_PACK_BUFFER)); GL(UnmapBuffer(GL_PIXEL_PACK_BUFFER));
} }
GL(BindBuffer(GL_PIXEL_PACK_BUFFER, 0)); GL(BindBuffer(GL_PIXEL_PACK_BUFFER, 0));

View File

@ -110,22 +110,21 @@ bool PlaybackController::OnGui(const animation::Animation& _animation,
_im_gui->DoCheckBox("Loop", &loop_, _enabled); _im_gui->DoCheckBox("Loop", &loop_, _enabled);
char label[64]; char szLabel[64];
// Uses a local copy of time_ so that set_time is used to actually apply // Uses a local copy of time_ so that set_time is used to actually apply
// changes. Otherwise previous time would be incorrect. // changes. Otherwise previous time would be incorrect.
float ratio = time_ratio(); float ratio = time_ratio();
std::snprintf(label, sizeof(label), "Animation time: %.2f", std::sprintf(szLabel, "Animation time: %.2f", ratio * _animation.duration());
ratio * _animation.duration()); if (_im_gui->DoSlider(szLabel, 0.f, 1.f, &ratio, 1.f,
if (_im_gui->DoSlider(label, 0.f, 1.f, &ratio, 1.f,
_enabled && _allow_set_time)) { _enabled && _allow_set_time)) {
set_time_ratio(ratio); set_time_ratio(ratio);
// Pause the time if slider as moved. // Pause the time if slider as moved.
play_ = false; play_ = false;
time_changed = true; time_changed = true;
} }
std::snprintf(label, sizeof(label), "Playback speed: %.2f", playback_speed_); std::sprintf(szLabel, "Playback speed: %.2f", playback_speed_);
_im_gui->DoSlider(label, -5.f, 5.f, &playback_speed_, 1.f, _enabled); _im_gui->DoSlider(szLabel, -5.f, 5.f, &playback_speed_, 1.f, _enabled);
// Allow to reset speed if it is not the default value. // Allow to reset speed if it is not the default value.
if (_im_gui->DoButton("Reset playback speed", if (_im_gui->DoButton("Reset playback speed",
@ -140,7 +139,7 @@ bool OnRawSkeletonJointGui(
ozz::sample::ImGui* _im_gui, ozz::sample::ImGui* _im_gui,
ozz::animation::offline::RawSkeleton::Joint::Children* _children, ozz::animation::offline::RawSkeleton::Joint::Children* _children,
ozz::vector<bool>::iterator* _oc_state) { ozz::vector<bool>::iterator* _oc_state) {
char label[255]; char txt[255];
bool modified = false; bool modified = false;
for (size_t i = 0; i < _children->size(); ++i) { for (size_t i = 0; i < _children->size(); ++i) {
@ -153,23 +152,23 @@ bool OnRawSkeletonJointGui(
// Translation // Translation
ozz::math::Float3& translation = joint.transform.translation; ozz::math::Float3& translation = joint.transform.translation;
_im_gui->DoLabel("Translation"); _im_gui->DoLabel("Translation");
snprintf(label, sizeof(label), "x %.2g", translation.x); sprintf(txt, "x %.2g", translation.x);
modified |= _im_gui->DoSlider(label, -1.f, 1.f, &translation.x); modified |= _im_gui->DoSlider(txt, -1.f, 1.f, &translation.x);
snprintf(label, sizeof(label), "y %.2g", translation.y); sprintf(txt, "y %.2g", translation.y);
modified |= _im_gui->DoSlider(label, -1.f, 1.f, &translation.y); modified |= _im_gui->DoSlider(txt, -1.f, 1.f, &translation.y);
snprintf(label, sizeof(label), "z %.2g", translation.z); sprintf(txt, "z %.2g", translation.z);
modified |= _im_gui->DoSlider(label, -1.f, 1.f, &translation.z); modified |= _im_gui->DoSlider(txt, -1.f, 1.f, &translation.z);
// Rotation (in euler form) // Rotation (in euler form)
ozz::math::Quaternion& rotation = joint.transform.rotation; ozz::math::Quaternion& rotation = joint.transform.rotation;
_im_gui->DoLabel("Rotation"); _im_gui->DoLabel("Rotation");
ozz::math::Float3 euler = ToEuler(rotation) * ozz::math::kRadianToDegree; ozz::math::Float3 euler = ToEuler(rotation) * ozz::math::kRadianToDegree;
snprintf(label, sizeof(label), "x %.3g", euler.x); sprintf(txt, "x %.3g", euler.x);
bool euler_modified = _im_gui->DoSlider(label, -180.f, 180.f, &euler.x); bool euler_modified = _im_gui->DoSlider(txt, -180.f, 180.f, &euler.x);
snprintf(label, sizeof(label), "y %.3g", euler.y); sprintf(txt, "y %.3g", euler.y);
euler_modified |= _im_gui->DoSlider(label, -180.f, 180.f, &euler.y); euler_modified |= _im_gui->DoSlider(txt, -180.f, 180.f, &euler.y);
snprintf(label, sizeof(label), "z %.3g", euler.z); sprintf(txt, "z %.3g", euler.z);
euler_modified |= _im_gui->DoSlider(label, -180.f, 180.f, &euler.z); euler_modified |= _im_gui->DoSlider(txt, -180.f, 180.f, &euler.z);
if (euler_modified) { if (euler_modified) {
modified = true; modified = true;
ozz::math::Float3 euler_rad = euler * ozz::math::kDegreeToRadian; ozz::math::Float3 euler_rad = euler * ozz::math::kDegreeToRadian;
@ -180,8 +179,8 @@ bool OnRawSkeletonJointGui(
// Scale (must be uniform and not 0) // Scale (must be uniform and not 0)
_im_gui->DoLabel("Scale"); _im_gui->DoLabel("Scale");
ozz::math::Float3& scale = joint.transform.scale; ozz::math::Float3& scale = joint.transform.scale;
snprintf(label, sizeof(label), "%.2g", scale.x); sprintf(txt, "%.2g", scale.x);
if (_im_gui->DoSlider(label, -1.f, 1.f, &scale.x)) { if (_im_gui->DoSlider(txt, -1.f, 1.f, &scale.x)) {
modified = true; modified = true;
scale.y = scale.z = scale.x = scale.x != 0.f ? scale.x : .01f; scale.y = scale.z = scale.x = scale.x != 0.f ? scale.x : .01f;
} }

View File

@ -387,15 +387,15 @@ class LookAtSampleApplication : public ozz::sample::Application {
virtual void OnDestroy() {} virtual void OnDestroy() {}
virtual bool OnGui(ozz::sample::ImGui* _im_gui) { virtual bool OnGui(ozz::sample::ImGui* _im_gui) {
char label[64]; char txt[64];
_im_gui->DoCheckBox("Enable ik", &enable_ik_); _im_gui->DoCheckBox("Enable ik", &enable_ik_);
snprintf(label, sizeof(label), "IK chain length: %d", chain_length_); sprintf(txt, "IK chain length: %d", chain_length_);
_im_gui->DoSlider(label, 0, kMaxChainLength, &chain_length_); _im_gui->DoSlider(txt, 0, kMaxChainLength, &chain_length_);
snprintf(label, sizeof(label), "Joint weight %.2g", joint_weight_); sprintf(txt, "Joint weight %.2g", joint_weight_);
_im_gui->DoSlider(label, 0.f, 1.f, &joint_weight_); _im_gui->DoSlider(txt, 0.f, 1.f, &joint_weight_);
snprintf(label, sizeof(label), "Chain weight %.2g", chain_weight_); sprintf(txt, "Chain weight %.2g", chain_weight_);
_im_gui->DoSlider(label, 0.f, 1.f, &chain_weight_); _im_gui->DoSlider(txt, 0.f, 1.f, &chain_weight_);
// Exposes animation runtime playback controls. // Exposes animation runtime playback controls.
{ {
@ -413,15 +413,15 @@ class LookAtSampleApplication : public ozz::sample::Application {
const float kTargetRange = 3.f; const float kTargetRange = 3.f;
_im_gui->DoLabel("Animated extent"); _im_gui->DoLabel("Animated extent");
snprintf(label, sizeof(label), "%.2g", target_extent_); sprintf(txt, "%.2g", target_extent_);
_im_gui->DoSlider(label, 0.f, kTargetRange, &target_extent_); _im_gui->DoSlider(txt, 0.f, kTargetRange, &target_extent_);
snprintf(label, sizeof(label), "x %.2g", target_offset_.x); sprintf(txt, "x %.2g", target_offset_.x);
_im_gui->DoSlider(label, -kTargetRange, kTargetRange, &target_offset_.x); _im_gui->DoSlider(txt, -kTargetRange, kTargetRange, &target_offset_.x);
snprintf(label, sizeof(label), "y %.2g", target_offset_.y); sprintf(txt, "y %.2g", target_offset_.y);
_im_gui->DoSlider(label, -kTargetRange, kTargetRange, &target_offset_.y); _im_gui->DoSlider(txt, -kTargetRange, kTargetRange, &target_offset_.y);
snprintf(label, sizeof(label), "z %.2g", target_offset_.z); sprintf(txt, "z %.2g", target_offset_.z);
_im_gui->DoSlider(label, -kTargetRange, kTargetRange, &target_offset_.z); _im_gui->DoSlider(txt, -kTargetRange, kTargetRange, &target_offset_.z);
} }
} }
@ -430,12 +430,12 @@ class LookAtSampleApplication : public ozz::sample::Application {
ozz::sample::ImGui::OpenClose oc(_im_gui, "Eyes offset", &opened); ozz::sample::ImGui::OpenClose oc(_im_gui, "Eyes offset", &opened);
if (opened) { if (opened) {
const float kOffsetRange = .5f; const float kOffsetRange = .5f;
snprintf(label, sizeof(label), "x %.2g", eyes_offset_.x); sprintf(txt, "x %.2g", eyes_offset_.x);
_im_gui->DoSlider(label, -kOffsetRange, kOffsetRange, &eyes_offset_.x); _im_gui->DoSlider(txt, -kOffsetRange, kOffsetRange, &eyes_offset_.x);
snprintf(label, sizeof(label), "y %.2g", eyes_offset_.y); sprintf(txt, "y %.2g", eyes_offset_.y);
_im_gui->DoSlider(label, -kOffsetRange, kOffsetRange, &eyes_offset_.y); _im_gui->DoSlider(txt, -kOffsetRange, kOffsetRange, &eyes_offset_.y);
snprintf(label, sizeof(label), "z %.2g", eyes_offset_.z); sprintf(txt, "z %.2g", eyes_offset_.z);
_im_gui->DoSlider(label, -kOffsetRange, kOffsetRange, &eyes_offset_.z); _im_gui->DoSlider(txt, -kOffsetRange, kOffsetRange, &eyes_offset_.z);
} }
} }

View File

@ -152,7 +152,7 @@ class MillipedeSampleApplication : public ozz::sample::Application {
// Rebuilds all if the number of joints has changed. // Rebuilds all if the number of joints has changed.
int joints = skeleton_->num_joints(); int joints = skeleton_->num_joints();
char label[64]; char label[64];
std::snprintf(label, sizeof(label), "Joints count: %d", joints); std::sprintf(label, "Joints count: %d", joints);
// Uses an exponential scale in the slider to maintain enough precision in // Uses an exponential scale in the slider to maintain enough precision in
// the lowest values. // the lowest values.
@ -219,17 +219,17 @@ class MillipedeSampleApplication : public ozz::sample::Application {
root->transform.rotation = Quaternion::identity(); root->transform.rotation = Quaternion::identity();
root->transform.scale = Float3::one(); root->transform.scale = Float3::one();
char number[16]; char buf[16];
for (int i = 0; i < slice_count_; ++i) { for (int i = 0; i < slice_count_; ++i) {
// Format joint number. // Format joint number.
std::snprintf(number, sizeof(number), "%d", i); std::sprintf(buf, "%d", i);
root->children.resize(3); root->children.resize(3);
// Left leg. // Left leg.
RawSkeleton::Joint& lu = root->children[0]; RawSkeleton::Joint& lu = root->children[0];
lu.name = "lu"; lu.name = "lu";
lu.name += number; lu.name += buf;
lu.transform.translation = kTransUp; lu.transform.translation = kTransUp;
lu.transform.rotation = kRotLeftUp; lu.transform.rotation = kRotLeftUp;
lu.transform.scale = Float3::one(); lu.transform.scale = Float3::one();
@ -237,7 +237,7 @@ class MillipedeSampleApplication : public ozz::sample::Application {
lu.children.resize(1); lu.children.resize(1);
RawSkeleton::Joint& ld = lu.children[0]; RawSkeleton::Joint& ld = lu.children[0];
ld.name = "ld"; ld.name = "ld";
ld.name += number; ld.name += buf;
ld.transform.translation = kTransDown; ld.transform.translation = kTransDown;
ld.transform.rotation = kRotLeftDown; ld.transform.rotation = kRotLeftDown;
ld.transform.scale = Float3::one(); ld.transform.scale = Float3::one();
@ -245,7 +245,7 @@ class MillipedeSampleApplication : public ozz::sample::Application {
ld.children.resize(1); ld.children.resize(1);
RawSkeleton::Joint& lf = ld.children[0]; RawSkeleton::Joint& lf = ld.children[0];
lf.name = "lf"; lf.name = "lf";
lf.name += number; lf.name += buf;
lf.transform.translation = Float3::x_axis(); lf.transform.translation = Float3::x_axis();
lf.transform.rotation = Quaternion::identity(); lf.transform.rotation = Quaternion::identity();
lf.transform.scale = Float3::one(); lf.transform.scale = Float3::one();
@ -253,7 +253,7 @@ class MillipedeSampleApplication : public ozz::sample::Application {
// Right leg. // Right leg.
RawSkeleton::Joint& ru = root->children[1]; RawSkeleton::Joint& ru = root->children[1];
ru.name = "ru"; ru.name = "ru";
ru.name += number; ru.name += buf;
ru.transform.translation = kTransUp; ru.transform.translation = kTransUp;
ru.transform.rotation = kRotRightUp; ru.transform.rotation = kRotRightUp;
ru.transform.scale = Float3::one(); ru.transform.scale = Float3::one();
@ -261,7 +261,7 @@ class MillipedeSampleApplication : public ozz::sample::Application {
ru.children.resize(1); ru.children.resize(1);
RawSkeleton::Joint& rd = ru.children[0]; RawSkeleton::Joint& rd = ru.children[0];
rd.name = "rd"; rd.name = "rd";
rd.name += number; rd.name += buf;
rd.transform.translation = kTransDown; rd.transform.translation = kTransDown;
rd.transform.rotation = kRotRightDown; rd.transform.rotation = kRotRightDown;
rd.transform.scale = Float3::one(); rd.transform.scale = Float3::one();
@ -269,7 +269,7 @@ class MillipedeSampleApplication : public ozz::sample::Application {
rd.children.resize(1); rd.children.resize(1);
RawSkeleton::Joint& rf = rd.children[0]; RawSkeleton::Joint& rf = rd.children[0];
rf.name = "rf"; rf.name = "rf";
rf.name += number; rf.name += buf;
rf.transform.translation = Float3::x_axis(); rf.transform.translation = Float3::x_axis();
rf.transform.rotation = Quaternion::identity(); rf.transform.rotation = Quaternion::identity();
rf.transform.scale = Float3::one(); rf.transform.scale = Float3::one();
@ -277,7 +277,7 @@ class MillipedeSampleApplication : public ozz::sample::Application {
// Spine. // Spine.
RawSkeleton::Joint& sp = root->children[2]; RawSkeleton::Joint& sp = root->children[2];
sp.name = "sp"; sp.name = "sp";
sp.name += number; sp.name += buf;
sp.transform.translation = Float3(0.f, 0.f, kSpinLength); sp.transform.translation = Float3(0.f, 0.f, kSpinLength);
sp.transform.rotation = Quaternion::identity(); sp.transform.rotation = Quaternion::identity();
sp.transform.scale = Float3::one(); sp.transform.scale = Float3::one();

View File

@ -287,10 +287,10 @@ class MultithreadSampleApplication : public ozz::sample::Application {
ozz::sample::ImGui::OpenClose oc(_im_gui, "Sample control", &oc_open); ozz::sample::ImGui::OpenClose oc(_im_gui, "Sample control", &oc_open);
if (oc_open) { if (oc_open) {
char label[64]; char label[64];
std::snprintf(label, sizeof(label), "Number of entities: %d", num_characters_); std::sprintf(label, "Number of entities: %d", num_characters_);
_im_gui->DoSlider(label, 1, kMaxCharacters, &num_characters_, .7f); _im_gui->DoSlider(label, 1, kMaxCharacters, &num_characters_, .7f);
const int num_joints = num_characters_ * skeleton_.num_joints(); const int num_joints = num_characters_ * skeleton_.num_joints();
std::snprintf(label, sizeof(label), "Number of joints: %d", num_joints); std::sprintf(label, "Number of joints: %d", num_joints);
_im_gui->DoLabel(label); _im_gui->DoLabel(label);
} }
} }
@ -303,11 +303,11 @@ class MultithreadSampleApplication : public ozz::sample::Application {
has_threading_support_); has_threading_support_);
if (enable_theading_) { if (enable_theading_) {
char label[64]; char label[64];
std::snprintf(label, sizeof(label), "Grain size: %d", grain_size_); std::sprintf(label, "Grain size: %d", grain_size_);
_im_gui->DoSlider(label, kMinGrainSize, kMaxCharacters, &grain_size_, _im_gui->DoSlider(label, kMinGrainSize, kMaxCharacters, &grain_size_,
.2f); .2f);
const int num_threads = monitor_.ThreadCount(); const int num_threads = monitor_.ThreadCount();
std::snprintf(label, sizeof(label), "Thread/task count: %d/%d", num_threads, std::sprintf(label, "Thread/task count: %d/%d", num_threads,
monitor_.TaskCount()); monitor_.TaskCount());
_im_gui->DoLabel(label); _im_gui->DoLabel(label);
} }

View File

@ -312,31 +312,28 @@ class OptimizeSampleApplication : public ozz::sample::Application {
rebuild |= _im_gui->DoCheckBox("Enable optimizations", &optimize_); rebuild |= _im_gui->DoCheckBox("Enable optimizations", &optimize_);
std::snprintf(label, sizeof(label), "Tolerance: %0.2f mm", std::sprintf(label, "Tolerance: %0.2f mm", setting_.tolerance * 1000);
setting_.tolerance * 1000);
rebuild |= _im_gui->DoSlider(label, 0.f, .1f, &setting_.tolerance, .5f, rebuild |= _im_gui->DoSlider(label, 0.f, .1f, &setting_.tolerance, .5f,
optimize_); optimize_);
std::snprintf(label, sizeof(label), "Distance: %0.2f mm", std::sprintf(label, "Distance: %0.2f mm", setting_.distance * 1000);
setting_.distance * 1000);
rebuild |= _im_gui->DoSlider(label, 0.f, 1.f, &setting_.distance, .5f, rebuild |= _im_gui->DoSlider(label, 0.f, 1.f, &setting_.distance, .5f,
optimize_); optimize_);
rebuild |= _im_gui->DoCheckBox("Enable joint setting", rebuild |= _im_gui->DoCheckBox("Enable joint setting",
&joint_setting_enable_, optimize_); &joint_setting_enable_, optimize_);
std::snprintf(label, sizeof(label), "%s (%d)", std::sprintf(label, "%s (%d)", skeleton_.joint_names()[joint_], joint_);
skeleton_.joint_names()[joint_], joint_);
rebuild |= rebuild |=
_im_gui->DoSlider(label, 0, skeleton_.num_joints() - 1, &joint_, _im_gui->DoSlider(label, 0, skeleton_.num_joints() - 1, &joint_,
1.f, joint_setting_enable_ && optimize_); 1.f, joint_setting_enable_ && optimize_);
std::snprintf(label, sizeof(label), "Tolerance: %0.2f mm", std::sprintf(label, "Tolerance: %0.2f mm",
joint_setting_.tolerance * 1000); joint_setting_.tolerance * 1000);
rebuild |= _im_gui->DoSlider(label, 0.f, .1f, &joint_setting_.tolerance, rebuild |= _im_gui->DoSlider(label, 0.f, .1f, &joint_setting_.tolerance,
.5f, joint_setting_enable_ && optimize_); .5f, joint_setting_enable_ && optimize_);
std::snprintf(label, sizeof(label), "Distance: %0.2f mm", std::sprintf(label, "Distance: %0.2f mm",
joint_setting_.distance * 1000); joint_setting_.distance * 1000);
rebuild |= _im_gui->DoSlider(label, 0.f, 1.f, &joint_setting_.distance, rebuild |= _im_gui->DoSlider(label, 0.f, 1.f, &joint_setting_.distance,
.5f, joint_setting_enable_ && optimize_); .5f, joint_setting_enable_ && optimize_);
@ -359,18 +356,18 @@ class OptimizeSampleApplication : public ozz::sample::Application {
static bool open = true; static bool open = true;
ozz::sample::ImGui::OpenClose ocb(_im_gui, "Memory size", &open); ozz::sample::ImGui::OpenClose ocb(_im_gui, "Memory size", &open);
if (open) { if (open) {
std::snprintf(label, sizeof(label), "Original: %dKB", std::sprintf(label, "Original: %dKB",
static_cast<int>(raw_animation_.size() >> 10)); static_cast<int>(raw_animation_.size() >> 10));
_im_gui->DoLabel(label); _im_gui->DoLabel(label);
std::snprintf(label, sizeof(label), "Optimized: %dKB (%.1f:1)", std::sprintf(label, "Optimized: %dKB (%.1f:1)",
static_cast<int>(raw_optimized_animation_.size() >> 10), static_cast<int>(raw_optimized_animation_.size() >> 10),
static_cast<float>(raw_animation_.size()) / static_cast<float>(raw_animation_.size()) /
raw_optimized_animation_.size()); raw_optimized_animation_.size());
_im_gui->DoLabel(label); _im_gui->DoLabel(label);
std::snprintf( std::sprintf(
label, sizeof(label), "Compressed: %dKB (%.1f:1)", label, "Compressed: %dKB (%.1f:1)",
static_cast<int>(animation_rt_->size() >> 10), static_cast<int>(animation_rt_->size() >> 10),
static_cast<float>(raw_animation_.size()) / animation_rt_->size()); static_cast<float>(raw_animation_.size()) / animation_rt_->size());
_im_gui->DoLabel(label); _im_gui->DoLabel(label);
@ -391,36 +388,37 @@ class OptimizeSampleApplication : public ozz::sample::Application {
// Show absolute error. // Show absolute error.
{ {
char szLabel[64];
static bool error_open = true; static bool error_open = true;
ozz::sample::ImGui::OpenClose oc_stats(_im_gui, "Absolute error", ozz::sample::ImGui::OpenClose oc_stats(_im_gui, "Absolute error",
&error_open); &error_open);
if (error_open) { if (error_open) {
{ {
std::snprintf(label, sizeof(label), "Median error: %.2fmm", std::sprintf(szLabel, "Median error: %.2fmm",
*error_record_med_.cursor()); *error_record_med_.cursor());
const ozz::sample::Record::Statistics error_stats = const ozz::sample::Record::Statistics error_stats =
error_record_med_.GetStatistics(); error_record_med_.GetStatistics();
_im_gui->DoGraph(label, 0.f, error_stats.max, error_stats.latest, _im_gui->DoGraph(szLabel, 0.f, error_stats.max, error_stats.latest,
error_record_med_.cursor(), error_record_med_.cursor(),
error_record_med_.record_begin(), error_record_med_.record_begin(),
error_record_med_.record_end()); error_record_med_.record_end());
} }
{ {
std::snprintf(label, sizeof(label), "Maximum error: %.2fmm", std::sprintf(szLabel, "Maximum error: %.2fmm",
*error_record_max_.cursor()); *error_record_max_.cursor());
const ozz::sample::Record::Statistics error_stats = const ozz::sample::Record::Statistics error_stats =
error_record_max_.GetStatistics(); error_record_max_.GetStatistics();
_im_gui->DoGraph(label, 0.f, error_stats.max, error_stats.latest, _im_gui->DoGraph(szLabel, 0.f, error_stats.max, error_stats.latest,
error_record_max_.cursor(), error_record_max_.cursor(),
error_record_max_.record_begin(), error_record_max_.record_begin(),
error_record_max_.record_end()); error_record_max_.record_end());
} }
{ {
std::snprintf(label, sizeof(label), "Joint %d error: %.2fmm", joint_, std::sprintf(szLabel, "Joint %d error: %.2fmm", joint_,
*joint_error_record_.cursor()); *joint_error_record_.cursor());
const ozz::sample::Record::Statistics error_stats = const ozz::sample::Record::Statistics error_stats =
joint_error_record_.GetStatistics(); joint_error_record_.GetStatistics();
_im_gui->DoGraph(label, 0.f, error_stats.max, error_stats.latest, _im_gui->DoGraph(szLabel, 0.f, error_stats.max, error_stats.latest,
joint_error_record_.cursor(), joint_error_record_.cursor(),
joint_error_record_.record_begin(), joint_error_record_.record_begin(),
joint_error_record_.record_end()); joint_error_record_.record_end());

View File

@ -251,7 +251,7 @@ class PartialBlendSampleApplication : public ozz::sample::Application {
_im_gui->DoCheckBox("Use automatic blending settings", &automatic); _im_gui->DoCheckBox("Use automatic blending settings", &automatic);
static float coeff = 1.f; // All power to the partial animation. static float coeff = 1.f; // All power to the partial animation.
std::snprintf(label, sizeof(label), "Upper body weight: %.2f", coeff); std::sprintf(label, "Upper body weight: %.2f", coeff);
_im_gui->DoSlider(label, 0.f, 1.f, &coeff, 1.f, automatic); _im_gui->DoSlider(label, 0.f, 1.f, &coeff, 1.f, automatic);
Sampler& lower_body_sampler = samplers_[kLowerBody]; Sampler& lower_body_sampler = samplers_[kLowerBody];
@ -267,27 +267,27 @@ class PartialBlendSampleApplication : public ozz::sample::Application {
_im_gui->DoLabel("Manual settings:"); _im_gui->DoLabel("Manual settings:");
_im_gui->DoLabel("Lower body layer:"); _im_gui->DoLabel("Lower body layer:");
std::snprintf(label, sizeof(label), "Layer weight: %.2f", std::sprintf(label, "Layer weight: %.2f",
lower_body_sampler.weight_setting); lower_body_sampler.weight_setting);
_im_gui->DoSlider(label, 0.f, 1.f, &lower_body_sampler.weight_setting, _im_gui->DoSlider(label, 0.f, 1.f, &lower_body_sampler.weight_setting,
1.f, !automatic); 1.f, !automatic);
std::snprintf(label, sizeof(label), "Joints weight: %.2f", std::sprintf(label, "Joints weight: %.2f",
lower_body_sampler.joint_weight_setting); lower_body_sampler.joint_weight_setting);
_im_gui->DoSlider(label, 0.f, 1.f, _im_gui->DoSlider(label, 0.f, 1.f,
&lower_body_sampler.joint_weight_setting, 1.f, &lower_body_sampler.joint_weight_setting, 1.f,
!automatic); !automatic);
_im_gui->DoLabel("Upper body layer:"); _im_gui->DoLabel("Upper body layer:");
std::snprintf(label, sizeof(label), "Layer weight: %.2f", std::sprintf(label, "Layer weight: %.2f",
upper_body_sampler.weight_setting); upper_body_sampler.weight_setting);
_im_gui->DoSlider(label, 0.f, 1.f, &upper_body_sampler.weight_setting, _im_gui->DoSlider(label, 0.f, 1.f, &upper_body_sampler.weight_setting,
1.f, !automatic); 1.f, !automatic);
std::snprintf(label, sizeof(label), "Joints weight: %.2f", std::sprintf(label, "Joints weight: %.2f",
upper_body_sampler.joint_weight_setting); upper_body_sampler.joint_weight_setting);
_im_gui->DoSlider(label, 0.f, 1.f, _im_gui->DoSlider(label, 0.f, 1.f,
&upper_body_sampler.joint_weight_setting, 1.f, &upper_body_sampler.joint_weight_setting, 1.f,
!automatic); !automatic);
_im_gui->DoLabel("Global settings:"); _im_gui->DoLabel("Global settings:");
std::snprintf(label, sizeof(label), "Threshold: %.2f", threshold_); std::sprintf(label, "Threshold: %.2f", threshold_);
_im_gui->DoSlider(label, .01f, 1.f, &threshold_); _im_gui->DoSlider(label, .01f, 1.f, &threshold_);
SetupPerJointWeights(); SetupPerJointWeights();
@ -301,7 +301,7 @@ class PartialBlendSampleApplication : public ozz::sample::Application {
_im_gui->DoLabel("Root of the upper body hierarchy:", _im_gui->DoLabel("Root of the upper body hierarchy:",
ozz::sample::ImGui::kLeft, false); ozz::sample::ImGui::kLeft, false);
char label[64]; char label[64];
std::snprintf(label, sizeof(label), "%s (%d)", std::sprintf(label, "%s (%d)",
skeleton_.joint_names()[upper_body_root_], skeleton_.joint_names()[upper_body_root_],
upper_body_root_); upper_body_root_);
if (_im_gui->DoSlider(label, 0, skeleton_.num_joints() - 1, if (_im_gui->DoSlider(label, 0, skeleton_.num_joints() - 1,

View File

@ -186,28 +186,28 @@ class SkinningSampleApplication : public ozz::sample::Application {
ozz::sample::ImGui::OpenClose oc(_im_gui, "Model statisitics", &open); ozz::sample::ImGui::OpenClose oc(_im_gui, "Model statisitics", &open);
if (open) { if (open) {
char label[255]; char label[255];
std::snprintf(label, sizeof(label), "%d animated joints", skeleton_.num_joints()); sprintf(label, "%d animated joints", skeleton_.num_joints());
_im_gui->DoLabel(label); _im_gui->DoLabel(label);
int influences = 0; int influences = 0;
for (const auto& mesh : meshes_) { for (const auto& mesh : meshes_) {
influences = ozz::math::Max(influences, mesh.max_influences_count()); influences = ozz::math::Max(influences, mesh.max_influences_count());
} }
std::snprintf(label, sizeof(label), "%d influences (max)", influences); sprintf(label, "%d influences (max)", influences);
_im_gui->DoLabel(label); _im_gui->DoLabel(label);
int vertices = 0; int vertices = 0;
for (const auto& mesh : meshes_) { for (const auto& mesh : meshes_) {
vertices += mesh.vertex_count(); vertices += mesh.vertex_count();
} }
std::snprintf(label, sizeof(label), "%.1fK vertices", vertices / 1000.f); sprintf(label, "%.1fK vertices", vertices / 1000.f);
_im_gui->DoLabel(label); _im_gui->DoLabel(label);
int indices = 0; int indices = 0;
for (const auto& mesh : meshes_) { for (const auto& mesh : meshes_) {
indices += mesh.triangle_index_count(); indices += mesh.triangle_index_count();
} }
std::snprintf(label, sizeof(label), "%.1fK triangles", indices / 3000.f); sprintf(label, "%.1fK triangles", indices / 3000.f);
_im_gui->DoLabel(label); _im_gui->DoLabel(label);
} }
} }

View File

@ -264,7 +264,7 @@ class TwoBoneIKSampleApplication : public ozz::sample::Application {
virtual void OnDestroy() {} virtual void OnDestroy() {}
virtual bool OnGui(ozz::sample::ImGui* _im_gui) { virtual bool OnGui(ozz::sample::ImGui* _im_gui) {
char label[32]; char txt[32];
// IK parameters // IK parameters
_im_gui->DoCheckBox("Fix initial transform", &fix_initial_transform_); _im_gui->DoCheckBox("Fix initial transform", &fix_initial_transform_);
@ -273,25 +273,25 @@ class TwoBoneIKSampleApplication : public ozz::sample::Application {
static bool opened = true; static bool opened = true;
ozz::sample::ImGui::OpenClose oc(_im_gui, "IK parameters", &opened); ozz::sample::ImGui::OpenClose oc(_im_gui, "IK parameters", &opened);
if (opened) { if (opened) {
snprintf(label, sizeof(label), "Soften: %.2g", soften_); sprintf(txt, "Soften: %.2g", soften_);
_im_gui->DoSlider(label, 0.f, 1.f, &soften_, 2.f); _im_gui->DoSlider(txt, 0.f, 1.f, &soften_, 2.f);
snprintf(label, sizeof(label), "Twist angle: %.0f", sprintf(txt, "Twist angle: %.0f",
twist_angle_ * ozz::math::kRadianToDegree); twist_angle_ * ozz::math::kRadianToDegree);
_im_gui->DoSlider(label, -ozz::math::kPi, ozz::math::kPi, &twist_angle_); _im_gui->DoSlider(txt, -ozz::math::kPi, ozz::math::kPi, &twist_angle_);
snprintf(label, sizeof(label), "Weight: %.2g", weight_); sprintf(txt, "Weight: %.2g", weight_);
_im_gui->DoSlider(label, 0.f, 1.f, &weight_); _im_gui->DoSlider(txt, 0.f, 1.f, &weight_);
{ {
// Pole vector // Pole vector
static bool pole_opened = true; static bool pole_opened = true;
ozz::sample::ImGui::OpenClose oc_pole(_im_gui, "Pole vector", ozz::sample::ImGui::OpenClose oc_pole(_im_gui, "Pole vector",
&pole_opened); &pole_opened);
if (pole_opened) { if (pole_opened) {
snprintf(label, sizeof(label), "x %.2g", pole_vector.x); sprintf(txt, "x %.2g", pole_vector.x);
_im_gui->DoSlider(label, -1.f, 1.f, &pole_vector.x); _im_gui->DoSlider(txt, -1.f, 1.f, &pole_vector.x);
snprintf(label, sizeof(label), "y %.2g", pole_vector.y); sprintf(txt, "y %.2g", pole_vector.y);
_im_gui->DoSlider(label, -1.f, 1.f, &pole_vector.y); _im_gui->DoSlider(txt, -1.f, 1.f, &pole_vector.y);
snprintf(label, sizeof(label), "z %.2g", pole_vector.z); sprintf(txt, "z %.2g", pole_vector.z);
_im_gui->DoSlider(label, -1.f, 1.f, &pole_vector.z); _im_gui->DoSlider(txt, -1.f, 1.f, &pole_vector.z);
} }
} }
} }
@ -301,17 +301,17 @@ class TwoBoneIKSampleApplication : public ozz::sample::Application {
ozz::sample::ImGui::OpenClose oc(_im_gui, "Target position", &opened); ozz::sample::ImGui::OpenClose oc(_im_gui, "Target position", &opened);
if (opened) { if (opened) {
_im_gui->DoLabel("Target animation extent"); _im_gui->DoLabel("Target animation extent");
snprintf(label, sizeof(label), "%.2g", target_extent_); sprintf(txt, "%.2g", target_extent_);
_im_gui->DoSlider(label, 0.f, 1.f, &target_extent_); _im_gui->DoSlider(txt, 0.f, 1.f, &target_extent_);
_im_gui->DoLabel("Target Offset"); _im_gui->DoLabel("Target Offset");
const float kOffsetRange = 1.f; const float kOffsetRange = 1.f;
snprintf(label, sizeof(label), "x %.2g", target_offset_.x); sprintf(txt, "x %.2g", target_offset_.x);
_im_gui->DoSlider(label, -kOffsetRange, kOffsetRange, &target_offset_.x); _im_gui->DoSlider(txt, -kOffsetRange, kOffsetRange, &target_offset_.x);
snprintf(label, sizeof(label), "y %.2g", target_offset_.y); sprintf(txt, "y %.2g", target_offset_.y);
_im_gui->DoSlider(label, -kOffsetRange, kOffsetRange, &target_offset_.y); _im_gui->DoSlider(txt, -kOffsetRange, kOffsetRange, &target_offset_.y);
snprintf(label, sizeof(label), "z %.2g", target_offset_.z); sprintf(txt, "z %.2g", target_offset_.z);
_im_gui->DoSlider(label, -kOffsetRange, kOffsetRange, &target_offset_.z); _im_gui->DoSlider(txt, -kOffsetRange, kOffsetRange, &target_offset_.z);
} }
} }
{ // Root { // Root
@ -320,28 +320,28 @@ class TwoBoneIKSampleApplication : public ozz::sample::Application {
if (opened) { if (opened) {
// Translation // Translation
_im_gui->DoLabel("Translation"); _im_gui->DoLabel("Translation");
snprintf(label, sizeof(label), "x %.2g", root_translation_.x); sprintf(txt, "x %.2g", root_translation_.x);
_im_gui->DoSlider(label, -1.f, 1.f, &root_translation_.x); _im_gui->DoSlider(txt, -1.f, 1.f, &root_translation_.x);
snprintf(label, sizeof(label), "y %.2g", root_translation_.y); sprintf(txt, "y %.2g", root_translation_.y);
_im_gui->DoSlider(label, -1.f, 1.f, &root_translation_.y); _im_gui->DoSlider(txt, -1.f, 1.f, &root_translation_.y);
snprintf(label, sizeof(label), "z %.2g", root_translation_.z); sprintf(txt, "z %.2g", root_translation_.z);
_im_gui->DoSlider(label, -1.f, 1.f, &root_translation_.z); _im_gui->DoSlider(txt, -1.f, 1.f, &root_translation_.z);
// Rotation (in euler form) // Rotation (in euler form)
_im_gui->DoLabel("Rotation"); _im_gui->DoLabel("Rotation");
ozz::math::Float3 euler = root_euler_ * ozz::math::kRadianToDegree; ozz::math::Float3 euler = root_euler_ * ozz::math::kRadianToDegree;
snprintf(label, sizeof(label), "yaw %.3g", euler.x); sprintf(txt, "yaw %.3g", euler.x);
_im_gui->DoSlider(label, -180.f, 180.f, &euler.x); _im_gui->DoSlider(txt, -180.f, 180.f, &euler.x);
snprintf(label, sizeof(label), "pitch %.3g", euler.y); sprintf(txt, "pitch %.3g", euler.y);
_im_gui->DoSlider(label, -180.f, 180.f, &euler.y); _im_gui->DoSlider(txt, -180.f, 180.f, &euler.y);
snprintf(label, sizeof(label), "roll %.3g", euler.z); sprintf(txt, "roll %.3g", euler.z);
_im_gui->DoSlider(label, -180.f, 180.f, &euler.z); _im_gui->DoSlider(txt, -180.f, 180.f, &euler.z);
root_euler_ = euler * ozz::math::kDegreeToRadian; root_euler_ = euler * ozz::math::kDegreeToRadian;
// Scale (must be uniform and not 0) // Scale (must be uniform and not 0)
_im_gui->DoLabel("Scale"); _im_gui->DoLabel("Scale");
snprintf(label, sizeof(label), "%.2g", root_scale_); sprintf(txt, "%.2g", root_scale_);
_im_gui->DoSlider(label, -1.f, 1.f, &root_scale_); _im_gui->DoSlider(txt, -1.f, 1.f, &root_scale_);
} }
} }
{ // Display options { // Display options

View File

@ -62,7 +62,6 @@ target_compile_definitions(ozz_base
PRIVATE $<$<BOOL:${BUILD_SHARED_LIBS}>:OZZ_BUILD_BASE_LIB>) PRIVATE $<$<BOOL:${BUILD_SHARED_LIBS}>:OZZ_BUILD_BASE_LIB>)
target_compile_options(ozz_base PUBLIC $<$<CXX_COMPILER_ID:MSVC>:/wd4251>) target_compile_options(ozz_base PUBLIC $<$<CXX_COMPILER_ID:MSVC>:/wd4251>)
target_include_directories(ozz_base PUBLIC target_include_directories(ozz_base PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include>) $<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include>)

View File

@ -25,13 +25,15 @@
// // // //
//----------------------------------------------------------------------------// //----------------------------------------------------------------------------//
#include "gtest/gtest.h"
#include "ozz/animation/runtime/ik_aim_job.h" #include "ozz/animation/runtime/ik_aim_job.h"
#include "ozz/base/maths/gtest_math_helper.h"
#include "ozz/base/maths/quaternion.h" #include "ozz/base/maths/quaternion.h"
#include "ozz/base/maths/simd_math.h" #include "ozz/base/maths/simd_math.h"
#include "ozz/base/maths/simd_quaternion.h" #include "ozz/base/maths/simd_quaternion.h"
#include "gtest/gtest.h"
#include "ozz/base/maths/gtest_math_helper.h"
TEST(JobValidity, IKAimJob) { TEST(JobValidity, IKAimJob) {
const ozz::math::Float4x4 joint = ozz::math::Float4x4::identity(); const ozz::math::Float4x4 joint = ozz::math::Float4x4::identity();
ozz::math::SimdQuaternion quat; ozz::math::SimdQuaternion quat;
@ -426,23 +428,42 @@ TEST(Twist, IKAimJob) {
EXPECT_SIMDQUATERNION_EQ_TOL(quat, 0.f, 0.f, 0.f, 1.f, 2e-3f); 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 { // Pole y, twist pi/2
job.pole_vector = ozz::math::simd_float4::y_axis(); job.pole_vector = ozz::math::simd_float4::y_axis();
job.twist_angle = ozz::math::kPi_2; job.twist_angle = ozz::math::kPi_2;
EXPECT_TRUE(job.Run()); EXPECT_TRUE(job.Run());
const ozz::math::Quaternion x_Pi = ozz::math::Quaternion::FromAxisAngle( const ozz::math::Quaternion x_Pi_2 = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::x_axis(), ozz::math::kPi_2); ozz::math::Float3::x_axis(), ozz::math::kPi_2);
EXPECT_SIMDQUATERNION_EQ_TOL(quat, x_Pi.x, x_Pi.y, x_Pi.z, x_Pi.w, 2e-3f); EXPECT_SIMDQUATERNION_EQ_TOL(quat, x_Pi_2.x, x_Pi_2.y, x_Pi_2.z, x_Pi_2.w,
2e-3f);
} }
{ // Pole y, twist -pi / 2 { // Pole z, twist pi/2
job.pole_vector = ozz::math::simd_float4::y_axis(); job.pole_vector = ozz::math::simd_float4::z_axis();
job.twist_angle = -ozz::math::kPi_2; job.twist_angle = ozz::math::kPi_2;
EXPECT_TRUE(job.Run()); EXPECT_TRUE(job.Run());
const ozz::math::Quaternion x_mPi = ozz::math::Quaternion::FromAxisAngle( const ozz::math::Quaternion x_Pi = ozz::math::Quaternion::FromAxisAngle(
ozz::math::Float3::x_axis(), -ozz::math::kPi_2); 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, EXPECT_SIMDQUATERNION_EQ_TOL(quat, x_Pi.x, x_Pi.y, x_Pi.z, x_Pi.w, 2e-3f);
2e-3f);
} }
} }

View File

@ -3,8 +3,6 @@ target_include_directories(test_intrusive_list
PUBLIC "${PROJECT_SOURCE_DIR}/include") PUBLIC "${PROJECT_SOURCE_DIR}/include")
target_link_libraries(test_intrusive_list target_link_libraries(test_intrusive_list
gtest) gtest)
target_compile_options(test_intrusive_list
PRIVATE $<$<BOOL:${W_UNUSED_RESULT}>:-Wno-unused-result>)
target_copy_shared_libraries(test_intrusive_list) target_copy_shared_libraries(test_intrusive_list)
add_test(NAME test_intrusive_list COMMAND test_intrusive_list) add_test(NAME test_intrusive_list COMMAND test_intrusive_list)
set_target_properties(test_intrusive_list PROPERTIES FOLDER "ozz/tests/base") set_target_properties(test_intrusive_list PROPERTIES FOLDER "ozz/tests/base")
@ -13,8 +11,6 @@ add_executable(test_std_containers std_containers_tests.cc)
target_link_libraries(test_std_containers target_link_libraries(test_std_containers
ozz_base ozz_base
gtest) gtest)
target_compile_options(test_std_containers
PRIVATE $<$<BOOL:${W_UNUSED_RESULT}>:-Wno-unused-result>)
target_copy_shared_libraries(test_std_containers) target_copy_shared_libraries(test_std_containers)
add_test(NAME test_std_containers COMMAND test_std_containers) add_test(NAME test_std_containers COMMAND test_std_containers)
set_target_properties(test_std_containers PROPERTIES FOLDER "ozz/tests/base") set_target_properties(test_std_containers PROPERTIES FOLDER "ozz/tests/base")

View File

@ -46,22 +46,14 @@ add_library(AnimTestbedCode OBJECT
src/SyncTrack.cc src/SyncTrack.cc
src/SyncTrack.h src/SyncTrack.h
src/ozzutils.cc src/ozzutils.cc
# src/AnimGraph/AnimGraphBlendTreeResource.cc src/AnimGraph/AnimGraphResource.cc
# src/AnimGraph/AnimGraphBlendTreeResource.h src/AnimGraph/AnimGraphResource.h
src/AnimGraph/AnimGraph.cc src/AnimGraph/AnimGraph.cc
src/AnimGraph/AnimGraph.h src/AnimGraph/AnimGraph.h
src/AnimGraph/AnimGraphNodes.cc src/AnimGraph/AnimGraphNodes.cc
src/AnimGraph/AnimGraphNodes.h src/AnimGraph/AnimGraphNodes.h
src/AnimGraph/AnimGraphData.cc src/AnimGraph/AnimGraphData.cc
src/AnimGraph/AnimGraphData.h src/AnimGraph/AnimGraphData.h)
src/AnimGraph/AnimGraphBlendTree.cc
src/AnimGraph/AnimGraphBlendTree.h
src/AnimGraph/AnimGraphStateMachine.cc
src/AnimGraph/AnimGraphStateMachine.h
src/AnimGraph/AnimNode.cc
src/AnimGraph/AnimNode.h
src/AnimGraph/AnimGraphResource.cc
src/AnimGraph/AnimGraphResource.h)
target_include_directories( target_include_directories(
AnimTestbedCode AnimTestbedCode
@ -128,7 +120,7 @@ set(ozz_offline_test_objs
target_sources(runtests PRIVATE target_sources(runtests PRIVATE
tests/AnimGraphResourceTests.cc tests/AnimGraphResourceTests.cc
# tests/AnimGraphEvalTests.cc tests/AnimGraphEvalTests.cc
tests/NodeDescriptorTests.cc tests/NodeDescriptorTests.cc
tests/SyncTrackTests.cc tests/SyncTrackTests.cc
tests/main.cc tests/main.cc

View File

@ -1,3 +1,202 @@
// //
// Created by martin on 25.03.22. // Created by martin on 25.03.22.
// //
#include "AnimGraph.h"
#include <algorithm>
#include <cstring>
bool AnimGraph::init(AnimGraphContext& context) {
context.m_graph = this;
for (size_t i = 2; i < m_nodes.size(); i++) {
if (!m_nodes[i]->Init(context)) {
return false;
}
}
for (size_t i = 0; i < m_animdata_blocks.size(); i++) {
int num_soa_joints = context.m_skeleton->num_soa_joints();
m_animdata_blocks[i]->m_local_matrices.resize(num_soa_joints);
}
return true;
}
void AnimGraph::updateOrderedNodes() {
m_eval_ordered_nodes.clear();
updateOrderedNodesRecursive(0);
}
void AnimGraph::updateOrderedNodesRecursive(int node_index) {
AnimNode* node = m_nodes[node_index];
const std::vector<AnimGraphConnection>& node_input_connections =
m_node_input_connections[node_index];
for (size_t i = 0, n = node_input_connections.size(); i < n; i++) {
int input_node_index =
getAnimNodeIndex(node_input_connections.at(i).m_source_node);
if (input_node_index == 1) {
continue;
}
updateOrderedNodesRecursive(input_node_index);
}
if (node_index != 0) {
// In case we have multiple output connections from the node we here
// ensure that use the node evaluation that is the furthest away from
// the output.
std::vector<AnimNode*>::iterator find_iter = std::find(
m_eval_ordered_nodes.begin(),
m_eval_ordered_nodes.end(),
node);
if (find_iter != m_eval_ordered_nodes.end()) {
m_eval_ordered_nodes.erase(find_iter);
}
m_eval_ordered_nodes.push_back(node);
}
}
void AnimGraph::markActiveNodes() {
for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
m_nodes[i]->m_state = AnimNodeEvalState::Deactivated;
}
const std::vector<AnimGraphConnection>& graph_output_inputs =
m_node_input_connections[0];
for (size_t i = 0, n = graph_output_inputs.size(); i < n; i++) {
const AnimGraphConnection& graph_input = graph_output_inputs[i];
AnimNode* node = graph_input.m_source_node;
if (node != nullptr) {
node->m_state = AnimNodeEvalState::Activated;
}
}
for (size_t i = m_eval_ordered_nodes.size() - 1; i > 0; i--) {
AnimNode* node = m_eval_ordered_nodes[i];
if (checkIsNodeActive(node)) {
int node_index = node->m_index;
node->MarkActiveInputs(m_node_input_connections[node_index]);
// Non-animation data inputs are always active.
for (size_t j = 0, nj = m_node_input_connections[node_index].size();
j < nj;
j++) {
const AnimGraphConnection& input =
m_node_input_connections[node_index][j];
if (input.m_source_node != nullptr
&& input.m_target_socket.m_type
!= SocketType::SocketTypeAnimation) {
input.m_source_node->m_state = AnimNodeEvalState::Activated;
}
}
}
}
}
void AnimGraph::evalSyncTracks() {
for (size_t i = m_eval_ordered_nodes.size() - 1; i >= 0; i--) {
AnimNode* node = m_eval_ordered_nodes[i];
int node_index = node->m_index;
if (node->m_state == AnimNodeEvalState::Deactivated) {
continue;
}
node->CalcSyncTrack(m_node_input_connections[node_index]);
}
}
void AnimGraph::updateTime(float dt) {
const std::vector<AnimGraphConnection>& graph_output_inputs =
m_node_input_connections[0];
for (size_t i = 0, n = graph_output_inputs.size(); i < n; i++) {
AnimNode* node = graph_output_inputs[i].m_source_node;
if (node != nullptr) {
node->UpdateTime(node->m_time_now, node->m_time_now + dt);
}
}
for (size_t i = m_eval_ordered_nodes.size() - 1; i > 0; --i) {
AnimNode* node = m_eval_ordered_nodes[i];
if (node->m_state != AnimNodeEvalState::TimeUpdated) {
continue;
}
int node_index = node->m_index;
float node_time_now = node->m_time_now;
float node_time_last = node->m_time_last;
const std::vector<AnimGraphConnection>& node_input_connections =
m_node_input_connections[node_index];
for (size_t i = 0, n = node_input_connections.size(); i < n; i++) {
AnimNode* input_node = node_input_connections[i].m_source_node;
// Only propagate time updates via animation sockets.
if (input_node != nullptr
&& node_input_connections[i].m_target_socket.m_type
== SocketType::SocketTypeAnimation
&& input_node->m_state == AnimNodeEvalState::Activated) {
input_node->UpdateTime(node_time_last, node_time_now);
}
}
}
}
void AnimGraph::evaluate(AnimGraphContext& context) {
for (int i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) {
AnimNode* node = m_eval_ordered_nodes[i];
if (node->m_state == AnimNodeEvalState::Deactivated) {
continue;
}
node->Evaluate(context);
}
}
Socket* AnimGraph::getInputSocket(const std::string& name) {
for (size_t i = 0, n = m_node_output_connections[1].size(); i < n; i++) {
AnimGraphConnection& connection = m_node_output_connections[1][i];
if (connection.m_source_socket.m_name == name) {
return &connection.m_source_socket;
}
}
return nullptr;
}
Socket* AnimGraph::getOutputSocket(const std::string& name) {
for (size_t i = 0, n = m_node_input_connections[0].size(); i < n; i++) {
AnimGraphConnection& connection = m_node_input_connections[0][i];
if (connection.m_target_socket.m_name == name) {
return &connection.m_target_socket;
}
}
return nullptr;
}
const Socket* AnimGraph::getInputSocket(const std::string& name) const {
for (size_t i = 0, n = m_node_output_connections[1].size(); i < n; i++) {
const AnimGraphConnection& connection = m_node_output_connections[1][i];
if (connection.m_source_socket.m_name == name) {
return &connection.m_source_socket;
}
}
return nullptr;
}
const Socket* AnimGraph::getOutputSocket(const std::string& name) const {
for (size_t i = 0, n = m_node_input_connections[0].size(); i < n; i++) {
const AnimGraphConnection& connection = m_node_input_connections[0][i];
if (connection.m_target_socket.m_name == name) {
return &connection.m_target_socket;
}
}
return nullptr;
}

View File

@ -5,34 +5,227 @@
#ifndef ANIMTESTBED_ANIMGRAPH_H #ifndef ANIMTESTBED_ANIMGRAPH_H
#define ANIMTESTBED_ANIMGRAPH_H #define ANIMTESTBED_ANIMGRAPH_H
#include "AnimNode.h"
#include "AnimGraphData.h" #include "AnimGraphData.h"
#include "AnimGraphNodes.h"
// //
// AnimGraph (Runtime) // AnimGraph (Runtime)
// //
struct AnimGraph { struct AnimGraph {
~AnimGraph() {} AnimData m_local_transforms;
bool Init(AnimGraphContext& context) { std::vector<AnimNode*> m_nodes;
m_root_node->Init(context); std::vector<AnimNode*> m_eval_ordered_nodes;
} std::vector<std::vector<AnimGraphConnection> > m_node_input_connections;
void UpdateTime(float dt) { std::vector<std::vector<AnimGraphConnection> > m_node_output_connections;
m_time_last = m_time_now; std::vector<AnimData*> m_animdata_blocks;
m_time_now = m_time_now + dt; NodeDescriptorBase* m_node_descriptor = nullptr;
m_root_node->UpdateTime(m_time_last, m_time_now); char* m_input_buffer = nullptr;
} char* m_output_buffer = nullptr;
void Evaluate(AnimGraphContext& context) { char* m_connection_data_storage = nullptr;
char* m_const_node_inputs = nullptr;
std::vector<Socket>& getGraphOutputs() { return m_node_descriptor->m_inputs; }
std::vector<Socket>& getGraphInputs() { return m_node_descriptor->m_outputs; }
AnimDataAllocator m_anim_data_allocator;
~AnimGraph() { dealloc(); }
bool init(AnimGraphContext& context);
void dealloc() {
for (size_t i = 0; i < m_animdata_blocks.size(); i++) {
m_animdata_blocks[i]->m_local_matrices.vector::~vector();
}
m_animdata_blocks.clear();
m_node_input_connections.clear();
m_node_output_connections.clear();
delete[] m_input_buffer;
delete[] m_output_buffer;
delete[] m_connection_data_storage;
delete[] m_const_node_inputs;
for (int i = 0; i < m_nodes.size(); i++) {
delete m_nodes[i];
}
m_nodes.clear();
delete m_node_descriptor;
} }
AnimNode* m_root_node = nullptr; void updateOrderedNodes();
void updateOrderedNodesRecursive(int node_index);
void markActiveNodes();
bool checkIsNodeActive(AnimNode* node) {
return node->m_state != AnimNodeEvalState::Deactivated;
}
float m_time_now = 0.f; void evalSyncTracks();
float m_time_last = 0.f; void updateTime(float dt);
void evaluate(AnimGraphContext& context);
void resetNodeStates() {
for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
m_nodes[i]->m_time_now = 0.f;
m_nodes[i]->m_time_last = 0.f;
m_nodes[i]->m_state = AnimNodeEvalState::Undefined;
}
}
Vec3 m_root_bone_translation = {}; Socket* getInputSocket(const std::string& name);
Quat m_root_bone_rotation = {}; Socket* getOutputSocket(const std::string& name);
const Socket* getInputSocket(const std::string& name) const;
const Socket* getOutputSocket(const std::string& name) const;
/** Sets the address that is used for the specified AnimGraph input Socket.
*
* @tparam T Type of the Socket.
* @param name Name of the Socket.
* @param value_ptr Pointer where the input is fetched during evaluation.
*/
template <typename T>
void SetInput(const char* name, T* value_ptr) {
m_node_descriptor->SetOutput(name, value_ptr);
for (int i = 0; i < m_node_output_connections[1].size(); i++) {
const AnimGraphConnection& graph_input_connection =
m_node_output_connections[1][i];
if (graph_input_connection.m_source_socket.m_name == name) {
*graph_input_connection.m_target_socket.m_reference.ptr_ptr = value_ptr;
}
}
}
/** Sets the address that is used for the specified AnimGraph output Socket.
*
* @tparam T Type of the Socket.
* @param name Name of the Socket.
* @param value_ptr Pointer where the graph output output is written to at the end of evaluation.
*/
template <typename T>
void SetOutput(const char* name, T* value_ptr) {
m_node_descriptor->SetInput(name, value_ptr);
for (int i = 0; i < m_node_input_connections[0].size(); i++) {
const AnimGraphConnection& graph_output_connection =
m_node_input_connections[0][i];
if (graph_output_connection.m_target_socket.m_name == name) {
if (graph_output_connection.m_source_node == m_nodes[1]
&& graph_output_connection.m_target_node == m_nodes[0]) {
std::cerr << "Error: cannot set output for direct graph input to graph "
"output connections. Use GetOutptPtr for output instead!"
<< std::endl;
return;
}
*graph_output_connection.m_source_socket.m_reference.ptr_ptr =
value_ptr;
// Make sure all other output connections of this pin use the same output pointer
int source_node_index = getAnimNodeIndex(graph_output_connection.m_source_node);
for (int j = 0; j < m_node_output_connections[source_node_index].size(); j++) {
const AnimGraphConnection& source_output_connection = m_node_output_connections[source_node_index][j];
if (source_output_connection.m_target_node == m_nodes[0]) {
continue;
}
if (source_output_connection.m_source_socket.m_name == graph_output_connection.m_source_socket.m_name) {
*source_output_connection.m_target_socket.m_reference.ptr_ptr = value_ptr;
}
}
}
}
}
/** Returns the address that is used for the specified AnimGraph output Socket.
*
* This function is needed for connections that directly connect an AnimGraph
* input Socket to an output Socket of the same AnimGraph.
*
* @tparam T Type of the Socket.
* @param name Name of the Socket.
* @return Address that is used for the specified AnimGraph output Socket.
*/
template <typename T>
T* GetOutputPtr(const char* name) {
for (int i = 0; i < m_node_input_connections[0].size(); i++) {
const AnimGraphConnection& graph_output_connection =
m_node_input_connections[0][i];
if (graph_output_connection.m_target_socket.m_name == name) {
return static_cast<float*>(*graph_output_connection.m_source_socket.m_reference.ptr_ptr);
}
}
return nullptr;
}
void* getInputPtr(const std::string& name) const {
const Socket* input_socket = getInputSocket(name);
if (input_socket != nullptr) {
return input_socket->m_reference.ptr;
}
return nullptr;
}
void* getOutputPtr(const std::string& name) const {
const Socket* input_socket = getOutputSocket(name);
if (input_socket != nullptr) {
return input_socket->m_reference.ptr;
}
return nullptr;
}
int getNodeEvalOrderIndex(const AnimNode* node) {
for (size_t i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) {
if (m_eval_ordered_nodes[i] == node) {
return i;
}
}
return -1;
}
const AnimNode* getAnimNodeForInput(
size_t node_index,
const std::string& input_name) const {
assert(node_index < m_nodes.size());
const std::vector<AnimGraphConnection>& input_connection =
m_node_input_connections[node_index];
for (size_t i = 0, n = input_connection.size(); i < n; i++) {
if (input_connection[i].m_target_socket.m_name == input_name) {
return input_connection[i].m_source_node;
}
}
return nullptr;
}
AnimNode* getAnimNode(const char* name) {
for (size_t i = 0; i < m_nodes.size(); i++) {
if (m_nodes[i]->m_name == name) {
return m_nodes[i];
}
}
return nullptr;
}
size_t getAnimNodeIndex(AnimNode* node) {
for (size_t i = 0; i < m_nodes.size(); i++) {
if (m_nodes[i] == node) {
return i;
}
}
return -1;
}
}; };
#endif //ANIMTESTBED_ANIMGRAPH_H #endif //ANIMTESTBED_ANIMGRAPH_H

View File

@ -1,201 +0,0 @@
//
// Created by martin on 17.03.24.
//
#include "AnimGraphBlendTree.h"
#include <algorithm>
#include <cstring>
bool AnimGraphBlendTree::Init(AnimGraphContext& context) {
for (size_t i = 2; i < m_nodes.size(); i++) {
if (!m_nodes[i]->Init(context)) {
return false;
}
}
for (size_t i = 0; i < m_animdata_blocks.size(); i++) {
int num_soa_joints = context.m_skeleton->num_soa_joints();
m_animdata_blocks[i]->m_local_matrices.resize(num_soa_joints);
}
return true;
}
void AnimGraphBlendTree::UpdateOrderedNodes() {
m_eval_ordered_nodes.clear();
UpdateOrderedNodesRecursive(0);
}
void AnimGraphBlendTree::UpdateOrderedNodesRecursive(int node_index) {
AnimNode* node = m_nodes[node_index];
const std::vector<AnimGraphConnection>& node_input_connections =
m_node_input_connections[node_index];
for (size_t i = 0, n = node_input_connections.size(); i < n; i++) {
int input_node_index =
GetAnimNodeIndex(node_input_connections.at(i).m_source_node);
if (input_node_index == 1) {
continue;
}
UpdateOrderedNodesRecursive(input_node_index);
}
if (node_index != 0) {
// In case we have multiple output connections from the node we here
// ensure that use the node evaluation that is the furthest away from
// the output.
std::vector<AnimNode*>::iterator find_iter = std::find(
m_eval_ordered_nodes.begin(),
m_eval_ordered_nodes.end(),
node);
if (find_iter != m_eval_ordered_nodes.end()) {
m_eval_ordered_nodes.erase(find_iter);
}
m_eval_ordered_nodes.push_back(node);
}
}
void AnimGraphBlendTree::MarkActiveInputs() {
for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
m_nodes[i]->m_state = AnimNodeEvalState::Deactivated;
}
const std::vector<AnimGraphConnection>& graph_output_inputs =
m_node_input_connections[0];
for (size_t i = 0, n = graph_output_inputs.size(); i < n; i++) {
const AnimGraphConnection& graph_input = graph_output_inputs[i];
AnimNode* node = graph_input.m_source_node;
if (node != nullptr) {
node->m_state = AnimNodeEvalState::Activated;
}
}
for (size_t i = m_eval_ordered_nodes.size() - 1; i > 0; i--) {
AnimNode* node = m_eval_ordered_nodes[i];
if (checkIsNodeActive(node)) {
node->MarkActiveInputs();
size_t node_index = GetAnimNodeIndex(node);
// Non-animation data inputs are always active.
for (size_t j = 0, nj = m_node_input_connections[node_index].size();
j < nj;
j++) {
const AnimGraphConnection& input =
m_node_input_connections[node_index][j];
if (input.m_source_node != nullptr
&& input.m_target_socket.m_type
!= SocketType::SocketTypeAnimation) {
input.m_source_node->m_state = AnimNodeEvalState::Activated;
}
}
}
}
}
void AnimGraphBlendTree::CalcSyncTrack() {
for (size_t i = m_eval_ordered_nodes.size() - 1; i >= 0; i--) {
AnimNode* node = m_eval_ordered_nodes[i];
if (node->m_state == AnimNodeEvalState::Deactivated) {
continue;
}
node->CalcSyncTrack();
}
}
void AnimGraphBlendTree::UpdateTime(float time_last, float time_now) {
float dt = time_now - time_last;
const std::vector<AnimGraphConnection>& graph_output_inputs =
m_node_input_connections[0];
for (size_t i = 0, n = graph_output_inputs.size(); i < n; i++) {
AnimNode* node = graph_output_inputs[i].m_source_node;
if (node != nullptr) {
node->UpdateTime(node->m_time_now, node->m_time_now + dt);
}
}
for (size_t i = m_eval_ordered_nodes.size() - 1; i > 0; --i) {
AnimNode* node = m_eval_ordered_nodes[i];
if (node->m_state != AnimNodeEvalState::TimeUpdated) {
continue;
}
size_t node_index = GetAnimNodeIndex(node);
float node_time_now = node->m_time_now;
float node_time_last = node->m_time_last;
const std::vector<AnimGraphConnection>& node_input_connections =
m_node_input_connections[node_index];
for (size_t i = 0, n = node_input_connections.size(); i < n; i++) {
AnimNode* input_node = node_input_connections[i].m_source_node;
// Only propagate time updates via animation sockets.
if (input_node != nullptr
&& node_input_connections[i].m_target_socket.m_type
== SocketType::SocketTypeAnimation
&& input_node->m_state == AnimNodeEvalState::Activated) {
input_node->UpdateTime(node_time_last, node_time_now);
}
}
}
}
void AnimGraphBlendTree::Evaluate(AnimGraphContext& context) {
for (int i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) {
AnimNode* node = m_eval_ordered_nodes[i];
if (node->m_state == AnimNodeEvalState::Deactivated) {
continue;
}
node->Evaluate(context);
}
}
Socket* AnimGraphBlendTree::getInputSocket(const std::string& name) {
for (size_t i = 0, n = m_node_output_connections[1].size(); i < n; i++) {
AnimGraphConnection& connection = m_node_output_connections[1][i];
if (connection.m_source_socket.m_name == name) {
return &connection.m_source_socket;
}
}
return nullptr;
}
Socket* AnimGraphBlendTree::getOutputSocket(const std::string& name) {
for (size_t i = 0, n = m_node_input_connections[0].size(); i < n; i++) {
AnimGraphConnection& connection = m_node_input_connections[0][i];
if (connection.m_target_socket.m_name == name) {
return &connection.m_target_socket;
}
}
return nullptr;
}
const Socket* AnimGraphBlendTree::getInputSocket(const std::string& name) const {
for (size_t i = 0, n = m_node_output_connections[1].size(); i < n; i++) {
const AnimGraphConnection& connection = m_node_output_connections[1][i];
if (connection.m_source_socket.m_name == name) {
return &connection.m_source_socket;
}
}
return nullptr;
}
const Socket* AnimGraphBlendTree::getOutputSocket(const std::string& name) const {
for (size_t i = 0, n = m_node_input_connections[0].size(); i < n; i++) {
const AnimGraphConnection& connection = m_node_input_connections[0][i];
if (connection.m_target_socket.m_name == name) {
return &connection.m_target_socket;
}
}
return nullptr;
}

View File

@ -1,234 +0,0 @@
//
// Created by martin on 17.03.24.
//
#ifndef ANIMTESTBED_ANIMGRAPHBLENDTREE_H
#define ANIMTESTBED_ANIMGRAPHBLENDTREE_H
#include "AnimNode.h"
//
// AnimGraph (Runtime)
//
struct AnimGraphBlendTree : public AnimNode {
AnimData m_local_transforms;
std::vector<AnimNode*> m_nodes;
std::vector<AnimNode*> m_eval_ordered_nodes;
std::vector<std::vector<AnimGraphConnection> > m_node_input_connections;
std::vector<std::vector<AnimGraphConnection> > m_node_output_connections;
std::vector<AnimData*> m_animdata_blocks;
NodeDescriptorBase* m_node_descriptor = nullptr;
char* m_input_buffer = nullptr;
char* m_output_buffer = nullptr;
char* m_connection_data_storage = nullptr;
char* m_const_node_inputs = nullptr;
std::vector<Socket>& getGraphOutputs() { return m_node_descriptor->m_inputs; }
std::vector<Socket>& getGraphInputs() { return m_node_descriptor->m_outputs; }
AnimDataAllocator m_anim_data_allocator;
~AnimGraphBlendTree() { dealloc(); }
// AnimNode overrides
bool Init(AnimGraphContext& context);
void MarkActiveInputs() override;
void CalcSyncTrack() override;
void UpdateTime(float time_last, float time_now) override;
void Evaluate(AnimGraphContext& context) override;
void dealloc() {
for (size_t i = 0; i < m_animdata_blocks.size(); i++) {
m_animdata_blocks[i]->m_local_matrices.vector::~vector();
}
m_animdata_blocks.clear();
m_node_input_connections.clear();
m_node_output_connections.clear();
delete[] m_input_buffer;
delete[] m_output_buffer;
delete[] m_connection_data_storage;
delete[] m_const_node_inputs;
for (int i = 0; i < m_nodes.size(); i++) {
delete m_nodes[i];
}
m_nodes.clear();
delete m_node_descriptor;
}
void UpdateOrderedNodes();
void UpdateOrderedNodesRecursive(int node_index);
bool checkIsNodeActive(AnimNode* node) {
return node->m_state != AnimNodeEvalState::Deactivated;
}
void ResetNodeStates() {
for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
m_nodes[i]->m_time_now = 0.f;
m_nodes[i]->m_time_last = 0.f;
m_nodes[i]->m_state = AnimNodeEvalState::Undefined;
}
}
Socket* getInputSocket(const std::string& name);
Socket* getOutputSocket(const std::string& name);
const Socket* getInputSocket(const std::string& name) const;
const Socket* getOutputSocket(const std::string& name) const;
/** Sets the address that is used for the specified AnimGraph input Socket.
*
* @tparam T Type of the Socket.
* @param name Name of the Socket.
* @param value_ptr Pointer where the input is fetched during evaluation.
*/
template <typename T>
void SetInput(const char* name, T* value_ptr) {
m_node_descriptor->SetOutput(name, value_ptr);
for (int i = 0; i < m_node_output_connections[1].size(); i++) {
const AnimGraphConnection& graph_input_connection =
m_node_output_connections[1][i];
if (graph_input_connection.m_source_socket.m_name == name) {
*graph_input_connection.m_target_socket.m_reference.ptr_ptr = value_ptr;
}
}
}
/** Sets the address that is used for the specified AnimGraph output Socket.
*
* @tparam T Type of the Socket.
* @param name Name of the Socket.
* @param value_ptr Pointer where the graph output output is written to at the end of evaluation.
*/
template <typename T>
void SetOutput(const char* name, T* value_ptr) {
m_node_descriptor->SetInput(name, value_ptr);
for (int i = 0; i < m_node_input_connections[0].size(); i++) {
const AnimGraphConnection& graph_output_connection =
m_node_input_connections[0][i];
if (graph_output_connection.m_target_socket.m_name == name) {
if (graph_output_connection.m_source_node == m_nodes[1]
&& graph_output_connection.m_target_node == m_nodes[0]) {
std::cerr << "Error: cannot set output for direct graph input to graph "
"output connections. Use GetOutptPtr for output instead!"
<< std::endl;
return;
}
*graph_output_connection.m_source_socket.m_reference.ptr_ptr =
value_ptr;
// Make sure all other output connections of this pin use the same output pointer
int source_node_index =
GetAnimNodeIndex(graph_output_connection.m_source_node);
for (int j = 0; j < m_node_output_connections[source_node_index].size(); j++) {
const AnimGraphConnection& source_output_connection = m_node_output_connections[source_node_index][j];
if (source_output_connection.m_target_node == m_nodes[0]) {
continue;
}
if (source_output_connection.m_source_socket.m_name == graph_output_connection.m_source_socket.m_name) {
*source_output_connection.m_target_socket.m_reference.ptr_ptr = value_ptr;
}
}
}
}
}
/** Returns the address that is used for the specified AnimGraph output Socket.
*
* This function is needed for connections that directly connect an AnimGraph
* input Socket to an output Socket of the same AnimGraph.
*
* @tparam T Type of the Socket.
* @param name Name of the Socket.
* @return Address that is used for the specified AnimGraph output Socket.
*/
template <typename T>
T* GetOutputPtr(const char* name) {
for (int i = 0; i < m_node_input_connections[0].size(); i++) {
const AnimGraphConnection& graph_output_connection =
m_node_input_connections[0][i];
if (graph_output_connection.m_target_socket.m_name == name) {
return static_cast<float*>(*graph_output_connection.m_source_socket.m_reference.ptr_ptr);
}
}
return nullptr;
}
void* getInputPtr(const std::string& name) const {
const Socket* input_socket = getInputSocket(name);
if (input_socket != nullptr) {
return input_socket->m_reference.ptr;
}
return nullptr;
}
void* getOutputPtr(const std::string& name) const {
const Socket* input_socket = getOutputSocket(name);
if (input_socket != nullptr) {
return input_socket->m_reference.ptr;
}
return nullptr;
}
int getNodeEvalOrderIndex(const AnimNode* node) {
for (size_t i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) {
if (m_eval_ordered_nodes[i] == node) {
return i;
}
}
return -1;
}
const AnimNode* getAnimNodeForInput(
size_t node_index,
const std::string& input_name) const {
assert(node_index < m_nodes.size());
const std::vector<AnimGraphConnection>& input_connection =
m_node_input_connections[node_index];
for (size_t i = 0, n = input_connection.size(); i < n; i++) {
if (input_connection[i].m_target_socket.m_name == input_name) {
return input_connection[i].m_source_node;
}
}
return nullptr;
}
AnimNode* getAnimNode(const char* name) {
for (size_t i = 0; i < m_nodes.size(); i++) {
if (m_nodes[i]->m_name == name) {
return m_nodes[i];
}
}
return nullptr;
}
size_t GetAnimNodeIndex(AnimNode* node) {
for (size_t i = 0; i < m_nodes.size(); i++) {
if (m_nodes[i] == node) {
return i;
}
}
return -1;
}
};
#endif //ANIMTESTBED_ANIMGRAPHBLENDTREE_H

View File

@ -1,616 +0,0 @@
//
// Created by martin on 04.02.22.
//
#include <cstring>
#include <fstream>
#include "3rdparty/json/json.hpp"
#include "AnimGraphBlendTreeResource.h"
#include "AnimGraphNodes.h"
using json = nlohmann::json;
//
// Socket <-> json
//
std::string sSocketTypeToStr(SocketType pin_type) {
if (pin_type < SocketType::SocketTypeUndefined
|| pin_type >= SocketType::SocketTypeLast) {
return "Unknown";
}
return SocketTypeNames[static_cast<int>(pin_type)];
}
json sSocketToJson(const Socket& socket) {
json result;
result["name"] = socket.m_name;
result["type"] = sSocketTypeToStr(socket.m_type);
if (socket.m_type == SocketType::SocketTypeString
&& !socket.m_value_string.empty()) {
result["value"] = socket.m_value_string;
} else if (socket.m_value.flag) {
if (socket.m_type == SocketType::SocketTypeBool) {
result["value"] = socket.m_value.flag;
} else if (socket.m_type == SocketType::SocketTypeAnimation) {
} else if (socket.m_type == SocketType::SocketTypeInt) {
result["value"] = socket.m_value.int_value;
} else if (socket.m_type == SocketType::SocketTypeFloat) {
result["value"] = socket.m_value.float_value;
} else if (socket.m_type == SocketType::SocketTypeVec3) {
result["value"][0] = socket.m_value.vec3.v[0];
result["value"][1] = socket.m_value.vec3.v[1];
result["value"][2] = socket.m_value.vec3.v[2];
} else if (socket.m_type == SocketType::SocketTypeQuat) {
result["value"][0] = socket.m_value.quat.v[0];
result["value"][1] = socket.m_value.quat.v[1];
result["value"][2] = socket.m_value.quat.v[2];
result["value"][3] = socket.m_value.quat.v[3];
} else {
std::cerr << "Invalid socket type '" << static_cast<int>(socket.m_type)
<< "'." << std::endl;
}
}
return result;
}
Socket sJsonToSocket(const json& json_data) {
Socket result;
result.m_type = SocketType::SocketTypeUndefined;
result.m_reference.ptr = &result.m_value.int_value;
result.m_name = json_data["name"];
std::string type_string = json_data["type"];
bool have_value = json_data.contains("value");
if (type_string == "Bool") {
result.m_type = SocketType::SocketTypeBool;
result.m_type_size = sizeof(bool);
result.m_reference.ptr = &result.m_value.int_value;
if (have_value) {
result.m_value.flag = json_data["value"];
}
} else if (type_string == "Animation") {
result.m_type = SocketType::SocketTypeAnimation;
result.m_type_size = sizeof(AnimData);
} else if (type_string == "Int") {
result.m_type = SocketType::SocketTypeInt;
result.m_type_size = sizeof(int);
if (have_value) {
result.m_value.int_value = json_data["value"];
}
} else if (type_string == "Float") {
result.m_type = SocketType::SocketTypeFloat;
result.m_type_size = sizeof(float);
if (have_value) {
result.m_value.float_value = json_data["value"];
}
} else if (type_string == "Vec3") {
result.m_type = SocketType::SocketTypeVec3;
result.m_type_size = sizeof(Vec3);
if (have_value) {
result.m_value.vec3.x = json_data["value"][0];
result.m_value.vec3.y = json_data["value"][1];
result.m_value.vec3.z = json_data["value"][2];
}
} else if (type_string == "Quat") {
result.m_type = SocketType::SocketTypeQuat;
result.m_type_size = sizeof(Quat);
if (have_value) {
result.m_value.quat.x = json_data["value"][0];
result.m_value.quat.y = json_data["value"][1];
result.m_value.quat.z = json_data["value"][2];
result.m_value.quat.w = json_data["value"][3];
}
} else if (type_string == "String") {
result.m_type = SocketType::SocketTypeString;
result.m_type_size = sizeof(std::string);
if (have_value) {
result.m_value_string = json_data["value"];
}
} else {
std::cerr << "Invalid socket type '" << type_string << "'." << std::endl;
}
return result;
}
//
// AnimGraphNode <-> json
//
json sAnimGraphNodeToJson(
const AnimNodeResource& node,
size_t node_index,
const std::vector<AnimGraphConnectionResource>& connections) {
json result;
result["name"] = node.m_name;
result["type"] = "AnimNodeResource";
result["node_type"] = node.m_type_name;
for (size_t j = 0; j < 2; j++) {
result["position"][j] = node.m_position[j];
}
for (const auto & socket : node.m_socket_accessor->m_inputs) {
if (socket.m_type == SocketType::SocketTypeAnimation) {
continue;
}
bool socket_connected = false;
for (const auto & connection : connections) {
if (connection.source_node_index == node_index
&& connection.source_socket_name == socket.m_name) {
socket_connected = true;
break;
}
}
if (!socket_connected) {
result["inputs"].push_back(sSocketToJson(socket));
}
}
for (auto & property : node.m_socket_accessor->m_properties) {
result["properties"][property.m_name] = sSocketToJson(property);
}
return result;
}
AnimNodeResource sAnimGraphNodeFromJson(const json& json_node, size_t node_index) {
AnimNodeResource result;
result.m_name = json_node["name"];
result.m_type_name = json_node["node_type"];
result.m_position[0] = json_node["position"][0];
result.m_position[1] = json_node["position"][1];
result.m_anim_node = AnimNodeFactory(result.m_type_name);
result.m_socket_accessor =
AnimNodeDescriptorFactory(result.m_type_name, result.m_anim_node);
for (auto & property : result.m_socket_accessor->m_properties) {
property = sJsonToSocket(json_node["properties"][property.m_name]);
}
if (node_index != 0 && node_index != 1 && json_node.contains("inputs")) {
for (size_t j = 0, n = json_node["inputs"].size(); j < n; j++) {
assert(json_node["inputs"][j].contains("name"));
std::string input_name = json_node["inputs"][j]["name"];
Socket* input_socket =
result.m_socket_accessor->GetInputSocket(input_name.c_str());
if (input_socket == nullptr) {
std::cerr << "Could not find input socket with name " << input_name
<< " for node type " << result.m_type_name << std::endl;
abort();
}
*input_socket = sJsonToSocket(json_node["inputs"][j]);
}
}
return result;
}
//
// AnimGraphConnectionResource <-> Json
//
json sAnimGraphConnectionToJson(
const AnimGraphConnectionResource& connection) {
json result;
result["type"] = "AnimGraphConnectionResource";
result["source_node_index"] = connection.source_node_index;
result["source_socket_name"] = connection.source_socket_name;
result["target_node_index"] = connection.target_node_index;
result["target_socket_name"] = connection.target_socket_name;
return result;
}
AnimGraphConnectionResource sAnimGraphConnectionFromJson(
const json& json_node) {
AnimGraphConnectionResource connection;
connection.source_node_index = json_node["source_node_index"];
connection.source_socket_name = json_node["source_socket_name"];
connection.target_node_index = json_node["target_node_index"];
connection.target_socket_name = json_node["target_socket_name"];
return connection;
}
void AnimGraphBlendTreeResource::clear() {
m_name = "";
clearNodes();
m_connections.clear();
initGraphConnectors();
}
void AnimGraphBlendTreeResource::clearNodes() {
for (auto & m_node : m_nodes) {
delete m_node.m_socket_accessor;
m_node.m_socket_accessor = nullptr;
delete m_node.m_anim_node;
m_node.m_anim_node = nullptr;
}
m_nodes.clear();
}
void AnimGraphBlendTreeResource::initGraphConnectors() {
m_nodes.push_back(AnimNodeResourceFactory("BlendTree"));
m_nodes[0].m_name = "Outputs";
m_nodes.push_back(AnimNodeResourceFactory("BlendTree"));
m_nodes[1].m_name = "Inputs";
}
bool AnimGraphBlendTreeResource::saveToFile(const char* filename) const {
json result;
result["name"] = m_name;
result["type"] = "AnimGraphResource";
for (size_t i = 0; i < m_nodes.size(); i++) {
const AnimNodeResource& node = m_nodes[i];
result["nodes"][i] = sAnimGraphNodeToJson(node, i, m_connections);
}
for (size_t i = 0; i < m_connections.size(); i++) {
const AnimGraphConnectionResource& connection = m_connections[i];
result["connections"][i] = sAnimGraphConnectionToJson(connection);
}
// Graph inputs and outputs
{
const AnimNodeResource& graph_output_node = m_nodes[0];
const std::vector<Socket> graph_inputs =
graph_output_node.m_socket_accessor->m_inputs;
for (size_t i = 0; i < graph_inputs.size(); i++) {
result["nodes"][0]["inputs"][i] = sSocketToJson(graph_inputs[i]);
}
const AnimNodeResource& graph_input_node = m_nodes[1];
const std::vector<Socket> graph_outputs =
graph_input_node.m_socket_accessor->m_outputs;
for (size_t i = 0; i < graph_outputs.size(); i++) {
result["nodes"][1]["outputs"][i] = sSocketToJson(graph_outputs[i]);
}
}
std::ofstream output_file;
output_file.open(filename);
output_file << result.dump(4, ' ') << std::endl;
output_file.close();
return true;
}
bool AnimGraphBlendTreeResource::loadFromFile(const char* filename) {
std::ifstream input_file;
input_file.open(filename);
std::stringstream buffer;
buffer << input_file.rdbuf();
json json_data = json::parse(buffer.str(), nullptr, false);
if (json_data.is_discarded()) {
std::cerr << "Error parsing json of file '" << filename << "'."
<< std::endl;
}
if (json_data["type"] != "AnimGraphResource") {
std::cerr
<< "Invalid json object. Expected type 'AnimGraphResource' but got '"
<< json_data["type"] << "'." << std::endl;
}
clear();
clearNodes();
m_name = json_data["name"];
// Load nodes
for (size_t i = 0, n = json_data["nodes"].size(); i < n; i++) {
const json& json_node = json_data["nodes"][i];
if (json_node["type"] != "AnimNodeResource") {
std::cerr
<< "Invalid json object. Expected type 'AnimNodeResource' but got '"
<< json_node["type"] << "'." << std::endl;
return false;
}
AnimNodeResource node = sAnimGraphNodeFromJson(json_node, i);
m_nodes.push_back(node);
}
// Setup graph inputs and outputs
const json& graph_outputs = json_data["nodes"][0]["inputs"];
for (const auto & graph_output : graph_outputs) {
AnimNodeResource& graph_node = m_nodes[0];
graph_node.m_socket_accessor->m_inputs.push_back(
sJsonToSocket(graph_output));
}
const json& graph_inputs = json_data["nodes"][1]["outputs"];
for (const auto & graph_input : graph_inputs) {
AnimNodeResource& graph_node = m_nodes[1];
graph_node.m_socket_accessor->m_outputs.push_back(
sJsonToSocket(graph_input));
}
// Load connections
for (const auto & json_connection : json_data["connections"]) {
if (json_connection["type"] != "AnimGraphConnectionResource") {
std::cerr
<< "Invalid json object. Expected type 'AnimGraphConnectionResource' "
"but got '"
<< json_connection["type"] << "'." << std::endl;
return false;
}
AnimGraphConnectionResource connection =
sAnimGraphConnectionFromJson(json_connection);
m_connections.push_back(connection);
}
return true;
}
void AnimGraphBlendTreeResource::createRuntimeNodeInstances(AnimGraph& instance) const {
for (int i = 0; i < m_nodes.size(); i++) {
const AnimNodeResource& node_resource = m_nodes[i];
AnimNode* node = AnimNodeFactory(node_resource.m_type_name);
node->m_name = node_resource.m_name;
node->m_node_type_name = node_resource.m_type_name;
node->m_index = i;
instance.m_nodes.push_back(node);
// runtime node connections
instance.m_node_input_connections.emplace_back();
instance.m_node_output_connections.emplace_back();
}
}
void AnimGraphBlendTreeResource::prepareGraphIOData(AnimGraph& instance) const {
instance.m_node_descriptor =
AnimNodeDescriptorFactory("BlendTree", instance.m_nodes[0]);
instance.m_node_descriptor->m_outputs =
m_nodes[1].m_socket_accessor->m_outputs;
instance.m_node_descriptor->m_inputs = m_nodes[0].m_socket_accessor->m_inputs;
//
// graph inputs
//
int input_block_size = 0;
std::vector<Socket>& graph_inputs = instance.getGraphInputs();
for (int i = 0; i < graph_inputs.size(); i++) {
input_block_size += sizeof(void*);
}
if (input_block_size > 0) {
instance.m_input_buffer = new char[input_block_size];
memset(instance.m_input_buffer, 0, input_block_size);
}
int input_block_offset = 0;
for (int i = 0; i < graph_inputs.size(); i++) {
graph_inputs[i].m_reference.ptr =
(void*)&instance.m_input_buffer[input_block_offset];
instance.m_node_descriptor->m_outputs[i].m_reference.ptr =
&instance.m_input_buffer[input_block_offset];
input_block_offset += sizeof(void*);
}
//
// graph outputs
//
int output_block_size = 0;
std::vector<Socket>& graph_outputs = instance.getGraphOutputs();
for (int i = 0; i < graph_outputs.size(); i++) {
output_block_size += sizeof(void*);
}
if (output_block_size > 0) {
instance.m_output_buffer = new char[output_block_size];
memset(instance.m_output_buffer, 0, output_block_size);
}
int output_block_offset = 0;
for (int i = 0; i < graph_outputs.size(); i++) {
instance.m_node_descriptor->m_inputs[i].m_reference.ptr =
&instance.m_output_buffer[output_block_offset];
output_block_offset += sizeof(void*);
}
// connections: make source and target sockets point to the same address in the connection data storage.
// TODO: instead of every connection, only create data blocks for the source sockets and make sure every source socket gets allocated once.
size_t connection_data_storage_size = 0;
for (const auto & connection : m_connections) {
const AnimNodeResource& source_node = m_nodes[connection.source_node_index];
Socket* source_socket = source_node.m_socket_accessor->GetOutputSocket(
connection.source_socket_name.c_str());
connection_data_storage_size += source_socket->m_type_size;
}
if (connection_data_storage_size > 0) {
instance.m_connection_data_storage = new char[connection_data_storage_size];
memset(instance.m_connection_data_storage, 0, connection_data_storage_size);
}
std::vector<NodeDescriptorBase*> instance_node_descriptors(
m_nodes.size(),
nullptr);
for (int i = 0; i < m_nodes.size(); i++) {
instance_node_descriptors[i] = AnimNodeDescriptorFactory(
m_nodes[i].m_type_name,
instance.m_nodes[i]);
}
instance_node_descriptors[0]->m_inputs = instance.m_node_descriptor->m_inputs;
instance_node_descriptors[1]->m_outputs =
instance.m_node_descriptor->m_outputs;
size_t connection_data_offset = 0;
for (const auto & connection : m_connections) {
NodeDescriptorBase* source_node_descriptor =
instance_node_descriptors[connection.source_node_index];
NodeDescriptorBase* target_node_descriptor =
instance_node_descriptors[connection.target_node_index];
AnimNode* source_node = instance.m_nodes[connection.source_node_index];
AnimNode* target_node = instance.m_nodes[connection.target_node_index];
Socket* source_socket = source_node_descriptor->GetOutputSocket(
connection.source_socket_name.c_str());
Socket* target_socket = target_node_descriptor->GetInputSocket(
connection.target_socket_name.c_str());
AnimGraphConnection instance_connection;
instance_connection.m_source_node = source_node;
instance_connection.m_source_socket = *source_socket;
instance_connection.m_target_node = target_node;
instance_connection.m_target_socket = *target_socket;
instance.m_node_input_connections[connection.target_node_index].push_back(
instance_connection);
instance.m_node_output_connections[connection.source_node_index].push_back(
instance_connection);
source_node_descriptor->SetOutputUnchecked(
connection.source_socket_name.c_str(),
&instance.m_connection_data_storage[connection_data_offset]);
target_node_descriptor->SetInputUnchecked(
connection.target_socket_name.c_str(),
&instance.m_connection_data_storage[connection_data_offset]);
if (source_socket->m_type == SocketType::SocketTypeAnimation) {
instance.m_animdata_blocks.push_back(
(AnimData*)(&instance
.m_connection_data_storage[connection_data_offset]));
}
connection_data_offset += source_socket->m_type_size;
}
//
// const node inputs
//
std::vector<Socket*> const_inputs =
getConstNodeInputs( instance_node_descriptors);
size_t const_node_inputs_buffer_size = 0;
for (auto & const_input : const_inputs) {
if (const_input->m_type == SocketType::SocketTypeString) {
// TODO: implement string const node input support
std::cerr << "Error: const inputs for strings not yet implemented!"
<< std::endl;
abort();
}
const_node_inputs_buffer_size += const_input->m_type_size;
}
if (const_node_inputs_buffer_size > 0) {
instance.m_const_node_inputs = new char[const_node_inputs_buffer_size];
memset(instance.m_const_node_inputs, '\0', const_node_inputs_buffer_size);
}
size_t const_input_buffer_offset = 0;
for (auto & i : const_inputs) {
Socket* const_input = i;
// TODO: implement string const node input support
assert(const_input->m_type != SocketType::SocketTypeString);
*const_input->m_reference.ptr_ptr =
&instance.m_const_node_inputs[const_input_buffer_offset];
memcpy (*const_input->m_reference.ptr_ptr, &const_input->m_value, i->m_type_size);
const_input_buffer_offset += i->m_type_size;
}
for (int i = 0; i < m_nodes.size(); i++) {
delete instance_node_descriptors[i];
}
}
void AnimGraphBlendTreeResource::setRuntimeNodeProperties(AnimGraph& instance) const {
for (int i = 2; i < m_nodes.size(); i++) {
const AnimNodeResource& node_resource = m_nodes[i];
NodeDescriptorBase* node_instance_accessor = AnimNodeDescriptorFactory(
node_resource.m_type_name,
instance.m_nodes[i]);
std::vector<Socket>& resource_properties =
node_resource.m_socket_accessor->m_properties;
for (const auto & property : resource_properties) {
const std::string& name = property.m_name;
switch (property.m_type) {
case SocketType::SocketTypeBool:
node_instance_accessor->SetProperty(
name.c_str(),
property.m_value.flag);
break;
case SocketType::SocketTypeInt:
node_instance_accessor->SetProperty(
name.c_str(),
property.m_value.int_value);
break;
case SocketType::SocketTypeFloat:
node_instance_accessor->SetProperty(
name.c_str(),
property.m_value.float_value);
break;
case SocketType::SocketTypeVec3:
node_instance_accessor->SetProperty<Vec3>(
name.c_str(),
property.m_value.vec3);
break;
case SocketType::SocketTypeQuat:
node_instance_accessor->SetProperty(
name.c_str(),
property.m_value.quat);
break;
case SocketType::SocketTypeString:
node_instance_accessor->SetProperty(
name.c_str(),
property.m_value_string);
break;
default:
std::cerr << "Invalid socket type "
<< static_cast<int>(property.m_type) << std::endl;
}
}
delete node_instance_accessor;
}
}
std::vector<Socket*> AnimGraphBlendTreeResource::getConstNodeInputs(
std::vector<NodeDescriptorBase*>& instance_node_descriptors) const {
std::vector<Socket*> result;
for (size_t i = 0; i < m_nodes.size(); i++) {
for (size_t j = 0, num_inputs = instance_node_descriptors[i]->m_inputs.size();
j < num_inputs;
j++) {
Socket& input = instance_node_descriptors[i]->m_inputs[j];
if (*input.m_reference.ptr_ptr == nullptr) {
memcpy(&input.m_value, &m_nodes[i].m_socket_accessor->m_inputs[j].m_value, sizeof(Socket::SocketValue));
result.push_back(&input);
}
}
}
return result;
}

View File

@ -1,138 +0,0 @@
//
// Created by martin on 04.02.22.
//
#ifndef ANIMTESTBED_ANIMGRAPHBLENDTREERESOURCE_H
#define ANIMTESTBED_ANIMGRAPHBLENDTREERESOURCE_H
#include <cstring>
#include <iostream>
#include <map>
#include <string>
#include <type_traits>
#include <vector>
#include "AnimGraph.h"
#include "AnimGraphData.h"
#include "AnimGraphNodes.h"
#include "SyncTrack.h"
struct AnimNode;
struct AnimNodeResource {
std::string m_name;
std::string m_type_name;
AnimNode* m_anim_node = nullptr;
NodeDescriptorBase* m_socket_accessor = nullptr;
float m_position[2] = {0.f, 0.f};
};
//
// AnimGraphResource
//
struct AnimGraphConnectionResource {
size_t source_node_index = -1;
std::string source_socket_name;
size_t target_node_index = -1;
std::string target_socket_name;
};
struct AnimGraphBlendTreeResource {
std::string m_name;
std::vector<AnimNodeResource> m_nodes;
std::vector<AnimGraphConnectionResource> m_connections;
~AnimGraphBlendTreeResource() {
for (auto & m_node : m_nodes) {
delete m_node.m_anim_node;
delete m_node.m_socket_accessor;
}
}
AnimGraphBlendTreeResource() { clear(); }
void clear();
void clearNodes();
void initGraphConnectors();
bool saveToFile(const char* filename) const;
bool loadFromFile(const char* filename);
AnimNodeResource& getGraphOutputNode() { return m_nodes[0]; }
AnimNodeResource& getGraphInputNode() { return m_nodes[1]; }
size_t getNodeIndex(const AnimNodeResource& node_resource) const {
for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
if (&m_nodes[i] == &node_resource) {
return i;
}
}
return -1;
}
size_t addNode(const AnimNodeResource &node_resource) {
m_nodes.push_back(node_resource);
return m_nodes.size() - 1;
}
bool connectSockets(
const AnimNodeResource& source_node,
const std::string& source_socket_name,
const AnimNodeResource& target_node,
const std::string& target_socket_name) {
size_t source_node_index = getNodeIndex(source_node);
size_t target_node_index = getNodeIndex(target_node);
if (source_node_index >= m_nodes.size()
|| target_node_index >= m_nodes.size()) {
std::cerr << "Cannot connect nodes: could not find nodes." << std::endl;
return false;
}
Socket* source_socket =
source_node.m_socket_accessor->GetOutputSocket(source_socket_name.c_str());
Socket* target_socket =
target_node.m_socket_accessor->GetInputSocket(target_socket_name.c_str());
if (source_socket == nullptr || target_socket == nullptr) {
std::cerr << "Cannot connect nodes: could not find sockets." << std::endl;
return false;
}
AnimGraphConnectionResource connection;
connection.source_node_index = source_node_index;
connection.source_socket_name = source_socket_name;
connection.target_node_index = target_node_index;
connection.target_socket_name = target_socket_name;
m_connections.push_back(connection);
return true;
}
bool isSocketConnected(
const AnimNodeResource& node,
const std::string& socket_name) {
size_t node_index = getNodeIndex(node);
for (const auto & connection : m_connections) {
if ((connection.source_node_index == node_index
&& connection.source_socket_name == socket_name)
|| ((connection.target_node_index == node_index)
&& connection.target_socket_name == socket_name)) {
return true;
}
}
return false;
}
void createInstance(AnimGraph& result) const;
void createRuntimeNodeInstances(AnimGraph& instance) const;
void prepareGraphIOData(AnimGraph& instance) const;
void setRuntimeNodeProperties(AnimGraph& instance) const;
std::vector<Socket*> getConstNodeInputs(std::vector<NodeDescriptorBase*>& instance_node_descriptors) const;
};
#endif //ANIMTESTBED_ANIMGRAPHBLENDTREERESOURCE_H

View File

@ -22,8 +22,8 @@
// //
// Data types // Data types
// //
struct AnimGraph; struct AnimGraph;
struct AnimNode;
struct AnimData { struct AnimData {
ozz::vector<ozz::math::SoaTransform> m_local_matrices; ozz::vector<ozz::math::SoaTransform> m_local_matrices;
@ -260,14 +260,6 @@ SocketType GetSocketType() {
return SocketType::SocketTypeUndefined; return SocketType::SocketTypeUndefined;
} }
struct AnimGraphConnection {
AnimNode* m_source_node = nullptr;
Socket m_source_socket;
AnimNode* m_target_node = nullptr;
Socket m_target_socket;
};
struct NodeDescriptorBase { struct NodeDescriptorBase {
std::vector<Socket> m_inputs; std::vector<Socket> m_inputs;
std::vector<Socket> m_outputs; std::vector<Socket> m_outputs;

View File

@ -7,14 +7,13 @@
#include <sstream> #include <sstream>
#include "3rdparty/imgui-node-editor/imgui_node_editor.h" #include "3rdparty/imgui-node-editor/imgui_node_editor.h"
#include "AnimGraphBlendTreeResource.h" #include "AnimGraphResource.h"
#include "SkinnedMesh.h" #include "SkinnedMesh.h"
#include "imgui.h" #include "imgui.h"
#include "imnodes.h" #include "imnodes.h"
#include "misc/cpp/imgui_stdlib.h" #include "misc/cpp/imgui_stdlib.h"
static AnimGraphBlendTreeResource sGraphGresource = static AnimGraphResource sGraphGresource = AnimGraphResource();
AnimGraphBlendTreeResource();
static bool sGraphLoadedThisFrame = false; static bool sGraphLoadedThisFrame = false;
ImNodesPinShape sGetSocketShapeFromSocketType(const SocketType& socket_type) { ImNodesPinShape sGetSocketShapeFromSocketType(const SocketType& socket_type) {
@ -59,7 +58,7 @@ void NodeSocketEditor(Socket& socket) {
} }
void RemoveConnectionsForSocket( void RemoveConnectionsForSocket(
AnimGraphBlendTreeResource& graph_resource, AnimGraphResource& graph_resource,
AnimNodeResource& node_resource, AnimNodeResource& node_resource,
Socket& socket) { Socket& socket) {
std::vector<AnimGraphConnectionResource>::iterator iter = std::vector<AnimGraphConnectionResource>::iterator iter =
@ -160,7 +159,7 @@ void SkinnedMeshWidget(SkinnedMesh* skinned_mesh) {
} }
void AnimGraphEditorRenderSidebar( void AnimGraphEditorRenderSidebar(
AnimGraphBlendTreeResource& graph_resource, AnimGraphResource& graph_resource,
AnimNodeResource& node_resource) { AnimNodeResource& node_resource) {
ImGui::Text("[%s]", node_resource.m_type_name.c_str()); ImGui::Text("[%s]", node_resource.m_type_name.c_str());

View File

@ -7,13 +7,72 @@
#include <vector> #include <vector>
#include "AnimNode.h"
#include "AnimGraphData.h" #include "AnimGraphData.h"
#include "SyncTrack.h" #include "SyncTrack.h"
#include "ozz/animation/runtime/sampling_job.h" #include "ozz/animation/runtime/sampling_job.h"
struct AnimNode; struct AnimNode;
enum class AnimNodeEvalState {
Undefined,
Deactivated,
Activated,
SyncTrackUpdated,
TimeUpdated,
Evaluated
};
struct AnimGraphConnection {
AnimNode* m_source_node = nullptr;
Socket m_source_socket;
AnimNode* m_target_node = nullptr;
Socket m_target_socket;
};
struct AnimNode {
std::string m_name;
std::string m_node_type_name;
float m_time_now = 0.f;
float m_time_last = 0.f;
size_t m_index = -1;
AnimNodeEvalState m_state = AnimNodeEvalState::Undefined;
SyncTrack m_sync_track;
virtual ~AnimNode() = default;
virtual bool Init(AnimGraphContext& context) { return true; };
virtual void MarkActiveInputs(const std::vector<AnimGraphConnection>& inputs) {
for (const auto & input : inputs) {
AnimNode* input_node = input.m_source_node;
if (input_node != nullptr) {
input_node->m_state = AnimNodeEvalState::Activated;
}
}
}
virtual void CalcSyncTrack(const std::vector<AnimGraphConnection>& inputs) {
for (const auto & input : inputs) {
AnimNode* input_node = input.m_source_node;
if (input_node != nullptr
&& input.m_source_socket.m_type == SocketType::SocketTypeAnimation
&& input_node->m_state != AnimNodeEvalState::Deactivated) {
m_sync_track = input_node->m_sync_track;
return;
}
}
}
virtual void UpdateTime(float time_last, float time_now) {
m_time_last = time_last;
m_time_now = time_now;
m_state = AnimNodeEvalState::TimeUpdated;
}
virtual void Evaluate(AnimGraphContext& context){};
};
// //
// BlendTreeNode // BlendTreeNode
// //
@ -34,8 +93,8 @@ struct Blend2Node : public AnimNode {
float* i_blend_weight = nullptr; float* i_blend_weight = nullptr;
bool m_sync_blend = false; bool m_sync_blend = false;
virtual void MarkActiveInputs() override { virtual void MarkActiveInputs(const std::vector<AnimGraphConnection>& inputs) override {
for (const auto & input : m_inputs) { for (const auto & input : inputs) {
AnimNode* input_node = input.m_source_node; AnimNode* input_node = input.m_source_node;
if (input_node == nullptr) { if (input_node == nullptr) {
continue; continue;

View File

@ -1,5 +1,5 @@
// //
// Created by martin on 17.03.24. // Created by martin on 04.02.22.
// //
#include "AnimGraphResource.h" #include "AnimGraphResource.h"
@ -9,9 +9,6 @@
#include "3rdparty/json/json.hpp" #include "3rdparty/json/json.hpp"
#include "AnimGraphBlendTree.h"
#include "AnimGraphNodes.h"
using json = nlohmann::json; using json = nlohmann::json;
// //
@ -127,7 +124,7 @@ Socket sJsonToSocket(const json& json_data) {
json sAnimGraphNodeToJson( json sAnimGraphNodeToJson(
const AnimNodeResource& node, const AnimNodeResource& node,
size_t node_index, size_t node_index,
const std::vector<BlendTreeConnectionResource>& connections) { const std::vector<AnimGraphConnectionResource>& connections) {
json result; json result;
result["name"] = node.m_name; result["name"] = node.m_name;
@ -164,9 +161,7 @@ json sAnimGraphNodeToJson(
return result; return result;
} }
AnimNodeResource sAnimGraphNodeFromJson( AnimNodeResource sAnimGraphNodeFromJson(const json& json_node, size_t node_index) {
const json& json_node,
size_t node_index) {
AnimNodeResource result; AnimNodeResource result;
result.m_name = json_node["name"]; result.m_name = json_node["name"];
@ -203,7 +198,8 @@ AnimNodeResource sAnimGraphNodeFromJson(
// //
// AnimGraphConnectionResource <-> Json // AnimGraphConnectionResource <-> Json
// //
json sAnimGraphConnectionToJson(const BlendTreeConnectionResource& connection) { json sAnimGraphConnectionToJson(
const AnimGraphConnectionResource& connection) {
json result; json result;
result["type"] = "AnimGraphConnectionResource"; result["type"] = "AnimGraphConnectionResource";
@ -217,9 +213,9 @@ json sAnimGraphConnectionToJson(const BlendTreeConnectionResource& connection) {
return result; return result;
} }
BlendTreeConnectionResource sAnimGraphConnectionFromJson( AnimGraphConnectionResource sAnimGraphConnectionFromJson(
const json& json_node) { const json& json_node) {
BlendTreeConnectionResource connection; AnimGraphConnectionResource connection;
connection.source_node_index = json_node["source_node_index"]; connection.source_node_index = json_node["source_node_index"];
connection.source_socket_name = json_node["source_socket_name"]; connection.source_socket_name = json_node["source_socket_name"];
@ -230,136 +226,58 @@ BlendTreeConnectionResource sAnimGraphConnectionFromJson(
return connection; return connection;
} }
bool AnimGraphResource::LoadFromFile(const char* filename) { void AnimGraphResource::clear() {
std::ifstream input_file; m_name = "";
input_file.open(filename);
std::stringstream buffer;
buffer << input_file.rdbuf();
json json_data = json::parse(buffer.str(), nullptr, false); clearNodes();
if (json_data.is_discarded()) { m_connections.clear();
std::cerr << "Error parsing json of file '" << filename << "'."
<< std::endl;
return false; initGraphConnectors();
} }
if (json_data["type"] != "AnimNodeResource") { void AnimGraphResource::clearNodes() {
std::cerr for (auto & m_node : m_nodes) {
<< "Invalid json object. Expected type 'AnimNodeResource' but got '" delete m_node.m_socket_accessor;
<< json_data["type"] << "'." << std::endl; m_node.m_socket_accessor = nullptr;
delete m_node.m_anim_node;
return false; m_node.m_anim_node = nullptr;
}
m_nodes.clear();
} }
if (json_data["node_type"] == "BlendTree") { void AnimGraphResource::initGraphConnectors() {
return LoadBlendTreeResourceFromJson(json_data); m_nodes.push_back(AnimNodeResourceFactory("BlendTree"));
} else if (json_data["node_type"] == "StateMachine") { m_nodes[0].m_name = "Outputs";
return LoadStateMachineResourceFromJson(json_data); m_nodes.push_back(AnimNodeResourceFactory("BlendTree"));
m_nodes[1].m_name = "Inputs";
} }
std::cerr << "Invalid node_type. Expected type 'BlendTree' or " bool AnimGraphResource::saveToFile(const char* filename) const {
"'StateMachine' but got '"
<< json_data["node_type"] << "'." << std::endl;
return false;
}
bool AnimGraphResource::LoadBlendTreeResourceFromJson(nlohmann::json const& json_data) {
m_blend_tree_resource.Reset();
m_type = "BlendTree";
m_name = json_data["name"];
// Load nodes
for (size_t i = 0, n = json_data["nodes"].size(); i < n; i++) {
const json& json_node = json_data["nodes"][i];
if (json_node["type"] != "AnimNodeResource") {
std::cerr
<< "Invalid json object. Expected type 'AnimNodeResource' but got '"
<< json_node["type"] << "'." << std::endl;
return false;
}
AnimNodeResource node = sAnimGraphNodeFromJson(json_node, i);
m_blend_tree_resource.m_nodes.push_back(node);
}
// Setup graph inputs and outputs
const json& graph_outputs = json_data["nodes"][0]["inputs"];
for (const auto& graph_output : graph_outputs) {
AnimNodeResource& graph_node = m_blend_tree_resource.m_nodes[0];
graph_node.m_socket_accessor->m_inputs.push_back(
sJsonToSocket(graph_output));
}
const json& graph_inputs = json_data["nodes"][1]["outputs"];
for (const auto& graph_input : graph_inputs) {
AnimNodeResource& graph_node = m_blend_tree_resource.m_nodes[1];
graph_node.m_socket_accessor->m_outputs.push_back(
sJsonToSocket(graph_input));
}
// Load connections
for (const auto& json_connection : json_data["connections"]) {
if (json_connection["type"] != "AnimGraphConnectionResource") {
std::cerr << "Invalid json object. Expected type "
"'AnimGraphConnectionResource' "
"but got '"
<< json_connection["type"] << "'." << std::endl;
return false;
}
BlendTreeConnectionResource connection =
sAnimGraphConnectionFromJson(json_connection);
m_blend_tree_resource.m_connections.push_back(connection);
}
return true;
}
bool AnimGraphResource::SaveToFile(const char* filename) const {
if (m_type == "BlendTree") {
return SaveBlendTreeResourceToFile(filename);
} else if (m_type == "StateMachine") {
return SaveStateMachineResourceToFile(filename);
}
std::cerr << "Invalid AnimGraphResource type: " << m_type << "." << std::endl;
return false;
}
bool AnimGraphResource::SaveBlendTreeResourceToFile(
const char* filename) const {
json result; json result;
result["name"] = m_name; result["name"] = m_name;
result["type"] = "AnimNodeResource"; result["type"] = "AnimGraphResource";
result["node_type"] = "BlendTree";
for (size_t i = 0; i < m_blend_tree_resource.m_nodes.size(); i++) { for (size_t i = 0; i < m_nodes.size(); i++) {
const AnimNodeResource& node = m_blend_tree_resource.m_nodes[i]; const AnimNodeResource& node = m_nodes[i];
result["nodes"][i] = result["nodes"][i] = sAnimGraphNodeToJson(node, i, m_connections);
sAnimGraphNodeToJson(node, i, m_blend_tree_resource.m_connections);
} }
for (size_t i = 0; i < m_blend_tree_resource.m_connections.size(); i++) { for (size_t i = 0; i < m_connections.size(); i++) {
const BlendTreeConnectionResource& connection = const AnimGraphConnectionResource& connection = m_connections[i];
m_blend_tree_resource.m_connections[i];
result["connections"][i] = sAnimGraphConnectionToJson(connection); result["connections"][i] = sAnimGraphConnectionToJson(connection);
} }
// Graph inputs and outputs // Graph inputs and outputs
{ {
const AnimNodeResource& graph_output_node = const AnimNodeResource& graph_output_node = m_nodes[0];
m_blend_tree_resource.m_nodes[0];
const std::vector<Socket> graph_inputs = const std::vector<Socket> graph_inputs =
graph_output_node.m_socket_accessor->m_inputs; graph_output_node.m_socket_accessor->m_inputs;
for (size_t i = 0; i < graph_inputs.size(); i++) { for (size_t i = 0; i < graph_inputs.size(); i++) {
result["nodes"][0]["inputs"][i] = sSocketToJson(graph_inputs[i]); result["nodes"][0]["inputs"][i] = sSocketToJson(graph_inputs[i]);
} }
const AnimNodeResource& graph_input_node = m_blend_tree_resource.m_nodes[1]; const AnimNodeResource& graph_input_node = m_nodes[1];
const std::vector<Socket> graph_outputs = const std::vector<Socket> graph_outputs =
graph_input_node.m_socket_accessor->m_outputs; graph_input_node.m_socket_accessor->m_outputs;
for (size_t i = 0; i < graph_outputs.size(); i++) { for (size_t i = 0; i < graph_outputs.size(); i++) {
@ -375,45 +293,107 @@ bool AnimGraphResource::SaveBlendTreeResourceToFile(
return true; return true;
} }
void AnimGraphResource::CreateBlendTreeInstance( bool AnimGraphResource::loadFromFile(const char* filename) {
AnimGraphBlendTree& result) const { std::ifstream input_file;
if (m_type != "BlendTree") { input_file.open(filename);
std::cerr << "Invalid AnimGraphResource. Expected type 'BlendTree' but got '" std::stringstream buffer;
<< m_type << "'." << std::endl; buffer << input_file.rdbuf();
return;
json json_data = json::parse(buffer.str(), nullptr, false);
if (json_data.is_discarded()) {
std::cerr << "Error parsing json of file '" << filename << "'."
<< std::endl;
} }
CreateBlendTreeRuntimeNodeInstances(result); if (json_data["type"] != "AnimGraphResource") {
PrepareBlendTreeIOData(result); std::cerr
SetRuntimeNodeProperties(result); << "Invalid json object. Expected type 'AnimGraphResource' but got '"
<< json_data["type"] << "'." << std::endl;
result.UpdateOrderedNodes();
result.ResetNodeStates();
} }
void AnimGraphResource::CreateBlendTreeRuntimeNodeInstances( clear();
AnimGraphBlendTree& result) const { clearNodes();
for (int i = 0; i < m_blend_tree_resource.m_nodes.size(); i++) {
const AnimNodeResource& node_resource = m_blend_tree_resource.m_nodes[i]; m_name = json_data["name"];
// Load nodes
for (size_t i = 0, n = json_data["nodes"].size(); i < n; i++) {
const json& json_node = json_data["nodes"][i];
if (json_node["type"] != "AnimNodeResource") {
std::cerr
<< "Invalid json object. Expected type 'AnimNodeResource' but got '"
<< json_node["type"] << "'." << std::endl;
return false;
}
AnimNodeResource node = sAnimGraphNodeFromJson(json_node, i);
m_nodes.push_back(node);
}
// Setup graph inputs and outputs
const json& graph_outputs = json_data["nodes"][0]["inputs"];
for (const auto & graph_output : graph_outputs) {
AnimNodeResource& graph_node = m_nodes[0];
graph_node.m_socket_accessor->m_inputs.push_back(
sJsonToSocket(graph_output));
}
const json& graph_inputs = json_data["nodes"][1]["outputs"];
for (const auto & graph_input : graph_inputs) {
AnimNodeResource& graph_node = m_nodes[1];
graph_node.m_socket_accessor->m_outputs.push_back(
sJsonToSocket(graph_input));
}
// Load connections
for (const auto & json_connection : json_data["connections"]) {
if (json_connection["type"] != "AnimGraphConnectionResource") {
std::cerr
<< "Invalid json object. Expected type 'AnimGraphConnectionResource' "
"but got '"
<< json_connection["type"] << "'." << std::endl;
return false;
}
AnimGraphConnectionResource connection =
sAnimGraphConnectionFromJson(json_connection);
m_connections.push_back(connection);
}
return true;
}
void AnimGraphResource::createInstance(AnimGraph& result) const {
createRuntimeNodeInstances(result);
prepareGraphIOData(result);
setRuntimeNodeProperties(result);
result.updateOrderedNodes();
result.resetNodeStates();
}
void AnimGraphResource::createRuntimeNodeInstances(AnimGraph& instance) const {
for (int i = 0; i < m_nodes.size(); i++) {
const AnimNodeResource& node_resource = m_nodes[i];
AnimNode* node = AnimNodeFactory(node_resource.m_type_name); AnimNode* node = AnimNodeFactory(node_resource.m_type_name);
node->m_name = node_resource.m_name; node->m_name = node_resource.m_name;
node->m_node_type_name = node_resource.m_type_name; node->m_node_type_name = node_resource.m_type_name;
result.m_nodes.push_back(node); node->m_index = i;
instance.m_nodes.push_back(node);
// runtime node connections // runtime node connections
result.m_node_input_connections.emplace_back(); instance.m_node_input_connections.emplace_back();
result.m_node_output_connections.emplace_back(); instance.m_node_output_connections.emplace_back();
} }
} }
void AnimGraphResource::PrepareBlendTreeIOData( void AnimGraphResource::prepareGraphIOData(AnimGraph& instance) const {
AnimGraphBlendTree& instance) const {
instance.m_node_descriptor = instance.m_node_descriptor =
AnimNodeDescriptorFactory("BlendTree", instance.m_nodes[0]); AnimNodeDescriptorFactory("BlendTree", instance.m_nodes[0]);
instance.m_node_descriptor->m_outputs = instance.m_node_descriptor->m_outputs =
m_blend_tree_resource.m_nodes[1].m_socket_accessor->m_outputs; m_nodes[1].m_socket_accessor->m_outputs;
instance.m_node_descriptor->m_inputs = instance.m_node_descriptor->m_inputs = m_nodes[0].m_socket_accessor->m_inputs;
m_blend_tree_resource.m_nodes[0].m_socket_accessor->m_inputs;
// //
// graph inputs // graph inputs
@ -462,9 +442,8 @@ void AnimGraphResource::PrepareBlendTreeIOData(
// connections: make source and target sockets point to the same address in the connection data storage. // connections: make source and target sockets point to the same address in the connection data storage.
// TODO: instead of every connection, only create data blocks for the source sockets and make sure every source socket gets allocated once. // TODO: instead of every connection, only create data blocks for the source sockets and make sure every source socket gets allocated once.
size_t connection_data_storage_size = 0; size_t connection_data_storage_size = 0;
for (const auto& connection : m_blend_tree_resource.m_connections) { for (const auto & connection : m_connections) {
const AnimNodeResource& source_node = const AnimNodeResource& source_node = m_nodes[connection.source_node_index];
m_blend_tree_resource.m_nodes[connection.source_node_index];
Socket* source_socket = source_node.m_socket_accessor->GetOutputSocket( Socket* source_socket = source_node.m_socket_accessor->GetOutputSocket(
connection.source_socket_name.c_str()); connection.source_socket_name.c_str());
connection_data_storage_size += source_socket->m_type_size; connection_data_storage_size += source_socket->m_type_size;
@ -476,11 +455,11 @@ void AnimGraphResource::PrepareBlendTreeIOData(
} }
std::vector<NodeDescriptorBase*> instance_node_descriptors( std::vector<NodeDescriptorBase*> instance_node_descriptors(
m_blend_tree_resource.m_nodes.size(), m_nodes.size(),
nullptr); nullptr);
for (int i = 0; i < m_blend_tree_resource.m_nodes.size(); i++) { for (int i = 0; i < m_nodes.size(); i++) {
instance_node_descriptors[i] = AnimNodeDescriptorFactory( instance_node_descriptors[i] = AnimNodeDescriptorFactory(
m_blend_tree_resource.m_nodes[i].m_type_name, m_nodes[i].m_type_name,
instance.m_nodes[i]); instance.m_nodes[i]);
} }
@ -489,7 +468,7 @@ void AnimGraphResource::PrepareBlendTreeIOData(
instance.m_node_descriptor->m_outputs; instance.m_node_descriptor->m_outputs;
size_t connection_data_offset = 0; size_t connection_data_offset = 0;
for (const auto& connection : m_blend_tree_resource.m_connections) { for (const auto & connection : m_connections) {
NodeDescriptorBase* source_node_descriptor = NodeDescriptorBase* source_node_descriptor =
instance_node_descriptors[connection.source_node_index]; instance_node_descriptors[connection.source_node_index];
NodeDescriptorBase* target_node_descriptor = NodeDescriptorBase* target_node_descriptor =
@ -534,7 +513,7 @@ void AnimGraphResource::PrepareBlendTreeIOData(
// const node inputs // const node inputs
// //
std::vector<Socket*> const_inputs = std::vector<Socket*> const_inputs =
m_blend_tree_resource.GetConstantNodeInputs(instance_node_descriptors); getConstNodeInputs( instance_node_descriptors);
size_t const_node_inputs_buffer_size = 0; size_t const_node_inputs_buffer_size = 0;
for (auto & const_input : const_inputs) { for (auto & const_input : const_inputs) {
if (const_input->m_type == SocketType::SocketTypeString) { if (const_input->m_type == SocketType::SocketTypeString) {
@ -560,26 +539,23 @@ void AnimGraphResource::PrepareBlendTreeIOData(
*const_input->m_reference.ptr_ptr = *const_input->m_reference.ptr_ptr =
&instance.m_const_node_inputs[const_input_buffer_offset]; &instance.m_const_node_inputs[const_input_buffer_offset];
memcpy( memcpy (*const_input->m_reference.ptr_ptr, &const_input->m_value, i->m_type_size);
*const_input->m_reference.ptr_ptr,
&const_input->m_value,
i->m_type_size);
const_input_buffer_offset += i->m_type_size; const_input_buffer_offset += i->m_type_size;
} }
for (int i = 0; i < m_blend_tree_resource.m_nodes.size(); i++) { for (int i = 0; i < m_nodes.size(); i++) {
delete instance_node_descriptors[i]; delete instance_node_descriptors[i];
} }
} }
void AnimGraphResource::SetRuntimeNodeProperties( void AnimGraphResource::setRuntimeNodeProperties(AnimGraph& instance) const {
AnimGraphBlendTree& result) const { for (int i = 2; i < m_nodes.size(); i++) {
for (int i = 2; i < m_blend_tree_resource.m_nodes.size(); i++) { const AnimNodeResource& node_resource = m_nodes[i];
const AnimNodeResource& node_resource = m_blend_tree_resource.m_nodes[i];
NodeDescriptorBase* node_instance_accessor = NodeDescriptorBase* node_instance_accessor = AnimNodeDescriptorFactory(
AnimNodeDescriptorFactory(node_resource.m_type_name, result.m_nodes[i]); node_resource.m_type_name,
instance.m_nodes[i]);
std::vector<Socket>& resource_properties = std::vector<Socket>& resource_properties =
node_resource.m_socket_accessor->m_properties; node_resource.m_socket_accessor->m_properties;
@ -627,15 +603,22 @@ void AnimGraphResource::SetRuntimeNodeProperties(
} }
} }
bool AnimGraphResource::SaveStateMachineResourceToFile( std::vector<Socket*> AnimGraphResource::getConstNodeInputs(
const char* filename) const { std::vector<NodeDescriptorBase*>& instance_node_descriptors) const {
assert(false && "Not yet implemented"); std::vector<Socket*> result;
return false; for (size_t i = 0; i < m_nodes.size(); i++) {
for (size_t j = 0, num_inputs = instance_node_descriptors[i]->m_inputs.size();
j < num_inputs;
j++) {
Socket& input = instance_node_descriptors[i]->m_inputs[j];
if (*input.m_reference.ptr_ptr == nullptr) {
memcpy(&input.m_value, &m_nodes[i].m_socket_accessor->m_inputs[j].m_value, sizeof(Socket::SocketValue));
result.push_back(&input);
}
}
} }
bool AnimGraphResource::LoadStateMachineResourceFromJson(nlohmann::json const& json_data) { return result;
assert(false && "Not yet implemented");
return false;
} }

View File

@ -1,17 +1,23 @@
// //
// Created by martin on 17.03.24. // Created by martin on 04.02.22.
// //
#ifndef ANIMTESTBED_ANIMGRAPHRESOURCE_H #ifndef ANIMTESTBED_ANIMGRAPHRESOURCE_H
#define ANIMTESTBED_ANIMGRAPHRESOURCE_H #define ANIMTESTBED_ANIMGRAPHRESOURCE_H
#include <cstring>
#include <iostream>
#include <map>
#include <string>
#include <type_traits>
#include <vector>
#include "AnimGraph.h" #include "AnimGraph.h"
#include "AnimGraphData.h"
#include "AnimGraphNodes.h" #include "AnimGraphNodes.h"
#include "SyncTrack.h"
#include "3rdparty/json/json.hpp" struct AnimNode;
struct AnimGraphBlendTree;
struct AnimGraphStateMachine;
struct AnimNodeResource { struct AnimNodeResource {
std::string m_name; std::string m_name;
@ -31,33 +37,40 @@ static inline AnimNodeResource AnimNodeResourceFactory(
return result; return result;
} }
struct BlendTreeConnectionResource { //
// AnimGraphResource
//
struct AnimGraphConnectionResource {
size_t source_node_index = -1; size_t source_node_index = -1;
std::string source_socket_name; std::string source_socket_name;
size_t target_node_index = -1; size_t target_node_index = -1;
std::string target_socket_name; std::string target_socket_name;
}; };
struct BlendTreeResource { struct AnimGraphResource {
std::string m_name;
std::vector<AnimNodeResource> m_nodes; std::vector<AnimNodeResource> m_nodes;
std::vector<BlendTreeConnectionResource> m_connections; std::vector<AnimGraphConnectionResource> m_connections;
void Reset() { ~AnimGraphResource() {
m_nodes.clear(); for (auto & m_node : m_nodes) {
m_connections.clear(); delete m_node.m_anim_node;
delete m_node.m_socket_accessor;
}
} }
void InitGraphConnectors() { AnimGraphResource() { clear(); }
m_nodes.push_back(AnimNodeResourceFactory("BlendTree"));
m_nodes[0].m_name = "Outputs";
m_nodes.push_back(AnimNodeResourceFactory("BlendTree"));
m_nodes[1].m_name = "Inputs";
}
AnimNodeResource& GetGraphOutputNode() { return m_nodes[0]; } void clear();
AnimNodeResource& GetGraphInputNode() { return m_nodes[1]; } void clearNodes();
void initGraphConnectors();
bool saveToFile(const char* filename) const;
bool loadFromFile(const char* filename);
size_t GetNodeIndex(const AnimNodeResource& node_resource) const { AnimNodeResource& getGraphOutputNode() { return m_nodes[0]; }
AnimNodeResource& getGraphInputNode() { return m_nodes[1]; }
size_t getNodeIndex(const AnimNodeResource& node_resource) const {
for (size_t i = 0, n = m_nodes.size(); i < n; i++) { for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
if (&m_nodes[i] == &node_resource) { if (&m_nodes[i] == &node_resource) {
return i; return i;
@ -67,13 +80,18 @@ struct BlendTreeResource {
return -1; return -1;
} }
bool ConnectSockets ( size_t addNode(const AnimNodeResource &node_resource) {
m_nodes.push_back(node_resource);
return m_nodes.size() - 1;
}
bool connectSockets(
const AnimNodeResource& source_node, const AnimNodeResource& source_node,
const std::string& source_socket_name, const std::string& source_socket_name,
const AnimNodeResource& target_node, const AnimNodeResource& target_node,
const std::string& target_socket_name) { const std::string& target_socket_name) {
size_t source_node_index = GetNodeIndex(source_node); size_t source_node_index = getNodeIndex(source_node);
size_t target_node_index = GetNodeIndex(target_node); size_t target_node_index = getNodeIndex(target_node);
if (source_node_index >= m_nodes.size() if (source_node_index >= m_nodes.size()
|| target_node_index >= m_nodes.size()) { || target_node_index >= m_nodes.size()) {
@ -91,7 +109,7 @@ struct BlendTreeResource {
return false; return false;
} }
BlendTreeConnectionResource connection; AnimGraphConnectionResource connection;
connection.source_node_index = source_node_index; connection.source_node_index = source_node_index;
connection.source_socket_name = source_socket_name; connection.source_socket_name = source_socket_name;
connection.target_node_index = target_node_index; connection.target_node_index = target_node_index;
@ -101,62 +119,28 @@ struct BlendTreeResource {
return true; return true;
} }
std::vector<Socket*> GetConstantNodeInputs( bool isSocketConnected(
std::vector<NodeDescriptorBase*>& instance_node_descriptors) const { const AnimNodeResource& node,
std::vector<Socket*> result; const std::string& socket_name) {
size_t node_index = getNodeIndex(node);
for (size_t i = 0; i < m_nodes.size(); i++) { for (const auto & connection : m_connections) {
for (size_t j = 0, num_inputs = instance_node_descriptors[i]->m_inputs.size(); if ((connection.source_node_index == node_index
j < num_inputs; && connection.source_socket_name == socket_name)
j++) { || ((connection.target_node_index == node_index)
Socket& input = instance_node_descriptors[i]->m_inputs[j]; && connection.target_socket_name == socket_name)) {
return true;
if (*input.m_reference.ptr_ptr == nullptr) {
memcpy(&input.m_value, &m_nodes[i].m_socket_accessor->m_inputs[j].m_value, sizeof(Socket::SocketValue));
result.push_back(&input);
} }
} }
return false;
} }
return result; void createInstance(AnimGraph& result) const;
}
};
struct StateMachineTransitionResources { void createRuntimeNodeInstances(AnimGraph& instance) const;
size_t source_state_index = -1; void prepareGraphIOData(AnimGraph& instance) const;
size_t target_state_index = -1; void setRuntimeNodeProperties(AnimGraph& instance) const;
float blend_time = 0.f; std::vector<Socket*> getConstNodeInputs(std::vector<NodeDescriptorBase*>& instance_node_descriptors) const;
bool sync_blend = false;
};
struct StateMachineResource {
std::vector<AnimNodeResource> m_states;
std::vector<StateMachineTransitionResources> m_transitions;
};
struct AnimGraphResource {
std::string m_type;
std::string m_name;
BlendTreeResource m_blend_tree_resource;
StateMachineResource m_state_machine_resource;
bool SaveToFile(const char* filename) const;
bool LoadFromFile(const char* filename);
void CreateBlendTreeInstance(AnimGraphBlendTree& result) const;
void CreateStateMachineInstance(AnimGraphStateMachine& result) const;
private:
// BlendTree
bool SaveBlendTreeResourceToFile(const char* filename) const;
bool LoadBlendTreeResourceFromJson(nlohmann::json const& json_data);
void CreateBlendTreeRuntimeNodeInstances(AnimGraphBlendTree& result) const;
void PrepareBlendTreeIOData(AnimGraphBlendTree& instance) const;
void SetRuntimeNodeProperties(AnimGraphBlendTree& result) const;
bool SaveStateMachineResourceToFile(const char* filename) const;
bool LoadStateMachineResourceFromJson(nlohmann::json const& json_data);
}; };
#endif //ANIMTESTBED_ANIMGRAPHRESOURCE_H #endif //ANIMTESTBED_ANIMGRAPHRESOURCE_H

View File

@ -1,25 +0,0 @@
//
// Created by martin on 17.03.24.
//
#include "AnimGraphStateMachine.h"
bool AnimGraphStateMachine::Init(AnimGraphContext& context) {
}
void AnimGraphStateMachine::MarkActiveInputs() {
}
void AnimGraphStateMachine::CalcSyncTrack() {
}
void AnimGraphStateMachine::UpdateTime(float time_last, float time_now) {
}
void AnimGraphStateMachine::Evaluate(AnimGraphContext& context) {
}

View File

@ -1,35 +0,0 @@
//
// Created by martin on 17.03.24.
//
#ifndef ANIMTESTBED_ANIMGRAPHSTATEMACHINE_H
#define ANIMTESTBED_ANIMGRAPHSTATEMACHINE_H
#include "AnimGraphNodes.h"
struct Transition {
AnimNode* m_source_state = nullptr;
AnimNode* m_target_state = nullptr;
float m_blend_time = 0.f;
bool m_sync_blend = false;
};
struct AnimGraphStateMachine : public AnimNode {
std::vector<AnimNode> m_states;
std::vector<Transition> m_transitions;
std::vector<std::vector<Transition*> > m_state_out_transitions;
AnimNode* m_next_state = nullptr;
AnimNode* m_current_state = nullptr;
Transition* m_active_transition = nullptr;
bool Init(AnimGraphContext& context);
void MarkActiveInputs() override;
void CalcSyncTrack() override;
void UpdateTime(float time_last, float time_now) override;
void Evaluate(AnimGraphContext& context) override;
};
#endif //ANIMTESTBED_ANIMGRAPHSTATEMACHINE_H

View File

@ -1,5 +0,0 @@
//
// Created by martin on 17.03.24.
//
#include "AnimNode.h"

View File

@ -1,70 +0,0 @@
//
// Created by martin on 17.03.24.
//
#ifndef ANIMTESTBED_ANIMNODE_H
#define ANIMTESTBED_ANIMNODE_H
#include <string>
#include <vector>
#include "SyncTrack.h"
#include "AnimGraphData.h"
struct AnimNode;
enum class AnimNodeEvalState {
Undefined,
Deactivated,
Activated,
SyncTrackUpdated,
TimeUpdated,
Evaluated
};
struct AnimNode {
std::string m_name;
std::string m_node_type_name;
AnimNodeEvalState m_state = AnimNodeEvalState::Undefined;
float m_time_now = 0.f;
float m_time_last = 0.f;
SyncTrack m_sync_track;
std::vector<AnimGraphConnection> m_inputs;
virtual ~AnimNode() = default;
virtual bool Init(AnimGraphContext& context) { return true; };
virtual void MarkActiveInputs() {
for (const auto & input : m_inputs) {
AnimNode* input_node = input.m_source_node;
if (input_node != nullptr) {
input_node->m_state = AnimNodeEvalState::Activated;
}
}
}
virtual void CalcSyncTrack() {
for (const auto & input : m_inputs) {
AnimNode* input_node = input.m_source_node;
if (input_node != nullptr
&& input.m_source_socket.m_type == SocketType::SocketTypeAnimation
&& input_node->m_state != AnimNodeEvalState::Deactivated) {
m_sync_track = input_node->m_sync_track;
return;
}
}
}
virtual void UpdateTime(float time_last, float time_now) {
m_time_last = time_last;
m_time_now = time_now;
m_state = AnimNodeEvalState::TimeUpdated;
}
virtual void Evaluate(AnimGraphContext& context){};
};
#endif //ANIMTESTBED_ANIMNODE_H

View File

@ -160,10 +160,8 @@ struct Viewport {
.compare = SG_COMPAREFUNC_LESS_EQUAL, .compare = SG_COMPAREFUNC_LESS_EQUAL,
.write_enabled = true .write_enabled = true
}, },
.cull_mode = SG_CULLMODE_BACK, .cull_mode = SG_CULLMODE_BACK
.sample_count = cMSAASampleCount
}; };
// this->pip = sg_make_pipeline(gl_pipeline_desc);
} }
}; };
@ -474,7 +472,7 @@ int main() {
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE); glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE);
glfwWindowHint(GLFW_SAMPLES, cMSAASampleCount); glfwWindowHint(GLFW_SAMPLES, 16);
GLFWwindow* w = GLFWwindow* w =
glfwCreateWindow(Width, Height, "ATP Editor", nullptr, nullptr); glfwCreateWindow(Width, Height, "ATP Editor", nullptr, nullptr);
glfwMakeContextCurrent(w); glfwMakeContextCurrent(w);
@ -529,7 +527,7 @@ int main() {
// setup sokol_gfx and sokol_time // setup sokol_gfx and sokol_time
stm_setup(); stm_setup();
sg_desc desc = {.logger = {.func = slog_func}, .context {.sample_count = cMSAASampleCount}}; sg_desc desc = {.logger = {.func = slog_func}};
sg_setup(&desc); sg_setup(&desc);
assert(sg_isvalid()); assert(sg_isvalid());
@ -660,7 +658,6 @@ int main() {
pip_desc.colors[0].blend.src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA; pip_desc.colors[0].blend.src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA;
pip_desc.colors[0].blend.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; pip_desc.colors[0].blend.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
pip_desc.colors[0].write_mask = SG_COLORMASK_RGB; pip_desc.colors[0].write_mask = SG_COLORMASK_RGB;
pip_desc.sample_count = cMSAASampleCount;
pip_desc.label = "imgui-rendering"; pip_desc.label = "imgui-rendering";
pip = sg_make_pipeline(&pip_desc); pip = sg_make_pipeline(&pip_desc);

View File

@ -3,8 +3,8 @@
// //
#include "AnimGraph/AnimGraph.h" #include "AnimGraph/AnimGraph.h"
#include "AnimGraph/AnimGraphBlendTreeResource.h"
#include "AnimGraph/AnimGraphEditor.h" #include "AnimGraph/AnimGraphEditor.h"
#include "AnimGraph/AnimGraphResource.h"
#include "catch.hpp" #include "catch.hpp"
#include "ozz/animation/offline/animation_builder.h" #include "ozz/animation/offline/animation_builder.h"
#include "ozz/animation/offline/raw_animation.h" #include "ozz/animation/offline/raw_animation.h"
@ -141,7 +141,7 @@ TEST_CASE_METHOD(
SimpleAnimFixture, SimpleAnimFixture,
"AnimGraphSimpleEval", "AnimGraphSimpleEval",
"[AnimGraphEvalTests]") { "[AnimGraphEvalTests]") {
AnimGraphBlendTreeResource graph_resource; AnimGraphResource graph_resource;
// Add nodes // Add nodes
size_t trans_x_node_index = size_t trans_x_node_index =

View File

@ -3,9 +3,7 @@
// //
#include "AnimGraph/AnimGraph.h" #include "AnimGraph/AnimGraph.h"
#include "AnimGraph/AnimGraphBlendTree.h"
#include "AnimGraph/AnimGraphEditor.h" #include "AnimGraph/AnimGraphEditor.h"
#include "AnimGraph/AnimGraphNodes.h"
#include "AnimGraph/AnimGraphResource.h" #include "AnimGraph/AnimGraphResource.h"
#include "catch.hpp" #include "catch.hpp"
#include "ozz/base/io/archive.h" #include "ozz/base/io/archive.h"
@ -35,65 +33,61 @@ bool load_skeleton(ozz::animation::Skeleton& skeleton, const char* filename) {
TEST_CASE("AnimSamplerGraph", "[AnimGraphResource]") { TEST_CASE("AnimSamplerGraph", "[AnimGraphResource]") {
AnimGraphResource graph_resource; AnimGraphResource graph_resource;
graph_resource.m_name = "AnimSamplerBlendTree";
graph_resource.m_type = "BlendTree";
BlendTreeResource& blend_tree_resource = graph_resource.m_blend_tree_resource; graph_resource.clear();
blend_tree_resource.Reset(); graph_resource.m_name = "AnimSamplerGraph";
blend_tree_resource.InitGraphConnectors();
// Prepare graph inputs and outputs // Prepare graph inputs and outputs
blend_tree_resource.m_nodes.push_back(AnimNodeResourceFactory("AnimSampler")); size_t walk_node_index =
size_t walk_node_index = blend_tree_resource.m_nodes.size() - 1; graph_resource.addNode(AnimNodeResourceFactory("AnimSampler"));
AnimNodeResource& walk_node = blend_tree_resource.m_nodes[walk_node_index]; AnimNodeResource& walk_node = graph_resource.m_nodes[walk_node_index];
walk_node.m_name = "WalkAnim"; walk_node.m_name = "WalkAnim";
walk_node.m_socket_accessor->SetPropertyValue( walk_node.m_socket_accessor->SetPropertyValue(
"Filename", "Filename",
std::string("media/Walking-loop.ozz")); std::string("media/Walking-loop.ozz"));
AnimNodeResource& graph_node = blend_tree_resource.m_nodes[0]; AnimNodeResource& graph_node = graph_resource.m_nodes[0];
graph_node.m_socket_accessor->RegisterInput<AnimData>("GraphOutput", nullptr); graph_node.m_socket_accessor->RegisterInput<AnimData>("GraphOutput", nullptr);
blend_tree_resource.ConnectSockets( graph_resource.connectSockets(
walk_node, walk_node,
"Output", "Output",
blend_tree_resource.GetGraphOutputNode(), graph_resource.getGraphOutputNode(),
"GraphOutput"); "GraphOutput");
graph_resource.SaveToFile("AnimSamplerBlendTree.json"); graph_resource.saveToFile("AnimSamplerGraph.animgraph.json");
AnimGraphResource graph_resource_loaded; AnimGraphResource graph_resource_loaded;
graph_resource_loaded.LoadFromFile("AnimSamplerBlendTree.json"); graph_resource_loaded.loadFromFile("AnimSamplerGraph.animgraph.json");
AnimGraphBlendTree anim_graph_blend_tree; AnimGraph graph;
graph_resource_loaded.CreateBlendTreeInstance(anim_graph_blend_tree); graph_resource_loaded.createInstance(graph);
AnimGraphContext graph_context; AnimGraphContext graph_context;
ozz::animation::Skeleton skeleton; ozz::animation::Skeleton skeleton;
REQUIRE(load_skeleton(skeleton, "media/skeleton.ozz")); REQUIRE(load_skeleton(skeleton, "media/skeleton.ozz"));
graph_context.m_skeleton = &skeleton; graph_context.m_skeleton = &skeleton;
REQUIRE(anim_graph_blend_tree.Init(graph_context)); REQUIRE(graph.init(graph_context));
REQUIRE(anim_graph_blend_tree.m_nodes.size() == 3); REQUIRE(graph.m_nodes.size() == 3);
REQUIRE(anim_graph_blend_tree.m_nodes[0]->m_node_type_name == "BlendTree"); REQUIRE(graph.m_nodes[0]->m_node_type_name == "BlendTree");
REQUIRE(anim_graph_blend_tree.m_nodes[1]->m_node_type_name == "BlendTree"); REQUIRE(graph.m_nodes[1]->m_node_type_name == "BlendTree");
REQUIRE(anim_graph_blend_tree.m_nodes[2]->m_node_type_name == "AnimSampler"); REQUIRE(graph.m_nodes[2]->m_node_type_name == "AnimSampler");
// connections within the graph // connections within the graph
AnimSamplerNode* anim_sampler_walk = AnimSamplerNode* anim_sampler_walk =
dynamic_cast<AnimSamplerNode*>(anim_graph_blend_tree.m_nodes[2]); dynamic_cast<AnimSamplerNode*>(graph.m_nodes[2]);
BlendTreeNode* graph_output_node = BlendTreeNode* graph_output_node =
dynamic_cast<BlendTreeNode*>(anim_graph_blend_tree.m_nodes[0]); dynamic_cast<BlendTreeNode*>(graph.m_nodes[0]);
// check node input dependencies // check node input dependencies
size_t anim_sampler_index = anim_graph_blend_tree.GetAnimNodeIndex(anim_sampler_walk); size_t anim_sampler_index = anim_sampler_walk->m_index;
REQUIRE(anim_graph_blend_tree.m_node_output_connections[anim_sampler_index].size() == 1); REQUIRE(graph.m_node_output_connections[anim_sampler_index].size() == 1);
CHECK( CHECK(
anim_graph_blend_tree.m_node_output_connections[anim_sampler_index][0].m_target_node graph.m_node_output_connections[anim_sampler_index][0].m_target_node
== graph_output_node); == graph_output_node);
// Ensure animation sampler nodes use the correct files // Ensure animation sampler nodes use the correct files
@ -103,11 +97,11 @@ TEST_CASE("AnimSamplerGraph", "[AnimGraphResource]") {
// Ensure that outputs are properly propagated. // Ensure that outputs are properly propagated.
AnimData output; AnimData output;
output.m_local_matrices.resize(skeleton.num_soa_joints()); output.m_local_matrices.resize(skeleton.num_soa_joints());
anim_graph_blend_tree.SetOutput("GraphOutput", &output); graph.SetOutput("GraphOutput", &output);
REQUIRE(anim_sampler_walk->o_output == &output); REQUIRE(anim_sampler_walk->o_output == &output);
WHEN("Emulating Graph Evaluation") { WHEN("Emulating Graph Evaluation") {
CHECK(anim_graph_blend_tree.m_anim_data_allocator.size() == 0); CHECK(graph.m_anim_data_allocator.size() == 0);
anim_sampler_walk->Evaluate(graph_context); anim_sampler_walk->Evaluate(graph_context);
} }
@ -115,12 +109,10 @@ TEST_CASE("AnimSamplerGraph", "[AnimGraphResource]") {
} }
/* /*
* Checks that node const inputs are properly set.
// */
// Checks that node const inputs are properly set.
//
TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") { TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") {
AnimGraphBlendTreeResource graph_resource; AnimGraphResource graph_resource;
graph_resource.clear(); graph_resource.clear();
graph_resource.m_name = "AnimSamplerSpeedScaleGraph"; graph_resource.m_name = "AnimSamplerSpeedScaleGraph";
@ -158,7 +150,7 @@ TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") {
"GraphOutput"); "GraphOutput");
graph_resource.saveToFile("AnimSamplerSpeedScaleGraph.animgraph.json"); graph_resource.saveToFile("AnimSamplerSpeedScaleGraph.animgraph.json");
AnimGraphBlendTreeResource graph_resource_loaded; AnimGraphResource graph_resource_loaded;
graph_resource_loaded.loadFromFile( graph_resource_loaded.loadFromFile(
"AnimSamplerSpeedScaleGraph.animgraph.json"); "AnimSamplerSpeedScaleGraph.animgraph.json");
@ -180,7 +172,7 @@ TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") {
TEST_CASE("Blend2Graph", "[AnimGraphResource]") { TEST_CASE("Blend2Graph", "[AnimGraphResource]") {
AnimGraphBlendTreeResource graph_resource; AnimGraphResource graph_resource;
graph_resource.clear(); graph_resource.clear();
graph_resource.m_name = "WalkRunBlendGraph"; graph_resource.m_name = "WalkRunBlendGraph";
@ -222,7 +214,7 @@ TEST_CASE("Blend2Graph", "[AnimGraphResource]") {
"GraphOutput"); "GraphOutput");
graph_resource.saveToFile("Blend2Graph.animgraph.json"); graph_resource.saveToFile("Blend2Graph.animgraph.json");
AnimGraphBlendTreeResource graph_resource_loaded; AnimGraphResource graph_resource_loaded;
graph_resource_loaded.loadFromFile("Blend2Graph.animgraph.json"); graph_resource_loaded.loadFromFile("Blend2Graph.animgraph.json");
AnimGraph graph; AnimGraph graph;
@ -319,7 +311,7 @@ TEST_CASE("InputAttributeConversion", "[AnimGraphResource]") {
} }
TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") { TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") {
AnimGraphBlendTreeResource graph_resource_origin; AnimGraphResource graph_resource_origin;
graph_resource_origin.clear(); graph_resource_origin.clear();
graph_resource_origin.m_name = "TestInputOutputGraph"; graph_resource_origin.m_name = "TestInputOutputGraph";
@ -378,7 +370,7 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") {
const char* filename = "ResourceSaveLoadGraphInputs.json"; const char* filename = "ResourceSaveLoadGraphInputs.json";
graph_resource_origin.saveToFile(filename); graph_resource_origin.saveToFile(filename);
AnimGraphBlendTreeResource graph_resource_loaded; AnimGraphResource graph_resource_loaded;
graph_resource_loaded.loadFromFile(filename); graph_resource_loaded.loadFromFile(filename);
const AnimNodeResource& graph_loaded_output_node = const AnimNodeResource& graph_loaded_output_node =
@ -452,7 +444,7 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") {
} }
TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") { TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") {
AnimGraphBlendTreeResource graph_resource_origin; AnimGraphResource graph_resource_origin;
graph_resource_origin.clear(); graph_resource_origin.clear();
graph_resource_origin.m_name = "TestInputOutputGraph"; graph_resource_origin.m_name = "TestInputOutputGraph";
@ -537,7 +529,7 @@ TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") {
const char* filename = "ResourceSaveLoadGraphInputs.json"; const char* filename = "ResourceSaveLoadGraphInputs.json";
graph_resource_origin.saveToFile(filename); graph_resource_origin.saveToFile(filename);
AnimGraphBlendTreeResource graph_resource_loaded; AnimGraphResource graph_resource_loaded;
graph_resource_loaded.loadFromFile(filename); graph_resource_loaded.loadFromFile(filename);
const AnimNodeResource& graph_loaded_output_node = const AnimNodeResource& graph_loaded_output_node =
@ -588,5 +580,3 @@ TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") {
} }
} }
} }
*/