Initial commit
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
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_atlas_raw.ozz"
|
||||
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation_raw.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_atlas_raw.ozz" "./media/animation_raw.ozz"
|
||||
VERBATIM)
|
||||
|
||||
add_executable(sample_optimize
|
||||
sample_optimize.cc
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation_raw.ozz")
|
||||
|
||||
target_link_libraries(sample_optimize
|
||||
ozz_animation_offline
|
||||
sample_framework)
|
||||
|
||||
set_target_properties(sample_optimize
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
# Resource files are embedded to the output file with emscripten.
|
||||
set_target_properties(sample_optimize
|
||||
PROPERTIES LINK_FLAGS "--embed-file media --embed-file README.md --memory-init-file 0")
|
||||
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_optimize.html
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_optimize.js
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_optimize.wasm
|
||||
DESTINATION bin/samples/optimize)
|
||||
else()
|
||||
install(TARGETS sample_optimize DESTINATION bin/samples/optimize)
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/media DESTINATION bin/samples/optimize)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/README.md DESTINATION bin/samples/optimize)
|
||||
endif(EMSCRIPTEN)
|
||||
|
||||
add_test(NAME sample_optimize_default COMMAND sample_optimize "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_optimize_path1 COMMAND sample_optimize "--skeleton=media/skeleton.ozz" "--animation=media/animation_raw.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_optimize_invalid_skeleton_path COMMAND sample_optimize "--skeleton=media/unexisting.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_optimize_invalid_skeleton_path PROPERTIES WILL_FAIL true)
|
||||
add_test(NAME sample_optimize_invalid_animation_path COMMAND sample_optimize "--animation=media/unexisting.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_optimize_invalid_animation_path PROPERTIES WILL_FAIL true)
|
||||
@@ -0,0 +1,37 @@
|
||||
# Ozz-animation sample: Animation keyframe optimization
|
||||
|
||||
## Description
|
||||
|
||||
This sample removes redundant keyframes (within a tolerance) from an animation in order to optimize its memory footprint and runtime update cost. It can display optimized and non-optimized version of the animation, as well as the difference between the two (aka the error generated by the optimization).
|
||||
|
||||
## Concept
|
||||
|
||||
This sample loads a runtime skeleton and a raw animation from ozz files. The raw animation (which is usually reserved for offline purpose) was exported using fbx2ozz tool with optimizations disabled. The raw animation object is converted to a run-time object using `ozz::animation::offline::AnimationBuilder` utility.
|
||||
Prior to the conversion, animation is optimized by removing redundant keyframes (within a tolerance). It uses `ozz::animation::offline::AnimationOptimizer` utility that takes as input a raw animation and outputs a new optimized one.
|
||||
The sample displays the source raw animation median and maximum "error" generated by the optimization stage. Actually it also includes the "error" generated by the quantization of the translation, rotation and scale values performed during runtime animation object creation.
|
||||
|
||||
## Sample usage
|
||||
|
||||
This samples allows to tweak animation key-frame optimization stage using 2 parameters:
|
||||
- tolerance: The maximum error that an optimization is allowed to generate on a whole joint hierarchy.
|
||||
- distance: The distance (from the joint) at which error is measured (if bigger that joint hierarchy). This allows to emulate effect on skinning or a long object (like a sword) attached to a joint (hand).
|
||||
The sample also exposes parameters to override the tolerance for one of the joint, as supported by `ozz::animation::offline::AnimationOptimizer`. This is useful for example when a joint a the skeleton has a contact point which requires more precision than other part.
|
||||
Optimization result can be checked within the sample:
|
||||
- Optimization quality: 3 rendering mode are provided to check optimization quality:
|
||||
- "Raw animation": Renders the source raw animation. No tolerance optimization or compression is performed.
|
||||
- "Run time" (default): Result of the optimization and compression passes, using sample tolerances.
|
||||
- "Absolute error": Renders the difference between the optimized and the source animation. This is especially useful for debugging as it makes it easy to detect compression artifacts.
|
||||
- Optimization quantity: Run-time animation size is displayed in the UI.
|
||||
|
||||
Some other parameters can be tuned from sample UI to affect animation playback.
|
||||
|
||||
## Implementation
|
||||
|
||||
1. Import the runtime skeleton and the unoptimized raw animation objects. These are imported from files as described in the "how to load an object from a file" section. Note that the animation is still in an offline format at this stage.
|
||||
2. Optimizes animation by removing interpolable keyframes using `ozz::animation::offline::AnimationOptimizer` utility. It processes the source raw animation and outputs a new one. Optimization tolerance values like translation (meter), rotation (radian) and scale (percentage) are exposed to sample UI.
|
||||
3. Convert the offline animation to the runtime format, using `ozz::animation::offline::AnimationBuilder`. This utility takes as input the optimized RawAnimation and outputs the runtime `ozz::animation::Animation`. The runtime format is the one used for sampling. In opposition with the offline one, it cannot be edited/modified. It is optimized for runtime usage in term of memory layout (cache coherence for sampling) and footprint (compression scheme).
|
||||
4. Local-space transformations (in SoA format as required by the runtime pipeline) are computed from the runtime and the source raw animations according the selected rendering mode:
|
||||
- "Raw animation": Every track of the source raw animation is traversed in order to extract each relevant keyframes. Keyframes are interpolated to match the exact expected time. This implementation isn't part of the API but done by the sample, as raw animations aren't intended to be used this way in runtime. Even though it works, performance are quite low compared with sampling a runtime animation. On the other end the result is the closest to the input file, kind of lossless.
|
||||
- "Run time" (default): Samples optimized runtime animation as usual.
|
||||
- "Absolute error": Samples both Raw and runtime animations as described above. Resulting local transformed are subtracted in order to compute the error generated by the runtime animation compression and optimization.
|
||||
5. The remaining code of the sample allocates runtime buffers and samples animation every frame. See playback sample for more details.
|
||||
@@ -0,0 +1,578 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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/local_to_model_job.h"
|
||||
#include "ozz/animation/runtime/sampling_job.h"
|
||||
#include "ozz/animation/runtime/skeleton.h"
|
||||
|
||||
#include "ozz/animation/offline/animation_builder.h"
|
||||
#include "ozz/animation/offline/animation_optimizer.h"
|
||||
#include "ozz/animation/offline/raw_animation.h"
|
||||
#include "ozz/animation/offline/raw_animation_utils.h"
|
||||
|
||||
#include "ozz/base/memory/unique_ptr.h"
|
||||
|
||||
#include "ozz/base/io/archive.h"
|
||||
#include "ozz/base/io/stream.h"
|
||||
#include "ozz/base/log.h"
|
||||
|
||||
#include "ozz/base/maths/math_ex.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/profile.h"
|
||||
#include "framework/renderer.h"
|
||||
#include "framework/utils.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
// Skeleton and animation file can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(skeleton, "Path to the runtime skeleton file.",
|
||||
"media/skeleton.ozz", false)
|
||||
|
||||
OZZ_OPTIONS_DECLARE_STRING(animation, "Path to the raw animation file.",
|
||||
"media/animation_raw.ozz", false)
|
||||
|
||||
namespace {
|
||||
|
||||
// Loads a raw animation from a file.
|
||||
bool LoadAnimation(const char* _filename,
|
||||
ozz::animation::offline::RawAnimation* _animation) {
|
||||
assert(_filename && _animation);
|
||||
ozz::log::Out() << "Loading raw animation archive: " << _filename << "."
|
||||
<< std::endl;
|
||||
ozz::io::File file(_filename, "rb");
|
||||
if (!file.opened()) {
|
||||
ozz::log::Err() << "Failed to open animation file " << _filename << "."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
ozz::io::IArchive archive(&file);
|
||||
if (!archive.TestTag<ozz::animation::offline::RawAnimation>()) {
|
||||
ozz::log::Err() << "Failed to load raw animation instance from file "
|
||||
<< _filename << "." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Once the tag is validated, reading cannot fail.
|
||||
archive >> *_animation;
|
||||
|
||||
// Ensure animation is valid.
|
||||
return _animation->Validate();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class OptimizeSampleApplication : public ozz::sample::Application {
|
||||
public:
|
||||
OptimizeSampleApplication()
|
||||
: selected_display_(eRuntimeAnimation),
|
||||
optimize_(true),
|
||||
joint_setting_enable_(true),
|
||||
joint_(0),
|
||||
error_record_med_(64),
|
||||
error_record_max_(64),
|
||||
joint_error_record_(64) {}
|
||||
|
||||
protected:
|
||||
// Updates current animation time and skeleton pose.
|
||||
virtual bool OnUpdate(float _dt, float) {
|
||||
// Updates current animation time.
|
||||
controller_.Update(*animation_rt_, _dt);
|
||||
|
||||
// Prepares sampling job.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.cache = &cache_;
|
||||
sampling_job.ratio = controller_.time_ratio();
|
||||
|
||||
// Samples optimized animation (_according to the display mode).
|
||||
sampling_job.animation = animation_rt_.get();
|
||||
sampling_job.output = make_span(locals_rt_);
|
||||
if (!sampling_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Also samples non-optimized animation, from the raw animation.
|
||||
if (!SampleRawAnimation(raw_animation_,
|
||||
controller_.time_ratio() * raw_animation_.duration,
|
||||
make_span(locals_raw_))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Computes difference between the optimized and non-optimized animations
|
||||
// in local space, and rebinds it to the bind pose.
|
||||
{
|
||||
const ozz::span<const ozz::math::SoaTransform>& bind_poses =
|
||||
skeleton_.joint_bind_poses();
|
||||
const ozz::math::SoaTransform* bind_pose = bind_poses.begin();
|
||||
const ozz::math::SoaTransform* locals_raw = locals_raw_.data();
|
||||
const ozz::math::SoaTransform* locals_rt = locals_rt_.data();
|
||||
ozz::math::SoaTransform* locals_diff = locals_diff_.data();
|
||||
for (; bind_pose < bind_poses.end();
|
||||
++locals_raw, ++locals_rt, ++locals_diff, ++bind_pose) {
|
||||
assert(locals_raw < array_end(locals_raw_) &&
|
||||
locals_rt < array_end(locals_rt_) &&
|
||||
locals_diff < array_end(locals_diff_) &&
|
||||
bind_pose < bind_poses.end());
|
||||
|
||||
// Computes difference.
|
||||
const ozz::math::SoaTransform diff = {
|
||||
locals_rt->translation - locals_raw->translation,
|
||||
locals_rt->rotation * Conjugate(locals_raw->rotation),
|
||||
locals_rt->scale / locals_raw->scale};
|
||||
|
||||
// Rebinds to the bind pose in the diff buffer.
|
||||
locals_diff->translation = bind_pose->translation + diff.translation;
|
||||
locals_diff->rotation = bind_pose->rotation * diff.rotation;
|
||||
locals_diff->scale = bind_pose->scale * diff.scale;
|
||||
}
|
||||
}
|
||||
|
||||
// Converts from local space to model space matrices.
|
||||
ozz::animation::LocalToModelJob ltm_job;
|
||||
ltm_job.skeleton = &skeleton_;
|
||||
|
||||
// Optimized samples.
|
||||
ltm_job.input = make_span(locals_rt_);
|
||||
ltm_job.output = make_span(models_rt_);
|
||||
if (!ltm_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Non-optimized samples (from the raw animation).
|
||||
ltm_job.input = make_span(locals_raw_);
|
||||
ltm_job.output = make_span(models_raw_);
|
||||
if (!ltm_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Difference between optimized and non-optimized samples.
|
||||
ltm_job.input = make_span(locals_diff_);
|
||||
ltm_job.output = make_span(models_diff_);
|
||||
if (!ltm_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Computes the absolute error, aka the difference between the raw and
|
||||
// runtime model space transformation.
|
||||
const size_t num_joints = models_rt_.size();
|
||||
float errors_sq[ozz::animation::Skeleton::kMaxJoints];
|
||||
for (size_t i = 0; i < num_joints; ++i) {
|
||||
// Computes error based on the translation difference.
|
||||
errors_sq[i] = ozz::math::GetX(ozz::math::Length3Sqr(
|
||||
models_rt_[i].cols[3] - models_raw_[i].cols[3]));
|
||||
}
|
||||
|
||||
std::sort(errors_sq, errors_sq + models_rt_.size());
|
||||
error_record_med_.Push(std::sqrt(errors_sq[num_joints / 2]) * 1000.f);
|
||||
error_record_max_.Push(std::sqrt(errors_sq[num_joints - 1]) * 1000.f);
|
||||
joint_error_record_.Push(std::sqrt(errors_sq[joint_]) * 1000.f);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SampleRawAnimation(
|
||||
const ozz::animation::offline::RawAnimation& _animation, float _time,
|
||||
ozz::span<ozz::math::SoaTransform> _locals) {
|
||||
// Ensure output is big enough.
|
||||
if (_locals.size() * 4 < _animation.tracks.size() &&
|
||||
locals_raw_aos_.size() * 4 < _animation.tracks.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sample raw animation and converts AoS transforms to SoA transform array.
|
||||
assert(_animation.Validate() && "Animation should be valid.");
|
||||
bool success = true;
|
||||
for (int i = 0; success && i < _animation.num_tracks(); i += 4) {
|
||||
ozz::math::SimdFloat4 translations[4];
|
||||
ozz::math::SimdFloat4 rotations[4];
|
||||
ozz::math::SimdFloat4 scales[4];
|
||||
|
||||
// Works on 4 consecutive tracks, or what remains to be processed if it's
|
||||
// lower than 4.
|
||||
const int jmax = ozz::math::Min(_animation.num_tracks() - i, 4);
|
||||
for (int j = 0; success && j < jmax; ++j) {
|
||||
// Samples raw animation.
|
||||
ozz::math::Transform transform;
|
||||
success &= SampleTrack(_animation.tracks[i + j], _time, &transform);
|
||||
|
||||
// Convert transform to AoS SimdFloat4 values.
|
||||
translations[j] =
|
||||
ozz::math::simd_float4::Load3PtrU(&transform.translation.x);
|
||||
rotations[j] = ozz::math::simd_float4::LoadPtrU(&transform.rotation.x);
|
||||
scales[j] = ozz::math::simd_float4::Load3PtrU(&transform.scale.x);
|
||||
}
|
||||
// Fills remaining transforms.
|
||||
for (int j = jmax; j < 4; ++j) {
|
||||
translations[j] = ozz::math::simd_float4::zero();
|
||||
rotations[j] = ozz::math::simd_float4::w_axis();
|
||||
scales[j] = ozz::math::simd_float4::one();
|
||||
}
|
||||
// Stores AoS keyframes to the SoA output.
|
||||
ozz::math::SoaTransform& output = _locals[i / 4];
|
||||
ozz::math::Transpose4x3(translations, &output.translation.x);
|
||||
ozz::math::Transpose4x4(rotations, &output.rotation.x);
|
||||
ozz::math::Transpose4x3(scales, &output.scale.x);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Selects model space matrices according to the display mode.
|
||||
ozz::span<const ozz::math::Float4x4> models() const {
|
||||
switch (selected_display_) {
|
||||
case eRuntimeAnimation:
|
||||
return make_span(models_rt_);
|
||||
case eRawAnimation:
|
||||
return make_span(models_raw_);
|
||||
case eAbsoluteError:
|
||||
return make_span(models_diff_);
|
||||
default: {
|
||||
assert(false && "Invalid display mode");
|
||||
return make_span(models_rt_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Samples animation, transforms to model space and renders.
|
||||
virtual bool OnDisplay(ozz::sample::Renderer* _renderer) {
|
||||
bool success = true;
|
||||
|
||||
const ozz::span<const ozz::math::Float4x4> transforms = models();
|
||||
|
||||
// Renders posture.
|
||||
success &= _renderer->DrawPosture(skeleton_, transforms,
|
||||
ozz::math::Float4x4::identity());
|
||||
|
||||
if (joint_setting_enable_) {
|
||||
// Renders an axes with targetted joint transform.
|
||||
const ozz::math::Float4x4 axes_scale = ozz::math::Float4x4::Scaling(
|
||||
ozz::math::simd_float4::Load1(joint_setting_.distance));
|
||||
success &= _renderer->DrawAxes(transforms[joint_] * axes_scale);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
virtual bool OnInitialize() {
|
||||
// Imports offline skeleton from a binary file.
|
||||
if (!ozz::sample::LoadSkeleton(OPTIONS_skeleton, &skeleton_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Imports offline animation from a binary file.
|
||||
// Invalid animations are rejected by the load function.
|
||||
if (!LoadAnimation(OPTIONS_animation, &raw_animation_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int num_joints = skeleton_.num_joints();
|
||||
const int num_soa_joints = skeleton_.num_soa_joints();
|
||||
|
||||
// Finds the joint where the object should be attached.
|
||||
for (int i = 0; i < num_joints; i++) {
|
||||
if (std::strstr(skeleton_.joint_names()[i], "L Finger2Nub")) {
|
||||
joint_ = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Builds the runtime animation from the raw one.
|
||||
if (!BuildAnimations()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocates runtime buffers.
|
||||
locals_rt_.resize(num_soa_joints);
|
||||
models_rt_.resize(num_joints);
|
||||
locals_raw_aos_.resize(num_joints);
|
||||
locals_raw_.resize(num_soa_joints);
|
||||
models_raw_.resize(num_joints);
|
||||
locals_diff_.resize(num_soa_joints);
|
||||
models_diff_.resize(num_joints);
|
||||
|
||||
// Allocates a cache that matches animation requirements.
|
||||
cache_.Resize(num_joints);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool OnGui(ozz::sample::ImGui* _im_gui) {
|
||||
char label[64];
|
||||
// Exposes animation runtime playback controls.
|
||||
{
|
||||
static bool open = true;
|
||||
ozz::sample::ImGui::OpenClose occ(_im_gui, "Animation control", &open);
|
||||
if (open) {
|
||||
controller_.OnGui(*animation_rt_, _im_gui);
|
||||
}
|
||||
}
|
||||
|
||||
// Exposes optimizer's tolerances.
|
||||
{
|
||||
static bool open = true;
|
||||
ozz::sample::ImGui::OpenClose ocb(_im_gui, "Optimization tolerances",
|
||||
&open);
|
||||
if (open) {
|
||||
bool rebuild = false;
|
||||
|
||||
rebuild |= _im_gui->DoCheckBox("Enable optimizations", &optimize_);
|
||||
|
||||
std::sprintf(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);
|
||||
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_);
|
||||
rebuild |=
|
||||
_im_gui->DoSlider(label, 0, skeleton_.num_joints() - 1, &joint_,
|
||||
1.f, joint_setting_enable_ && optimize_);
|
||||
|
||||
std::sprintf(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",
|
||||
joint_setting_.distance * 1000);
|
||||
rebuild |= _im_gui->DoSlider(label, 0.f, 1.f, &joint_setting_.distance,
|
||||
.5f, joint_setting_enable_ && optimize_);
|
||||
|
||||
if (rebuild) {
|
||||
// Invalidates the cache in case the new animation has the same
|
||||
// address as the previous one. Other cases (like changing animation)
|
||||
// are automatic handled by the cache. See SamplingCache::Invalidate
|
||||
// for more details.
|
||||
cache_.Invalidate();
|
||||
|
||||
// Rebuilds a new runtime animation.
|
||||
if (!BuildAnimations()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
static bool open = true;
|
||||
ozz::sample::ImGui::OpenClose ocb(_im_gui, "Memory size", &open);
|
||||
if (open) {
|
||||
std::sprintf(label, "Original: %dKB",
|
||||
static_cast<int>(raw_animation_.size() >> 10));
|
||||
_im_gui->DoLabel(label);
|
||||
|
||||
std::sprintf(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)",
|
||||
static_cast<int>(animation_rt_->size() >> 10),
|
||||
static_cast<float>(raw_animation_.size()) / animation_rt_->size());
|
||||
_im_gui->DoLabel(label);
|
||||
}
|
||||
}
|
||||
|
||||
// Selects display mode.
|
||||
static bool open_mode = true;
|
||||
ozz::sample::ImGui::OpenClose mode(_im_gui, "Display mode", &open_mode);
|
||||
if (open_mode) {
|
||||
_im_gui->DoRadioButton(eRuntimeAnimation, "Runtime animation",
|
||||
&selected_display_);
|
||||
_im_gui->DoRadioButton(eRawAnimation, "Raw animation",
|
||||
&selected_display_);
|
||||
_im_gui->DoRadioButton(eAbsoluteError, "Absolute error",
|
||||
&selected_display_);
|
||||
}
|
||||
|
||||
// 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",
|
||||
*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,
|
||||
error_record_med_.cursor(),
|
||||
error_record_med_.record_begin(),
|
||||
error_record_med_.record_end());
|
||||
}
|
||||
{
|
||||
std::sprintf(szLabel, "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,
|
||||
error_record_max_.cursor(),
|
||||
error_record_max_.record_begin(),
|
||||
error_record_max_.record_end());
|
||||
}
|
||||
{
|
||||
std::sprintf(szLabel, "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,
|
||||
joint_error_record_.cursor(),
|
||||
joint_error_record_.record_begin(),
|
||||
joint_error_record_.record_end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void OnDestroy() {}
|
||||
|
||||
bool BuildAnimations() {
|
||||
// Instantiate an animation builder.
|
||||
ozz::animation::offline::AnimationBuilder animation_builder;
|
||||
|
||||
// Builds the optimized animation.
|
||||
if (optimize_) {
|
||||
ozz::animation::offline::AnimationOptimizer optimizer;
|
||||
|
||||
// Setup global optimization settings.
|
||||
optimizer.setting = setting_;
|
||||
|
||||
// Setup joint specific optimization settings.
|
||||
if (joint_setting_enable_) {
|
||||
optimizer.joints_setting_override[joint_] = joint_setting_;
|
||||
} else {
|
||||
optimizer.joints_setting_override.clear();
|
||||
}
|
||||
|
||||
if (!optimizer(raw_animation_, skeleton_, &raw_optimized_animation_)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Builds runtime animation from the brute one.
|
||||
raw_optimized_animation_ = raw_animation_;
|
||||
}
|
||||
|
||||
// Builds runtime animation from the optimized one.
|
||||
animation_rt_ = animation_builder(raw_optimized_animation_);
|
||||
|
||||
// Check if building runtime animation was successful.
|
||||
if (!animation_rt_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void GetSceneBounds(ozz::math::Box* _bound) const {
|
||||
ozz::sample::ComputePostureBounds(models(), _bound);
|
||||
}
|
||||
|
||||
private:
|
||||
// Selects which animation is displayed.
|
||||
enum DisplayMode {
|
||||
eRuntimeAnimation,
|
||||
eRawAnimation,
|
||||
eAbsoluteError,
|
||||
};
|
||||
int selected_display_;
|
||||
|
||||
// Select whether optimization should be performed.
|
||||
bool optimize_;
|
||||
|
||||
// Imported non-optimized animation.
|
||||
ozz::animation::offline::RawAnimation raw_animation_;
|
||||
|
||||
// Optimized raw animation.
|
||||
ozz::animation::offline::RawAnimation raw_optimized_animation_;
|
||||
|
||||
// Optimizer global settings.
|
||||
ozz::animation::offline::AnimationOptimizer::Setting setting_;
|
||||
|
||||
// Optimizer joint specific settings.
|
||||
bool joint_setting_enable_;
|
||||
int joint_;
|
||||
ozz::animation::offline::AnimationOptimizer::Setting joint_setting_;
|
||||
|
||||
// Playback animation controller. This is a utility class that helps with
|
||||
// controlling animation playback time.
|
||||
ozz::sample::PlaybackController controller_;
|
||||
|
||||
// Runtime skeleton.
|
||||
ozz::animation::Skeleton skeleton_;
|
||||
|
||||
// Sampling cache, shared across optimized and non-optimized animations. This
|
||||
// is not optimal, but it's not an issue either.
|
||||
ozz::animation::SamplingCache cache_;
|
||||
|
||||
// Runtime optimized animation.
|
||||
ozz::unique_ptr<ozz::animation::Animation> animation_rt_;
|
||||
|
||||
// Buffer of local and model space transformations as sampled from the
|
||||
// rutime (optimized and compressed) animation.
|
||||
ozz::vector<ozz::math::SoaTransform> locals_rt_;
|
||||
ozz::vector<ozz::math::Float4x4> models_rt_;
|
||||
|
||||
// Buffer of local and model space transformations as sampled from the
|
||||
// non-optimized (raw) animation.
|
||||
// Sampling the raw animation results in AoS data, meaning we have to
|
||||
// allocate AoS data and do the SoA conversion by hand.
|
||||
ozz::vector<ozz::math::Transform> locals_raw_aos_;
|
||||
ozz::vector<ozz::math::SoaTransform> locals_raw_;
|
||||
ozz::vector<ozz::math::Float4x4> models_raw_;
|
||||
|
||||
// Buffer of local and model space transformations storing samples from the
|
||||
// difference between optimized and non-optimized animations.
|
||||
ozz::vector<ozz::math::SoaTransform> locals_diff_;
|
||||
ozz::vector<ozz::math::Float4x4> models_diff_;
|
||||
|
||||
// Record of accuracy errors produced by animation compression and
|
||||
// optimization.
|
||||
ozz::sample::Record error_record_med_;
|
||||
ozz::sample::Record error_record_max_;
|
||||
ozz::sample::Record joint_error_record_;
|
||||
};
|
||||
|
||||
int main(int _argc, const char** _argv) {
|
||||
const char* title = "Ozz-animation sample: Animation keyframe optimization";
|
||||
return OptimizeSampleApplication().Run(_argc, _argv, "1.0", title);
|
||||
}
|
||||
Reference in New Issue
Block a user