From d4759fc7674b457088092a8e78e68bd3e5304d06 Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Tue, 10 Dec 2024 10:04:29 -0800 Subject: [PATCH] gltfpack: Implement support for disabling resampling when frequency is 0 Using -af 0 now disables resampling and keeps the original time values. When resampling is disabled, we can still mark tracks as constant and remove them if they are not necessary, which cuts down on the animation size. In this mode it is more difficult to support ranges, so we assume track range will cover the relevant timespan if any tracks with time survive. Doing this analysis for cubic splines is slightly more difficult as if tangents are non-zero, an otherwise "constant" track will not result in constant output, so we disable analysis in that case for simplicity. --- gltf/animation.cpp | 45 +++++++++++++++++++++++++++------------------ gltf/gltfpack.cpp | 4 ++-- gltf/write.cpp | 18 +++++++----------- 3 files changed, 36 insertions(+), 31 deletions(-) diff --git a/gltf/animation.cpp b/gltf/animation.cpp index d78ccb492..46e1d976c 100644 --- a/gltf/animation.cpp +++ b/gltf/animation.cpp @@ -207,17 +207,17 @@ static void resampleKeyframes(std::vector& data, const std::vector& } } -static float getMaxDelta(const std::vector& data, cgltf_animation_path_type type, int frames, const Attr* value, size_t components) +static float getMaxDelta(const std::vector& data, cgltf_animation_path_type type, const Attr* value, size_t components) { - assert(data.size() == frames * components); + assert(data.size() % components == 0); float result = 0; - for (int i = 0; i < frames; ++i) + for (size_t i = 0; i < data.size(); i += components) { for (size_t j = 0; j < components; ++j) { - float delta = getDelta(value[j], data[i * components + j], type); + float delta = getDelta(value[j], data[i + j], type); result = (result < delta) ? delta : result; } @@ -287,15 +287,17 @@ void processAnimation(Animation& animation, const Settings& settings) maxt = std::max(maxt, track.time.back()); } - mint = std::min(mint, maxt); + animation.start = mint = std::min(mint, maxt); - // round the number of frames to nearest but favor the "up" direction - // this means that at 100 Hz resampling, we will try to preserve the last frame <10ms - // but if the last frame is <2ms we favor just removing this data - int frames = 1 + int((maxt - mint) * settings.anim_freq + 0.8f); + if (settings.anim_freq) + { + // round the number of frames to nearest but favor the "up" direction + // this means that at 100 Hz resampling, we will try to preserve the last frame <10ms + // but if the last frame is <2ms we favor just removing this data + int frames = 1 + int((maxt - mint) * settings.anim_freq + 0.8f); - animation.start = mint; - animation.frames = frames; + animation.frames = frames; + } std::vector base; @@ -303,12 +305,19 @@ void processAnimation(Animation& animation, const Settings& settings) { Track& track = animation.tracks[i]; - std::vector result; - resampleKeyframes(result, track.time, track.data, track.path, track.interpolation, track.components, frames, mint, settings.anim_freq); + if (settings.anim_freq) + { + std::vector result; + resampleKeyframes(result, track.time, track.data, track.path, track.interpolation, track.components, animation.frames, animation.start, settings.anim_freq); + + track.time.clear(); + track.data.swap(result); + track.interpolation = track.interpolation == cgltf_interpolation_type_cubic_spline ? cgltf_interpolation_type_linear : track.interpolation; + } - track.time.clear(); - track.data.swap(result); - track.interpolation = track.interpolation == cgltf_interpolation_type_cubic_spline ? cgltf_interpolation_type_linear : track.interpolation; + // getMaxDelta assumes linear/step interpolation for now + if (track.interpolation == cgltf_interpolation_type_cubic_spline) + continue; float tolerance = getDeltaTolerance(track.path); @@ -319,7 +328,7 @@ void processAnimation(Animation& animation, const Settings& settings) tolerance /= scale == 0.f ? 1.f : scale; } - float deviation = getMaxDelta(track.data, track.path, frames, &track.data[0], track.components); + float deviation = getMaxDelta(track.data, track.path, &track.data[0], track.components); if (deviation <= tolerance) { @@ -331,7 +340,7 @@ void processAnimation(Animation& animation, const Settings& settings) base.resize(track.components); getBaseTransform(&base[0], track.components, track.path, track.node); - track.dummy = getMaxDelta(track.data, track.path, 1, &base[0], track.components) <= tolerance; + track.dummy = getMaxDelta(track.data, track.path, &base[0], track.components) <= tolerance; } } } diff --git a/gltf/gltfpack.cpp b/gltf/gltfpack.cpp index 6dfaf8ada..cae1bc48d 100644 --- a/gltf/gltfpack.cpp +++ b/gltf/gltfpack.cpp @@ -1331,7 +1331,7 @@ int main(int argc, char** argv) } else if (strcmp(arg, "-af") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) { - settings.anim_freq = clamp(atoi(argv[++i]), 1, 100); + settings.anim_freq = clamp(atoi(argv[++i]), 0, 100); } else if (strcmp(arg, "-ac") == 0) { @@ -1615,7 +1615,7 @@ int main(int argc, char** argv) fprintf(stderr, "\t-at N: use N-bit quantization for translations (default: 16; N should be between 1 and 24)\n"); fprintf(stderr, "\t-ar N: use N-bit quantization for rotations (default: 12; N should be between 4 and 16)\n"); fprintf(stderr, "\t-as N: use N-bit quantization for scale (default: 16; N should be between 1 and 24)\n"); - fprintf(stderr, "\t-af N: resample animations at N Hz (default: 30)\n"); + fprintf(stderr, "\t-af N: resample animations at N Hz (default: 30; use 0 to disable)\n"); fprintf(stderr, "\t-ac: keep constant animation tracks even if they don't modify the node transform\n"); fprintf(stderr, "\nScene:\n"); fprintf(stderr, "\t-kn: keep named nodes and meshes attached to named nodes so that named nodes can be transformed externally\n"); diff --git a/gltf/write.cpp b/gltf/write.cpp index 93dc85456..938238b25 100644 --- a/gltf/write.cpp +++ b/gltf/write.cpp @@ -1420,19 +1420,15 @@ void writeAnimation(std::string& json, std::vector& views, std::stri { const Track& track = *tracks[j]; - if (track.time.empty()) - { - assert(track.data.size() == track.components * (track.constant ? 1 : animation.frames)); +#ifndef NDEBUG + size_t keyframe_size = (track.interpolation == cgltf_interpolation_type_cubic_spline) ? 3 : 1; + size_t time_size = track.constant ? 1 : (track.time.empty() ? animation.frames : track.time.size()); - needs_time = needs_time || !track.constant; - needs_pose = needs_pose || track.constant; - } - else - { - size_t keyframe_size = (track.interpolation == cgltf_interpolation_type_cubic_spline) ? 3 : 1; + assert(track.data.size() == keyframe_size * track.components * time_size); +#endif - assert(track.data.size() == keyframe_size * track.components * track.time.size()); - } + needs_time = needs_time || (track.time.empty() && !track.constant); + needs_pose = needs_pose || track.constant; } bool needs_range = needs_pose && !needs_time && animation.frames > 1;