Compare commits

...

2 Commits

Author SHA1 Message Date
Martin Felis
ccb9bc4e9b Working on unified BlendTree and StateMachine handling. 2024-03-17 22:06:27 +01:00
Martin Felis
c7d2d195a3 Updated ozz-animation to version 0.14.3 (commit 92c392b667844) 2024-03-17 12:47:11 +01:00
46 changed files with 1952 additions and 1017 deletions

View File

@ -1,3 +1,19 @@
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
----------------------

View File

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

View File

@ -18,7 +18,7 @@ Documentation and samples are available from [ozz-animation website](http://guil
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, the standard CRT 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, on the C and the C++ standard libraries, 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.

View File

@ -1,18 +1,18 @@
# Set compilers settings for all platforms/compilers.
#---------------------------------------------------
# ---------------------------------------------------
#-----------------
# -----------------
# Includes modules
include(CheckIncludeFiles)
#------------------------------
# ------------------------------
# Enables IDE folders y default
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
#------------------------
# ------------------------
# Available build options
#------------------------
# ------------------------
# Lists all the cxx flags
set(cxx_all_flags
CMAKE_CXX_FLAGS
@ -26,13 +26,14 @@ set(cxx_all_flags
CMAKE_CXX_FLAGS_RELEASE
CMAKE_C_FLAGS_RELEASE)
#--------------------------------------
# --------------------------------------
# Cross compiler compilation flags
# Requires C++11
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 11)
endif()
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
@ -41,15 +42,15 @@ if(ozz_build_simd_ref)
add_compile_definitions(OZZ_BUILD_SIMD_REF)
endif()
# Disables crt secure warnings
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
#--------------------------------------
# --------------------------------------
# Modify default MSVC compilation flags
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
#---------------------------
# ---------------------------
# For the common build flags
# Disables crt secure warnings
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
# Adds support for multiple processes builds
add_compile_options(/MP)
@ -60,62 +61,61 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
add_compile_options(/WX)
# 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)
string(REGEX REPLACE "/MT" "/MD" ${flag} "${${flag}}")
if(ozz_build_msvc_rt_dll)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
else()
string(REGEX REPLACE "/MD" "/MT" ${flag} "${${flag}}")
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()
endforeach()
#--------------------------------------
# --------------------------------------
# else consider the compiler as GCC compatible (inc clang)
else()
# Set the warning level to Wall
add_compile_options(-Wall)
# Enable extra level of warning
#add_compile_options(-Wextra)
# add_compile_options(-Wextra)
# Set warning as error
add_compile_options(-Werror)
# ignored-attributes reports issue when using _m128 as template argument
check_cxx_compiler_flag("-Wignored-attributes" W_IGNORED_ATTRIBUTES)
if(W_IGNORED_ATTRIBUTES)
add_compile_options(-Wno-ignored-attributes)
endif()
# Disables c98 retrocompatibility warnings
check_cxx_compiler_flag("-Wc++98-compat-pedantic" W_98_COMPAT_PEDANTIC)
if(W_98_COMPAT_PEDANTIC)
add_compile_options(-Wno-c++98-compat-pedantic)
endif()
# 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("-Wpragma-pack" W_PRAGMA_PACK)
#----------------------
# ----------------------
# Sets emscripten output
if(EMSCRIPTEN)
SET(CMAKE_EXECUTABLE_SUFFIX ".html")
add_link_options(-s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=0)
#if(NOT ozz_build_simd_ref)
# if(NOT ozz_build_simd_ref)
# set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS "-msse2")
#endif()
# endif()
endif()
endif()
#---------------------
# ---------------------
# Prints all the flags
message(STATUS "---------------------------------------------------------")
message(STATUS "Default build type is: ${CMAKE_BUILD_TYPE}")
message(STATUS "The following compilation flags will be used:")
foreach(flag ${cxx_all_flags})
message(${flag} " ${${flag}}")
endforeach()
@ -124,6 +124,7 @@ message(STATUS "---------------------------------------------------------")
get_directory_property(DirectoryCompileOptions DIRECTORY ${PROJECT_SOURCE_DIR} COMPILE_OPTIONS)
message(STATUS "Directory Compile Options:")
foreach(opt ${DirectoryCompileOptions})
message(STATUS ${opt})
endforeach()
@ -132,13 +133,14 @@ message(STATUS "---------------------------------------------------------")
get_directory_property(DirectoryCompileDefinitions DIRECTORY ${PROJECT_SOURCE_DIR} COMPILE_DEFINITIONS)
message(STATUS "Directory Compile Definitions:")
foreach(def ${DirectoryCompileDefinitions})
message(STATUS ${def})
endforeach()
message(STATUS "---------------------------------------------------------")
#----------------------------------------------
# ----------------------------------------------
# Modifies output directory for all executables
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ".")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ".")
@ -146,7 +148,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ".")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ".")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ".")
#-------------------------------
# -------------------------------
# Set a postfix for output files
if(ozz_build_postfix)
set(CMAKE_DEBUG_POSTFIX "_d")

View File

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

View File

@ -57,8 +57,6 @@ if(UNIX AND APPLE)
lib/cocoa/cocoa_joystick.m
lib/cocoa/cocoa_time.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.
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;
char formatString[6];
sprintf(formatString, "%%.%dg", precision);
snprintf(formatString, sizeof(formatString), "%%.%dg", precision);
// Print into the buffer. We need not request the alternative representation
// that always has a decimal point because JSON doesn't distingish the

View File

@ -66,12 +66,11 @@ template <typename _Ty, size_t _size = sizeof(_Ty)>
struct EndianSwapper;
// Internal macro used to swap two bytes.
#define OZZ_BYTE_SWAP(_a, _b) \
do { \
const ozz::byte temp = (_a); \
(_a) = (_b); \
(_b) = temp; \
} while (0)
OZZ_INLINE void _in_place_byte_swap(byte& _a, byte& _b) {
_a = _a ^ _b;
_b = _a ^ _b;
_a = _a ^ _b;
}
// EndianSwapper specialization for 1 byte types.
template <typename _Ty>
@ -86,12 +85,12 @@ struct EndianSwapper<_Ty, 2> {
OZZ_INLINE static void Swap(_Ty* _ty, size_t _count) {
byte* alias = reinterpret_cast<byte*>(_ty);
for (size_t i = 0; i < _count * 2; i += 2) {
OZZ_BYTE_SWAP(alias[i + 0], alias[i + 1]);
_in_place_byte_swap(alias[i + 0], alias[i + 1]);
}
}
OZZ_INLINE static _Ty Swap(_Ty _ty) { // Pass by copy to swap _ty in-place.
byte* alias = reinterpret_cast<byte*>(&_ty);
OZZ_BYTE_SWAP(alias[0], alias[1]);
_in_place_byte_swap(alias[0], alias[1]);
return _ty;
}
};
@ -102,14 +101,14 @@ struct EndianSwapper<_Ty, 4> {
OZZ_INLINE static void Swap(_Ty* _ty, size_t _count) {
byte* alias = reinterpret_cast<byte*>(_ty);
for (size_t i = 0; i < _count * 4; i += 4) {
OZZ_BYTE_SWAP(alias[i + 0], alias[i + 3]);
OZZ_BYTE_SWAP(alias[i + 1], alias[i + 2]);
_in_place_byte_swap(alias[i + 0], alias[i + 3]);
_in_place_byte_swap(alias[i + 1], alias[i + 2]);
}
}
OZZ_INLINE static _Ty Swap(_Ty _ty) { // Pass by copy to swap _ty in-place.
byte* alias = reinterpret_cast<byte*>(&_ty);
OZZ_BYTE_SWAP(alias[0], alias[3]);
OZZ_BYTE_SWAP(alias[1], alias[2]);
_in_place_byte_swap(alias[0], alias[3]);
_in_place_byte_swap(alias[1], alias[2]);
return _ty;
}
};
@ -120,25 +119,22 @@ struct EndianSwapper<_Ty, 8> {
OZZ_INLINE static void Swap(_Ty* _ty, size_t _count) {
byte* alias = reinterpret_cast<byte*>(_ty);
for (size_t i = 0; i < _count * 8; i += 8) {
OZZ_BYTE_SWAP(alias[i + 0], alias[i + 7]);
OZZ_BYTE_SWAP(alias[i + 1], alias[i + 6]);
OZZ_BYTE_SWAP(alias[i + 2], alias[i + 5]);
OZZ_BYTE_SWAP(alias[i + 3], alias[i + 4]);
_in_place_byte_swap(alias[i + 0], alias[i + 7]);
_in_place_byte_swap(alias[i + 1], alias[i + 6]);
_in_place_byte_swap(alias[i + 2], alias[i + 5]);
_in_place_byte_swap(alias[i + 3], alias[i + 4]);
}
}
OZZ_INLINE static _Ty Swap(_Ty _ty) { // Pass by copy to swap _ty in-place.
byte* alias = reinterpret_cast<byte*>(&_ty);
OZZ_BYTE_SWAP(alias[0], alias[7]);
OZZ_BYTE_SWAP(alias[1], alias[6]);
OZZ_BYTE_SWAP(alias[2], alias[5]);
OZZ_BYTE_SWAP(alias[3], alias[4]);
_in_place_byte_swap(alias[0], alias[7]);
_in_place_byte_swap(alias[1], alias[6]);
_in_place_byte_swap(alias[2], alias[5]);
_in_place_byte_swap(alias[3], alias[4]);
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.
template <typename _Ty>
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);
if (open) {
_im_gui->DoLabel("Main layer:");
std::sprintf(label, "Layer weight: %.2f", base_weight_);
std::snprintf(label, sizeof(label), "Layer weight: %.2f", base_weight_);
_im_gui->DoSlider(label, 0.f, 1.f, &base_weight_, 1.f);
_im_gui->DoLabel("Additive layer:");
@ -259,7 +259,7 @@ class AdditiveBlendSampleApplication : public ozz::sample::Application {
std::memcpy(weights.data(), additive_weigths_,
sizeof(additive_weigths_));
std::sprintf(label, "Weights\nCurl: %.2f\nSplay: %.2f",
std::snprintf(label, sizeof(label), "Weights\nCurl: %.2f\nSplay: %.2f",
additive_weigths_[kCurl], additive_weigths_[kSplay]);
if (_im_gui->DoSlider2D(label, {{0.f, 0.f}}, {{1.f, 1.f}}, &weights)) {
auto_animate_weights_ = false; // User interacted.

View File

@ -161,16 +161,16 @@ class AttachSampleApplication : public ozz::sample::Application {
if (open && skeleton_.num_joints() != 0) {
_im_gui->DoLabel("Select joint:");
char label[64];
std::sprintf(label, "%s (%d)", skeleton_.joint_names()[attachment_],
attachment_);
std::snprintf(label, sizeof(label), "%s (%d)",
skeleton_.joint_names()[attachment_], attachment_);
_im_gui->DoSlider(label, 0, skeleton_.num_joints() - 1, &attachment_);
_im_gui->DoLabel("Attachment offset:");
sprintf(label, "x: %02f", offset_.x);
std::snprintf(label, sizeof(label), "x: %02f", offset_.x);
_im_gui->DoSlider(label, -1.f, 1.f, &offset_.x);
sprintf(label, "y: %02f", offset_.y);
std::snprintf(label, sizeof(label), "y: %02f", offset_.y);
_im_gui->DoSlider(label, -1.f, 1.f, &offset_.y);
sprintf(label, "z: %02f", offset_.z);
std::snprintf(label, sizeof(label), "z: %02f", 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.
OZZ_OPTIONS_DECLARE_STRING(animation3,
"Path to the second animation (ozz archive format).",
"Path to the third animation (ozz archive format).",
"media/animation3.ozz", false)
class BlendSampleApplication : public ozz::sample::Application {
@ -242,16 +242,16 @@ class BlendSampleApplication : public ozz::sample::Application {
}
char label[64];
std::sprintf(label, "Blend ratio: %.2f", blend_ratio_);
std::snprintf(label, sizeof(label), "Blend ratio: %.2f", blend_ratio_);
_im_gui->DoSlider(label, 0.f, 1.f, &blend_ratio_, 1.f, !manual_);
for (int i = 0; i < kNumLayers; ++i) {
Sampler& sampler = samplers_[i];
std::sprintf(label, "Weight %d: %.2f", i, sampler.weight);
std::snprintf(label, sizeof(label), "Weight %d: %.2f", i, sampler.weight);
_im_gui->DoSlider(label, 0.f, 1.f, &sampler.weight, 1.f, manual_);
}
std::sprintf(label, "Threshold: %.2f", threshold_);
std::snprintf(label, sizeof(label), "Threshold: %.2f", 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 bool OnGui(ozz::sample::ImGui* _im_gui) {
char txt[32];
char label[32];
// Main options
{
@ -636,12 +636,12 @@ class FootIKSampleApplication : public ozz::sample::Application {
static bool opened = true;
ozz::sample::ImGui::OpenClose oc(_im_gui, "IK settings", &opened);
if (opened) {
sprintf(txt, "Foot height %.2g", foot_heigh_);
_im_gui->DoSlider(txt, 0.f, .3f, &foot_heigh_);
sprintf(txt, "Weight %.2g", weight_);
_im_gui->DoSlider(txt, 0.f, 1.f, &weight_);
sprintf(txt, "Soften %.2g", soften_);
_im_gui->DoSlider(txt, 0.f, 1.f, &soften_, 1.f, two_bone_ik_);
snprintf(label, sizeof(label), "Foot height %.2g", foot_heigh_);
_im_gui->DoSlider(label, 0.f, .3f, &foot_heigh_);
snprintf(label, sizeof(label), "Weight %.2g", weight_);
_im_gui->DoSlider(label, 0.f, 1.f, &weight_);
snprintf(label, sizeof(label), "Soften %.2g", soften_);
_im_gui->DoSlider(label, 0.f, 1.f, &soften_, 1.f, two_bone_ik_);
}
}
@ -652,19 +652,19 @@ class FootIKSampleApplication : public ozz::sample::Application {
bool moved = false;
// Translation
_im_gui->DoLabel("Translation");
sprintf(txt, "x %.2g", root_translation_.x);
moved |= _im_gui->DoSlider(txt, -10.f, 10.f, &root_translation_.x);
sprintf(txt, "y %.2g", root_translation_.y);
moved |= _im_gui->DoSlider(txt, 0.f, 5.f, &root_translation_.y, 1.f,
snprintf(label, sizeof(label), "x %.2g", root_translation_.x);
moved |= _im_gui->DoSlider(label, -10.f, 10.f, &root_translation_.x);
snprintf(label, sizeof(label), "y %.2g", root_translation_.y);
moved |= _im_gui->DoSlider(label, 0.f, 5.f, &root_translation_.y, 1.f,
!auto_character_height_);
sprintf(txt, "z %.2g", root_translation_.z);
moved |= _im_gui->DoSlider(txt, -10.f, 10.f, &root_translation_.z);
snprintf(label, sizeof(label), "z %.2g", root_translation_.z);
moved |= _im_gui->DoSlider(label, -10.f, 10.f, &root_translation_.z);
// Rotation (in euler form)
_im_gui->DoLabel("Rotation");
sprintf(txt, "yaw %.3g", root_yaw_ * ozz::math::kRadianToDegree);
snprintf(label, sizeof(label), "yaw %.3g", root_yaw_ * ozz::math::kRadianToDegree);
moved |=
_im_gui->DoSlider(txt, -ozz::math::kPi, ozz::math::kPi, &root_yaw_);
_im_gui->DoSlider(label, -ozz::math::kPi, ozz::math::kPi, &root_yaw_);
// Character position shouldn't be changed after the update. In this
// case, because UI is updated after "game" update, we need to recompute

View File

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

View File

@ -149,7 +149,7 @@ class Application {
enum LoopStatus {
kContinue, // Can continue with next loop.
kBreak, // Should stop looping (ex: exit).
kBreakFailure, // // Should stop looping beacause something went wrong.
kBreakFailure, // Should stop looping because something went wrong.
};
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) {
return false;
}
std::sprintf(_string, "%.2g\n", _value);
std::snprintf(_string, _string_end - _string, "%.2g\n", _value);
// Removes unnecessary '0' digits in the exponent.
char* exponent = strchr(_string, 'e');

View File

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

View File

@ -110,21 +110,22 @@ bool PlaybackController::OnGui(const animation::Animation& _animation,
_im_gui->DoCheckBox("Loop", &loop_, _enabled);
char szLabel[64];
char label[64];
// Uses a local copy of time_ so that set_time is used to actually apply
// changes. Otherwise previous time would be incorrect.
float ratio = time_ratio();
std::sprintf(szLabel, "Animation time: %.2f", ratio * _animation.duration());
if (_im_gui->DoSlider(szLabel, 0.f, 1.f, &ratio, 1.f,
std::snprintf(label, sizeof(label), "Animation time: %.2f",
ratio * _animation.duration());
if (_im_gui->DoSlider(label, 0.f, 1.f, &ratio, 1.f,
_enabled && _allow_set_time)) {
set_time_ratio(ratio);
// Pause the time if slider as moved.
play_ = false;
time_changed = true;
}
std::sprintf(szLabel, "Playback speed: %.2f", playback_speed_);
_im_gui->DoSlider(szLabel, -5.f, 5.f, &playback_speed_, 1.f, _enabled);
std::snprintf(label, sizeof(label), "Playback speed: %.2f", playback_speed_);
_im_gui->DoSlider(label, -5.f, 5.f, &playback_speed_, 1.f, _enabled);
// Allow to reset speed if it is not the default value.
if (_im_gui->DoButton("Reset playback speed",
@ -139,7 +140,7 @@ bool OnRawSkeletonJointGui(
ozz::sample::ImGui* _im_gui,
ozz::animation::offline::RawSkeleton::Joint::Children* _children,
ozz::vector<bool>::iterator* _oc_state) {
char txt[255];
char label[255];
bool modified = false;
for (size_t i = 0; i < _children->size(); ++i) {
@ -152,23 +153,23 @@ bool OnRawSkeletonJointGui(
// Translation
ozz::math::Float3& translation = joint.transform.translation;
_im_gui->DoLabel("Translation");
sprintf(txt, "x %.2g", translation.x);
modified |= _im_gui->DoSlider(txt, -1.f, 1.f, &translation.x);
sprintf(txt, "y %.2g", translation.y);
modified |= _im_gui->DoSlider(txt, -1.f, 1.f, &translation.y);
sprintf(txt, "z %.2g", translation.z);
modified |= _im_gui->DoSlider(txt, -1.f, 1.f, &translation.z);
snprintf(label, sizeof(label), "x %.2g", translation.x);
modified |= _im_gui->DoSlider(label, -1.f, 1.f, &translation.x);
snprintf(label, sizeof(label), "y %.2g", translation.y);
modified |= _im_gui->DoSlider(label, -1.f, 1.f, &translation.y);
snprintf(label, sizeof(label), "z %.2g", translation.z);
modified |= _im_gui->DoSlider(label, -1.f, 1.f, &translation.z);
// Rotation (in euler form)
ozz::math::Quaternion& rotation = joint.transform.rotation;
_im_gui->DoLabel("Rotation");
ozz::math::Float3 euler = ToEuler(rotation) * ozz::math::kRadianToDegree;
sprintf(txt, "x %.3g", euler.x);
bool euler_modified = _im_gui->DoSlider(txt, -180.f, 180.f, &euler.x);
sprintf(txt, "y %.3g", euler.y);
euler_modified |= _im_gui->DoSlider(txt, -180.f, 180.f, &euler.y);
sprintf(txt, "z %.3g", euler.z);
euler_modified |= _im_gui->DoSlider(txt, -180.f, 180.f, &euler.z);
snprintf(label, sizeof(label), "x %.3g", euler.x);
bool euler_modified = _im_gui->DoSlider(label, -180.f, 180.f, &euler.x);
snprintf(label, sizeof(label), "y %.3g", euler.y);
euler_modified |= _im_gui->DoSlider(label, -180.f, 180.f, &euler.y);
snprintf(label, sizeof(label), "z %.3g", euler.z);
euler_modified |= _im_gui->DoSlider(label, -180.f, 180.f, &euler.z);
if (euler_modified) {
modified = true;
ozz::math::Float3 euler_rad = euler * ozz::math::kDegreeToRadian;
@ -179,8 +180,8 @@ bool OnRawSkeletonJointGui(
// Scale (must be uniform and not 0)
_im_gui->DoLabel("Scale");
ozz::math::Float3& scale = joint.transform.scale;
sprintf(txt, "%.2g", scale.x);
if (_im_gui->DoSlider(txt, -1.f, 1.f, &scale.x)) {
snprintf(label, sizeof(label), "%.2g", scale.x);
if (_im_gui->DoSlider(label, -1.f, 1.f, &scale.x)) {
modified = true;
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 bool OnGui(ozz::sample::ImGui* _im_gui) {
char txt[64];
char label[64];
_im_gui->DoCheckBox("Enable ik", &enable_ik_);
sprintf(txt, "IK chain length: %d", chain_length_);
_im_gui->DoSlider(txt, 0, kMaxChainLength, &chain_length_);
sprintf(txt, "Joint weight %.2g", joint_weight_);
_im_gui->DoSlider(txt, 0.f, 1.f, &joint_weight_);
sprintf(txt, "Chain weight %.2g", chain_weight_);
_im_gui->DoSlider(txt, 0.f, 1.f, &chain_weight_);
snprintf(label, sizeof(label), "IK chain length: %d", chain_length_);
_im_gui->DoSlider(label, 0, kMaxChainLength, &chain_length_);
snprintf(label, sizeof(label), "Joint weight %.2g", joint_weight_);
_im_gui->DoSlider(label, 0.f, 1.f, &joint_weight_);
snprintf(label, sizeof(label), "Chain weight %.2g", chain_weight_);
_im_gui->DoSlider(label, 0.f, 1.f, &chain_weight_);
// Exposes animation runtime playback controls.
{
@ -413,15 +413,15 @@ class LookAtSampleApplication : public ozz::sample::Application {
const float kTargetRange = 3.f;
_im_gui->DoLabel("Animated extent");
sprintf(txt, "%.2g", target_extent_);
_im_gui->DoSlider(txt, 0.f, kTargetRange, &target_extent_);
snprintf(label, sizeof(label), "%.2g", target_extent_);
_im_gui->DoSlider(label, 0.f, kTargetRange, &target_extent_);
sprintf(txt, "x %.2g", target_offset_.x);
_im_gui->DoSlider(txt, -kTargetRange, kTargetRange, &target_offset_.x);
sprintf(txt, "y %.2g", target_offset_.y);
_im_gui->DoSlider(txt, -kTargetRange, kTargetRange, &target_offset_.y);
sprintf(txt, "z %.2g", target_offset_.z);
_im_gui->DoSlider(txt, -kTargetRange, kTargetRange, &target_offset_.z);
snprintf(label, sizeof(label), "x %.2g", target_offset_.x);
_im_gui->DoSlider(label, -kTargetRange, kTargetRange, &target_offset_.x);
snprintf(label, sizeof(label), "y %.2g", target_offset_.y);
_im_gui->DoSlider(label, -kTargetRange, kTargetRange, &target_offset_.y);
snprintf(label, sizeof(label), "z %.2g", target_offset_.z);
_im_gui->DoSlider(label, -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);
if (opened) {
const float kOffsetRange = .5f;
sprintf(txt, "x %.2g", eyes_offset_.x);
_im_gui->DoSlider(txt, -kOffsetRange, kOffsetRange, &eyes_offset_.x);
sprintf(txt, "y %.2g", eyes_offset_.y);
_im_gui->DoSlider(txt, -kOffsetRange, kOffsetRange, &eyes_offset_.y);
sprintf(txt, "z %.2g", eyes_offset_.z);
_im_gui->DoSlider(txt, -kOffsetRange, kOffsetRange, &eyes_offset_.z);
snprintf(label, sizeof(label), "x %.2g", eyes_offset_.x);
_im_gui->DoSlider(label, -kOffsetRange, kOffsetRange, &eyes_offset_.x);
snprintf(label, sizeof(label), "y %.2g", eyes_offset_.y);
_im_gui->DoSlider(label, -kOffsetRange, kOffsetRange, &eyes_offset_.y);
snprintf(label, sizeof(label), "z %.2g", eyes_offset_.z);
_im_gui->DoSlider(label, -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.
int joints = skeleton_->num_joints();
char label[64];
std::sprintf(label, "Joints count: %d", joints);
std::snprintf(label, sizeof(label), "Joints count: %d", joints);
// Uses an exponential scale in the slider to maintain enough precision in
// the lowest values.
@ -219,17 +219,17 @@ class MillipedeSampleApplication : public ozz::sample::Application {
root->transform.rotation = Quaternion::identity();
root->transform.scale = Float3::one();
char buf[16];
char number[16];
for (int i = 0; i < slice_count_; ++i) {
// Format joint number.
std::sprintf(buf, "%d", i);
std::snprintf(number, sizeof(number), "%d", i);
root->children.resize(3);
// Left leg.
RawSkeleton::Joint& lu = root->children[0];
lu.name = "lu";
lu.name += buf;
lu.name += number;
lu.transform.translation = kTransUp;
lu.transform.rotation = kRotLeftUp;
lu.transform.scale = Float3::one();
@ -237,7 +237,7 @@ class MillipedeSampleApplication : public ozz::sample::Application {
lu.children.resize(1);
RawSkeleton::Joint& ld = lu.children[0];
ld.name = "ld";
ld.name += buf;
ld.name += number;
ld.transform.translation = kTransDown;
ld.transform.rotation = kRotLeftDown;
ld.transform.scale = Float3::one();
@ -245,7 +245,7 @@ class MillipedeSampleApplication : public ozz::sample::Application {
ld.children.resize(1);
RawSkeleton::Joint& lf = ld.children[0];
lf.name = "lf";
lf.name += buf;
lf.name += number;
lf.transform.translation = Float3::x_axis();
lf.transform.rotation = Quaternion::identity();
lf.transform.scale = Float3::one();
@ -253,7 +253,7 @@ class MillipedeSampleApplication : public ozz::sample::Application {
// Right leg.
RawSkeleton::Joint& ru = root->children[1];
ru.name = "ru";
ru.name += buf;
ru.name += number;
ru.transform.translation = kTransUp;
ru.transform.rotation = kRotRightUp;
ru.transform.scale = Float3::one();
@ -261,7 +261,7 @@ class MillipedeSampleApplication : public ozz::sample::Application {
ru.children.resize(1);
RawSkeleton::Joint& rd = ru.children[0];
rd.name = "rd";
rd.name += buf;
rd.name += number;
rd.transform.translation = kTransDown;
rd.transform.rotation = kRotRightDown;
rd.transform.scale = Float3::one();
@ -269,7 +269,7 @@ class MillipedeSampleApplication : public ozz::sample::Application {
rd.children.resize(1);
RawSkeleton::Joint& rf = rd.children[0];
rf.name = "rf";
rf.name += buf;
rf.name += number;
rf.transform.translation = Float3::x_axis();
rf.transform.rotation = Quaternion::identity();
rf.transform.scale = Float3::one();
@ -277,7 +277,7 @@ class MillipedeSampleApplication : public ozz::sample::Application {
// Spine.
RawSkeleton::Joint& sp = root->children[2];
sp.name = "sp";
sp.name += buf;
sp.name += number;
sp.transform.translation = Float3(0.f, 0.f, kSpinLength);
sp.transform.rotation = Quaternion::identity();
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);
if (oc_open) {
char label[64];
std::sprintf(label, "Number of entities: %d", num_characters_);
std::snprintf(label, sizeof(label), "Number of entities: %d", num_characters_);
_im_gui->DoSlider(label, 1, kMaxCharacters, &num_characters_, .7f);
const int num_joints = num_characters_ * skeleton_.num_joints();
std::sprintf(label, "Number of joints: %d", num_joints);
std::snprintf(label, sizeof(label), "Number of joints: %d", num_joints);
_im_gui->DoLabel(label);
}
}
@ -303,11 +303,11 @@ class MultithreadSampleApplication : public ozz::sample::Application {
has_threading_support_);
if (enable_theading_) {
char label[64];
std::sprintf(label, "Grain size: %d", grain_size_);
std::snprintf(label, sizeof(label), "Grain size: %d", grain_size_);
_im_gui->DoSlider(label, kMinGrainSize, kMaxCharacters, &grain_size_,
.2f);
const int num_threads = monitor_.ThreadCount();
std::sprintf(label, "Thread/task count: %d/%d", num_threads,
std::snprintf(label, sizeof(label), "Thread/task count: %d/%d", num_threads,
monitor_.TaskCount());
_im_gui->DoLabel(label);
}

View File

@ -312,28 +312,31 @@ class OptimizeSampleApplication : public ozz::sample::Application {
rebuild |= _im_gui->DoCheckBox("Enable optimizations", &optimize_);
std::sprintf(label, "Tolerance: %0.2f mm", setting_.tolerance * 1000);
std::snprintf(label, sizeof(label), "Tolerance: %0.2f mm",
setting_.tolerance * 1000);
rebuild |= _im_gui->DoSlider(label, 0.f, .1f, &setting_.tolerance, .5f,
optimize_);
std::sprintf(label, "Distance: %0.2f mm", setting_.distance * 1000);
std::snprintf(label, sizeof(label), "Distance: %0.2f mm",
setting_.distance * 1000);
rebuild |= _im_gui->DoSlider(label, 0.f, 1.f, &setting_.distance, .5f,
optimize_);
rebuild |= _im_gui->DoCheckBox("Enable joint setting",
&joint_setting_enable_, optimize_);
std::sprintf(label, "%s (%d)", skeleton_.joint_names()[joint_], joint_);
std::snprintf(label, sizeof(label), "%s (%d)",
skeleton_.joint_names()[joint_], joint_);
rebuild |=
_im_gui->DoSlider(label, 0, skeleton_.num_joints() - 1, &joint_,
1.f, joint_setting_enable_ && optimize_);
std::sprintf(label, "Tolerance: %0.2f mm",
std::snprintf(label, sizeof(label), "Tolerance: %0.2f mm",
joint_setting_.tolerance * 1000);
rebuild |= _im_gui->DoSlider(label, 0.f, .1f, &joint_setting_.tolerance,
.5f, joint_setting_enable_ && optimize_);
std::sprintf(label, "Distance: %0.2f mm",
std::snprintf(label, sizeof(label), "Distance: %0.2f mm",
joint_setting_.distance * 1000);
rebuild |= _im_gui->DoSlider(label, 0.f, 1.f, &joint_setting_.distance,
.5f, joint_setting_enable_ && optimize_);
@ -356,18 +359,18 @@ class OptimizeSampleApplication : public ozz::sample::Application {
static bool open = true;
ozz::sample::ImGui::OpenClose ocb(_im_gui, "Memory size", &open);
if (open) {
std::sprintf(label, "Original: %dKB",
std::snprintf(label, sizeof(label), "Original: %dKB",
static_cast<int>(raw_animation_.size() >> 10));
_im_gui->DoLabel(label);
std::sprintf(label, "Optimized: %dKB (%.1f:1)",
std::snprintf(label, sizeof(label), "Optimized: %dKB (%.1f:1)",
static_cast<int>(raw_optimized_animation_.size() >> 10),
static_cast<float>(raw_animation_.size()) /
raw_optimized_animation_.size());
_im_gui->DoLabel(label);
std::sprintf(
label, "Compressed: %dKB (%.1f:1)",
std::snprintf(
label, sizeof(label), "Compressed: %dKB (%.1f:1)",
static_cast<int>(animation_rt_->size() >> 10),
static_cast<float>(raw_animation_.size()) / animation_rt_->size());
_im_gui->DoLabel(label);
@ -388,37 +391,36 @@ class OptimizeSampleApplication : public ozz::sample::Application {
// Show absolute error.
{
char szLabel[64];
static bool error_open = true;
ozz::sample::ImGui::OpenClose oc_stats(_im_gui, "Absolute error",
&error_open);
if (error_open) {
{
std::sprintf(szLabel, "Median error: %.2fmm",
std::snprintf(label, sizeof(label), "Median error: %.2fmm",
*error_record_med_.cursor());
const ozz::sample::Record::Statistics error_stats =
error_record_med_.GetStatistics();
_im_gui->DoGraph(szLabel, 0.f, error_stats.max, error_stats.latest,
_im_gui->DoGraph(label, 0.f, error_stats.max, error_stats.latest,
error_record_med_.cursor(),
error_record_med_.record_begin(),
error_record_med_.record_end());
}
{
std::sprintf(szLabel, "Maximum error: %.2fmm",
std::snprintf(label, sizeof(label), "Maximum error: %.2fmm",
*error_record_max_.cursor());
const ozz::sample::Record::Statistics error_stats =
error_record_max_.GetStatistics();
_im_gui->DoGraph(szLabel, 0.f, error_stats.max, error_stats.latest,
_im_gui->DoGraph(label, 0.f, error_stats.max, error_stats.latest,
error_record_max_.cursor(),
error_record_max_.record_begin(),
error_record_max_.record_end());
}
{
std::sprintf(szLabel, "Joint %d error: %.2fmm", joint_,
std::snprintf(label, sizeof(label), "Joint %d error: %.2fmm", joint_,
*joint_error_record_.cursor());
const ozz::sample::Record::Statistics error_stats =
joint_error_record_.GetStatistics();
_im_gui->DoGraph(szLabel, 0.f, error_stats.max, error_stats.latest,
_im_gui->DoGraph(label, 0.f, error_stats.max, error_stats.latest,
joint_error_record_.cursor(),
joint_error_record_.record_begin(),
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);
static float coeff = 1.f; // All power to the partial animation.
std::sprintf(label, "Upper body weight: %.2f", coeff);
std::snprintf(label, sizeof(label), "Upper body weight: %.2f", coeff);
_im_gui->DoSlider(label, 0.f, 1.f, &coeff, 1.f, automatic);
Sampler& lower_body_sampler = samplers_[kLowerBody];
@ -267,27 +267,27 @@ class PartialBlendSampleApplication : public ozz::sample::Application {
_im_gui->DoLabel("Manual settings:");
_im_gui->DoLabel("Lower body layer:");
std::sprintf(label, "Layer weight: %.2f",
std::snprintf(label, sizeof(label), "Layer weight: %.2f",
lower_body_sampler.weight_setting);
_im_gui->DoSlider(label, 0.f, 1.f, &lower_body_sampler.weight_setting,
1.f, !automatic);
std::sprintf(label, "Joints weight: %.2f",
std::snprintf(label, sizeof(label), "Joints weight: %.2f",
lower_body_sampler.joint_weight_setting);
_im_gui->DoSlider(label, 0.f, 1.f,
&lower_body_sampler.joint_weight_setting, 1.f,
!automatic);
_im_gui->DoLabel("Upper body layer:");
std::sprintf(label, "Layer weight: %.2f",
std::snprintf(label, sizeof(label), "Layer weight: %.2f",
upper_body_sampler.weight_setting);
_im_gui->DoSlider(label, 0.f, 1.f, &upper_body_sampler.weight_setting,
1.f, !automatic);
std::sprintf(label, "Joints weight: %.2f",
std::snprintf(label, sizeof(label), "Joints weight: %.2f",
upper_body_sampler.joint_weight_setting);
_im_gui->DoSlider(label, 0.f, 1.f,
&upper_body_sampler.joint_weight_setting, 1.f,
!automatic);
_im_gui->DoLabel("Global settings:");
std::sprintf(label, "Threshold: %.2f", threshold_);
std::snprintf(label, sizeof(label), "Threshold: %.2f", threshold_);
_im_gui->DoSlider(label, .01f, 1.f, &threshold_);
SetupPerJointWeights();
@ -301,7 +301,7 @@ class PartialBlendSampleApplication : public ozz::sample::Application {
_im_gui->DoLabel("Root of the upper body hierarchy:",
ozz::sample::ImGui::kLeft, false);
char label[64];
std::sprintf(label, "%s (%d)",
std::snprintf(label, sizeof(label), "%s (%d)",
skeleton_.joint_names()[upper_body_root_],
upper_body_root_);
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);
if (open) {
char label[255];
sprintf(label, "%d animated joints", skeleton_.num_joints());
std::snprintf(label, sizeof(label), "%d animated joints", skeleton_.num_joints());
_im_gui->DoLabel(label);
int influences = 0;
for (const auto& mesh : meshes_) {
influences = ozz::math::Max(influences, mesh.max_influences_count());
}
sprintf(label, "%d influences (max)", influences);
std::snprintf(label, sizeof(label), "%d influences (max)", influences);
_im_gui->DoLabel(label);
int vertices = 0;
for (const auto& mesh : meshes_) {
vertices += mesh.vertex_count();
}
sprintf(label, "%.1fK vertices", vertices / 1000.f);
std::snprintf(label, sizeof(label), "%.1fK vertices", vertices / 1000.f);
_im_gui->DoLabel(label);
int indices = 0;
for (const auto& mesh : meshes_) {
indices += mesh.triangle_index_count();
}
sprintf(label, "%.1fK triangles", indices / 3000.f);
std::snprintf(label, sizeof(label), "%.1fK triangles", indices / 3000.f);
_im_gui->DoLabel(label);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -46,14 +46,22 @@ add_library(AnimTestbedCode OBJECT
src/SyncTrack.cc
src/SyncTrack.h
src/ozzutils.cc
src/AnimGraph/AnimGraphResource.cc
src/AnimGraph/AnimGraphResource.h
# src/AnimGraph/AnimGraphBlendTreeResource.cc
# src/AnimGraph/AnimGraphBlendTreeResource.h
src/AnimGraph/AnimGraph.cc
src/AnimGraph/AnimGraph.h
src/AnimGraph/AnimGraphNodes.cc
src/AnimGraph/AnimGraphNodes.h
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(
AnimTestbedCode
@ -120,7 +128,7 @@ set(ozz_offline_test_objs
target_sources(runtests PRIVATE
tests/AnimGraphResourceTests.cc
tests/AnimGraphEvalTests.cc
# tests/AnimGraphEvalTests.cc
tests/NodeDescriptorTests.cc
tests/SyncTrackTests.cc
tests/main.cc

View File

@ -1,202 +1,3 @@
//
// 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,227 +5,34 @@
#ifndef ANIMTESTBED_ANIMGRAPH_H
#define ANIMTESTBED_ANIMGRAPH_H
#include "AnimNode.h"
#include "AnimGraphData.h"
#include "AnimGraphNodes.h"
//
// AnimGraph (Runtime)
//
struct AnimGraph {
AnimData m_local_transforms;
~AnimGraph() {}
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;
~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();
bool Init(AnimGraphContext& context) {
m_root_node->Init(context);
}
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];
void UpdateTime(float dt) {
m_time_last = m_time_now;
m_time_now = m_time_now + dt;
m_root_node->UpdateTime(m_time_last, m_time_now);
}
m_nodes.clear();
void Evaluate(AnimGraphContext& context) {
delete m_node_descriptor;
}
void updateOrderedNodes();
void updateOrderedNodesRecursive(int node_index);
void markActiveNodes();
bool checkIsNodeActive(AnimNode* node) {
return node->m_state != AnimNodeEvalState::Deactivated;
}
AnimNode* m_root_node = nullptr;
void evalSyncTracks();
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;
}
}
float m_time_now = 0.f;
float m_time_last = 0.f;
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;
}
Vec3 m_root_bone_translation = {};
Quat m_root_bone_rotation = {};
};
#endif //ANIMTESTBED_ANIMGRAPH_H
#endif // ANIMTESTBED_ANIMGRAPH_H

View File

@ -0,0 +1,201 @@
//
// 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

@ -0,0 +1,234 @@
//
// 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

@ -0,0 +1,616 @@
//
// 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

@ -0,0 +1,138 @@
//
// 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
//
struct AnimGraph;
struct AnimNode;
struct AnimData {
ozz::vector<ozz::math::SoaTransform> m_local_matrices;
@ -260,6 +260,14 @@ SocketType GetSocketType() {
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 {
std::vector<Socket> m_inputs;
std::vector<Socket> m_outputs;

View File

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

View File

@ -7,72 +7,13 @@
#include <vector>
#include "AnimNode.h"
#include "AnimGraphData.h"
#include "SyncTrack.h"
#include "ozz/animation/runtime/sampling_job.h"
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
//
@ -93,8 +34,8 @@ struct Blend2Node : public AnimNode {
float* i_blend_weight = nullptr;
bool m_sync_blend = false;
virtual void MarkActiveInputs(const std::vector<AnimGraphConnection>& inputs) override {
for (const auto & input : inputs) {
virtual void MarkActiveInputs() override {
for (const auto & input : m_inputs) {
AnimNode* input_node = input.m_source_node;
if (input_node == nullptr) {
continue;

View File

@ -1,5 +1,5 @@
//
// Created by martin on 04.02.22.
// Created by martin on 17.03.24.
//
#include "AnimGraphResource.h"
@ -9,6 +9,9 @@
#include "3rdparty/json/json.hpp"
#include "AnimGraphBlendTree.h"
#include "AnimGraphNodes.h"
using json = nlohmann::json;
//
@ -124,7 +127,7 @@ Socket sJsonToSocket(const json& json_data) {
json sAnimGraphNodeToJson(
const AnimNodeResource& node,
size_t node_index,
const std::vector<AnimGraphConnectionResource>& connections) {
const std::vector<BlendTreeConnectionResource>& connections) {
json result;
result["name"] = node.m_name;
@ -135,13 +138,13 @@ json sAnimGraphNodeToJson(
result["position"][j] = node.m_position[j];
}
for (const auto & socket : node.m_socket_accessor->m_inputs) {
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) {
for (const auto& connection : connections) {
if (connection.source_node_index == node_index
&& connection.source_socket_name == socket.m_name) {
socket_connected = true;
@ -154,14 +157,16 @@ json sAnimGraphNodeToJson(
}
}
for (auto & property : node.m_socket_accessor->m_properties) {
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 sAnimGraphNodeFromJson(
const json& json_node,
size_t node_index) {
AnimNodeResource result;
result.m_name = json_node["name"];
@ -173,7 +178,7 @@ AnimNodeResource sAnimGraphNodeFromJson(const json& json_node, size_t node_index
result.m_socket_accessor =
AnimNodeDescriptorFactory(result.m_type_name, result.m_anim_node);
for (auto & property : result.m_socket_accessor->m_properties) {
for (auto& property : result.m_socket_accessor->m_properties) {
property = sJsonToSocket(json_node["properties"][property.m_name]);
}
@ -198,8 +203,7 @@ AnimNodeResource sAnimGraphNodeFromJson(const json& json_node, size_t node_index
//
// AnimGraphConnectionResource <-> Json
//
json sAnimGraphConnectionToJson(
const AnimGraphConnectionResource& connection) {
json sAnimGraphConnectionToJson(const BlendTreeConnectionResource& connection) {
json result;
result["type"] = "AnimGraphConnectionResource";
@ -213,9 +217,9 @@ json sAnimGraphConnectionToJson(
return result;
}
AnimGraphConnectionResource sAnimGraphConnectionFromJson(
BlendTreeConnectionResource sAnimGraphConnectionFromJson(
const json& json_node) {
AnimGraphConnectionResource connection;
BlendTreeConnectionResource connection;
connection.source_node_index = json_node["source_node_index"];
connection.source_socket_name = json_node["source_socket_name"];
@ -226,58 +230,136 @@ AnimGraphConnectionResource sAnimGraphConnectionFromJson(
return connection;
}
void AnimGraphResource::clear() {
m_name = "";
bool AnimGraphResource::LoadFromFile(const char* filename) {
std::ifstream input_file;
input_file.open(filename);
std::stringstream buffer;
buffer << input_file.rdbuf();
clearNodes();
m_connections.clear();
json json_data = json::parse(buffer.str(), nullptr, false);
if (json_data.is_discarded()) {
std::cerr << "Error parsing json of file '" << filename << "'."
<< std::endl;
initGraphConnectors();
}
void AnimGraphResource::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;
return false;
}
m_nodes.clear();
if (json_data["type"] != "AnimNodeResource") {
std::cerr
<< "Invalid json object. Expected type 'AnimNodeResource' but got '"
<< json_data["type"] << "'." << std::endl;
return false;
}
if (json_data["node_type"] == "BlendTree") {
return LoadBlendTreeResourceFromJson(json_data);
} else if (json_data["node_type"] == "StateMachine") {
return LoadStateMachineResourceFromJson(json_data);
}
std::cerr << "Invalid node_type. Expected type 'BlendTree' or "
"'StateMachine' but got '"
<< json_data["node_type"] << "'." << std::endl;
return false;
}
void AnimGraphResource::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 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 {
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;
result["name"] = m_name;
result["type"] = "AnimGraphResource";
result["type"] = "AnimNodeResource";
result["node_type"] = "BlendTree";
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_blend_tree_resource.m_nodes.size(); i++) {
const AnimNodeResource& node = m_blend_tree_resource.m_nodes[i];
result["nodes"][i] =
sAnimGraphNodeToJson(node, i, m_blend_tree_resource.m_connections);
}
for (size_t i = 0; i < m_connections.size(); i++) {
const AnimGraphConnectionResource& connection = m_connections[i];
for (size_t i = 0; i < m_blend_tree_resource.m_connections.size(); i++) {
const BlendTreeConnectionResource& connection =
m_blend_tree_resource.m_connections[i];
result["connections"][i] = sAnimGraphConnectionToJson(connection);
}
// Graph inputs and outputs
{
const AnimNodeResource& graph_output_node = m_nodes[0];
const AnimNodeResource& graph_output_node =
m_blend_tree_resource.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 AnimNodeResource& graph_input_node = m_blend_tree_resource.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++) {
@ -293,107 +375,45 @@ bool AnimGraphResource::saveToFile(const char* filename) const {
return true;
}
bool AnimGraphResource::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;
void AnimGraphResource::CreateBlendTreeInstance(
AnimGraphBlendTree& result) const {
if (m_type != "BlendTree") {
std::cerr << "Invalid AnimGraphResource. Expected type 'BlendTree' but got '"
<< m_type << "'." << std::endl;
return;
}
if (json_data["type"] != "AnimGraphResource") {
std::cerr
<< "Invalid json object. Expected type 'AnimGraphResource' but got '"
<< json_data["type"] << "'." << std::endl;
}
CreateBlendTreeRuntimeNodeInstances(result);
PrepareBlendTreeIOData(result);
SetRuntimeNodeProperties(result);
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;
result.UpdateOrderedNodes();
result.ResetNodeStates();
}
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];
void AnimGraphResource::CreateBlendTreeRuntimeNodeInstances(
AnimGraphBlendTree& result) const {
for (int i = 0; i < m_blend_tree_resource.m_nodes.size(); i++) {
const AnimNodeResource& node_resource = m_blend_tree_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);
result.m_nodes.push_back(node);
// runtime node connections
instance.m_node_input_connections.emplace_back();
instance.m_node_output_connections.emplace_back();
result.m_node_input_connections.emplace_back();
result.m_node_output_connections.emplace_back();
}
}
void AnimGraphResource::prepareGraphIOData(AnimGraph& instance) const {
void AnimGraphResource::PrepareBlendTreeIOData(
AnimGraphBlendTree& 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;
m_blend_tree_resource.m_nodes[1].m_socket_accessor->m_outputs;
instance.m_node_descriptor->m_inputs =
m_blend_tree_resource.m_nodes[0].m_socket_accessor->m_inputs;
//
// graph inputs
@ -442,8 +462,9 @@ void AnimGraphResource::prepareGraphIOData(AnimGraph& instance) const {
// 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];
for (const auto& connection : m_blend_tree_resource.m_connections) {
const AnimNodeResource& source_node =
m_blend_tree_resource.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;
@ -455,11 +476,11 @@ void AnimGraphResource::prepareGraphIOData(AnimGraph& instance) const {
}
std::vector<NodeDescriptorBase*> instance_node_descriptors(
m_nodes.size(),
m_blend_tree_resource.m_nodes.size(),
nullptr);
for (int i = 0; i < m_nodes.size(); i++) {
for (int i = 0; i < m_blend_tree_resource.m_nodes.size(); i++) {
instance_node_descriptors[i] = AnimNodeDescriptorFactory(
m_nodes[i].m_type_name,
m_blend_tree_resource.m_nodes[i].m_type_name,
instance.m_nodes[i]);
}
@ -468,7 +489,7 @@ void AnimGraphResource::prepareGraphIOData(AnimGraph& instance) const {
instance.m_node_descriptor->m_outputs;
size_t connection_data_offset = 0;
for (const auto & connection : m_connections) {
for (const auto& connection : m_blend_tree_resource.m_connections) {
NodeDescriptorBase* source_node_descriptor =
instance_node_descriptors[connection.source_node_index];
NodeDescriptorBase* target_node_descriptor =
@ -513,9 +534,9 @@ void AnimGraphResource::prepareGraphIOData(AnimGraph& instance) const {
// const node inputs
//
std::vector<Socket*> const_inputs =
getConstNodeInputs( instance_node_descriptors);
m_blend_tree_resource.GetConstantNodeInputs(instance_node_descriptors);
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) {
// TODO: implement string const node input support
std::cerr << "Error: const inputs for strings not yet implemented!"
@ -531,7 +552,7 @@ void AnimGraphResource::prepareGraphIOData(AnimGraph& instance) const {
}
size_t const_input_buffer_offset = 0;
for (auto & i : const_inputs) {
for (auto& i : const_inputs) {
Socket* const_input = i;
// TODO: implement string const node input support
@ -539,27 +560,30 @@ void AnimGraphResource::prepareGraphIOData(AnimGraph& instance) const {
*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);
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++) {
for (int i = 0; i < m_blend_tree_resource.m_nodes.size(); i++) {
delete instance_node_descriptors[i];
}
}
void AnimGraphResource::setRuntimeNodeProperties(AnimGraph& instance) const {
for (int i = 2; i < m_nodes.size(); i++) {
const AnimNodeResource& node_resource = m_nodes[i];
void AnimGraphResource::SetRuntimeNodeProperties(
AnimGraphBlendTree& result) const {
for (int i = 2; i < m_blend_tree_resource.m_nodes.size(); i++) {
const AnimNodeResource& node_resource = m_blend_tree_resource.m_nodes[i];
NodeDescriptorBase* node_instance_accessor = AnimNodeDescriptorFactory(
node_resource.m_type_name,
instance.m_nodes[i]);
NodeDescriptorBase* node_instance_accessor =
AnimNodeDescriptorFactory(node_resource.m_type_name, result.m_nodes[i]);
std::vector<Socket>& resource_properties =
node_resource.m_socket_accessor->m_properties;
for (const auto & property : resource_properties) {
for (const auto& property : resource_properties) {
const std::string& name = property.m_name;
switch (property.m_type) {
@ -603,22 +627,15 @@ void AnimGraphResource::setRuntimeNodeProperties(AnimGraph& instance) const {
}
}
std::vector<Socket*> AnimGraphResource::getConstNodeInputs(
std::vector<NodeDescriptorBase*>& instance_node_descriptors) const {
std::vector<Socket*> result;
bool AnimGraphResource::SaveStateMachineResourceToFile(
const char* filename) const {
assert(false && "Not yet implemented");
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;
return false;
}
bool AnimGraphResource::LoadStateMachineResourceFromJson(nlohmann::json const& json_data) {
assert(false && "Not yet implemented");
return false;
}

View File

@ -1,23 +1,17 @@
//
// Created by martin on 04.02.22.
// Created by martin on 17.03.24.
//
#ifndef ANIMTESTBED_ANIMGRAPHRESOURCE_H
#define ANIMTESTBED_ANIMGRAPHRESOURCE_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;
#include "3rdparty/json/json.hpp"
struct AnimGraphBlendTree;
struct AnimGraphStateMachine;
struct AnimNodeResource {
std::string m_name;
@ -37,40 +31,33 @@ static inline AnimNodeResource AnimNodeResourceFactory(
return result;
}
//
// AnimGraphResource
//
struct AnimGraphConnectionResource {
struct BlendTreeConnectionResource {
size_t source_node_index = -1;
std::string source_socket_name;
size_t target_node_index = -1;
std::string target_socket_name;
};
struct AnimGraphResource {
std::string m_name;
struct BlendTreeResource {
std::vector<AnimNodeResource> m_nodes;
std::vector<AnimGraphConnectionResource> m_connections;
std::vector<BlendTreeConnectionResource> m_connections;
~AnimGraphResource() {
for (auto & m_node : m_nodes) {
delete m_node.m_anim_node;
delete m_node.m_socket_accessor;
}
void Reset() {
m_nodes.clear();
m_connections.clear();
}
AnimGraphResource() { clear(); }
void 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";
}
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]; }
AnimNodeResource& getGraphOutputNode() { return m_nodes[0]; }
AnimNodeResource& getGraphInputNode() { return m_nodes[1]; }
size_t getNodeIndex(const AnimNodeResource& node_resource) const {
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;
@ -80,18 +67,13 @@ struct AnimGraphResource {
return -1;
}
size_t addNode(const AnimNodeResource &node_resource) {
m_nodes.push_back(node_resource);
return m_nodes.size() - 1;
}
bool connectSockets(
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);
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()) {
@ -109,7 +91,7 @@ struct AnimGraphResource {
return false;
}
AnimGraphConnectionResource connection;
BlendTreeConnectionResource connection;
connection.source_node_index = source_node_index;
connection.source_socket_name = source_socket_name;
connection.target_node_index = target_node_index;
@ -119,28 +101,62 @@ struct AnimGraphResource {
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;
std::vector<Socket*> GetConstantNodeInputs(
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 false;
return result;
}
};
void createInstance(AnimGraph& result) const;
struct StateMachineTransitionResources {
size_t source_state_index = -1;
size_t target_state_index = -1;
float blend_time = 0.f;
bool sync_blend = false;
};
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;
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

View File

@ -0,0 +1,25 @@
//
// 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

@ -0,0 +1,35 @@
//
// 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

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

70
src/AnimGraph/AnimNode.h Normal file
View File

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

View File

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

View File

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