Initial commit

This commit is contained in:
Martin Felis
2021-11-11 21:22:24 +01:00
commit b78045ffe7
812 changed files with 421882 additions and 0 deletions
@@ -0,0 +1,53 @@
add_custom_command(
DEPENDS $<$<BOOL:${ozz_build_fbx}>:BUILD_DATA>
"${CMAKE_CURRENT_LIST_DIR}/README.md"
"${ozz_media_directory}/bin/pab_skeleton.ozz"
"${ozz_media_directory}/bin/pab_walk.ozz"
"${ozz_media_directory}/bin/pab_crossarms.ozz"
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/README.md"
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
"${CMAKE_CURRENT_BINARY_DIR}/media/animation_base.ozz"
"${CMAKE_CURRENT_BINARY_DIR}/media/animation_partial.ozz"
COMMAND ${CMAKE_COMMAND} -E make_directory media
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_LIST_DIR}/README.md" .
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_skeleton.ozz" "./media/skeleton.ozz"
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_walk.ozz" "./media/animation_base.ozz"
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_crossarms.ozz" "./media/animation_partial.ozz"
VERBATIM)
add_executable(sample_partial_blend
sample_partial_blend.cc
"${CMAKE_CURRENT_BINARY_DIR}/README.md"
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
"${CMAKE_CURRENT_BINARY_DIR}/media/animation_base.ozz"
"${CMAKE_CURRENT_BINARY_DIR}/media/animation_partial.ozz")
target_link_libraries(sample_partial_blend
sample_framework)
set_target_properties(sample_partial_blend
PROPERTIES FOLDER "samples")
if(EMSCRIPTEN)
# Resource files are embedded to the output file with emscripten
set_target_properties(sample_partial_blend
PROPERTIES LINK_FLAGS "--embed-file media --embed-file README.md --memory-init-file 0")
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/sample_partial_blend.html
${CMAKE_CURRENT_BINARY_DIR}/sample_partial_blend.js
${CMAKE_CURRENT_BINARY_DIR}/sample_partial_blend.wasm
DESTINATION bin/samples/partial_blend)
else()
install(TARGETS sample_partial_blend DESTINATION bin/samples/partial_blend)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/media DESTINATION bin/samples/partial_blend)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/README.md DESTINATION bin/samples/partial_blend)
endif(EMSCRIPTEN)
add_test(NAME sample_partial_blend COMMAND sample_partial_blend "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
add_test(NAME sample_partial_blend_path COMMAND sample_partial_blend "--skeleton=media/skeleton.ozz" "--lower_body_animation=media/animation_base.ozz" "--upper_body_animation=media/animation_partial.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
add_test(NAME sample_partial_blend_invalid_skeleton_path COMMAND sample_partial_blend "--skeleton=media/bad_skeleton.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
set_tests_properties(sample_partial_blend_invalid_skeleton_path PROPERTIES WILL_FAIL true)
add_test(NAME sample_partial_blend_invalid_animation_path1 COMMAND sample_partial_blend "--lower_body_animation=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
set_tests_properties(sample_partial_blend_invalid_animation_path1 PROPERTIES WILL_FAIL true)
add_test(NAME sample_partial_blend_invalid_animation_path2 COMMAND sample_partial_blend "--upper_body_animation=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
set_tests_properties(sample_partial_blend_invalid_animation_path2 PROPERTIES WILL_FAIL true)
+29
View File
@@ -0,0 +1,29 @@
# Ozz-animation sample: Partial animations blending
## Description
Uses partial animation blending technique to animate the lower and upper part of the skeleton with different animations.
## Concept
Partial animation blending uses the same job (aka ozz::animation::BlendingJob) as the full blending technique (see "blend" sample for more details). Partial blending uses a coefficient per joint to weight the animation influence, for every joint. This per-joint weight is modulated with layer's weight to compute the final influence of every joint. This set of weight coefficients are provided to the BlendingJob::Layer as an array of SoA floating point values, arranged in the same order as skeleton joints.
The sample uses ozz::animation::IterateJointsDF helper function to iterate all children of the upper body "root" joint, and set up per-joint weight masks.
## Sample usage
The sample proposes two modes of interaction:
- Automatic: With this mode the GUI proposes to automatically control all blending parameters from a single "upper body weight" slider. The selected coefficient is used to set up the per-joint weights of the upper body layer, while lower body weights are set to one minus upper body weights.
- Manuel: Allows to setup all blending parameters independently.
The GUI also proposes to select the "root" joint of the upper body hierarchy, which is affected by the partially animated layer.
## Implementation
1. This sample extends "blend" sample, and uses the same procedure to load skeleton and animations objects.
2. Samples each animation at their own speed, they do not need to be synchronized.
3. Prepares per-joint weight masks for the two lower and upper body layers. If interaction mode is set to automatic, then lower and upper body weight coefficients are computed from the single "Upper body weight" parameter. In this case upper body coefficient equals one minus lower body coefficient. If interaction mode is set to manual, then all parameters are set manually.
4. Uses ozz::animation::IterateJointsDF helper function to iterate all children of the upper body root joint, and set up per-joint weight masks as follows (note that weight coefficients are stored as SoA floats):
- Upper body weight mask: Affects upper body weight coefficient to all the joints that are part of the upper body, all others are set to zero.
- Lower body weight mask: Affects lower body weight coefficient to all the joints that are part of the lower body (ie: all the ones that are not part of the upper body), all others are set to one.
5. Sets ozz::animation::BlendingJob object with the two layers for the lower and upper body. Per-joint weight masks are provided as an input to each layer. All other arguments (bind-pose, input local-space transforms, weights, output) are the same as those used by the full skeleton hierarchy blending. See "blend" sample for more details.
6. Converts local-space transformations, outputted from the blending stage, to model-space matrices using ozz::animation::LocalToModelJob. It also takes as input the skeleton (to know about joint's hierarchy). Output is model-space matrices array.
7. Model-space matrices array can then be used for rendering (to skin a mesh) or updating the scene graph.
@@ -0,0 +1,405 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "ozz/animation/runtime/animation.h"
#include "ozz/animation/runtime/blending_job.h"
#include "ozz/animation/runtime/local_to_model_job.h"
#include "ozz/animation/runtime/sampling_job.h"
#include "ozz/animation/runtime/skeleton.h"
#include "ozz/animation/runtime/skeleton_utils.h"
#include "ozz/base/log.h"
#include "ozz/base/containers/vector.h"
#include "ozz/base/maths/simd_math.h"
#include "ozz/base/maths/soa_transform.h"
#include "ozz/base/maths/vec_float.h"
#include "ozz/options/options.h"
#include "framework/application.h"
#include "framework/imgui.h"
#include "framework/renderer.h"
#include "framework/utils.h"
#include <cstring>
// Skeleton archive can be specified as an option.
OZZ_OPTIONS_DECLARE_STRING(skeleton,
"Path to the skeleton (ozz archive format).",
"media/skeleton.ozz", false)
// Lower body animation archive can be specified as an option.
OZZ_OPTIONS_DECLARE_STRING(
lower_body_animation,
"Path to the lower body animation(ozz archive format).",
"media/animation_base.ozz", false)
// Upper body animation archive can be specified as an option.
OZZ_OPTIONS_DECLARE_STRING(
upper_body_animation,
"Path to the upper body animation (ozz archive format).",
"media/animation_partial.ozz", false)
class PartialBlendSampleApplication : public ozz::sample::Application {
public:
PartialBlendSampleApplication()
: upper_body_root_(0),
threshold_(ozz::animation::BlendingJob().threshold) {}
protected:
// Updates current animation time and skeleton pose.
virtual bool OnUpdate(float _dt, float) {
// Updates and samples both animations to their respective local space
// transform buffers.
for (int i = 0; i < kNumLayers; ++i) {
Sampler& sampler = samplers_[i];
// Updates animations time.
sampler.controller.Update(sampler.animation, _dt);
// Setup sampling job.
ozz::animation::SamplingJob sampling_job;
sampling_job.animation = &sampler.animation;
sampling_job.cache = &sampler.cache;
sampling_job.ratio = sampler.controller.time_ratio();
sampling_job.output = make_span(sampler.locals);
// Samples animation.
if (!sampling_job.Run()) {
return false;
}
}
// Blends animations.
// Blends the local spaces transforms computed by sampling all animations
// (1st stage just above), and outputs the result to the local space
// transform buffer blended_locals_
// Prepares blending layers.
ozz::animation::BlendingJob::Layer layers[kNumLayers];
for (int i = 0; i < kNumLayers; ++i) {
layers[i].transform = make_span(samplers_[i].locals);
layers[i].weight = samplers_[i].weight_setting;
// Set per-joint weights for the partially blended layer.
layers[i].joint_weights = make_span(samplers_[i].joint_weights);
}
// Setups blending job.
ozz::animation::BlendingJob blend_job;
blend_job.threshold = threshold_;
blend_job.layers = layers;
blend_job.bind_pose = skeleton_.joint_bind_poses();
blend_job.output = make_span(blended_locals_);
// Blends.
if (!blend_job.Run()) {
return false;
}
// Converts from local space to model space matrices.
// Gets the output of the blending stage, and converts it to model space.
// Setup local-to-model conversion job.
ozz::animation::LocalToModelJob ltm_job;
ltm_job.skeleton = &skeleton_;
ltm_job.input = make_span(blended_locals_);
ltm_job.output = make_span(models_);
// Run ltm job.
if (!ltm_job.Run()) {
return false;
}
return true;
}
// Samples animation, transforms to model space and renders.
virtual bool OnDisplay(ozz::sample::Renderer* _renderer) {
return _renderer->DrawPosture(skeleton_, make_span(models_),
ozz::math::Float4x4::identity());
}
virtual bool OnInitialize() {
// Reading skeleton.
if (!ozz::sample::LoadSkeleton(OPTIONS_skeleton, &skeleton_)) {
return false;
}
const int num_joints = skeleton_.num_joints();
const int num_soa_joints = skeleton_.num_soa_joints();
// Reading animations.
const char* filenames[] = {OPTIONS_lower_body_animation,
OPTIONS_upper_body_animation};
for (int i = 0; i < kNumLayers; ++i) {
Sampler& sampler = samplers_[i];
if (!ozz::sample::LoadAnimation(filenames[i], &sampler.animation)) {
return false;
}
// Allocates sampler runtime buffers.
sampler.locals.resize(num_soa_joints);
// Allocates per-joint weights used for the partial animation. Note that
// this is a Soa structure.
sampler.joint_weights.resize(num_soa_joints);
// Allocates a cache that matches animation requirements.
sampler.cache.Resize(num_joints);
}
// Default weight settings.
Sampler& lower_body_sampler = samplers_[kLowerBody];
lower_body_sampler.weight_setting = 1.f;
lower_body_sampler.joint_weight_setting = 0.f;
Sampler& upper_body_sampler = samplers_[kUpperBody];
upper_body_sampler.weight_setting = 1.f;
upper_body_sampler.joint_weight_setting = 1.f;
// Allocates local space runtime buffers of blended data.
blended_locals_.resize(num_soa_joints);
// Allocates model space runtime buffers of blended data.
models_.resize(num_joints);
// Finds the "Spine1" joint in the joint hierarchy.
for (int i = 0; i < num_joints; ++i) {
if (std::strstr(skeleton_.joint_names()[i], "Spine1")) {
upper_body_root_ = i;
break;
}
}
SetupPerJointWeights();
return true;
}
// Helper functor used to set weights while traversing joints hierarchy.
struct WeightSetupIterator {
WeightSetupIterator(ozz::vector<ozz::math::SimdFloat4>* _weights,
float _weight_setting)
: weights(_weights), weight_setting(_weight_setting) {}
void operator()(int _joint, int) {
ozz::math::SimdFloat4& soa_weight = weights->at(_joint / 4);
soa_weight = ozz::math::SetI(
soa_weight, ozz::math::simd_float4::Load1(weight_setting),
_joint % 4);
}
ozz::vector<ozz::math::SimdFloat4>* weights;
float weight_setting;
};
void SetupPerJointWeights() {
// Setup partial animation mask. This mask is defined by a weight_setting
// assigned to each joint of the hierarchy. Joint to disable are set to a
// weight_setting of 0.f, and enabled joints are set to 1.f.
// Per-joint weights of lower and upper body layers have opposed values
// (weight_setting and 1 - weight_setting) in order for a layer to select
// joints that are rejected by the other layer.
Sampler& lower_body_sampler = samplers_[kLowerBody];
Sampler& upper_body_sampler = samplers_[kUpperBody];
// Disables all joints: set all weights to 0.
for (int i = 0; i < skeleton_.num_soa_joints(); ++i) {
lower_body_sampler.joint_weights[i] = ozz::math::simd_float4::one();
upper_body_sampler.joint_weights[i] = ozz::math::simd_float4::zero();
}
// Sets the weight_setting of all the joints children of the lower and upper
// body weights. Note that they are stored in SoA format.
WeightSetupIterator lower_it(&lower_body_sampler.joint_weights,
lower_body_sampler.joint_weight_setting);
ozz::animation::IterateJointsDF(skeleton_, lower_it, upper_body_root_);
WeightSetupIterator upper_it(&upper_body_sampler.joint_weights,
upper_body_sampler.joint_weight_setting);
ozz::animation::IterateJointsDF(skeleton_, upper_it, upper_body_root_);
}
virtual void OnDestroy() {}
virtual bool OnGui(ozz::sample::ImGui* _im_gui) {
// Exposes blending parameters.
{
static bool open = true;
ozz::sample::ImGui::OpenClose oc(_im_gui, "Blending parameters", &open);
if (open) {
char label[64];
static bool automatic = true;
_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);
_im_gui->DoSlider(label, 0.f, 1.f, &coeff, 1.f, automatic);
Sampler& lower_body_sampler = samplers_[kLowerBody];
Sampler& upper_body_sampler = samplers_[kUpperBody];
if (automatic) {
// Blending values are forced when "automatic" mode is selected.
lower_body_sampler.weight_setting = 1.f;
lower_body_sampler.joint_weight_setting = 1.f - coeff;
upper_body_sampler.weight_setting = 1.f;
upper_body_sampler.joint_weight_setting = coeff;
}
_im_gui->DoLabel("Manual settings:");
_im_gui->DoLabel("Lower body layer:");
std::sprintf(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",
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",
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",
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_);
_im_gui->DoSlider(label, .01f, 1.f, &threshold_);
SetupPerJointWeights();
}
}
// Exposes selection of the root of the partial blending hierarchy.
{
static bool open = true;
ozz::sample::ImGui::OpenClose oc(_im_gui, "Root", &open);
if (open && skeleton_.num_joints() != 0) {
_im_gui->DoLabel("Root of the upper body hierarchy:",
ozz::sample::ImGui::kLeft, false);
char label[64];
std::sprintf(label, "%s (%d)",
skeleton_.joint_names()[upper_body_root_],
upper_body_root_);
if (_im_gui->DoSlider(label, 0, skeleton_.num_joints() - 1,
&upper_body_root_)) {
SetupPerJointWeights();
}
}
}
// Exposes animations runtime playback controls.
{
static bool oc_open = true;
ozz::sample::ImGui::OpenClose oc(_im_gui, "Animation control", &oc_open);
if (oc_open) {
static bool open[kNumLayers] = {true, true};
const char* oc_names[kNumLayers] = {"Lower body animation",
"Upper body animation"};
for (int i = 0; i < kNumLayers; ++i) {
Sampler& sampler = samplers_[i];
ozz::sample::ImGui::OpenClose loc(_im_gui, oc_names[i], nullptr);
if (open[i]) {
sampler.controller.OnGui(sampler.animation, _im_gui);
}
}
}
}
return true;
}
virtual void GetSceneBounds(ozz::math::Box* _bound) const {
ozz::sample::ComputePostureBounds(make_span(models_), _bound);
}
private:
// Runtime skeleton.
ozz::animation::Skeleton skeleton_;
// The number of layers to blend.
enum {
kLowerBody = 0,
kUpperBody = 1,
kNumLayers = 2,
};
// Sampler structure contains all the data required to sample a single
// animation.
struct Sampler {
// Constructor, default initialization.
Sampler() : weight_setting(1.f), joint_weight_setting(1.f) {}
// Playback animation controller. This is a utility class that helps with
// controlling animation playback time.
ozz::sample::PlaybackController controller;
// Blending weight_setting for the layer.
float weight_setting;
// Blending weight_setting setting of the joints of this layer that are
// affected
// by the masking.
float joint_weight_setting;
// Runtime animation.
ozz::animation::Animation animation;
// Sampling cache.
ozz::animation::SamplingCache cache;
// Buffer of local transforms as sampled from animation_.
ozz::vector<ozz::math::SoaTransform> locals;
// Per-joint weights used to define the partial animation mask. Allows to
// select which joints are considered during blending, and their individual
// weight_setting.
ozz::vector<ozz::math::SimdFloat4> joint_weights;
} samplers_[kNumLayers]; // kNumLayers animations to blend.
// Index of the joint at the base of the upper body hierarchy.
int upper_body_root_;
// Blending job bind pose threshold.
float threshold_;
// Buffer of local transforms which stores the blending result.
ozz::vector<ozz::math::SoaTransform> blended_locals_;
// Buffer of model space matrices. These are computed by the local-to-model
// job after the blending stage.
ozz::vector<ozz::math::Float4x4> models_;
};
int main(int _argc, const char** _argv) {
const char* title = "Ozz-animation sample: Partial animations blending";
return PartialBlendSampleApplication().Run(_argc, _argv, "1.0", title);
}