394 lines
16 KiB
C++
394 lines
16 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/additive_utils.h"
|
|
#include "acl/core/bitset.h"
|
|
#include "acl/core/iallocator.h"
|
|
#include "acl/core/iterator.h"
|
|
#include "acl/core/error.h"
|
|
#include "acl/core/track_formats.h"
|
|
#include "acl/core/impl/compiler_utils.h"
|
|
#include "acl/compression/compression_settings.h"
|
|
#include "acl/compression/impl/segment_context.h"
|
|
|
|
#include <rtm/quatf.h>
|
|
#include <rtm/vector4f.h>
|
|
|
|
#include <cstdint>
|
|
|
|
ACL_IMPL_FILE_PRAGMA_PUSH
|
|
|
|
namespace acl
|
|
{
|
|
namespace acl_impl
|
|
{
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Simple iterator utility class to allow easy looping
|
|
class BoneChainIterator
|
|
{
|
|
public:
|
|
BoneChainIterator(const uint32_t* bone_chain, bitset_description bone_chain_desc, uint32_t bone_index, uint32_t offset)
|
|
: m_bone_chain(bone_chain)
|
|
, m_bone_chain_desc(bone_chain_desc)
|
|
, m_bone_index(bone_index)
|
|
, m_offset(offset)
|
|
{}
|
|
|
|
BoneChainIterator& operator++()
|
|
{
|
|
ACL_ASSERT(m_offset <= m_bone_index, "Cannot increment the iterator, it is no longer valid");
|
|
|
|
// Skip the current bone
|
|
m_offset++;
|
|
|
|
// Iterate until we find the next bone part of the chain or until we reach the end of the chain
|
|
// TODO: Use clz or similar to find the next set bit starting at the current index
|
|
while (m_offset < m_bone_index && !bitset_test(m_bone_chain, m_bone_chain_desc, m_offset))
|
|
m_offset++;
|
|
|
|
return *this;
|
|
}
|
|
|
|
uint32_t operator*() const
|
|
{
|
|
ACL_ASSERT(m_offset <= m_bone_index, "Returned bone index doesn't belong to the bone chain");
|
|
ACL_ASSERT(bitset_test(m_bone_chain, m_bone_chain_desc, m_offset), "Returned bone index doesn't belong to the bone chain");
|
|
return m_offset;
|
|
}
|
|
|
|
// We only compare the offset in the bone chain. Two iterators on the same bone index
|
|
// from two different or equal chains will be equal.
|
|
bool operator==(const BoneChainIterator& other) const { return m_offset == other.m_offset; }
|
|
bool operator!=(const BoneChainIterator& other) const { return m_offset != other.m_offset; }
|
|
|
|
private:
|
|
const uint32_t* m_bone_chain;
|
|
bitset_description m_bone_chain_desc;
|
|
uint32_t m_bone_index;
|
|
uint32_t m_offset;
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Simple bone chain container to allow easy looping
|
|
//
|
|
// A bone chain allows looping over all bones up to a specific bone starting
|
|
// at the root bone.
|
|
//////////////////////////////////////////////////////////////////////////
|
|
struct BoneChain
|
|
{
|
|
BoneChain(const uint32_t* bone_chain, bitset_description bone_chain_desc, uint32_t bone_index)
|
|
: m_bone_chain(bone_chain)
|
|
, m_bone_chain_desc(bone_chain_desc)
|
|
, m_bone_index(bone_index)
|
|
{
|
|
// We don't know where this bone chain starts, find the root bone
|
|
// TODO: Use clz or similar to find the next set bit starting at the current index
|
|
uint32_t root_index = 0;
|
|
while (!bitset_test(bone_chain, bone_chain_desc, root_index))
|
|
root_index++;
|
|
|
|
m_root_index = root_index;
|
|
}
|
|
|
|
acl_impl::BoneChainIterator begin() const { return acl_impl::BoneChainIterator(m_bone_chain, m_bone_chain_desc, m_bone_index, m_root_index); }
|
|
acl_impl::BoneChainIterator end() const { return acl_impl::BoneChainIterator(m_bone_chain, m_bone_chain_desc, m_bone_index, m_bone_index + 1); }
|
|
|
|
const uint32_t* m_bone_chain;
|
|
bitset_description m_bone_chain_desc;
|
|
uint32_t m_root_index;
|
|
uint32_t m_bone_index;
|
|
};
|
|
|
|
struct transform_metadata
|
|
{
|
|
const uint32_t* transform_chain;
|
|
uint32_t parent_index;
|
|
float precision;
|
|
float shell_distance;
|
|
};
|
|
|
|
struct clip_context
|
|
{
|
|
SegmentContext* segments;
|
|
BoneRanges* ranges;
|
|
transform_metadata* metadata;
|
|
uint32_t* leaf_transform_chains;
|
|
|
|
uint32_t num_segments;
|
|
uint32_t num_bones;
|
|
uint32_t num_samples;
|
|
float sample_rate;
|
|
|
|
float duration;
|
|
|
|
bool are_rotations_normalized;
|
|
bool are_translations_normalized;
|
|
bool are_scales_normalized;
|
|
bool has_scale;
|
|
bool has_additive_base;
|
|
|
|
additive_clip_format8 additive_format;
|
|
|
|
uint32_t num_leaf_transforms;
|
|
|
|
iallocator* allocator = nullptr; // Never null if the context is initialized
|
|
|
|
// Stat tracking
|
|
uint32_t decomp_touched_bytes;
|
|
uint32_t decomp_touched_cache_lines;
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
bool is_initialized() const { return allocator != nullptr; }
|
|
iterator<SegmentContext> segment_iterator() { return iterator<SegmentContext>(segments, num_segments); }
|
|
const_iterator<SegmentContext> segment_iterator() const { return const_iterator<SegmentContext>(segments, num_segments); }
|
|
|
|
BoneChain get_bone_chain(uint32_t bone_index) const
|
|
{
|
|
ACL_ASSERT(bone_index < num_bones, "Invalid bone index: %u >= %u", bone_index, num_bones);
|
|
const transform_metadata& meta = metadata[bone_index];
|
|
return BoneChain(meta.transform_chain, bitset_description::make_from_num_bits(num_bones), bone_index);
|
|
}
|
|
};
|
|
|
|
inline bool initialize_clip_context(iallocator& allocator, const track_array_qvvf& track_list, const compression_settings& settings, additive_clip_format8 additive_format, clip_context& out_clip_context)
|
|
{
|
|
const uint32_t num_transforms = track_list.get_num_tracks();
|
|
const uint32_t num_samples = track_list.get_num_samples_per_track();
|
|
const float sample_rate = track_list.get_sample_rate();
|
|
|
|
ACL_ASSERT(num_transforms > 0, "Track array has no tracks!");
|
|
ACL_ASSERT(num_samples > 0, "Track array has no samples!");
|
|
|
|
// Create a single segment with the whole clip
|
|
out_clip_context.segments = allocate_type_array<SegmentContext>(allocator, 1);
|
|
out_clip_context.ranges = nullptr;
|
|
out_clip_context.metadata = allocate_type_array<transform_metadata>(allocator, num_transforms);
|
|
out_clip_context.leaf_transform_chains = nullptr;
|
|
out_clip_context.num_segments = 1;
|
|
out_clip_context.num_bones = num_transforms;
|
|
out_clip_context.num_samples = num_samples;
|
|
out_clip_context.sample_rate = sample_rate;
|
|
out_clip_context.duration = track_list.get_duration();
|
|
out_clip_context.are_rotations_normalized = false;
|
|
out_clip_context.are_translations_normalized = false;
|
|
out_clip_context.are_scales_normalized = false;
|
|
out_clip_context.has_additive_base = additive_format != additive_clip_format8::none;
|
|
out_clip_context.additive_format = additive_format;
|
|
out_clip_context.num_leaf_transforms = 0;
|
|
out_clip_context.allocator = &allocator;
|
|
|
|
bool has_scale = false;
|
|
bool are_samples_valid = true;
|
|
const rtm::vector4f default_scale = get_default_scale(additive_format);
|
|
|
|
SegmentContext& segment = out_clip_context.segments[0];
|
|
|
|
BoneStreams* bone_streams = allocate_type_array<BoneStreams>(allocator, num_transforms);
|
|
|
|
for (uint32_t transform_index = 0; transform_index < num_transforms; ++transform_index)
|
|
{
|
|
const track_qvvf& track = track_list[transform_index];
|
|
const track_desc_transformf& desc = track.get_description();
|
|
|
|
BoneStreams& bone_stream = bone_streams[transform_index];
|
|
|
|
bone_stream.segment = &segment;
|
|
bone_stream.bone_index = transform_index;
|
|
bone_stream.parent_bone_index = desc.parent_index;
|
|
bone_stream.output_index = desc.output_index;
|
|
|
|
bone_stream.rotations = RotationTrackStream(allocator, num_samples, sizeof(rtm::quatf), sample_rate, rotation_format8::quatf_full);
|
|
bone_stream.translations = TranslationTrackStream(allocator, num_samples, sizeof(rtm::vector4f), sample_rate, vector_format8::vector3f_full);
|
|
bone_stream.scales = ScaleTrackStream(allocator, num_samples, sizeof(rtm::vector4f), sample_rate, vector_format8::vector3f_full);
|
|
|
|
for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index)
|
|
{
|
|
const rtm::qvvf& transform = track[sample_index];
|
|
|
|
// If we request raw data and we are already normalized, retain the original value
|
|
// otherwise we normalize for safety
|
|
rtm::quatf rotation;
|
|
if (settings.rotation_format != rotation_format8::quatf_full || !rtm::quat_is_normalized(transform.rotation))
|
|
rotation = rtm::quat_normalize(transform.rotation);
|
|
else
|
|
rotation = transform.rotation;
|
|
|
|
are_samples_valid &= rtm::quat_is_finite(rotation);
|
|
are_samples_valid &= rtm::vector_is_finite3(transform.translation);
|
|
are_samples_valid &= rtm::vector_is_finite3(transform.scale);
|
|
|
|
bone_stream.rotations.set_raw_sample(sample_index, rotation);
|
|
bone_stream.translations.set_raw_sample(sample_index, transform.translation);
|
|
bone_stream.scales.set_raw_sample(sample_index, transform.scale);
|
|
}
|
|
|
|
{
|
|
const rtm::qvvf& first_transform = track[0];
|
|
const rtm::quatf first_rotation = rtm::quat_normalize(first_transform.rotation);
|
|
|
|
// If we request raw data, use a 0.0 threshold for safety
|
|
const float constant_rotation_threshold_angle = settings.rotation_format != rotation_format8::quatf_full ? desc.constant_rotation_threshold_angle : 0.0F;
|
|
const float constant_translation_threshold = settings.translation_format != vector_format8::vector3f_full ? desc.constant_translation_threshold : 0.0F;
|
|
const float constant_scale_threshold = settings.scale_format != vector_format8::vector3f_full ? desc.constant_scale_threshold : 0.0F;
|
|
|
|
bone_stream.is_rotation_constant = num_samples == 1;
|
|
bone_stream.is_rotation_default = bone_stream.is_rotation_constant && rtm::quat_near_identity(first_rotation, constant_rotation_threshold_angle);
|
|
bone_stream.is_translation_constant = num_samples == 1;
|
|
bone_stream.is_translation_default = bone_stream.is_translation_constant && rtm::vector_all_near_equal3(first_transform.translation, rtm::vector_zero(), constant_translation_threshold);
|
|
bone_stream.is_scale_constant = num_samples == 1;
|
|
bone_stream.is_scale_default = bone_stream.is_scale_constant && rtm::vector_all_near_equal3(first_transform.scale, default_scale, constant_scale_threshold);
|
|
}
|
|
|
|
has_scale |= !bone_stream.is_scale_default;
|
|
|
|
transform_metadata& metadata = out_clip_context.metadata[transform_index];
|
|
metadata.transform_chain = nullptr;
|
|
metadata.parent_index = desc.parent_index;
|
|
metadata.precision = desc.precision;
|
|
metadata.shell_distance = desc.shell_distance;
|
|
}
|
|
|
|
out_clip_context.has_scale = has_scale;
|
|
out_clip_context.decomp_touched_bytes = 0;
|
|
out_clip_context.decomp_touched_cache_lines = 0;
|
|
|
|
segment.bone_streams = bone_streams;
|
|
segment.clip = &out_clip_context;
|
|
segment.ranges = nullptr;
|
|
segment.num_samples = num_samples;
|
|
segment.num_bones = num_transforms;
|
|
segment.clip_sample_offset = 0;
|
|
segment.segment_index = 0;
|
|
segment.distribution = SampleDistribution8::Uniform;
|
|
segment.are_rotations_normalized = false;
|
|
segment.are_translations_normalized = false;
|
|
segment.are_scales_normalized = false;
|
|
|
|
segment.animated_pose_rotation_bit_size = 0;
|
|
segment.animated_pose_translation_bit_size = 0;
|
|
segment.animated_pose_scale_bit_size = 0;
|
|
segment.animated_pose_bit_size = 0;
|
|
segment.animated_data_size = 0;
|
|
segment.range_data_size = 0;
|
|
segment.total_header_size = 0;
|
|
|
|
// Initialize our hierarchy information
|
|
{
|
|
// Calculate which bones are leaf bones that have no children
|
|
bitset_description bone_bitset_desc = bitset_description::make_from_num_bits(num_transforms);
|
|
uint32_t* is_leaf_bitset = allocate_type_array<uint32_t>(allocator, bone_bitset_desc.get_size());
|
|
bitset_reset(is_leaf_bitset, bone_bitset_desc, false);
|
|
|
|
// By default and if we find a child, we'll mark it as non-leaf
|
|
bitset_set_range(is_leaf_bitset, bone_bitset_desc, 0, num_transforms, true);
|
|
|
|
#if defined(ACL_HAS_ASSERT_CHECKS)
|
|
uint32_t num_root_bones = 0;
|
|
#endif
|
|
|
|
// Move and validate the input data
|
|
for (uint32_t transform_index = 0; transform_index < num_transforms; ++transform_index)
|
|
{
|
|
const transform_metadata& metadata = out_clip_context.metadata[transform_index];
|
|
|
|
const bool is_root = metadata.parent_index == k_invalid_track_index;
|
|
|
|
// If we have a parent, mark it as not being a leaf bone (it has at least one child)
|
|
if (!is_root)
|
|
bitset_set(is_leaf_bitset, bone_bitset_desc, metadata.parent_index, false);
|
|
|
|
#if defined(ACL_HAS_ASSERT_CHECKS)
|
|
if (is_root)
|
|
num_root_bones++;
|
|
#endif
|
|
}
|
|
|
|
const uint32_t num_leaf_transforms = bitset_count_set_bits(is_leaf_bitset, bone_bitset_desc);
|
|
out_clip_context.num_leaf_transforms = num_leaf_transforms;
|
|
|
|
uint32_t* leaf_transform_chains = allocate_type_array<uint32_t>(allocator, size_t(num_leaf_transforms) * bone_bitset_desc.get_size());
|
|
out_clip_context.leaf_transform_chains = leaf_transform_chains;
|
|
|
|
uint32_t leaf_index = 0;
|
|
for (uint32_t transform_index = 0; transform_index < num_transforms; ++transform_index)
|
|
{
|
|
if (!bitset_test(is_leaf_bitset, bone_bitset_desc, transform_index))
|
|
continue; // Skip non-leaf bones
|
|
|
|
uint32_t* bone_chain = leaf_transform_chains + (leaf_index * bone_bitset_desc.get_size());
|
|
bitset_reset(bone_chain, bone_bitset_desc, false);
|
|
|
|
uint32_t chain_bone_index = transform_index;
|
|
while (chain_bone_index != k_invalid_track_index)
|
|
{
|
|
bitset_set(bone_chain, bone_bitset_desc, chain_bone_index, true);
|
|
|
|
transform_metadata& metadata = out_clip_context.metadata[chain_bone_index];
|
|
|
|
// We assign a bone chain the first time we find a bone that isn't part of one already
|
|
if (metadata.transform_chain == nullptr)
|
|
metadata.transform_chain = bone_chain;
|
|
|
|
chain_bone_index = metadata.parent_index;
|
|
}
|
|
|
|
leaf_index++;
|
|
}
|
|
|
|
ACL_ASSERT(num_root_bones > 0, "No root bone found. The root bones must have a parent index = 0xFFFF");
|
|
ACL_ASSERT(leaf_index == num_leaf_transforms, "Invalid number of leaf bone found");
|
|
deallocate_type_array(allocator, is_leaf_bitset, bone_bitset_desc.get_size());
|
|
}
|
|
|
|
return are_samples_valid;
|
|
}
|
|
|
|
inline void destroy_clip_context(clip_context& context)
|
|
{
|
|
if (context.allocator == nullptr)
|
|
return; // Not initialized
|
|
|
|
iallocator& allocator = *context.allocator;
|
|
|
|
for (SegmentContext& segment : context.segment_iterator())
|
|
destroy_segment_context(allocator, segment);
|
|
|
|
deallocate_type_array(allocator, context.segments, context.num_segments);
|
|
deallocate_type_array(allocator, context.ranges, context.num_bones);
|
|
deallocate_type_array(allocator, context.metadata, context.num_bones);
|
|
|
|
bitset_description bone_bitset_desc = bitset_description::make_from_num_bits(context.num_bones);
|
|
deallocate_type_array(allocator, context.leaf_transform_chains, size_t(context.num_leaf_transforms) * bone_bitset_desc.get_size());
|
|
}
|
|
|
|
constexpr bool segment_context_has_scale(const SegmentContext& segment) { return segment.clip->has_scale; }
|
|
constexpr bool bone_streams_has_scale(const BoneStreams& bone_streams) { return segment_context_has_scale(*bone_streams.segment); }
|
|
}
|
|
}
|
|
|
|
ACL_IMPL_FILE_PRAGMA_POP
|