cocos-engine-external/sources/acl/decompression/impl/transform_track_decompressi...

683 lines
30 KiB
C++

#pragma once
////////////////////////////////////////////////////////////////////////////////
// The MIT License (MIT)
//
// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors
//
// 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 "acl/core/bitset.h"
#include "acl/core/compressed_tracks.h"
#include "acl/core/compressed_tracks_version.h"
#include "acl/core/interpolation_utils.h"
#include "acl/core/range_reduction_types.h"
#include "acl/core/track_formats.h"
#include "acl/core/track_writer.h"
#include "acl/core/variable_bit_rates.h"
#include "acl/core/impl/compiler_utils.h"
#include "acl/decompression/impl/transform_animated_track_cache.h"
#include "acl/decompression/impl/transform_constant_track_cache.h"
#include "acl/decompression/impl/transform_decompression_context.h"
#include "acl/math/quatf.h"
#include "acl/math/quat_packing.h"
#include "acl/math/vector4f.h"
#include <rtm/quatf.h>
#include <rtm/scalarf.h>
#include <rtm/vector4f.h>
#include <cstdint>
#include <type_traits>
ACL_IMPL_FILE_PRAGMA_PUSH
namespace acl
{
namespace acl_impl
{
template<class decompression_settings_type>
inline bool initialize_v0(persistent_transform_decompression_context_v0& context, const compressed_tracks& tracks)
{
ACL_ASSERT(tracks.get_algorithm_type() == algorithm_type8::uniformly_sampled, "Invalid algorithm type [%s], expected [%s]", get_algorithm_name(tracks.get_algorithm_type()), get_algorithm_name(algorithm_type8::uniformly_sampled));
using translation_adapter = acl_impl::translation_decompression_settings_adapter<decompression_settings_type>;
using scale_adapter = acl_impl::scale_decompression_settings_adapter<decompression_settings_type>;
const tracks_header& header = get_tracks_header(tracks);
const transform_tracks_header& transform_header = get_transform_tracks_header(tracks);
const rotation_format8 packed_rotation_format = header.get_rotation_format();
const vector_format8 packed_translation_format = header.get_translation_format();
const vector_format8 packed_scale_format = header.get_scale_format();
const rotation_format8 rotation_format = get_rotation_format<decompression_settings_type>(packed_rotation_format);
const vector_format8 translation_format = get_vector_format<translation_adapter>(packed_translation_format);
const vector_format8 scale_format = get_vector_format<scale_adapter>(packed_scale_format);
ACL_ASSERT(rotation_format == packed_rotation_format, "Statically compiled rotation format (%s) differs from the compressed rotation format (%s)!", get_rotation_format_name(rotation_format), get_rotation_format_name(packed_rotation_format));
ACL_ASSERT(translation_format == packed_translation_format, "Statically compiled translation format (%s) differs from the compressed translation format (%s)!", get_vector_format_name(translation_format), get_vector_format_name(packed_translation_format));
ACL_ASSERT(scale_format == packed_scale_format, "Statically compiled scale format (%s) differs from the compressed scale format (%s)!", get_vector_format_name(scale_format), get_vector_format_name(packed_scale_format));
context.tracks = &tracks;
context.clip_hash = tracks.get_hash();
context.clip_duration = calculate_duration(header.num_samples, header.sample_rate);
context.sample_time = -1.0F;
context.default_tracks_bitset = transform_header.get_default_tracks_bitset();
context.constant_tracks_bitset = transform_header.get_constant_tracks_bitset();
context.constant_track_data = transform_header.get_constant_track_data();
context.clip_range_data = transform_header.get_clip_range_data();
for (uint32_t key_frame_index = 0; key_frame_index < 2; ++key_frame_index)
{
context.format_per_track_data[key_frame_index] = nullptr;
context.segment_range_data[key_frame_index] = nullptr;
context.animated_track_data[key_frame_index] = nullptr;
}
const bool has_scale = header.get_has_scale();
const uint32_t num_tracks_per_bone = has_scale ? 3 : 2;
context.bitset_desc = bitset_description::make_from_num_bits(header.num_tracks * num_tracks_per_bone);
range_reduction_flags8 range_reduction = range_reduction_flags8::none;
if (is_rotation_format_variable(rotation_format))
range_reduction |= range_reduction_flags8::rotations;
if (is_vector_format_variable(translation_format))
range_reduction |= range_reduction_flags8::translations;
if (is_vector_format_variable(scale_format))
range_reduction |= range_reduction_flags8::scales;
context.rotation_format = rotation_format;
context.translation_format = translation_format;
context.scale_format = scale_format;
context.range_reduction = range_reduction;
context.num_rotation_components = rotation_format == rotation_format8::quatf_full ? 4 : 3;
context.has_segments = transform_header.num_segments > 1;
return true;
}
inline bool is_dirty_v0(const persistent_transform_decompression_context_v0& context, const compressed_tracks& tracks)
{
if (context.tracks != &tracks)
return true;
if (context.clip_hash != tracks.get_hash())
return true;
return false;
}
template<class decompression_settings_type>
inline void seek_v0(persistent_transform_decompression_context_v0& context, float sample_time, sample_rounding_policy rounding_policy)
{
// Clamp for safety, the caller should normally handle this but in practice, it often isn't the case
if (decompression_settings_type::clamp_sample_time())
sample_time = rtm::scalar_clamp(sample_time, 0.0F, context.clip_duration);
if (context.sample_time == sample_time)
return;
context.sample_time = sample_time;
const tracks_header& header = get_tracks_header(*context.tracks);
const transform_tracks_header& transform_header = get_transform_tracks_header(*context.tracks);
uint32_t key_frame0;
uint32_t key_frame1;
find_linear_interpolation_samples_with_sample_rate(header.num_samples, header.sample_rate, sample_time, rounding_policy, key_frame0, key_frame1, context.interpolation_alpha);
uint32_t segment_key_frame0;
uint32_t segment_key_frame1;
const segment_header* segment_header0;
const segment_header* segment_header1;
const segment_header* segment_headers = transform_header.get_segment_headers();
const uint32_t num_segments = transform_header.num_segments;
if (num_segments == 1)
{
// Key frame 0 and 1 are in the only segment present
// This is a really common case and when it happens, we don't store the segment start index (zero)
segment_header0 = segment_headers;
segment_key_frame0 = key_frame0;
segment_header1 = segment_headers;
segment_key_frame1 = key_frame1;
}
else
{
const uint32_t* segment_start_indices = transform_header.get_segment_start_indices();
// See segment_streams(..) for implementation details. This implementation is directly tied to it.
const uint32_t approx_num_samples_per_segment = header.num_samples / num_segments; // TODO: Store in header?
const uint32_t approx_segment_index = key_frame0 / approx_num_samples_per_segment;
uint32_t segment_index0 = 0;
uint32_t segment_index1 = 0;
// Our approximate segment guess is just that, a guess. The actual segments we need could be just before or after.
// We start looking one segment earlier and up to 2 after. If we have too few segments after, we will hit the
// sentinel value of 0xFFFFFFFF and exit the loop.
// TODO: Can we do this with SIMD? Load all 4 values, set key_frame0, compare, move mask, count leading zeroes
const uint32_t start_segment_index = approx_segment_index > 0 ? (approx_segment_index - 1) : 0;
const uint32_t end_segment_index = start_segment_index + 4;
for (uint32_t segment_index = start_segment_index; segment_index < end_segment_index; ++segment_index)
{
if (key_frame0 < segment_start_indices[segment_index])
{
// We went too far, use previous segment
ACL_ASSERT(segment_index > 0, "Invalid segment index: %u", segment_index);
segment_index0 = segment_index - 1;
segment_index1 = key_frame1 < segment_start_indices[segment_index] ? segment_index0 : segment_index;
break;
}
}
segment_header0 = segment_headers + segment_index0;
segment_header1 = segment_headers + segment_index1;
segment_key_frame0 = key_frame0 - segment_start_indices[segment_index0];
segment_key_frame1 = key_frame1 - segment_start_indices[segment_index1];
}
transform_header.get_segment_data(*segment_header0, context.format_per_track_data[0], context.segment_range_data[0], context.animated_track_data[0]);
// More often than not the two segments are identical, when this is the case, just copy our pointers
if (segment_header0 != segment_header1)
transform_header.get_segment_data(*segment_header1, context.format_per_track_data[1], context.segment_range_data[1], context.animated_track_data[1]);
else
{
context.format_per_track_data[1] = context.format_per_track_data[0];
context.segment_range_data[1] = context.segment_range_data[0];
context.animated_track_data[1] = context.animated_track_data[0];
}
context.key_frame_bit_offsets[0] = segment_key_frame0 * segment_header0->animated_pose_bit_size;
context.key_frame_bit_offsets[1] = segment_key_frame1 * segment_header1->animated_pose_bit_size;
}
// TODO: Stage bitset decomp
// TODO: Merge the per track format and segment range info into a single buffer? Less to prefetch and used together
// TODO: How do we hide the cache miss after the seek to read the segment header? What work can we do while we prefetch?
// TODO: Port vector3 decomp to use SOA
// TODO: Unroll quat unpacking and convert to SOA
// TODO: Use AVX where we can
// TODO: Remove segment data alignment, no longer required?
template<class decompression_settings_type, class track_writer_type>
inline void decompress_tracks_v0(const persistent_transform_decompression_context_v0& context, track_writer_type& writer)
{
ACL_ASSERT(context.sample_time >= 0.0f, "Context not set to a valid sample time");
if (context.sample_time < 0.0F)
return; // Invalid sample time, we didn't seek yet
// Due to the SIMD operations, we sometimes overflow in the SIMD lanes not used.
// Disable floating point exceptions to avoid issues.
fp_environment fp_env;
if (decompression_settings_type::disable_fp_exeptions())
disable_fp_exceptions(fp_env);
const tracks_header& header = get_tracks_header(*context.tracks);
using translation_adapter = acl_impl::translation_decompression_settings_adapter<decompression_settings_type>;
using scale_adapter = acl_impl::scale_decompression_settings_adapter<decompression_settings_type>;
const rtm::vector4f default_translation = rtm::vector_zero();
const rtm::vector4f default_scale = rtm::vector_set(float(header.get_default_scale()));
const bool has_scale = header.get_has_scale();
const uint32_t num_tracks = header.num_tracks;
constant_track_cache_v0 constant_track_cache;
constant_track_cache.initialize<decompression_settings_type>(context);
animated_track_cache_v0 animated_track_cache;
animated_track_cache.initialize(context);
uint32_t sub_track_index = 0;
for (uint32_t track_index = 0; track_index < num_tracks; ++track_index)
{
if ((track_index % 4) == 0)
{
// Unpack our next 4 tracks
constant_track_cache.unpack_rotation_group<decompression_settings_type>(context);
constant_track_cache.unpack_translation_group();
animated_track_cache.unpack_rotation_group<decompression_settings_type>(context);
animated_track_cache.unpack_translation_group<translation_adapter>(context);
if (has_scale)
{
constant_track_cache.unpack_scale_group();
animated_track_cache.unpack_scale_group<scale_adapter>(context);
}
}
{
const bitset_index_ref track_index_bit_ref(context.bitset_desc, sub_track_index);
const bool is_sample_default = bitset_test(context.default_tracks_bitset, track_index_bit_ref);
rtm::quatf rotation;
if (is_sample_default)
{
rotation = rtm::quat_identity();
}
else
{
const bool is_sample_constant = bitset_test(context.constant_tracks_bitset, track_index_bit_ref);
if (is_sample_constant)
rotation = constant_track_cache.consume_rotation();
else
rotation = animated_track_cache.consume_rotation();
}
ACL_ASSERT(rtm::quat_is_finite(rotation), "Rotation is not valid!");
ACL_ASSERT(rtm::quat_is_normalized(rotation), "Rotation is not normalized!");
if (!track_writer_type::skip_all_rotations() && !writer.skip_track_rotation(track_index))
writer.write_rotation(track_index, rotation);
sub_track_index++;
}
{
const bitset_index_ref track_index_bit_ref(context.bitset_desc, sub_track_index);
const bool is_sample_default = bitset_test(context.default_tracks_bitset, track_index_bit_ref);
rtm::vector4f translation;
if (is_sample_default)
{
translation = default_translation;
}
else
{
const bool is_sample_constant = bitset_test(context.constant_tracks_bitset, track_index_bit_ref);
if (is_sample_constant)
translation = constant_track_cache.consume_translation();
else
translation = animated_track_cache.consume_translation();
}
ACL_ASSERT(rtm::vector_is_finite3(translation), "Translation is not valid!");
if (!track_writer_type::skip_all_translations() && !writer.skip_track_translation(track_index))
writer.write_translation(track_index, translation);
sub_track_index++;
}
if (has_scale)
{
const bitset_index_ref track_index_bit_ref(context.bitset_desc, sub_track_index);
const bool is_sample_default = bitset_test(context.default_tracks_bitset, track_index_bit_ref);
rtm::vector4f scale;
if (is_sample_default)
{
scale = default_scale;
}
else
{
const bool is_sample_constant = bitset_test(context.constant_tracks_bitset, track_index_bit_ref);
if (is_sample_constant)
scale = constant_track_cache.consume_scale();
else
scale = animated_track_cache.consume_scale();
}
ACL_ASSERT(rtm::vector_is_finite3(scale), "Scale is not valid!");
if (!track_writer_type::skip_all_scales() && !writer.skip_track_scale(track_index))
writer.write_scale(track_index, scale);
sub_track_index++;
}
else if (!track_writer_type::skip_all_scales() && !writer.skip_track_scale(track_index))
writer.write_scale(track_index, default_scale);
}
if (decompression_settings_type::disable_fp_exeptions())
restore_fp_exceptions(fp_env);
}
template<class decompression_settings_type, class track_writer_type>
inline void decompress_track_v0(const persistent_transform_decompression_context_v0& context, uint32_t track_index, track_writer_type& writer)
{
ACL_ASSERT(context.sample_time >= 0.0f, "Context not set to a valid sample time");
if (context.sample_time < 0.0F)
return; // Invalid sample time, we didn't seek yet
const tracks_header& tracks_header_ = get_tracks_header(*context.tracks);
ACL_ASSERT(track_index < tracks_header_.num_tracks, "Invalid track index");
if (track_index >= tracks_header_.num_tracks)
return; // Invalid track index
// Due to the SIMD operations, we sometimes overflow in the SIMD lanes not used.
// Disable floating point exceptions to avoid issues.
fp_environment fp_env;
if (decompression_settings_type::disable_fp_exeptions())
disable_fp_exceptions(fp_env);
using translation_adapter = acl_impl::translation_decompression_settings_adapter<decompression_settings_type>;
using scale_adapter = acl_impl::scale_decompression_settings_adapter<decompression_settings_type>;
const rtm::quatf default_rotation = rtm::quat_identity();
const rtm::vector4f default_translation = rtm::vector_zero();
const rtm::vector4f default_scale = rtm::vector_set(float(tracks_header_.get_default_scale()));
const bool has_scale = tracks_header_.get_has_scale();
// To decompress a single track, we need a few things:
// - if our rot/trans/scale is the default value, this is a trivial bitset lookup
// - constant and animated sub-tracks need to know which group they below to so it can be unpacked
const uint32_t num_tracks_per_bone = has_scale ? 3 : 2;
const uint32_t sub_track_index = track_index * num_tracks_per_bone;
const bitset_index_ref rotation_sub_track_index_bit_ref(context.bitset_desc, sub_track_index + 0);
const bitset_index_ref translation_sub_track_index_bit_ref(context.bitset_desc, sub_track_index + 1);
const bitset_index_ref scale_sub_track_index_bit_ref(context.bitset_desc, sub_track_index + 2);
const bool is_rotation_default = bitset_test(context.default_tracks_bitset, rotation_sub_track_index_bit_ref);
const bool is_translation_default = bitset_test(context.default_tracks_bitset, translation_sub_track_index_bit_ref);
const bool is_scale_default = has_scale ? bitset_test(context.default_tracks_bitset, scale_sub_track_index_bit_ref) : true;
if (is_rotation_default && is_translation_default && is_scale_default)
{
// Everything is default
writer.write_rotation(track_index, default_rotation);
writer.write_translation(track_index, default_translation);
writer.write_scale(track_index, default_scale);
return;
}
const bool is_rotation_constant = !is_rotation_default && bitset_test(context.constant_tracks_bitset, rotation_sub_track_index_bit_ref);
const bool is_translation_constant = !is_translation_default && bitset_test(context.constant_tracks_bitset, translation_sub_track_index_bit_ref);
const bool is_scale_constant = !is_scale_default && has_scale ? bitset_test(context.constant_tracks_bitset, scale_sub_track_index_bit_ref) : false;
const bool is_rotation_animated = !is_rotation_default && !is_rotation_constant;
const bool is_translation_animated = !is_translation_default && !is_translation_constant;
const bool is_scale_animated = !is_scale_default && !is_scale_constant;
uint32_t num_default_rotations = 0;
uint32_t num_default_translations = 0;
uint32_t num_default_scales = 0;
uint32_t num_constant_rotations = 0;
uint32_t num_constant_translations = 0;
uint32_t num_constant_scales = 0;
if (has_scale)
{
uint32_t rotation_track_bit_mask = 0x92492492; // b100100100..
uint32_t translation_track_bit_mask = 0x49249249; // b010010010..
uint32_t scale_track_bit_mask = 0x24924924; // b001001001..
const uint32_t last_offset = sub_track_index / 32;
uint32_t offset = 0;
for (; offset < last_offset; ++offset)
{
const uint32_t default_value = context.default_tracks_bitset[offset];
num_default_rotations += count_set_bits(default_value & rotation_track_bit_mask);
num_default_translations += count_set_bits(default_value & translation_track_bit_mask);
num_default_scales += count_set_bits(default_value & scale_track_bit_mask);
const uint32_t constant_value = context.constant_tracks_bitset[offset];
num_constant_rotations += count_set_bits(constant_value & rotation_track_bit_mask);
num_constant_translations += count_set_bits(constant_value & translation_track_bit_mask);
num_constant_scales += count_set_bits(constant_value & scale_track_bit_mask);
// Because the number of tracks in a 32 bit value isn't a multiple of the number of tracks we have (3),
// we have to cycle the masks. There are 3 possible masks, just swap them.
const uint32_t old_rotation_track_bit_mask = rotation_track_bit_mask;
rotation_track_bit_mask = translation_track_bit_mask;
translation_track_bit_mask = scale_track_bit_mask;
scale_track_bit_mask = old_rotation_track_bit_mask;
}
const uint32_t remaining_tracks = sub_track_index % 32;
if (remaining_tracks != 0)
{
const uint32_t not_up_to_track_mask = ((1 << (32 - remaining_tracks)) - 1);
const uint32_t default_value = and_not(not_up_to_track_mask, context.default_tracks_bitset[offset]);
num_default_rotations += count_set_bits(default_value & rotation_track_bit_mask);
num_default_translations += count_set_bits(default_value & translation_track_bit_mask);
num_default_scales += count_set_bits(default_value & scale_track_bit_mask);
const uint32_t constant_value = and_not(not_up_to_track_mask, context.constant_tracks_bitset[offset]);
num_constant_rotations += count_set_bits(constant_value & rotation_track_bit_mask);
num_constant_translations += count_set_bits(constant_value & translation_track_bit_mask);
num_constant_scales += count_set_bits(constant_value & scale_track_bit_mask);
}
}
else
{
const uint32_t rotation_track_bit_mask = 0xAAAAAAAA; // b10101010..
const uint32_t translation_track_bit_mask = 0x55555555; // b01010101..
const uint32_t last_offset = sub_track_index / 32;
uint32_t offset = 0;
for (; offset < last_offset; ++offset)
{
const uint32_t default_value = context.default_tracks_bitset[offset];
num_default_rotations += count_set_bits(default_value & rotation_track_bit_mask);
num_default_translations += count_set_bits(default_value & translation_track_bit_mask);
const uint32_t constant_value = context.constant_tracks_bitset[offset];
num_constant_rotations += count_set_bits(constant_value & rotation_track_bit_mask);
num_constant_translations += count_set_bits(constant_value & translation_track_bit_mask);
}
const uint32_t remaining_tracks = sub_track_index % 32;
if (remaining_tracks != 0)
{
const uint32_t not_up_to_track_mask = ((1 << (32 - remaining_tracks)) - 1);
const uint32_t default_value = and_not(not_up_to_track_mask, context.default_tracks_bitset[offset]);
num_default_rotations += count_set_bits(default_value & rotation_track_bit_mask);
num_default_translations += count_set_bits(default_value & translation_track_bit_mask);
const uint32_t constant_value = and_not(not_up_to_track_mask, context.constant_tracks_bitset[offset]);
num_constant_rotations += count_set_bits(constant_value & rotation_track_bit_mask);
num_constant_translations += count_set_bits(constant_value & translation_track_bit_mask);
}
}
uint32_t rotation_group_index = 0;
uint32_t translation_group_index = 0;
uint32_t scale_group_index = 0;
constant_track_cache_v0 constant_track_cache;
// Skip the constant track data
if (is_rotation_constant || is_translation_constant || is_scale_constant)
{
constant_track_cache.initialize<decompression_settings_type>(context);
// Calculate how many constant groups of each sub-track type we need to skip
// Constant groups are easy to skip since they are contiguous in memory, we can just skip N trivially
// Tracks that are default are also constant
// Unpack the groups we need and skip the tracks before us
if (is_rotation_constant)
{
const uint32_t num_constant_rotations_packed = num_constant_rotations - num_default_rotations;
const uint32_t num_rotation_constant_groups_to_skip = num_constant_rotations_packed / 4;
if (num_rotation_constant_groups_to_skip != 0)
constant_track_cache.skip_rotation_groups<decompression_settings_type>(context, num_rotation_constant_groups_to_skip);
rotation_group_index = num_constant_rotations_packed % 4;
}
if (is_translation_constant)
{
const uint32_t num_constant_translations_packed = num_constant_translations - num_default_translations;
const uint32_t num_translation_constant_groups_to_skip = num_constant_translations_packed / 4;
if (num_translation_constant_groups_to_skip != 0)
constant_track_cache.skip_translation_groups(num_translation_constant_groups_to_skip);
translation_group_index = num_constant_translations_packed % 4;
}
if (is_scale_constant)
{
const uint32_t num_constant_scales_packed = num_constant_scales - num_default_scales;
const uint32_t num_scale_constant_groups_to_skip = num_constant_scales_packed / 4;
if (num_scale_constant_groups_to_skip != 0)
constant_track_cache.skip_scale_groups(num_scale_constant_groups_to_skip);
scale_group_index = num_constant_scales_packed % 4;
}
}
else
{
// Fake init to avoid compiler warning...
constant_track_cache.rotations.num_left_to_unpack = 0;
constant_track_cache.constant_data_rotations = nullptr;
constant_track_cache.constant_data_translations = nullptr;
constant_track_cache.constant_data_scales = nullptr;
}
animated_track_cache_v0 animated_track_cache;
animated_group_cursor_v0 rotation_group_cursor;
animated_group_cursor_v0 translation_group_cursor;
animated_group_cursor_v0 scale_group_cursor;
// Skip the animated track data
if (is_rotation_animated || is_translation_animated || is_scale_animated)
{
animated_track_cache.initialize(context);
// Calculate how many animated groups of each sub-track type we need to skip
// Skipping animated groups is a bit more complicated because they are interleaved in the order
// they are needed
// Tracks that are default are also constant
const uint32_t num_animated_rotations = track_index - num_constant_rotations;
if (is_rotation_animated)
rotation_group_index = num_animated_rotations % 4;
const uint32_t num_animated_translations = track_index - num_constant_translations;
if (is_translation_animated)
translation_group_index = num_animated_translations % 4;
const uint32_t num_animated_scales = has_scale ? (track_index - num_constant_scales) : 0;
if (is_scale_animated)
scale_group_index = num_animated_scales % 4;
uint32_t num_rotations_to_unpack = is_rotation_animated ? num_animated_rotations : ~0U;
uint32_t num_translations_to_unpack = is_translation_animated ? num_animated_translations : ~0U;
uint32_t num_scales_to_unpack = is_scale_animated ? num_animated_scales : ~0U;
uint32_t num_animated_groups_to_unpack = is_rotation_animated + is_translation_animated + is_scale_animated;
const transform_tracks_header& transform_header = get_transform_tracks_header(*context.tracks);
const animation_track_type8* group_types = transform_header.get_animated_group_types();
while (num_animated_groups_to_unpack != 0)
{
const animation_track_type8 group_type = *group_types;
group_types++;
ACL_ASSERT(group_type != static_cast<animation_track_type8>(0xFF), "Reached terminator");
if (group_type == animation_track_type8::rotation)
{
if (num_rotations_to_unpack < 4)
{
// This is the group we need, cache our cursor
animated_track_cache.get_rotation_cursor(rotation_group_cursor);
num_animated_groups_to_unpack--;
}
animated_track_cache.skip_rotation_group<decompression_settings_type>(context);
num_rotations_to_unpack -= 4;
}
else if (group_type == animation_track_type8::translation)
{
if (num_translations_to_unpack < 4)
{
// This is the group we need, cache our cursor
animated_track_cache.get_translation_cursor(translation_group_cursor);
num_animated_groups_to_unpack--;
}
animated_track_cache.skip_translation_group<translation_adapter>(context);
num_translations_to_unpack -= 4;
}
else // scale
{
if (num_scales_to_unpack < 4)
{
// This is the group we need, cache our cursor
animated_track_cache.get_scale_cursor(scale_group_cursor);
num_animated_groups_to_unpack--;
}
animated_track_cache.skip_scale_group<scale_adapter>(context);
num_scales_to_unpack -= 4;
}
}
}
// Finally reached our desired track, unpack it
{
rtm::quatf rotation;
if (is_rotation_default)
rotation = default_rotation;
else if (is_rotation_constant)
rotation = constant_track_cache.unpack_rotation_within_group<decompression_settings_type>(context, rotation_group_index);
else
rotation = animated_track_cache.unpack_rotation_within_group<decompression_settings_type>(context, rotation_group_cursor, rotation_group_index);
writer.write_rotation(track_index, rotation);
}
{
rtm::vector4f translation;
if (is_translation_default)
translation = default_translation;
else if (is_translation_constant)
translation = constant_track_cache.unpack_translation_within_group(translation_group_index);
else
translation = animated_track_cache.unpack_translation_within_group<translation_adapter>(context, translation_group_cursor, translation_group_index);
writer.write_translation(track_index, translation);
}
{
rtm::vector4f scale;
if (is_scale_default)
scale = default_scale;
else if (is_scale_constant)
scale = constant_track_cache.unpack_scale_within_group(scale_group_index);
else
scale = animated_track_cache.unpack_scale_within_group<scale_adapter>(context, scale_group_cursor, scale_group_index);
writer.write_scale(track_index, scale);
}
if (decompression_settings_type::disable_fp_exeptions())
restore_fp_exceptions(fp_env);
}
}
}
ACL_IMPL_FILE_PRAGMA_POP