Skip to content

Commit

Permalink
First motion blending sample implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
guillaumeblanc committed Apr 23, 2024
1 parent 9f75da7 commit 08bdb67
Show file tree
Hide file tree
Showing 6 changed files with 561 additions and 25 deletions.
1 change: 1 addition & 0 deletions samples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ add_subdirectory(blend)
add_subdirectory(foot_ik)
add_subdirectory(look_at)
add_subdirectory(millipede)
add_subdirectory(motion_blend)
add_subdirectory(motion_extraction)
add_subdirectory(motion_playback)
add_subdirectory(multithread)
Expand Down
52 changes: 35 additions & 17 deletions samples/framework/motion_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -103,26 +103,40 @@ bool SampleMotion(const MotionTrack& _tracks, float _ratio,
return true;
}

void MotionDeltaAccumulator::Update(const ozz::math::Transform& _delta) {
Update(_delta, ozz::math::Quaternion::identity());
}

void MotionDeltaAccumulator::Update(const ozz::math::Transform& _delta,
const ozz::math::Quaternion& _rotation) {
// Accumulates rotation.
rotation_accum_ = Normalize(rotation_accum_ * _rotation);

// Updates current transform.
current.translation = current.translation +
TransformVector(rotation_accum_, _delta.translation);
current.rotation = Normalize(current.rotation * _delta.rotation * _rotation);
}
void MotionDeltaAccumulator::Teleport(const ozz::math::Transform& _origin) {
current = _origin;

// Resets rotation accumulator.
rotation_accum_ = ozz::math::Quaternion::identity();
}

void MotionAccumulator::Update(const ozz::math::Transform& _new) {
return Update(_new, ozz::math::Quaternion::identity());
}

// Accumulates motion deltas (new transform - last).
void MotionAccumulator::Update(const ozz::math::Transform& _new,
const ozz::math::Quaternion& _delta_rotation) {
// Accumulates rotation.
// Normalizes to avoid accumulating error.
rotation_accum_ = Normalize(rotation_accum_ * _delta_rotation);

// Computes delta translation.
const auto delta_p = _new.translation - last.translation;
current.translation =
current.translation + TransformVector(rotation_accum_, delta_p);
// Computes delta.
delta.translation = _new.translation - last.translation;
delta.rotation = Conjugate(last.rotation) * _new.rotation;

// Computes delta rotation.
// Normalizes to avoid accumulating a denormalization error.
const auto delta_r = Conjugate(last.rotation) * _new.rotation;
current.rotation = Normalize(current.rotation * delta_r * _delta_rotation);
// Updates current transform based on computed delta.
MotionDeltaAccumulator::Update(delta, _delta_rotation);

// Next time, delta will be computed from the _new transform.
last = _new;
Expand All @@ -133,11 +147,13 @@ void MotionAccumulator::ResetOrigin(const ozz::math::Transform& _origin) {
}

void MotionAccumulator::Teleport(const ozz::math::Transform& _origin) {
MotionDeltaAccumulator::Teleport(_origin);

// Resets current transform to new _origin
current = last = _origin;
last = current;

// Resets rotation accumulator.
rotation_accum_ = ozz::math::Quaternion::identity();
// No delta between last and current
delta = ozz::math::Transform::identity();
}

bool MotionSampler::Update(const MotionTrack& _motion, float _ratio,
Expand All @@ -154,10 +170,12 @@ bool MotionSampler::Update(const MotionTrack& _motion, float _ratio, int _loops,
// motion done during the loop(s).

// Uses a local accumulator to accumulate motion during loops.
MotionAccumulator local_accumulator{last, last};
MotionAccumulator local_accumulator;
local_accumulator.Teleport(last);

for (; _loops; _loops > 0 ? --_loops : ++_loops) {
// Samples motion at loop end (or begin depending on playback direction).
// Samples motion at loop end (or begin depending on playback
// direction).
if (!SampleMotion(_motion, _loops > 0 ? 1.f : 0.f, &sample)) {
return false;
}
Expand Down
30 changes: 22 additions & 8 deletions samples/framework/motion_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,35 @@ struct MotionTrack {
// and _track must be non-nullptr.
bool LoadMotionTrack(const char* _filename, MotionTrack* _track);

struct MotionDeltaAccumulator {
void Update(const ozz::math::Transform& _delta);

void Update(const ozz::math::Transform& _delta,
const ozz::math::Quaternion& _rotation);

// Teleports accumulator to a new transform. This also resets the origin, so
// next delta is computed from the new origin.
void Teleport(const ozz::math::Transform& _origin);

// Character's current transform.
ozz::math::Transform current = ozz::math::Transform::identity();

// Accumulated rotation (since last teleport).
ozz::math::Quaternion rotation_accum_ = ozz::math::Quaternion::identity();
};

// Helper object that manages motion accumulation to compute character's
// transform.
struct MotionAccumulator {
struct MotionAccumulator : public MotionDeltaAccumulator {
// Accumulates motion delta (new - last) and updates current transform.
void Update(const ozz::math::Transform& _new);

// Accumulates motion delta (new - last) and updates current transform.
// _delta_rotation is the rotation to pply to deform the path since last
// _delta_rotation is the rotation to apply to deform the path since last
// update. Hence, user is responsible for taking care of applying delta time
// if he wants to achieve a specific angular speed.
void Update(const ozz::math::Transform& _new,
const ozz::math::Quaternion& _delta_rotation);
const ozz::math::Quaternion& _rotation);

// Tells the accumulator that the _new transform is the new origin.
// This is useful when animation loops, so next delta is computed from the new
Expand All @@ -77,11 +94,8 @@ struct MotionAccumulator {
// Last value sample from the motion track, used to compute delta.
ozz::math::Transform last = ozz::math::Transform::identity();

// Character's current transform.
ozz::math::Transform current = ozz::math::Transform::identity();

// Accumulated rotation (since last teleport).
ozz::math::Quaternion rotation_accum_ = ozz::math::Quaternion::identity();
// Delta transformation between last and current frame.
ozz::math::Transform delta = ozz::math::Transform::identity();
};

// Helper object samples a motion track to update a MotionAccumulator.
Expand Down
74 changes: 74 additions & 0 deletions samples/motion_blend/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Generates sample data

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_no_motion.ozz"
"${ozz_media_directory}/bin/pab_jog_no_motion.ozz"
"${ozz_media_directory}/bin/pab_jog_no_motion.ozz"
"${ozz_media_directory}/bin/pab_walk_motion_track.ozz"
"${ozz_media_directory}/bin/pab_jog_motion_track.ozz"
"${ozz_media_directory}/bin/pab_jog_motion_track.ozz"
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/README.md"
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
"${CMAKE_CURRENT_BINARY_DIR}/media/animation1.ozz"
"${CMAKE_CURRENT_BINARY_DIR}/media/animation2.ozz"
"${CMAKE_CURRENT_BINARY_DIR}/media/animation3.ozz"
"${CMAKE_CURRENT_BINARY_DIR}/media/motion1.ozz"
"${CMAKE_CURRENT_BINARY_DIR}/media/motion2.ozz"
"${CMAKE_CURRENT_BINARY_DIR}/media/motion3.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_no_motion.ozz" "./media/animation1.ozz"
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_jog_no_motion.ozz" "./media/animation2.ozz"
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_run_no_motion.ozz" "./media/animation3.ozz"
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_walk_motion_track.ozz" "./media/motion1.ozz"
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_jog_motion_track.ozz" "./media/motion2.ozz"
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_run_motion_track.ozz" "./media/motion3.ozz"
VERBATIM)

# Adds sample executable
add_executable(sample_motion_blend
sample_motion_blend.cc
"${CMAKE_CURRENT_BINARY_DIR}/README.md"
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
"${CMAKE_CURRENT_BINARY_DIR}/media/animation1.ozz"
"${CMAKE_CURRENT_BINARY_DIR}/media/animation2.ozz"
"${CMAKE_CURRENT_BINARY_DIR}/media/animation3.ozz"
"${CMAKE_CURRENT_BINARY_DIR}/media/motion1.ozz"
"${CMAKE_CURRENT_BINARY_DIR}/media/motion2.ozz"
"${CMAKE_CURRENT_BINARY_DIR}/media/motion3.ozz")

target_link_libraries(sample_motion_blend
sample_framework)
target_copy_shared_libraries(sample_motion_blend)

set_target_properties(sample_motion_blend
PROPERTIES FOLDER "samples")

if(EMSCRIPTEN)
# Resource files are embedded to the output file with emscripten
set_target_properties(sample_motion_blend
PROPERTIES LINK_FLAGS "--embed-file media --embed-file README.md")

install(FILES
${CMAKE_CURRENT_BINARY_DIR}/sample_motion_blend.html
${CMAKE_CURRENT_BINARY_DIR}/sample_motion_blend.js
${CMAKE_CURRENT_BINARY_DIR}/sample_motion_blend.wasm
DESTINATION bin/samples/motion_blend)
else()
install(TARGETS sample_motion_blend DESTINATION bin/samples/motion_blend)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/media DESTINATION bin/samples/motion_blend)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/README.md DESTINATION bin/samples/motion_blend)
endif(EMSCRIPTEN)

add_test(NAME sample_motion_blend COMMAND sample_motion_blend "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
add_test(NAME sample_motion_blend_path COMMAND sample_motion_blend "--skeleton=media/skeleton.ozz" "--animation1=media/animation1.ozz" "--animation2=media/animation2.ozz" "--animation3=media/animation3.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
add_test(NAME sample_motion_blend_invalid_skeleton_path COMMAND sample_motion_blend "--skeleton=media/bad_skeleton.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
set_tests_properties(sample_motion_blend_invalid_skeleton_path PROPERTIES WILL_FAIL true)
add_test(NAME sample_motion_blend_invalid_animation_path COMMAND sample_motion_blend "--animation1=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
set_tests_properties(sample_motion_blend_invalid_animation_path PROPERTIES WILL_FAIL true)
add_test(NAME sample_motion_blend_invalid_motion_path COMMAND sample_motion_blend "--motion2=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
set_tests_properties(sample_motion_blend_invalid_motion_path PROPERTIES WILL_FAIL true)
9 changes: 9 additions & 0 deletions samples/motion_blend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Ozz-animation sample: Animation blending

## Description

## Concept

## Sample usage

## Implementation
Loading

0 comments on commit 08bdb67

Please sign in to comment.