From d3accce9b03d8e4ee333b7fc47e092288cd0ddbf Mon Sep 17 00:00:00 2001 From: Guillaume Blanc Date: Mon, 22 Apr 2024 22:27:43 +0200 Subject: [PATCH] Updates documentation --- samples/motion_playback/README.md | 26 ++++++++++++++ .../motion_playback/sample_motion_playback.cc | 35 ++++++++++--------- 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/samples/motion_playback/README.md b/samples/motion_playback/README.md index 89ed7283e..65fe972ea 100644 --- a/samples/motion_playback/README.md +++ b/samples/motion_playback/README.md @@ -2,8 +2,34 @@ ## Description +Extracting root motion is the process of capturing character motion (translation and rotation) from an animation. The purpose is to re-apply motion at runtime to drive the character and its collision capsule. This allows the capsule (responsible for physical collisions) to follow precisely the character, even if it move during the animation. Furthermore it also allows to animate motion with varying speeds. + ## Concept +The sample uses animation and motion tracks converted using *2ozz tools, relying itself on `ozz::animation::offline::MotionExtractor`. +The motion track is composed of: +- A translation track, built from the xz projection of the root joint. +- A rotation track, built from the yaw only movement of the root joint. Motion extractor loop option is also activated to ensure animation begin and end rotation matches. + +*2ozz removed (ie: baked) the motion from the animation, so it can be applied at runtime. + +At runtime, the position and rotation are sampled from the motion tracks each frame. They are accumulated to move the character transform (by delta motion each frame), allowing to continue moving when the animation loops. +The sample uses `ozz::sample::MotionAccumulator` and `ozz::sample::MotionSampler` utilities to help with the complexity of managing loops. + ## Sample usage +The sample allows to activate / deactivate usage of motion tracks. Deactivating motion tracks shows the baked animation only. + +The sample exposes `ozz::sample::MotionAccumulator` capability of applying a rotation to the path. + +Finally, different options allows to render the motion track/path, as well as character trace. + ## Implementation + +1. Load the skeleton and tha animation with root motion extracted. See "playback" sample for more details. +2. Load position (ozz::animation::Float3Track) and rotation (ozz::animation::QuaternionTrack) tracks from the file exported by *2ozz tool. +3. Sample the animation local-space transforms and convert them to model-space. +4. Uses animation sampling time/ratio to sample also the motion tracks. +5. Accumulate motion using `ozz::sample::MotionAccumulator` utility. It automatically handles animation loops. +6. The rotation to apply this frame is computed from the angular velocity and frame duration. +6. Get current transform from the accumulator. Convert it to a Float4x4 matrix to render the character at this location. \ No newline at end of file diff --git a/samples/motion_playback/sample_motion_playback.cc b/samples/motion_playback/sample_motion_playback.cc index 0dac448fa..14f4be0af 100644 --- a/samples/motion_playback/sample_motion_playback.cc +++ b/samples/motion_playback/sample_motion_playback.cc @@ -164,13 +164,13 @@ class MotionPlaybackSampleApplication : public ozz::sample::Application { return false; } - // Reading motion tracks. - if (!ozz::sample::LoadMotionTrack(OPTIONS_motion, &motion_track_)) { + // Skeleton and animation needs to match. + if (skeleton_.num_joints() != animation_.num_tracks()) { return false; } - // Skeleton and animation needs to match. - if (skeleton_.num_joints() != animation_.num_tracks()) { + // Reading motion tracks. + if (!ozz::sample::LoadMotionTrack(OPTIONS_motion, &motion_track_)) { return false; } @@ -189,6 +189,7 @@ class MotionPlaybackSampleApplication : public ozz::sample::Application { virtual void OnDestroy() {} virtual bool OnGui(ozz::sample::ImGui* _im_gui) { + char label[64]; // Exposes animation runtime playback controls. { static bool open = true; @@ -204,7 +205,11 @@ class MotionPlaybackSampleApplication : public ozz::sample::Application { if (open) { _im_gui->DoCheckBox("Use motion position", &apply_motion_position_); _im_gui->DoCheckBox("Use motion rotation", &apply_motion_rotation_); - if (_im_gui->DoButton("Reset accumulator")) { + std::snprintf(label, sizeof(label), "Angular vel: %.0f deg/s", + angular_velocity_ * 180.f / ozz::math::kPi); + _im_gui->DoSlider(label, -ozz::math::kPi_2, ozz::math::kPi_2, + &angular_velocity_); + if (_im_gui->DoButton("Teleport")) { motion_sampler_.Teleport(ozz::math::Transform::identity()); } } @@ -213,36 +218,31 @@ class MotionPlaybackSampleApplication : public ozz::sample::Application { static bool open = true; ozz::sample::ImGui::OpenClose oc(_im_gui, "Motion display", &open); if (open) { - char label[64]; _im_gui->DoCheckBox("Show box", &show_box_); _im_gui->DoCheckBox("Show trace", &show_trace_); std::snprintf(label, sizeof(label), "Trace size: %d", trace_size_); - _im_gui->DoSlider(label, 100, 2000, &trace_size_); + _im_gui->DoSlider(label, 100, 2000, &trace_size_, 2.f); _im_gui->DoCheckBox("Show motion", &show_motion_); - _im_gui->DoCheckBox("Floating display", &floating_display_); + _im_gui->DoCheckBox("Floating display", &floating_display_, + show_motion_); std::snprintf(label, sizeof(label), "Motion before: %.0f%%", floating_before_ * 100.f); _im_gui->DoSlider(label, 0.f, 3.f, &floating_before_, 1.f, - floating_display_); + floating_display_ && show_motion_); std::snprintf(label, sizeof(label), "Motion after: %.0f%%", floating_after_ * 100.f); _im_gui->DoSlider(label, 0.f, 3.f, &floating_after_, 1.f, - floating_display_); - - std::snprintf(label, sizeof(label), "Angular vel: %.0f deg/s", - angular_velocity_ * 180.f / ozz::math::kPi); - _im_gui->DoSlider(label, -ozz::math::kPi_2, ozz::math::kPi_2, - &angular_velocity_); + floating_display_ && show_motion_); } } return true; } virtual void GetSceneBounds(ozz::math::Box* _bound) const { - ozz::sample::ComputePostureBounds(make_span(models_), transform_, _bound); + *_bound = TransformBox(transform_, bounding_); } private: @@ -274,6 +274,9 @@ class MotionPlaybackSampleApplication : public ozz::sample::Application { // Buffer of model space matrices. ozz::vector models_; + // Character bounding box. + const ozz::math::Box bounding_{{-.3f, 0.f, -.2f}, {.3f, 1.8f, .2f}}; + // GUI options to apply root motion. bool apply_motion_position_ = true; bool apply_motion_rotation_ = true;