Skip to content

Commit

Permalink
Improves compaction for identity and constant tracks.
Browse files Browse the repository at this point in the history
  • Loading branch information
guillaumeblanc committed Jun 20, 2024
1 parent 601f83d commit 6804187
Show file tree
Hide file tree
Showing 18 changed files with 223 additions and 120 deletions.
Binary file modified media/bin/pab_atlas.ozz
Binary file not shown.
Binary file modified media/bin/pab_atlas_motion_track.ozz
Binary file not shown.
Binary file modified media/bin/pab_atlas_no_motion.ozz
Binary file not shown.
Binary file modified media/bin/pab_atlas_raw.ozz
Binary file not shown.
Binary file modified media/bin/pab_jog_motion_track.ozz
Binary file not shown.
Binary file modified media/bin/pab_jog_no_motion.ozz
Binary file not shown.
Binary file modified media/bin/pab_run_motion_track.ozz
Binary file not shown.
Binary file modified media/bin/pab_run_no_motion.ozz
Binary file not shown.
Binary file modified media/bin/pab_walk_motion_track.ozz
Binary file not shown.
Binary file modified media/bin/pab_walk_no_motion.ozz
Binary file not shown.
33 changes: 21 additions & 12 deletions src/animation/offline/animation_optimizer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,12 @@ class PositionAdapter {
_ref.time, LerpTranslation(_left.value, _right.value, alpha)};
return key;
}
float Distance(const RawAnimation::TranslationKey& _a,
const RawAnimation::TranslationKey& _b) const {
return Length(_a.value - _b.value) * scale_;
float Distance(const RawAnimation::TranslationKey::Value& _a,
const RawAnimation::TranslationKey::Value& _b) const {
return Length(_a - _b) * scale_;
}
inline static RawAnimation::TranslationKey::Value identity() {
return RawAnimation::TranslationKey::identity();
}

private:
Expand All @@ -197,11 +200,11 @@ class RotationAdapter {
_ref.time, LerpRotation(_left.value, _right.value, alpha)};
return key;
}
float Distance(const RawAnimation::RotationKey& _left,
const RawAnimation::RotationKey& _right) const {
float Distance(const RawAnimation::RotationKey::Value& _left,
const RawAnimation::RotationKey::Value& _right) const {
// Compute the shortest unsigned angle between the 2 quaternions.
// cos_half_angle is w component of a-1 * b.
const float cos_half_angle = Dot(_left.value, _right.value);
const float cos_half_angle = Dot(_left, _right);
const float sine_half_angle =
std::sqrt(1.f - math::Min(1.f, cos_half_angle * cos_half_angle));
// Deduces distance between 2 points on a circle with radius and a given
Expand All @@ -210,6 +213,9 @@ class RotationAdapter {
const float distance = 2.f * sine_half_angle * radius_;
return distance;
}
inline static RawAnimation::RotationKey::Value identity() {
return RawAnimation::RotationKey::identity();
}

private:
float radius_;
Expand All @@ -228,9 +234,12 @@ class ScaleAdapter {
_ref.time, LerpScale(_left.value, _right.value, alpha)};
return key;
}
float Distance(const RawAnimation::ScaleKey& _left,
const RawAnimation::ScaleKey& _right) const {
return Length(_left.value - _right.value) * length_;
float Distance(const RawAnimation::ScaleKey::Value& _left,
const RawAnimation::ScaleKey::Value& _right) const {
return Length(_left - _right) * length_;
}
inline static RawAnimation::ScaleKey::Value identity() {
return RawAnimation::ScaleKey::identity();
}

private:
Expand Down Expand Up @@ -286,13 +295,13 @@ bool AnimationOptimizer::operator()(const RawAnimation& _input,
// Filters independently T, R and S tracks.
// This joint translation is affected by parent scale.
const PositionAdapter tadap(parent_scale);
Decimate(input.translations, tadap, tolerance, &output.translations);
output.translations = Decimate(input.translations, tadap, tolerance);
// This joint rotation affects children translations/length.
const RotationAdapter radap(joint_length);
Decimate(input.rotations, radap, tolerance, &output.rotations);
output.rotations = Decimate(input.rotations, radap, tolerance);
// This joint scale affects children translations/length.
const ScaleAdapter sadap(joint_length);
Decimate(input.scales, sadap, tolerance, &output.scales);
output.scales = Decimate(input.scales, sadap, tolerance);
}

// Output animation is always valid though.
Expand Down
138 changes: 73 additions & 65 deletions src/animation/offline/decimate.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@
#error "This header is private, it cannot be included from public headers."
#endif // OZZ_INCLUDE_PRIVATE_HEADER

#include <cassert>

#include "ozz/base/containers/stack.h"
#include "ozz/base/containers/vector.h"

#include <cassert>

namespace ozz {
namespace animation {
namespace offline {
Expand All @@ -48,87 +48,95 @@ namespace offline {
// struct Adapter {
// bool Decimable(const Key&) const;
// Key Lerp(const Key& _left, const Key& _right, const Key& _ref) const;
// float Distance(const Key& _a, const Key& _b) const;
// float Distance(const Value& _a, const Value& _b) const;
// };
template <typename _Track, typename _Adapter>
void Decimate(const _Track& _src, const _Adapter& _adapter, float _tolerance,
_Track* _dest) {
// Early out if not enough data.
if (_src.size() < 2) {
*_dest = _src;
return;
}

// Stack of segments to process.
typedef std::pair<size_t, size_t> Segment;
ozz::stack<Segment> segments;
_Track Decimate(const _Track& _src, const _Adapter& _adapter,
float _tolerance) {
_Track output;
if (_src.size() < 2) { // Nothing to decimate.
output = _src;
} else {
// Stack of segments to process.
typedef std::pair<size_t, size_t> Segment;
ozz::stack<Segment> segments;

// Bit vector of all points to included.
ozz::vector<bool> included(_src.size(), false);
// Bit vector of all points to included.
ozz::vector<bool> included(_src.size(), false);

// Pushes segment made from first and last points.
segments.push(Segment(0, _src.size() - 1));
included[0] = true;
included[_src.size() - 1] = true;
// Pushes segment made from first and last points.
segments.push(Segment(0, _src.size() - 1));
included[0] = true;
included[_src.size() - 1] = true;

// Empties segments stack.
while (!segments.empty()) {
// Pops next segment to process.
const Segment segment = segments.top();
segments.pop();
// Empties segments stack.
while (!segments.empty()) {
// Pops next segment to process.
const Segment segment = segments.top();
segments.pop();

// Looks for the furthest point from the segment.
float max = -1.f;
size_t candidate = segment.first;
typename _Track::const_reference left = _src[segment.first];
typename _Track::const_reference right = _src[segment.second];
for (size_t i = segment.first + 1; i < segment.second; ++i) {
assert(!included[i] && "Included points should be processed once only.");
typename _Track::const_reference test = _src[i];
if (!_adapter.Decimable(test)) {
candidate = i;
break;
} else {
const float distance =
_adapter.Distance(_adapter.Lerp(left, right, test), test);
if (distance > _tolerance && distance > max) {
max = distance;
// Looks for the furthest point from the segment.
float max = -1.f;
size_t candidate = segment.first;
typename _Track::const_reference left = _src[segment.first];
typename _Track::const_reference right = _src[segment.second];
for (size_t i = segment.first + 1; i < segment.second; ++i) {
assert(!included[i] &&
"Included points should be processed once only.");
typename _Track::const_reference test = _src[i];
if (!_adapter.Decimable(test)) {
candidate = i;
break;
} else {
const float distance = _adapter.Distance(
_adapter.Lerp(left, right, test).value, test.value);
if (distance > _tolerance && distance > max) {
max = distance;
candidate = i;
}
}
}
}

// If found, include the point and pushes the 2 new segments (before and
// after the new point).
if (candidate != segment.first) {
included[candidate] = true;
if (candidate - segment.first > 1) {
segments.push(Segment(segment.first, candidate));
}
if (segment.second - candidate > 1) {
segments.push(Segment(candidate, segment.second));
// If found, include the point and pushes the 2 new segments (before and
// after the new point).
if (candidate != segment.first) {
included[candidate] = true;
if (candidate - segment.first > 1) {
segments.push(Segment(segment.first, candidate));
}
if (segment.second - candidate > 1) {
segments.push(Segment(candidate, segment.second));
}
}
}
}

// Copy all included points.
_dest->clear();
for (size_t i = 0; i < _src.size(); ++i) {
if (included[i]) {
_dest->push_back(_src[i]);
// Copy all included points.
for (size_t i = 0; i < _src.size(); ++i) {
if (included[i]) {
output.push_back(_src[i]);
}
}
}

// Removes last key if constant.
if (_dest->size() > 1) {
typename _Track::const_iterator end = _dest->end();
typename _Track::const_reference last = *(--end);
typename _Track::const_reference penultimate = *(--end);
const float distance = _adapter.Distance(penultimate, last);
if (_adapter.Decimable(last) && distance <= _tolerance) {
_dest->pop_back();
// RDP algo ends with a minimum of 2 points (first and last).
// Removes last keys if track is constant or identity.
while (!output.empty()) {
const bool last_key = output.size() == 1;
typename _Track::const_reference back = output.back();
if (!last_key && !_adapter.Decimable(back)) {
break; // Not allowed, only meaningful if not last key
}
const auto& penultimate =
last_key ? _Adapter::identity() : output[output.size() - 2].value;
const float distance = _adapter.Distance(penultimate, back.value);
if (distance > _tolerance) {
break; // Too far, not decimable
}
// Decimation is possible, remove last key.
output.pop_back();
}

return output;
}
} // namespace offline
} // namespace animation
Expand Down
18 changes: 2 additions & 16 deletions src/animation/offline/track_builder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -46,24 +46,12 @@ template <typename _RawTrack>
void PatchBeginEndKeys(const _RawTrack& _input,
typename _RawTrack::Keyframes* keyframes) {
if (_input.keyframes.empty()) {
const typename _RawTrack::ValueType default_value =
animation::internal::TrackPolicy<
typename _RawTrack::ValueType>::identity();

const typename _RawTrack::Keyframe begin = {RawTrackInterpolation::kLinear,
0.f, default_value};
keyframes->push_back(begin);
const typename _RawTrack::Keyframe end = {RawTrackInterpolation::kLinear,
1.f, default_value};
keyframes->push_back(end);
} else if (_input.keyframes.size() == 1) {
// Empty (identity) is supported during sampling.
} else if (_input.keyframes.size() < 2) { // Constant
const typename _RawTrack::Keyframe& src_key = _input.keyframes.front();
const typename _RawTrack::Keyframe begin = {RawTrackInterpolation::kLinear,
0.f, src_key.value};
keyframes->push_back(begin);
const typename _RawTrack::Keyframe end = {RawTrackInterpolation::kLinear,
1.f, src_key.value};
keyframes->push_back(end);
} else {
// Copy all source data.
// Push an initial and last keys if they don't exist.
Expand Down Expand Up @@ -171,8 +159,6 @@ namespace {
template <>
void Fixup<RawQuaternionTrack::Keyframes>(
RawQuaternionTrack::Keyframes* _keyframes) {
assert(_keyframes->size() >= 2);

const math::Quaternion identity = math::Quaternion::identity();
for (size_t i = 0; i < _keyframes->size(); ++i) {
RawQuaternionTrack::ValueType& src_key = _keyframes->at(i).value;
Expand Down
8 changes: 5 additions & 3 deletions src/animation/offline/track_optimizer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,11 @@ struct Adapter {
return key;
}

float Distance(const _KeyFrame& _a, const _KeyFrame& _b) const {
return Policy::Distance(_a.value, _b.value);
float Distance(const ValueType& _a, const ValueType& _b) const {
return Policy::Distance(_a, _b);
}

inline static ValueType identity() { return Policy::identity(); }
};

template <typename _Track>
Expand All @@ -95,7 +97,7 @@ inline bool Optimize(float _tolerance, const _Track& _input, _Track* _output) {

// Optimizes.
const Adapter<typename _Track::Keyframe> adapter;
Decimate(_input.keyframes, adapter, _tolerance, &_output->keyframes);
_output->keyframes = Decimate(_input.keyframes, adapter, _tolerance);

// Output animation is always valid though.
return _output->Validate();
Expand Down
9 changes: 2 additions & 7 deletions src/animation/runtime/track_sampling_job.cc
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,9 @@ bool TrackSamplingJob<_Track>::Run() const {
assert(ratios.size() == values.size() &&
track->steps().size() * 8 >= values.size());

// Default track returns identity.
if (ratios.size() == 0) {
if (ratios.size() == 0) { // Default (empty) track returns identity.
*result = internal::TrackPolicy<ValueType>::identity();
return true;
}

// Handles cases that do not require a search.
if (ratio <= 0.f) {
} else if (ratios.size() == 1 || ratio <= 0.f) {
*result = values.front();
} else if (ratio >= 1.f) {
*result = values.back();
Expand Down
60 changes: 60 additions & 0 deletions test/animation/offline/track_optimizer_tests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,66 @@ TEST(Name, FloatTrackOptimizer) {
EXPECT_STREQ(raw_float_track.name.c_str(), output.name.c_str());
}

TEST(Identity, TrackOptimizer) {
TrackOptimizer optimizer;

RawFloatTrack raw_float_track;
const RawFloatTrack::Keyframe key0 = {RawTrackInterpolation::kLinear, .5f,
0.f};
raw_float_track.keyframes.push_back(key0);
const RawFloatTrack::Keyframe key1 = {RawTrackInterpolation::kLinear, .7f,
0.f};
raw_float_track.keyframes.push_back(key1);
const RawFloatTrack::Keyframe key2 = {RawTrackInterpolation::kLinear, .8f,
0.f};
raw_float_track.keyframes.push_back(key2);

RawFloatTrack output;
ASSERT_TRUE(optimizer(raw_float_track, &output));
EXPECT_TRUE(output.keyframes.empty());

// Step keys aren't optimized.
raw_float_track.keyframes[1].interpolation = RawTrackInterpolation::kStep;
ASSERT_TRUE(optimizer(raw_float_track, &output));
EXPECT_EQ(output.keyframes.size(), 2u);
}

TEST(Constant, TrackOptimizer) {
TrackOptimizer optimizer;

RawFloatTrack raw_float_track;
const RawFloatTrack::Keyframe key0 = {RawTrackInterpolation::kLinear, .5f,
46.f};
raw_float_track.keyframes.push_back(key0);
const RawFloatTrack::Keyframe key1 = {RawTrackInterpolation::kLinear, .7f,
46.f};
raw_float_track.keyframes.push_back(key1);
const RawFloatTrack::Keyframe key2 = {RawTrackInterpolation::kLinear, .8f,
46.f};
raw_float_track.keyframes.push_back(key2);

RawFloatTrack output;
ASSERT_TRUE(optimizer(raw_float_track, &output));
EXPECT_EQ(output.keyframes.size(), 1u);

EXPECT_EQ(output.keyframes[0].interpolation, key0.interpolation);
EXPECT_FLOAT_EQ(output.keyframes[0].ratio, key0.ratio);
EXPECT_FLOAT_EQ(output.keyframes[0].value, key0.value);

// Step keys aren't optimized.
raw_float_track.keyframes[2].interpolation = RawTrackInterpolation::kStep;
ASSERT_TRUE(optimizer(raw_float_track, &output));
EXPECT_EQ(output.keyframes.size(), 2u);

EXPECT_EQ(output.keyframes[0].interpolation, key0.interpolation);
EXPECT_FLOAT_EQ(output.keyframes[0].ratio, key0.ratio);
EXPECT_FLOAT_EQ(output.keyframes[0].value, key0.value);

EXPECT_EQ(output.keyframes[1].interpolation, RawTrackInterpolation::kStep);
EXPECT_FLOAT_EQ(output.keyframes[1].ratio, key2.ratio);
EXPECT_FLOAT_EQ(output.keyframes[1].value, key2.value);
}

TEST(OptimizeSteps, TrackOptimizer) {
// Step keys aren't optimized.
TrackOptimizer optimizer;
Expand Down
Loading

0 comments on commit 6804187

Please sign in to comment.