637 lines
27 KiB
C++
637 lines
27 KiB
C++
#pragma once
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// The MIT License (MIT)
|
|
//
|
|
// Copyright (c) 2017 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/impl/compiler_utils.h"
|
|
#include "acl/core/iallocator.h"
|
|
#include "acl/core/error.h"
|
|
#include "acl/core/track_formats.h"
|
|
#include "acl/core/variable_bit_rates.h"
|
|
#include "acl/compression/impl/animated_track_utils.h"
|
|
#include "acl/compression/impl/clip_context.h"
|
|
|
|
#include "acl/core/impl/compressed_headers.h"
|
|
|
|
#include <cstdint>
|
|
|
|
ACL_IMPL_FILE_PRAGMA_PUSH
|
|
|
|
namespace acl
|
|
{
|
|
namespace acl_impl
|
|
{
|
|
inline uint32_t get_constant_data_size(const clip_context& clip)
|
|
{
|
|
// Only use the first segment, it contains the necessary information
|
|
const SegmentContext& segment = clip.segments[0];
|
|
|
|
uint32_t constant_data_size = 0;
|
|
|
|
for (uint32_t bone_index = 0; bone_index < clip.num_bones; ++bone_index)
|
|
{
|
|
const BoneStreams& bone_stream = segment.bone_streams[bone_index];
|
|
if (bone_stream.output_index == k_invalid_track_index)
|
|
continue; // Stripped
|
|
|
|
if (!bone_stream.is_rotation_default && bone_stream.is_rotation_constant)
|
|
constant_data_size += bone_stream.rotations.get_packed_sample_size();
|
|
|
|
if (!bone_stream.is_translation_default && bone_stream.is_translation_constant)
|
|
constant_data_size += bone_stream.translations.get_packed_sample_size();
|
|
|
|
if (clip.has_scale && !bone_stream.is_scale_default && bone_stream.is_scale_constant)
|
|
constant_data_size += bone_stream.scales.get_packed_sample_size();
|
|
}
|
|
|
|
return constant_data_size;
|
|
}
|
|
|
|
inline void get_num_constant_samples(const clip_context& clip, uint32_t& out_num_constant_rotation_samples, uint32_t& out_num_constant_translation_samples, uint32_t& out_num_constant_scale_samples)
|
|
{
|
|
uint32_t num_constant_rotation_samples = 0;
|
|
uint32_t num_constant_translation_samples = 0;
|
|
uint32_t num_constant_scale_samples = 0;
|
|
|
|
// Only use the first segment, it contains the necessary information
|
|
const SegmentContext& segment = clip.segments[0];
|
|
|
|
for (uint32_t bone_index = 0; bone_index < clip.num_bones; ++bone_index)
|
|
{
|
|
const BoneStreams& bone_stream = segment.bone_streams[bone_index];
|
|
if (bone_stream.output_index == k_invalid_track_index)
|
|
continue; // Stripped
|
|
|
|
if (!bone_stream.is_rotation_default && bone_stream.is_rotation_constant)
|
|
num_constant_rotation_samples++;
|
|
|
|
if (!bone_stream.is_translation_default && bone_stream.is_translation_constant)
|
|
num_constant_translation_samples++;
|
|
|
|
if (clip.has_scale && !bone_stream.is_scale_default && bone_stream.is_scale_constant)
|
|
num_constant_scale_samples++;
|
|
}
|
|
|
|
out_num_constant_rotation_samples = num_constant_rotation_samples;
|
|
out_num_constant_translation_samples = num_constant_translation_samples;
|
|
out_num_constant_scale_samples = num_constant_scale_samples;
|
|
}
|
|
|
|
inline void get_animated_variable_bit_rate_data_size(const TrackStream& track_stream, uint32_t num_samples, uint32_t& out_num_animated_data_bits, uint32_t& out_num_animated_pose_bits)
|
|
{
|
|
const uint8_t bit_rate = track_stream.get_bit_rate();
|
|
const uint32_t num_bits_at_bit_rate = get_num_bits_at_bit_rate(bit_rate) * 3; // 3 components
|
|
out_num_animated_data_bits += num_bits_at_bit_rate * num_samples;
|
|
out_num_animated_pose_bits += num_bits_at_bit_rate;
|
|
}
|
|
|
|
inline void calculate_animated_data_size(const TrackStream& track_stream, uint32_t& num_animated_data_bits, uint32_t& num_animated_pose_bits)
|
|
{
|
|
const uint32_t num_samples = track_stream.get_num_samples();
|
|
|
|
if (track_stream.is_bit_rate_variable())
|
|
{
|
|
get_animated_variable_bit_rate_data_size(track_stream, num_samples, num_animated_data_bits, num_animated_pose_bits);
|
|
}
|
|
else
|
|
{
|
|
const uint32_t sample_size = track_stream.get_packed_sample_size();
|
|
num_animated_data_bits += sample_size * num_samples * 8;
|
|
num_animated_pose_bits += sample_size * 8;
|
|
}
|
|
}
|
|
|
|
inline void calculate_animated_data_size(clip_context& clip, const uint32_t* output_bone_mapping, uint32_t num_output_bones)
|
|
{
|
|
for (SegmentContext& segment : clip.segment_iterator())
|
|
{
|
|
uint32_t num_animated_rotation_data_bits = 0;
|
|
uint32_t num_animated_translation_data_bits = 0;
|
|
uint32_t num_animated_scale_data_bits = 0;
|
|
uint32_t num_animated_pose_bits = 0;
|
|
|
|
for (uint32_t output_index = 0; output_index < num_output_bones; ++output_index)
|
|
{
|
|
const uint32_t bone_index = output_bone_mapping[output_index];
|
|
const BoneStreams& bone_stream = segment.bone_streams[bone_index];
|
|
|
|
if (!bone_stream.is_rotation_constant)
|
|
calculate_animated_data_size(bone_stream.rotations, num_animated_rotation_data_bits, num_animated_pose_bits);
|
|
|
|
if (!bone_stream.is_translation_constant)
|
|
calculate_animated_data_size(bone_stream.translations, num_animated_translation_data_bits, num_animated_pose_bits);
|
|
|
|
if (!bone_stream.is_scale_constant)
|
|
calculate_animated_data_size(bone_stream.scales, num_animated_scale_data_bits, num_animated_pose_bits);
|
|
}
|
|
|
|
const uint32_t num_animated_data_bits = num_animated_rotation_data_bits + num_animated_translation_data_bits + num_animated_scale_data_bits;
|
|
|
|
segment.animated_pose_rotation_bit_size = num_animated_rotation_data_bits;
|
|
segment.animated_pose_translation_bit_size = num_animated_translation_data_bits;
|
|
segment.animated_pose_scale_bit_size = num_animated_scale_data_bits;
|
|
segment.animated_data_size = align_to(num_animated_data_bits, 8) / 8;
|
|
segment.animated_pose_bit_size = num_animated_pose_bits;
|
|
}
|
|
}
|
|
|
|
inline uint32_t get_format_per_track_data_size(const clip_context& clip, rotation_format8 rotation_format, vector_format8 translation_format, vector_format8 scale_format, uint32_t* out_num_animated_variable_sub_tracks_padded = nullptr)
|
|
{
|
|
const bool is_rotation_variable = is_rotation_format_variable(rotation_format);
|
|
const bool is_translation_variable = is_vector_format_variable(translation_format);
|
|
const bool is_scale_variable = is_vector_format_variable(scale_format);
|
|
|
|
// Only use the first segment, it contains the necessary information
|
|
const SegmentContext& segment = clip.segments[0];
|
|
|
|
uint32_t format_per_track_data_size = 0;
|
|
uint32_t num_animated_variable_rotations = 0;
|
|
|
|
for (const BoneStreams& bone_stream : segment.const_bone_iterator())
|
|
{
|
|
if (bone_stream.is_stripped_from_output())
|
|
continue;
|
|
|
|
if (!bone_stream.is_rotation_constant && is_rotation_variable)
|
|
{
|
|
format_per_track_data_size++;
|
|
num_animated_variable_rotations++;
|
|
}
|
|
|
|
if (!bone_stream.is_translation_constant && is_translation_variable)
|
|
format_per_track_data_size++;
|
|
|
|
if (!bone_stream.is_scale_constant && is_scale_variable)
|
|
format_per_track_data_size++;
|
|
}
|
|
|
|
// Rotations are padded for alignment
|
|
const uint32_t num_partial_rotations = num_animated_variable_rotations % 4;
|
|
if (num_partial_rotations != 0)
|
|
format_per_track_data_size += 4 - num_partial_rotations;
|
|
|
|
if (out_num_animated_variable_sub_tracks_padded != nullptr)
|
|
*out_num_animated_variable_sub_tracks_padded = format_per_track_data_size; // 1 byte per sub-track
|
|
|
|
return format_per_track_data_size;
|
|
}
|
|
|
|
inline uint32_t write_constant_track_data(const clip_context& clip, rotation_format8 rotation_format, uint8_t* constant_data, uint32_t constant_data_size, const uint32_t* output_bone_mapping, uint32_t num_output_bones)
|
|
{
|
|
ACL_ASSERT(constant_data != nullptr, "'constant_data' cannot be null!");
|
|
(void)constant_data_size;
|
|
|
|
// Only use the first segment, it contains the necessary information
|
|
const SegmentContext& segment = clip.segments[0];
|
|
|
|
#if defined(ACL_HAS_ASSERT_CHECKS)
|
|
const uint8_t* constant_data_end = add_offset_to_ptr<uint8_t>(constant_data, constant_data_size);
|
|
#endif
|
|
|
|
const uint8_t* constant_data_start = constant_data;
|
|
|
|
#if defined(ACL_IMPL_USE_CONSTANT_GROUPS)
|
|
// Data is ordered in groups of 4 constant sub-tracks (e.g rot0, rot1, rot2, rot3)
|
|
// Order depends on animated track order. If we have 6 constant rotation tracks before the first constant
|
|
// translation track, we'll have 8 constant rotation sub-tracks followed by 4 constant translation sub-tracks.
|
|
// Once we reach the end, there is no extra padding. The last group might be less than 4 sub-tracks.
|
|
// This is because we always process 4 constant sub-tracks at a time and cache the results.
|
|
|
|
// Groups are written in the order of first use and as such are sorted by their lowest sub-track index.
|
|
|
|
// If our rotation format drops the W component, we swizzle the data to store XXXX, YYYY, ZZZZ
|
|
const bool swizzle_rotations = get_rotation_variant(rotation_format) == rotation_variant8::quat_drop_w;
|
|
|
|
float xxxx_group[4];
|
|
float yyyy_group[4];
|
|
float zzzz_group[4];
|
|
rtm::vector4f constant_group4[4];
|
|
rtm::float3f constant_group3[4];
|
|
|
|
auto group_entry_action = [&](animation_track_type8 group_type, uint32_t group_size, uint32_t bone_index)
|
|
{
|
|
const BoneStreams& bone_stream = segment.bone_streams[bone_index];
|
|
|
|
if (group_type == animation_track_type8::rotation)
|
|
{
|
|
if (swizzle_rotations)
|
|
{
|
|
const rtm::vector4f sample = bone_stream.rotations.get_raw_sample<rtm::vector4f>(0);
|
|
xxxx_group[group_size] = rtm::vector_get_x(sample);
|
|
yyyy_group[group_size] = rtm::vector_get_y(sample);
|
|
zzzz_group[group_size] = rtm::vector_get_z(sample);
|
|
}
|
|
else
|
|
{
|
|
const rtm::vector4f sample = bone_stream.rotations.get_raw_sample<rtm::vector4f>(0);
|
|
constant_group4[group_size] = sample;
|
|
}
|
|
}
|
|
else if (group_type == animation_track_type8::translation)
|
|
{
|
|
const rtm::vector4f sample = bone_stream.translations.get_raw_sample<rtm::vector4f>(0);
|
|
rtm::vector_store3(sample, &constant_group3[group_size]);
|
|
}
|
|
else
|
|
{
|
|
const rtm::vector4f sample = bone_stream.scales.get_raw_sample<rtm::vector4f>(0);
|
|
rtm::vector_store3(sample, &constant_group3[group_size]);
|
|
}
|
|
};
|
|
|
|
auto group_flush_action = [&](animation_track_type8 group_type, uint32_t group_size)
|
|
{
|
|
if (group_type == animation_track_type8::rotation)
|
|
{
|
|
if (swizzle_rotations)
|
|
{
|
|
std::memcpy(constant_data, &xxxx_group[0], group_size * sizeof(float));
|
|
constant_data += group_size * sizeof(float);
|
|
std::memcpy(constant_data, &yyyy_group[0], group_size * sizeof(float));
|
|
constant_data += group_size * sizeof(float);
|
|
std::memcpy(constant_data, &zzzz_group[0], group_size * sizeof(float));
|
|
constant_data += group_size * sizeof(float);
|
|
}
|
|
else
|
|
{
|
|
// If we don't swizzle, we have a full quaternion
|
|
std::memcpy(constant_data, &constant_group4[0], group_size * sizeof(rtm::vector4f));
|
|
constant_data += group_size * sizeof(rtm::vector4f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::memcpy(constant_data, &constant_group3[0], group_size * sizeof(rtm::float3f));
|
|
constant_data += group_size * sizeof(rtm::float3f);
|
|
}
|
|
|
|
ACL_ASSERT(constant_data <= constant_data_end, "Invalid constant data offset. Wrote too much data.");
|
|
};
|
|
|
|
constant_group_writer(segment, output_bone_mapping, num_output_bones, group_entry_action, group_flush_action);
|
|
#else
|
|
// If our rotation format drops the W component, we swizzle the data to store XXXX, YYYY, ZZZZ
|
|
const bool swizzle_rotations = get_rotation_variant(rotation_format) == rotation_variant8::quat_drop_w;
|
|
float xxxx[4];
|
|
float yyyy[4];
|
|
float zzzz[4];
|
|
uint32_t num_swizzle_written = 0;
|
|
|
|
// Write rotations first
|
|
for (uint32_t output_index = 0; output_index < num_output_bones; ++output_index)
|
|
{
|
|
const uint32_t bone_index = output_bone_mapping[output_index];
|
|
const BoneStreams& bone_stream = segment.bone_streams[bone_index];
|
|
|
|
if (!bone_stream.is_rotation_default && bone_stream.is_rotation_constant)
|
|
{
|
|
if (swizzle_rotations)
|
|
{
|
|
const rtm::vector4f rotation = bone_stream.rotations.get_raw_sample<rtm::vector4f>(0);
|
|
xxxx[num_swizzle_written] = rtm::vector_get_x(rotation);
|
|
yyyy[num_swizzle_written] = rtm::vector_get_y(rotation);
|
|
zzzz[num_swizzle_written] = rtm::vector_get_z(rotation);
|
|
num_swizzle_written++;
|
|
|
|
if (num_swizzle_written >= 4)
|
|
{
|
|
std::memcpy(constant_data, &xxxx[0], sizeof(xxxx));
|
|
constant_data += sizeof(xxxx);
|
|
std::memcpy(constant_data, &yyyy[0], sizeof(yyyy));
|
|
constant_data += sizeof(yyyy);
|
|
std::memcpy(constant_data, &zzzz[0], sizeof(zzzz));
|
|
constant_data += sizeof(zzzz);
|
|
num_swizzle_written = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const uint8_t* rotation_ptr = bone_stream.rotations.get_raw_sample_ptr(0);
|
|
uint32_t sample_size = bone_stream.rotations.get_sample_size();
|
|
std::memcpy(constant_data, rotation_ptr, sample_size);
|
|
constant_data += sample_size;
|
|
}
|
|
|
|
ACL_ASSERT(constant_data <= constant_data_end, "Invalid constant data offset. Wrote too much data.");
|
|
}
|
|
}
|
|
|
|
if (swizzle_rotations && num_swizzle_written != 0)
|
|
{
|
|
std::memcpy(constant_data, &xxxx[0], num_swizzle_written * sizeof(float));
|
|
constant_data += num_swizzle_written * sizeof(float);
|
|
std::memcpy(constant_data, &yyyy[0], num_swizzle_written * sizeof(float));
|
|
constant_data += num_swizzle_written * sizeof(float);
|
|
std::memcpy(constant_data, &zzzz[0], num_swizzle_written * sizeof(float));
|
|
constant_data += num_swizzle_written * sizeof(float);
|
|
|
|
ACL_ASSERT(constant_data <= constant_data_end, "Invalid constant data offset. Wrote too much data.");
|
|
}
|
|
|
|
// Next, write translations
|
|
for (uint32_t output_index = 0; output_index < num_output_bones; ++output_index)
|
|
{
|
|
const uint32_t bone_index = output_bone_mapping[output_index];
|
|
const BoneStreams& bone_stream = segment.bone_streams[bone_index];
|
|
|
|
if (!bone_stream.is_translation_default && bone_stream.is_translation_constant)
|
|
{
|
|
const uint8_t* translation_ptr = bone_stream.translations.get_raw_sample_ptr(0);
|
|
uint32_t sample_size = bone_stream.translations.get_sample_size();
|
|
std::memcpy(constant_data, translation_ptr, sample_size);
|
|
constant_data += sample_size;
|
|
|
|
ACL_ASSERT(constant_data <= constant_data_end, "Invalid constant data offset. Wrote too much data.");
|
|
}
|
|
}
|
|
|
|
// Finally, write scales
|
|
if (clip.has_scale)
|
|
{
|
|
for (uint32_t output_index = 0; output_index < num_output_bones; ++output_index)
|
|
{
|
|
const uint32_t bone_index = output_bone_mapping[output_index];
|
|
const BoneStreams& bone_stream = segment.bone_streams[bone_index];
|
|
|
|
if (!bone_stream.is_scale_default && bone_stream.is_scale_constant)
|
|
{
|
|
const uint8_t* scale_ptr = bone_stream.scales.get_raw_sample_ptr(0);
|
|
uint32_t sample_size = bone_stream.scales.get_sample_size();
|
|
std::memcpy(constant_data, scale_ptr, sample_size);
|
|
constant_data += sample_size;
|
|
|
|
ACL_ASSERT(constant_data <= constant_data_end, "Invalid constant data offset. Wrote too much data.");
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
ACL_ASSERT(constant_data == constant_data_end, "Invalid constant data offset. Wrote too little data.");
|
|
return safe_static_cast<uint32_t>(constant_data - constant_data_start);
|
|
}
|
|
|
|
inline void write_animated_track_data(const TrackStream& track_stream, uint32_t sample_index, uint8_t* animated_track_data_begin, uint8_t*& out_animated_track_data, uint64_t& out_bit_offset)
|
|
{
|
|
const uint8_t* raw_sample_ptr = track_stream.get_raw_sample_ptr(sample_index);
|
|
|
|
if (track_stream.is_bit_rate_variable())
|
|
{
|
|
const uint8_t bit_rate = track_stream.get_bit_rate();
|
|
const uint64_t num_bits_at_bit_rate = get_num_bits_at_bit_rate(bit_rate) * 3; // 3 components
|
|
|
|
// Track is constant, our constant sample is stored in the range information
|
|
ACL_ASSERT(!is_constant_bit_rate(bit_rate), "Cannot write constant variable track data");
|
|
|
|
if (is_raw_bit_rate(bit_rate))
|
|
{
|
|
const uint32_t* raw_sample_u32 = safe_ptr_cast<const uint32_t>(raw_sample_ptr);
|
|
|
|
const uint32_t x = byte_swap(raw_sample_u32[0]);
|
|
memcpy_bits(animated_track_data_begin, out_bit_offset + 0, &x, 0, 32);
|
|
const uint32_t y = byte_swap(raw_sample_u32[1]);
|
|
memcpy_bits(animated_track_data_begin, out_bit_offset + 32, &y, 0, 32);
|
|
const uint32_t z = byte_swap(raw_sample_u32[2]);
|
|
memcpy_bits(animated_track_data_begin, out_bit_offset + 64, &z, 0, 32);
|
|
}
|
|
else
|
|
{
|
|
const uint64_t raw_sample_u64 = *safe_ptr_cast<const uint64_t>(raw_sample_ptr);
|
|
memcpy_bits(animated_track_data_begin, out_bit_offset, &raw_sample_u64, 0, num_bits_at_bit_rate);
|
|
}
|
|
|
|
out_bit_offset += num_bits_at_bit_rate;
|
|
out_animated_track_data = animated_track_data_begin + (out_bit_offset / 8);
|
|
}
|
|
else
|
|
{
|
|
const uint32_t* raw_sample_u32 = safe_ptr_cast<const uint32_t>(raw_sample_ptr);
|
|
|
|
const uint32_t x = byte_swap(raw_sample_u32[0]);
|
|
memcpy_bits(animated_track_data_begin, out_bit_offset + 0, &x, 0, 32);
|
|
const uint32_t y = byte_swap(raw_sample_u32[1]);
|
|
memcpy_bits(animated_track_data_begin, out_bit_offset + 32, &y, 0, 32);
|
|
const uint32_t z = byte_swap(raw_sample_u32[2]);
|
|
memcpy_bits(animated_track_data_begin, out_bit_offset + 64, &z, 0, 32);
|
|
|
|
const uint32_t sample_size = track_stream.get_packed_sample_size();
|
|
const bool has_w_component = sample_size == (sizeof(float) * 4);
|
|
if (has_w_component)
|
|
{
|
|
const uint32_t w = byte_swap(raw_sample_u32[3]);
|
|
memcpy_bits(animated_track_data_begin, out_bit_offset + 96, &w, 0, 32);
|
|
}
|
|
|
|
out_bit_offset += has_w_component ? 128 : 96;
|
|
out_animated_track_data = animated_track_data_begin + (out_bit_offset / 8);
|
|
}
|
|
}
|
|
|
|
inline uint32_t write_animated_track_data(const SegmentContext& segment, uint8_t* animated_track_data, uint32_t animated_data_size, const uint32_t* output_bone_mapping, uint32_t num_output_bones)
|
|
{
|
|
ACL_ASSERT(animated_track_data != nullptr, "'animated_track_data' cannot be null!");
|
|
(void)animated_data_size;
|
|
|
|
uint8_t* animated_track_data_begin = animated_track_data;
|
|
|
|
#if defined(ACL_HAS_ASSERT_CHECKS)
|
|
const uint8_t* animated_track_data_end = add_offset_to_ptr<uint8_t>(animated_track_data, animated_data_size);
|
|
#endif
|
|
|
|
const uint8_t* animated_track_data_start = animated_track_data;
|
|
uint64_t bit_offset = 0;
|
|
|
|
// Data is sorted first by time, second by bone.
|
|
// This ensures that all bones are contiguous in memory when we sample a particular time.
|
|
|
|
// Data is ordered in groups of 4 animated sub-tracks (e.g rot0, rot1, rot2, rot3)
|
|
// Order depends on animated track order. If we have 6 animated rotation tracks before the first animated
|
|
// translation track, we'll have 8 animated rotation sub-tracks followed by 4 animated translation sub-tracks.
|
|
// Once we reach the end, there is no extra padding. The last group might be less than 4 sub-tracks.
|
|
// This is because we always process 4 animated sub-tracks at a time and cache the results.
|
|
|
|
// Groups are written in the order of first use and as such are sorted by their lowest sub-track index.
|
|
|
|
// For animated samples, when we have a constant bit rate (bit rate 0), we do not store samples
|
|
// and as such the group that contains that sub-track won't contain 4 samples.
|
|
// The largest sample is a full precision vector4f, we can contain at most 4 samples
|
|
alignas(16) uint8_t group_animated_track_data[sizeof(rtm::vector4f) * 4];
|
|
uint64_t group_bit_offset = 0;
|
|
uint32_t num_group_samples = 0;
|
|
uint8_t* dummy_animated_track_data_ptr = nullptr;
|
|
|
|
auto group_filter_action = [&](animation_track_type8 group_type, uint32_t bone_index)
|
|
{
|
|
(void)group_type;
|
|
(void)bone_index;
|
|
|
|
// We want a group of every animated track
|
|
// If a track is variable with a constant bit rate (bit rate 0), the group will have fewer entries
|
|
return true;
|
|
};
|
|
|
|
auto group_flush_action = [&](animation_track_type8 group_type, uint32_t group_size)
|
|
{
|
|
(void)group_type;
|
|
|
|
if (group_size == 0)
|
|
return; // Empty group, skip
|
|
|
|
memcpy_bits(animated_track_data_begin, bit_offset, &group_animated_track_data[0], 0, group_bit_offset);
|
|
|
|
bit_offset += group_bit_offset;
|
|
group_bit_offset = 0;
|
|
num_group_samples = 0;
|
|
|
|
animated_track_data = animated_track_data_begin + (bit_offset / 8);
|
|
|
|
ACL_ASSERT(animated_track_data <= animated_track_data_end, "Invalid animated track data offset. Wrote too much data.");
|
|
};
|
|
|
|
// TODO: Use a group writer context object to avoid alloc/free/work in loop for every sample when it doesn't change
|
|
for (uint32_t sample_index = 0; sample_index < segment.num_samples; ++sample_index)
|
|
{
|
|
auto group_entry_action = [&](animation_track_type8 group_type, uint32_t group_size, uint32_t bone_index)
|
|
{
|
|
(void)group_size;
|
|
|
|
const BoneStreams& bone_stream = segment.bone_streams[bone_index];
|
|
if (group_type == animation_track_type8::rotation)
|
|
{
|
|
if (!is_constant_bit_rate(bone_stream.rotations.get_bit_rate()))
|
|
{
|
|
write_animated_track_data(bone_stream.rotations, sample_index, group_animated_track_data, dummy_animated_track_data_ptr, group_bit_offset);
|
|
num_group_samples++;
|
|
}
|
|
}
|
|
else if (group_type == animation_track_type8::translation)
|
|
{
|
|
if (!is_constant_bit_rate(bone_stream.translations.get_bit_rate()))
|
|
{
|
|
write_animated_track_data(bone_stream.translations, sample_index, group_animated_track_data, dummy_animated_track_data_ptr, group_bit_offset);
|
|
num_group_samples++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!is_constant_bit_rate(bone_stream.scales.get_bit_rate()))
|
|
{
|
|
write_animated_track_data(bone_stream.scales, sample_index, group_animated_track_data, dummy_animated_track_data_ptr, group_bit_offset);
|
|
num_group_samples++;
|
|
}
|
|
}
|
|
};
|
|
|
|
animated_group_writer(segment, output_bone_mapping, num_output_bones, group_filter_action, group_entry_action, group_flush_action);
|
|
}
|
|
|
|
if (bit_offset != 0)
|
|
animated_track_data = animated_track_data_begin + (align_to(bit_offset, 8) / 8);
|
|
|
|
ACL_ASSERT((bit_offset / segment.num_samples) == segment.animated_pose_bit_size, "Unexpected number of bits written");
|
|
ACL_ASSERT(animated_track_data == animated_track_data_end, "Invalid animated track data offset. Wrote too little data.");
|
|
return safe_static_cast<uint32_t>(animated_track_data - animated_track_data_start);
|
|
}
|
|
|
|
inline uint32_t write_format_per_track_data(const SegmentContext& segment, uint8_t* format_per_track_data, uint32_t format_per_track_data_size, const uint32_t* output_bone_mapping, uint32_t num_output_bones)
|
|
{
|
|
ACL_ASSERT(format_per_track_data != nullptr, "'format_per_track_data' cannot be null!");
|
|
(void)format_per_track_data_size;
|
|
|
|
#if defined(ACL_HAS_ASSERT_CHECKS)
|
|
const uint8_t* format_per_track_data_end = add_offset_to_ptr<uint8_t>(format_per_track_data, format_per_track_data_size);
|
|
#endif
|
|
|
|
const uint8_t* format_per_track_data_start = format_per_track_data;
|
|
|
|
// Data is ordered in groups of 4 animated sub-tracks (e.g rot0, rot1, rot2, rot3)
|
|
// Order depends on animated track order. If we have 6 animated rotation tracks before the first animated
|
|
// translation track, we'll have 8 animated rotation sub-tracks followed by 4 animated translation sub-tracks.
|
|
// Once we reach the end, there is no extra padding. The last group might be less than 4 sub-tracks.
|
|
// This is because we always process 4 animated sub-tracks at a time and cache the results.
|
|
|
|
// Groups are written in the order of first use and as such are sorted by their lowest sub-track index.
|
|
|
|
// To keep decompression simpler, rotations are padded to 4 elements even if the last group is partial
|
|
uint8_t format_per_track_group[4];
|
|
|
|
auto group_filter_action = [&](animation_track_type8 group_type, uint32_t bone_index)
|
|
{
|
|
const BoneStreams& bone_stream = segment.bone_streams[bone_index];
|
|
if (group_type == animation_track_type8::rotation)
|
|
return bone_stream.rotations.is_bit_rate_variable();
|
|
else if (group_type == animation_track_type8::translation)
|
|
return bone_stream.translations.is_bit_rate_variable();
|
|
else
|
|
return bone_stream.scales.is_bit_rate_variable();
|
|
};
|
|
|
|
auto group_entry_action = [&](animation_track_type8 group_type, uint32_t group_size, uint32_t bone_index)
|
|
{
|
|
const BoneStreams& bone_stream = segment.bone_streams[bone_index];
|
|
if (group_type == animation_track_type8::rotation)
|
|
format_per_track_group[group_size] = (uint8_t)get_num_bits_at_bit_rate(bone_stream.rotations.get_bit_rate());
|
|
else if (group_type == animation_track_type8::translation)
|
|
format_per_track_group[group_size] = (uint8_t)get_num_bits_at_bit_rate(bone_stream.translations.get_bit_rate());
|
|
else
|
|
format_per_track_group[group_size] = (uint8_t)get_num_bits_at_bit_rate(bone_stream.scales.get_bit_rate());
|
|
};
|
|
|
|
auto group_flush_action = [&](animation_track_type8 group_type, uint32_t group_size)
|
|
{
|
|
const uint32_t copy_size = group_type == animation_track_type8::rotation ? 4 : group_size;
|
|
std::memcpy(format_per_track_data, &format_per_track_group[0], copy_size);
|
|
format_per_track_data += copy_size;
|
|
|
|
// Zero out the temporary buffer for the final group to not contain partial garbage
|
|
std::memset(&format_per_track_group[0], 0, sizeof(format_per_track_group));
|
|
|
|
ACL_ASSERT(format_per_track_data <= format_per_track_data_end, "Invalid format per track data offset. Wrote too much data.");
|
|
};
|
|
|
|
animated_group_writer(segment, output_bone_mapping, num_output_bones, group_filter_action, group_entry_action, group_flush_action);
|
|
|
|
ACL_ASSERT(format_per_track_data == format_per_track_data_end, "Invalid format per track data offset. Wrote too little data.");
|
|
|
|
return safe_static_cast<uint32_t>(format_per_track_data - format_per_track_data_start);
|
|
}
|
|
|
|
inline uint32_t write_animated_group_types(const animation_track_type8* animated_sub_track_groups, uint32_t num_animated_groups, animation_track_type8* animated_sub_track_groups_data, uint32_t animated_sub_track_groups_data_size)
|
|
{
|
|
(void)animated_sub_track_groups_data_size;
|
|
|
|
const animation_track_type8* animated_sub_track_groups_data_start = animated_sub_track_groups_data;
|
|
|
|
std::memcpy(animated_sub_track_groups_data, animated_sub_track_groups, sizeof(animation_track_type8) * num_animated_groups);
|
|
animated_sub_track_groups_data += num_animated_groups;
|
|
animated_sub_track_groups_data[0] = static_cast<animation_track_type8>(0xFF); // Terminator
|
|
animated_sub_track_groups_data++;
|
|
|
|
ACL_ASSERT(animated_sub_track_groups_data == animated_sub_track_groups_data_start + animated_sub_track_groups_data_size, "Too little or too much data written");
|
|
return static_cast<uint32_t>(animated_sub_track_groups_data - animated_sub_track_groups_data_start);
|
|
}
|
|
}
|
|
}
|
|
|
|
ACL_IMPL_FILE_PRAGMA_POP
|