<NME> clip_reader.h
<BEF> #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.
////////////////////////////////////////////////////////////////////////////////

#if defined(ACL_USE_SJSON)

#include "acl/version.h"
#include "acl/io/clip_reader_error.h"
#include "acl/compression/compression_settings.h"
#include "acl/compression/track_array.h"
#include "acl/core/algorithm_types.h"
#include "acl/core/impl/compiler_utils.h"
#include "acl/core/iallocator.h"
#include "acl/core/string.h"
#include "acl/core/track_desc.h"
#include "acl/core/unique_ptr.h"
#include "acl/math/quatf.h"

#include <rtm/quatd.h>
#include <rtm/quatf.h>
#include <rtm/vector4d.h>
#include <rtm/vector4f.h>
#include <rtm/qvvd.h>
#include <rtm/qvvf.h>

#include <sjson/parser.h>

#include <cstdint>

ACL_IMPL_FILE_PRAGMA_PUSH

namespace acl
{
	ACL_IMPL_VERSION_NAMESPACE_BEGIN

	//////////////////////////////////////////////////////////////////////////
	// Enum to describe each type of raw content that an SJSON ACL file might contain.
	enum class sjson_file_type
	{
		std::unique_ptr<AnimationClip, Deleter<AnimationClip>> clip;
		std::unique_ptr<RigidSkeleton, Deleter<RigidSkeleton>> skeleton;

		bool has_settings;
		algorithm_type8 algorithm_type;
	// Raw transform tracks
	struct sjson_raw_clip
	{
		track_array_qvvf track_list;

		track_array_qvvf additive_base_track_list;
		additive_clip_format8 additive_format = additive_clip_format8::none;

		track_qvvf bind_pose;

		bool has_settings = false;
		compression_settings settings;
	};

	//////////////////////////////////////////////////////////////////////////
	// Raw scalar tracks
	struct sjson_raw_track_list
	{
		track_array track_list;

		track_qvvf bind_pose;

		bool has_settings = false;
		compression_settings settings;
	};

	//////////////////////////////////////////////////////////////////////////
	// An SJSON ACL file reader.
	class clip_reader
	{
	public:
		clip_reader(iallocator& allocator, const char* sjson_input, size_t input_length)
			: m_allocator(allocator)
			, m_parser(sjson_input, input_length)
		{
		}

		~clip_reader()
		{
			deallocate_type_array(m_allocator, m_bone_names, m_num_bones);
		}

		sjson_file_type get_file_type()
		{
			reset_state();

			if (!read_version())
				return sjson_file_type::unknown;

			if (m_parser.object_begins("clip"))
				return sjson_file_type::raw_clip;

			if (!read_settings(&out_data.has_settings, &out_data.algorithm_type, &out_data.settings))
				return false;

			if (!create_skeleton(out_data.skeleton))
				return false;

			if (!create_clip(out_data.clip, *out_data.skeleton))
				return false;

			if (!read_tracks(*out_data.clip, *out_data.skeleton))
				return false;

			return nothing_follows();
			if (!read_raw_clip_header())
				return false;

			if (!read_settings(&out_data.has_settings, &out_data.settings))
				return false;

			if (!create_skeleton(out_data.track_list, out_data.bind_pose))
				return false;

			if (!read_tracks(out_data.track_list, out_data.additive_base_track_list))
				return false;

			out_data.additive_format = !out_data.additive_base_track_list.is_empty() ? m_additive_format : additive_clip_format8::none;

			return nothing_follows();
		}

		bool read_raw_track_list(sjson_raw_track_list& out_data)
		{
			reset_state();

			if (!read_version())
				return false;

			if (!read_raw_track_list_header())
				return false;

			if (!read_settings(&out_data.has_settings, &out_data.settings))
				return false;

			if (!create_track_list(out_data.track_list, out_data.bind_pose))
				return false;

			return nothing_follows();
		}

		clip_reader_error get_error() const { return m_error; }

	private:
		clip_reader(const clip_reader&) = delete;
		clip_reader& operator=(const clip_reader&) = delete;

		iallocator& m_allocator;
		sjson::Parser m_parser;
		clip_reader_error m_error;

		uint32_t m_version								= 0;
		uint32_t m_num_samples							= 0;
		float m_sample_rate								= 0.0F;
		sjson::StringView m_clip_name;
		bool m_is_binary_exact							= false;
		additive_clip_format8 m_additive_format			= additive_clip_format8::none;
		sjson::StringView m_additive_base_name;
		uint32_t m_additive_base_num_samples			= 0;
		float m_additive_base_sample_rate				= 0.0F;

		bool m_has_settings								= false;
		float m_constant_rotation_threshold_angle		= 0.0F;
		float m_constant_translation_threshold			= 0.0F;
		float m_constant_scale_threshold				= 0.0F;
		float m_error_threshold							= 0.0F;

		sjson::StringView* m_bone_names					= nullptr;
		uint32_t m_num_bones							= 0;

		string convert_string(const sjson::StringView& str) const
		{
			return string(m_allocator, str.c_str(), str.size());
		}

		void reset_state()
		{
			m_parser.reset_state();
			set_error(clip_reader_error::None);
		}

		bool read_version()
		{
			if (!m_parser.read("version", m_version))
			{
				m_error = m_parser.get_error();
				return false;
			}

			if (m_version > 5)
			{
				set_error(clip_reader_error::UnsupportedVersion);
				return false;
			}

			return true;
		}

		bool read_raw_clip_header()
		{
			sjson::StringView additive_format;

			if (!m_parser.object_begins("clip"))
				goto error;

			m_parser.try_read("name", m_clip_name, "");

			double num_samples;
			if (!m_parser.read("num_samples", num_samples))
				goto error;

			m_num_samples = static_cast<uint32_t>(num_samples);
			if (static_cast<double>(m_num_samples) != num_samples)
			{
				set_error(clip_reader_error::UnsignedIntegerExpected);
				return false;
			}

			double sample_rate;
			if (!m_parser.read("sample_rate", sample_rate))
				goto error;

			m_sample_rate = static_cast<float>(sample_rate);
			if (m_sample_rate <= 0.0F)
			{
				set_error(clip_reader_error::PositiveValueExpected);
				return false;
			}

			// Version 1 had an error_threshold field, skip it
			double error_threshold;
			if (m_version == 1 && !m_parser.read("error_threshold", error_threshold))
				goto error;

			// Optional value
			m_parser.try_read("is_binary_exact", m_is_binary_exact, false);

			// Optional value
			m_parser.try_read("additive_format", additive_format, "none");
			if (!get_additive_clip_format(additive_format.c_str(), m_additive_format))
			{
				set_error(clip_reader_error::InvalidAdditiveClipFormat);
				return false;
			}

			m_parser.try_read("additive_base_name", m_additive_base_name, "");
			m_parser.try_read("additive_base_num_samples", num_samples, 1.0);
			m_additive_base_num_samples = static_cast<uint32_t>(num_samples);
			if (static_cast<double>(m_additive_base_num_samples) != num_samples || m_additive_base_num_samples == 0)
			{
				set_error(clip_reader_error::UnsignedIntegerExpected);
				return false;
			}
			m_parser.try_read("additive_base_sample_rate", sample_rate, 30.0);
			m_additive_base_sample_rate = static_cast<float>(sample_rate);
			if (m_additive_base_sample_rate <= 0.0F)
			{
				set_error(clip_reader_error::PositiveValueExpected);
				return false;
			}

			if (!m_parser.object_ends())
				goto error;

			return true;

		error:
			m_error = m_parser.get_error();
			return false;
		}

		bool read_raw_track_list_header()
		{
			if (!m_parser.object_begins("track_list"))
				goto error;

			m_parser.try_read("name", m_clip_name, "");

			double num_samples;
			if (!m_parser.read("num_samples", num_samples))
				goto error;

			m_num_samples = static_cast<uint32_t>(num_samples);
			if (static_cast<double>(m_num_samples) != num_samples)
			{
				set_error(clip_reader_error::UnsignedIntegerExpected);
				return false;
			}

			double sample_rate;
			if (!m_parser.read("sample_rate", sample_rate))
				goto error;

			m_sample_rate = static_cast<float>(sample_rate);
			if (m_sample_rate <= 0.0F)
			{
				set_error(clip_reader_error::PositiveValueExpected);
				return false;
			}

			// Optional value
			m_parser.try_read("is_binary_exact", m_is_binary_exact, false);

			if (!m_parser.object_ends())
				goto error;

			return true;

		error:
			m_error = m_parser.get_error();
			return false;
		}

		bool read_settings(bool* out_has_settings, compression_settings* out_settings)
		{
			m_has_settings = false;

			if (!m_parser.try_object_begins("settings"))
			{
				if (out_has_settings != nullptr)
					*out_has_settings = false;

				// Settings are optional, all good
				return true;
			}

			compression_settings default_settings;

			sjson::StringView algorithm_name;
			sjson::StringView compression_level;
			sjson::StringView rotation_format;
			sjson::StringView translation_format;
			sjson::StringView scale_format;
			bool rotation_range_reduction;
			bool translation_range_reduction;
			bool scale_range_reduction;
			double constant_rotation_threshold_angle;
			double constant_translation_threshold;
			double constant_scale_threshold;
			double error_threshold;

			double segmenting_ideal_num_samples = 0.0;	// Legacy, no longer used
			double segmenting_max_num_samples = 0.0;	// Legacy, no longer used

			m_parser.try_read("algorithm_name", algorithm_name, get_algorithm_name(algorithm_type8::uniformly_sampled));
			m_parser.try_read("level", compression_level, get_compression_level_name(default_settings.level));
			m_parser.try_read("rotation_format", rotation_format, get_rotation_format_name(default_settings.rotation_format));
			m_parser.try_read("translation_format", translation_format, get_vector_format_name(default_settings.translation_format));
			m_parser.try_read("scale_format", scale_format, get_vector_format_name(default_settings.scale_format));
			m_parser.try_read("rotation_range_reduction", rotation_range_reduction, false);			// Legacy, no longer used
			m_parser.try_read("translation_range_reduction", translation_range_reduction, false);	// Legacy, no longer used
			m_parser.try_read("scale_range_reduction", scale_range_reduction, false);				// Legacy, no longer used

			if (m_parser.try_object_begins("segmenting"))
			{
				bool segmenting_enabled;
				bool segmenting_rotation_range_reduction;
				bool segmenting_translation_range_reduction;
				bool segmenting_scale_range_reduction;
				m_parser.try_read("enabled", segmenting_enabled, false);											// Legacy, no longer used
				m_parser.try_read("ideal_num_samples", segmenting_ideal_num_samples, 0.0);							// Legacy, no longer used
				m_parser.try_read("max_num_samples", segmenting_max_num_samples, 0.0);								// Legacy, no longer used
				m_parser.try_read("rotation_range_reduction", segmenting_rotation_range_reduction, false);			// Legacy, no longer used
				m_parser.try_read("translation_range_reduction", segmenting_translation_range_reduction, false);	// Legacy, no longer used
				m_parser.try_read("scale_range_reduction", segmenting_scale_range_reduction, false);				// Legacy, no longer used

				if (!m_parser.is_valid() || !m_parser.object_ends())
					goto parsing_error;
			}

			// Skip deprecated values
			m_parser.try_read("constant_rotation_threshold_angle", constant_rotation_threshold_angle, 0.00284714461);
			m_parser.try_read("constant_translation_threshold", constant_translation_threshold, 0.001);
			m_parser.try_read("constant_scale_threshold", constant_scale_threshold, 0.00001);
			m_parser.try_read("error_threshold", error_threshold, 0.01);

			if (!m_parser.is_valid() || !m_parser.object_ends())
				goto parsing_error;

			if (out_has_settings != nullptr)
			{
				*out_has_settings = true;

				if (!get_compression_level(compression_level.c_str(), out_settings->level))
					goto invalid_value_error;
			return false;
		}

		bool create_skeleton(std::unique_ptr<RigidSkeleton, Deleter<RigidSkeleton>>& skeleton)
		{
			sjson::ParserState before_bones = m_parser.save_state();

				if (!get_vector_format(scale_format.c_str(), out_settings->scale_format))
					goto invalid_value_error;

				m_constant_rotation_threshold_angle = float(constant_rotation_threshold_angle);
				m_constant_translation_threshold = float(constant_translation_threshold);
				m_constant_scale_threshold = float(constant_scale_threshold);
				m_error_threshold = float(error_threshold);
			}

			m_has_settings = true;
			return true;

		parsing_error:
			m_error = m_parser.get_error();
			return false;

			skeleton = make_unique<RigidSkeleton>(m_allocator, m_allocator, bones, num_bones);
			deallocate_type_array(m_allocator, bones, num_allocated_bones);

			return true;
		}

		bool read_skeleton()
		{
			uint16_t num_bones;
			return process_each_bone(nullptr, num_bones);
		}

		static double hex_to_double(const sjson::StringView& value)
			track_list = track_array_qvvf(m_allocator, num_bones);
			track_list.set_name(convert_string(m_clip_name));
			bind_pose = track_qvvf::make_reserve(track_desc_transformf{}, m_allocator, num_bones, 30.0F);	// 1 sample per track
			bind_pose.set_name(string(m_allocator, "bind pose"));

			const uint32_t num_allocated_bones = num_bones;

			if (!process_each_bone(&track_list, &bind_pose, num_bones))
				return false;

			(void)num_allocated_bones;
			ACL_ASSERT(num_bones == num_allocated_bones, "Number of bones read mismatch");

			return true;
		}

		static double hex_to_double(const sjson::StringView& value)
		{
			union UInt64ToDouble
			{
				uint64_t u64;
				double dbl;

				constexpr explicit UInt64ToDouble(uint64_t u64_value) : u64(u64_value) {}
			};

			ACL_ASSERT(value.size() <= 16, "Invalid binary exact double value");
			uint64_t value_u64 = acl_impl::strtoull(value.c_str(), nullptr, 16);
			return UInt64ToDouble(value_u64).dbl;
		}

		static float hex_to_float(const sjson::StringView& value)
		{
			union UInt32ToFloat
			{
				uint32_t u32;
				float flt;

				constexpr explicit UInt32ToFloat(uint32_t u32_value) : u32(u32_value) {}
			};

			ACL_ASSERT(value.size() <= 8, "Invalid binary exact float value");
			uint32_t value_u32 = safe_static_cast<uint32_t>(std::strtoul(value.c_str(), nullptr, 16));
			return UInt32ToFloat(value_u32).flt;
		}

		static rtm::quatd hex_to_quat(const sjson::StringView values[4])
		{
			return rtm::quat_set(hex_to_double(values[0]), hex_to_double(values[1]), hex_to_double(values[2]), hex_to_double(values[3]));
		}

		static rtm::vector4d hex_to_vector3(const sjson::StringView values[3])
		{
			return rtm::vector_set(hex_to_double(values[0]), hex_to_double(values[1]), hex_to_double(values[2]));
		}

		static rtm::float4f hex_to_float4f(const sjson::StringView values[4], uint32_t num_components)
		{
			ACL_ASSERT(num_components <= 4, "Invalid number of components");

			rtm::float4f result = { 0.0F, 0.0F, 0.0F, 0.0F };
			float* result_ptr = &result.x;

			for (uint32_t component_index = 0; component_index < num_components; ++component_index)
				result_ptr[component_index] = hex_to_float(values[component_index]);

			return result;
		}

		bool process_each_bone(track_array_qvvf* tracks, track_qvvf* bind_pose, uint32_t& num_bones)
		{
			bool counting = tracks == nullptr;
			num_bones = 0;

			if (!m_parser.array_begins("bones"))
				goto error;

			for (uint32_t i = 0; !m_parser.try_array_ends(); ++i)
			{
				if (!m_parser.object_begins())
					goto error;

				sjson::StringView name;
				if (!m_parser.read("name", name))
					goto error;

				if (tracks != nullptr)
					m_bone_names[i] = name;

				sjson::StringView parent;
				if (!m_parser.read("parent", parent))
					goto error;

				uint32_t parent_index = k_invalid_track_index;
				if (tracks != nullptr && !parent.empty())
				{
					parent_index = find_bone(parent);
					if (parent_index == k_invalid_track_index)
					{
						set_error(clip_reader_error::NoParentBoneWithThatName);
						return false;
					}
				}

				float vertex_distance;
				if (!m_parser.read("vertex_distance", vertex_distance))
					goto error;

				rtm::qvvd bind_transform = rtm::qvv_identity();
				if (m_is_binary_exact)
				{
					sjson::StringView rotation[4];
					if (m_parser.try_read("bind_rotation", rotation, 4, nullptr) && !counting)
						bind_transform.rotation = hex_to_quat(rotation);

					sjson::StringView translation[3];
					if (m_parser.try_read("bind_translation", translation, 3, nullptr) && !counting)
						bind_transform.translation = hex_to_vector3(translation);

					sjson::StringView scale[3];
					if (m_parser.try_read("bind_scale", scale, 3, nullptr) && !counting)
						bind_transform.scale = hex_to_vector3(scale);
				}
				else
				{
					double rotation[4] = { 0.0, 0.0, 0.0, 0.0 };
					if (m_parser.try_read("bind_rotation", rotation, 4, 0.0) && !counting)
						bind_transform.rotation = rtm::quat_load(&rotation[0]);

					double translation[3] = { 0.0, 0.0, 0.0 };
					if (m_parser.try_read("bind_translation", translation, 3, 0.0) && !counting)
						bind_transform.translation = rtm::vector_load3(&translation[0]);

					double scale[3] = { 0.0, 0.0, 0.0 };
					if (m_parser.try_read("bind_scale", scale, 3, 0.0) && !counting)
						bind_transform.scale = rtm::vector_load3(&scale[0]);
				}

				if (tracks != nullptr)
				{
					track_desc_transformf desc;
					desc.parent_index = parent_index;
					desc.shell_distance = vertex_distance;

					if (m_has_settings)
					{
						desc.precision = m_error_threshold;
						desc.constant_rotation_threshold_angle = m_constant_rotation_threshold_angle;
						desc.constant_translation_threshold = m_constant_translation_threshold;
						desc.constant_scale_threshold = m_constant_scale_threshold;
					}

					// Create a dummy track for now to hold our arguments
					(*tracks)[i] = track_qvvf::make_ref(desc, nullptr, 0, 30.0F);
					(*tracks)[i].set_name(convert_string(name));

					rtm::qvvf bind_transform_ = rtm::qvv_cast(bind_transform);
					bind_transform_.rotation = rtm::quat_normalize_deterministic(bind_transform_.rotation);

					(*tracks)[i].get_description().default_value = bind_transform_;

					(*bind_pose)[i] = bind_transform_;
				}

				if (!m_parser.object_ends())
					goto error;

				++num_bones;
			}

			return true;

		error:
			m_error = m_parser.get_error();
			return false;
		}

		uint32_t find_bone(const sjson::StringView& name) const
		{
			for (uint32_t i = 0; i < m_num_bones; ++i)
			{
				if (name == m_bone_names[i])
					return i;
			}

			return k_invalid_track_index;
		}

		float read_optional_float(const char* key, float default_value)
		{
			float value = default_value;
			if (m_is_binary_exact)
			{
				sjson::StringView value_str;
				if (m_parser.try_read(key, value_str, ""))
					value = hex_to_float(value_str);
			}
			else
				m_parser.try_read(key, value, default_value);

			return value;
		}

		bool read_qvv_sample(rtm::qvvf& out_sample)
		{
			rtm::qvvf sample = rtm::qvv_identity();
			sjson::StringView values_str[4];
			double values[4] = { 0.0, 0.0, 0.0, 0.0 };

			{
				if (!m_parser.array_begins())
					return false;

				rtm::float4f value;
				if (m_is_binary_exact)
				{
					if (!m_parser.read(values_str, 4))
						return false;

					value = hex_to_float4f(values_str, 4);
				}
				else
				{
					if (!m_parser.read(values, 4))
						return false;

					value = { static_cast<float>(values[0]), static_cast<float>(values[1]), static_cast<float>(values[2]), static_cast<float>(values[3])};
				}

				sample.rotation = rtm::quat_load(&value);
				if (!rtm::quat_is_finite(sample.rotation))
					return false;

				if (!m_parser.array_ends())
					return false;
			}

			if (!m_parser.read_comma())
				return false;

			{
				if (!m_parser.array_begins())
					return false;

				rtm::float4f value;
				if (m_is_binary_exact)
				{
					if (!m_parser.read(values_str, 3))
						return false;

					value = hex_to_float4f(values_str, 4);
				}
				else
				{
					if (!m_parser.read(values, 3))
						return false;

					value = { static_cast<float>(values[0]), static_cast<float>(values[1]), static_cast<float>(values[2]), 0.0F };
				}

				sample.translation = vector_load(&value);
				if (!rtm::vector_is_finite3(sample.translation))
					return false;

				if (!m_parser.array_ends())
					return false;
			}

			if (!m_parser.read_comma())
				return false;

			{
				if (!m_parser.array_begins())
					return false;

				rtm::float4f value;
				if (m_is_binary_exact)
				{
					if (!m_parser.read(values_str, 3))
						return false;

					value = hex_to_float4f(values_str, 4);
				}
				else
				{
					if (!m_parser.read(values, 3))
						return false;

					value = { static_cast<float>(values[0]), static_cast<float>(values[1]), static_cast<float>(values[2]), 0.0F };
				}

				sample.scale = vector_load(&value);
				if (!rtm::vector_is_finite3(sample.scale))
					return false;

				if (!m_parser.array_ends())
					return false;
			}

			out_sample = sample;
			return true;
		}

		bool process_track_list(track* tracks, track_qvvf* bind_pose, uint32_t& num_tracks)
		{
			const bool counting = tracks == nullptr;
			track dummy;
			track_type8 track_list_type = track_type8::float1f;

			num_tracks = 0;

			if (!m_parser.array_begins("tracks"))
				goto error;

			for (uint32_t i = 0; !m_parser.try_array_ends(); ++i)
			{
				track& track_ = counting ? dummy : tracks[i];

				if (!m_parser.object_begins())
					goto error;

				sjson::StringView name;
				m_parser.try_read("name", name, "");

				sjson::StringView type;
				if (!m_parser.read("type", type))
					goto error;

				track_type8 track_type;
				if (!get_track_type(type.c_str(), track_type))
				{
					m_error.error = clip_reader_error::InvalidTrackType;
					return false;
				}

				if (num_tracks == 0)
				{
					// Found our first track, this is the type we'll use for every track
					track_list_type = track_type;
				}
				else if (track_type != track_list_type)
				{
					// Track type doesn't match the type of the first track
					m_error.error = clip_reader_error::InvalidTrackType;
					return false;
				}

				const uint32_t num_components = get_track_num_sample_elements(track_type);
				ACL_ASSERT((num_components > 0 && num_components <= 4) || num_components == 12, "Must have between 1 and 4 components or 12");

				track_desc_scalarf scalar_desc;
				track_desc_transformf transform_desc;

				float precision = read_optional_float("precision", -1.0F);

				// Deprecated, no longer used
				read_optional_float("constant_threshold", -1.0F);

				uint32_t output_index;
				m_parser.try_read("output_index", output_index, i);

				m_parser.try_read("parent_index", transform_desc.parent_index, k_invalid_track_index);

				transform_desc.shell_distance = read_optional_float("shell_distance", transform_desc.shell_distance);
				transform_desc.constant_rotation_threshold_angle = read_optional_float("constant_rotation_threshold_angle", transform_desc.constant_rotation_threshold_angle);
				transform_desc.constant_translation_threshold = read_optional_float("constant_translation_threshold", transform_desc.constant_translation_threshold);
				transform_desc.constant_scale_threshold = read_optional_float("constant_scale_threshold", transform_desc.constant_scale_threshold);

				scalar_desc.output_index = output_index;
				transform_desc.output_index = output_index;

				if (track_type == track_type8::qvvf)
					transform_desc.precision = precision < 0.0F ? transform_desc.precision : precision;
				else
					scalar_desc.precision = precision < 0.0F ? scalar_desc.precision : precision;

				rtm::qvvf bind_transform = rtm::qvv_identity();
				if (m_is_binary_exact)
				{
					sjson::StringView rotation[4];
					if (m_parser.try_read("bind_rotation", rotation, 4, nullptr) && !counting)
					{
						const rtm::float4f value = hex_to_float4f(rotation, 4);
						bind_transform.rotation = rtm::quat_load(&value);
					}

					sjson::StringView translation[3];
					if (m_parser.try_read("bind_translation", translation, 3, nullptr) && !counting)
					{
						const rtm::float4f value = hex_to_float4f(translation, 3);
						bind_transform.translation = rtm::vector_load(&value);
					}

					sjson::StringView scale[3];
					if (m_parser.try_read("bind_scale", scale, 3, nullptr) && !counting)
					{
						const rtm::float4f value = hex_to_float4f(scale, 3);
						bind_transform.scale = rtm::vector_load(&value);
					}
				}
				else
				{
					double rotation[4] = { 0.0, 0.0, 0.0, 0.0 };
					if (m_parser.try_read("bind_rotation", rotation, 4, 0.0) && !counting)
						bind_transform.rotation = rtm::quat_normalize_deterministic(rtm::quat_cast(rtm::quat_load(&rotation[0])));

					double translation[3] = { 0.0, 0.0, 0.0 };
					if (m_parser.try_read("bind_translation", translation, 3, 0.0) && !counting)
						bind_transform.translation = rtm::vector_cast(rtm::vector_load3(&translation[0]));

					double scale[3] = { 0.0, 0.0, 0.0 };
					if (m_parser.try_read("bind_scale", scale, 3, 0.0) && !counting)
						bind_transform.scale = rtm::vector_cast(rtm::vector_load3(&scale[0]));
				}

			return true;
		}

		bool read_tracks(AnimationClip& clip, const RigidSkeleton& skeleton)
		{
			std::unique_ptr<AnimationClip, Deleter<AnimationClip>> base_clip;

			if (m_parser.try_array_begins("base_tracks"))
					void*			any;
					float*			float1f;
					rtm::float2f*	float2f;
					rtm::float3f*	float3f;
					rtm::float4f*	float4f;
					rtm::vector4f*	vector4f;
					rtm::qvvf*		qvvf;
				};
				track_samples_ptr_union track_samples_typed = { nullptr };

				switch (track_type)
				{
				case track_type8::float1f:
					track_samples_typed.float1f = allocate_type_array<float>(m_allocator, m_num_samples);
					break;
				case track_type8::float2f:
					track_samples_typed.float2f = allocate_type_array<rtm::float2f>(m_allocator, m_num_samples);
					break;
				case track_type8::float3f:
					track_samples_typed.float3f = allocate_type_array<rtm::float3f>(m_allocator, m_num_samples);
					break;
				case track_type8::float4f:
					track_samples_typed.float4f = allocate_type_array<rtm::float4f>(m_allocator, m_num_samples);
					break;
				case track_type8::vector4f:
					track_samples_typed.vector4f = allocate_type_array<rtm::vector4f>(m_allocator, m_num_samples);
					break;
				case track_type8::qvvf:
					track_samples_typed.qvvf = allocate_type_array<rtm::qvvf>(m_allocator, m_num_samples);
					break;
				default:
					ACL_ASSERT(false, "Unsupported track type");
					// Initialize our value to itself, we'll never use it
					// This silences an MSVC static analysis warning
					track_samples_typed.any = &track_samples_typed;
					break;
				}

				bool has_error = false;
				for (uint32_t sample_index = 0; sample_index < m_num_samples; ++sample_index)
				{
					if (!m_parser.array_begins())
					{
						has_error = true;
						break;
					}

					if (m_is_binary_exact)
					{
						switch (track_type)
						{
						case track_type8::float1f:
						case track_type8::float2f:
						case track_type8::float3f:
						case track_type8::float4f:
						case track_type8::vector4f:
						{
							sjson::StringView values[4];
							if (m_parser.read(values, num_components))
							{
								const rtm::float4f value = hex_to_float4f(values, num_components);
								std::memcpy(track_samples_typed.float1f + (size_t(sample_index) * num_components), &value, sizeof(float) * num_components);
							}
							else
								has_error = true;
							break;
						}
						case track_type8::qvvf:
						{
							rtm::qvvf sample;
							if (!read_qvv_sample(sample))
								has_error = true;
							else
								std::memcpy(track_samples_typed.qvvf + sample_index, &sample, sizeof(rtm::qvvf));
							break;
						}
						default:
							ACL_ASSERT(false, "Unsupported track type");
							break;
						}

						if (has_error)
							break;
					}
					else
					{
						switch (track_type)
						{
						case track_type8::float1f:
						case track_type8::float2f:
						case track_type8::float3f:
						case track_type8::float4f:
						case track_type8::vector4f:
						{
							double values[4] = { 0.0, 0.0, 0.0, 0.0 };
							if (m_parser.read(values, num_components))
							{
								const rtm::float4f value = { static_cast<float>(values[0]), static_cast<float>(values[1]), static_cast<float>(values[2]), static_cast<float>(values[3]) };
								std::memcpy(track_samples_typed.float1f + (size_t(sample_index) * num_components), &value, sizeof(float) * num_components);
							}
							else
								has_error = true;
							break;
						}
						case track_type8::qvvf:
						{
							rtm::qvvf sample;
							if (!read_qvv_sample(sample))
								has_error = true;
							else
								std::memcpy(track_samples_typed.qvvf + sample_index, &sample, sizeof(rtm::qvvf));
							break;
						}
						default:
							ACL_ASSERT(false, "Unsupported track type");
							break;

			clip.set_additive_base(base_clip.release(), m_additive_format);

			return true;

		error:
					{
					case track_type8::float1f:
						deallocate_type_array<float>(m_allocator, track_samples_typed.float1f, m_num_samples);
						break;
					case track_type8::float2f:
						deallocate_type_array<rtm::float2f>(m_allocator, track_samples_typed.float2f, m_num_samples);
						break;
					case track_type8::float3f:
						deallocate_type_array<rtm::float3f>(m_allocator, track_samples_typed.float3f, m_num_samples);
						break;
					case track_type8::float4f:
						deallocate_type_array<rtm::float4f>(m_allocator, track_samples_typed.float4f, m_num_samples);
						break;
					case track_type8::vector4f:
						deallocate_type_array<rtm::vector4f>(m_allocator, track_samples_typed.vector4f, m_num_samples);
						break;
					case track_type8::qvvf:
						deallocate_type_array<rtm::qvvf>(m_allocator, track_samples_typed.qvvf, m_num_samples);
						break;
					default:
						ACL_ASSERT(false, "Unsupported track type");
						break;
					}
				}
				else
				{
					switch (track_type)
					{
					case track_type8::float1f:
						track_ = track_float1f::make_owner(scalar_desc, m_allocator, track_samples_typed.float1f, m_num_samples, m_sample_rate);
						break;
					case track_type8::float2f:
						track_ = track_float2f::make_owner(scalar_desc, m_allocator, track_samples_typed.float2f, m_num_samples, m_sample_rate);
						break;
					case track_type8::float3f:
						track_ = track_float3f::make_owner(scalar_desc, m_allocator, track_samples_typed.float3f, m_num_samples, m_sample_rate);
						break;
					case track_type8::float4f:
						track_ = track_float4f::make_owner(scalar_desc, m_allocator, track_samples_typed.float4f, m_num_samples, m_sample_rate);
						break;
					case track_type8::vector4f:
						track_ = track_vector4f::make_owner(scalar_desc, m_allocator, track_samples_typed.vector4f, m_num_samples, m_sample_rate);
						break;
					case track_type8::qvvf:
						track_ = track_qvvf::make_owner(transform_desc, m_allocator, track_samples_typed.qvvf, m_num_samples, m_sample_rate);
						break;
					default:
						ACL_ASSERT(false, "Unsupported track type");
						break;
					}
				}

				track_.set_name(convert_string(name));

				num_tracks++;
			}

			return true;

		error:
			m_error = m_parser.get_error();
			return false;
		}

		bool create_track_list(track_array& track_list, track_qvvf& bind_pose)
		{
			const sjson::ParserState before_tracks = m_parser.save_state();

			uint32_t num_tracks;
			if (!process_track_list(nullptr, nullptr, num_tracks))
				return false;

			m_parser.restore_state(before_tracks);

			track_list = track_array(m_allocator, num_tracks);
			track_list.set_name(convert_string(m_clip_name));
			bind_pose = track_qvvf::make_reserve(track_desc_transformf{}, m_allocator, num_tracks, 30.0F);	// 1 sample per track
			bind_pose.set_name(string(m_allocator, "bind pose"));

			if (!process_track_list(track_list.begin(), &bind_pose, num_tracks))
				return false;

			ACL_ASSERT(num_tracks == track_list.get_num_tracks(), "Number of tracks read mismatch");

			return true;
		}

		bool read_tracks(track_array_qvvf& track_list, track_array_qvvf& additive_base_track_list)
		{
			const uint32_t num_transforms = track_list.get_num_tracks();

			if (m_parser.try_array_begins("base_tracks"))
			{
				// Copy our metadata from the actual clip
				additive_base_track_list = track_array_qvvf(m_allocator, num_transforms);
				additive_base_track_list.set_name(string(m_allocator, "additive base"));
				for (uint32_t transform_index = 0; transform_index < num_transforms; ++transform_index)
					additive_base_track_list[transform_index].get_description() = track_list[transform_index].get_description();

				while (!m_parser.try_array_ends())
				{
					if (!m_parser.object_begins())
						goto error;

					sjson::StringView name;
					if (!m_parser.read("name", name))
						goto error;

					const uint32_t bone_index = find_bone(name);
					if (bone_index == k_invalid_track_index)
					{
						set_error(clip_reader_error::NoBoneWithThatName);
						return false;
					}

					track_desc_transformf desc = additive_base_track_list[bone_index].get_description();	// Copy our description
					desc.output_index = bone_index;

					track_qvvf track = track_qvvf::make_reserve(desc, m_allocator, m_additive_base_num_samples, m_additive_base_sample_rate);
					track.set_name(convert_string(name));

					if (m_parser.try_array_begins("rotations"))
					{
						if (!read_track_rotations(track, m_additive_base_num_samples) || !m_parser.array_ends())
							goto error;
					}
					else
					{
						for (uint32_t sample_index = 0; sample_index < m_additive_base_num_samples; ++sample_index)
							track[sample_index].rotation = rtm::quat_identity();
					}

					if (m_parser.try_array_begins("translations"))
					{
						if (!read_track_translations(track, m_additive_base_num_samples) || !m_parser.array_ends())
							goto error;
					}
					else
					{
						for (uint32_t sample_index = 0; sample_index < m_additive_base_num_samples; ++sample_index)
							track[sample_index].translation = rtm::vector_zero();
					}

					if (m_parser.try_array_begins("scales"))
					{
						if (!read_track_scales(track, m_additive_base_num_samples) || !m_parser.array_ends())
							goto error;
					}
					else
					{
						for (uint32_t sample_index = 0; sample_index < m_additive_base_num_samples; ++sample_index)
							track[sample_index].scale = rtm::vector_set(1.0F);
					}

					additive_base_track_list[bone_index] = std::move(track);

					if (!m_parser.object_ends())
						goto error;
				}
			}

			if (!m_parser.array_begins("tracks"))
				goto error;

			while (!m_parser.try_array_ends())
			{
				if (!m_parser.object_begins())
					goto error;

				sjson::StringView name;
				if (!m_parser.read("name", name))
					goto error;

				const uint32_t bone_index = find_bone(name);
				if (bone_index == k_invalid_track_index)
				{
					set_error(clip_reader_error::NoBoneWithThatName);
					return false;
				}

				track_desc_transformf desc = track_list[bone_index].get_description();	// Copy our description
				desc.output_index = bone_index;

				track_qvvf track = track_qvvf::make_reserve(desc, m_allocator, m_num_samples, m_sample_rate);
				track.set_name(convert_string(name));

				if (m_parser.try_array_begins("rotations"))
				{
					if (!read_track_rotations(track, m_num_samples) || !m_parser.array_ends())
						goto error;
				}
				else
				{
					for (uint32_t sample_index = 0; sample_index < m_num_samples; ++sample_index)
						track[sample_index].rotation = rtm::quat_identity();
				}

				if (m_parser.try_array_begins("translations"))
				{
					if (!read_track_translations(track, m_num_samples) || !m_parser.array_ends())
						goto error;
				}
				else
				{
					for (uint32_t sample_index = 0; sample_index < m_num_samples; ++sample_index)
						track[sample_index].translation = rtm::vector_zero();
				}

				if (m_parser.try_array_begins("scales"))
				{
					if (!read_track_scales(track, m_num_samples) || !m_parser.array_ends())
						goto error;
				}
				else
				{
					for (uint32_t sample_index = 0; sample_index < m_num_samples; ++sample_index)
						track[sample_index].scale = rtm::vector_set(1.0F);
				}

				track_list[bone_index] = std::move(track);

				if (!m_parser.object_ends())
					goto error;
			}

			return true;

		error:
			m_error = m_parser.get_error();
			return false;
		}

		bool read_track_rotations(track_qvvf& track, uint32_t num_samples_expected)
		{
			for (uint32_t i = 0; i < num_samples_expected; ++i)
			{
				if (!m_parser.array_begins())
					return false;

				rtm::quatd rotation;

				if (m_is_binary_exact)
				{
					sjson::StringView values[4];
					if (!m_parser.read(values, 4))
						return false;

					rotation = hex_to_quat(values);
				}
				else
				{
					double values[4] = { 0.0, 0.0, 0.0, 0.0 };
					if (!m_parser.read(values, 4))
						return false;

					rotation = rtm::quat_load(values);
				}

				if (!m_parser.array_ends())
					return false;

				track[i].rotation = rtm::quat_normalize_deterministic(rtm::quat_cast(rotation));
			}

			return true;
		}

		bool read_track_translations(track_qvvf& track, uint32_t num_samples_expected)
		{
			for (uint32_t i = 0; i < num_samples_expected; ++i)
			{
				if (!m_parser.array_begins())
					return false;

				rtm::vector4d translation;

				if (m_is_binary_exact)
				{
					sjson::StringView values[3];
					if (!m_parser.read(values, 3))
						return false;

					translation = hex_to_vector3(values);
				}
				else
				{
					double values[3];
					if (!m_parser.read(values, 3))
						return false;

					translation = rtm::vector_load3(values);
				}

				if (!m_parser.array_ends())
					return false;

				track[i].translation = rtm::vector_cast(translation);
			}

			return true;
		}

		bool read_track_scales(track_qvvf& track, uint32_t num_samples_expected)
		{
			for (uint32_t i = 0; i < num_samples_expected; ++i)
			{
				if (!m_parser.array_begins())
					return false;

				rtm::vector4d scale;

				if (m_is_binary_exact)
				{
					sjson::StringView values[3];
					if (!m_parser.read(values, 3))
						return false;

					scale = hex_to_vector3(values);
				}
				else
				{
					double values[3];
					if (!m_parser.read(values, 3))
						return false;

					scale = rtm::vector_load3(values);
				}

				if (!m_parser.array_ends())
					return false;

				track[i].scale = rtm::vector_cast(scale);
			}

			return true;
		}

		bool nothing_follows()
		{
			if (!m_parser.remainder_is_comments_and_whitespace())
			{
				m_error = m_parser.get_error();
				return false;
			}

			return true;
		}

		void set_error(uint32_t reason)
		{
			m_parser.get_position(m_error.line, m_error.column);
			m_error.error = reason;
		}
	};

	ACL_IMPL_VERSION_NAMESPACE_END
}

ACL_IMPL_FILE_PRAGMA_POP

#endif	// #if defined(ACL_USE_SJSON)

<MSG> feat(compression): read qvv track list from sjson files

<DFF> @@ -61,6 +61,7 @@ namespace acl
 	{
 		std::unique_ptr<AnimationClip, Deleter<AnimationClip>> clip;
 		std::unique_ptr<RigidSkeleton, Deleter<RigidSkeleton>> skeleton;
+		track_array_qvvf track_list;
 
 		bool has_settings;
 		algorithm_type8 algorithm_type;
@@ -119,13 +120,13 @@ namespace acl
 			if (!read_settings(&out_data.has_settings, &out_data.algorithm_type, &out_data.settings))
 				return false;
 
-			if (!create_skeleton(out_data.skeleton))
+			if (!create_skeleton(out_data.skeleton, out_data.track_list))
 				return false;
 
 			if (!create_clip(out_data.clip, *out_data.skeleton))
 				return false;
 
-			if (!read_tracks(*out_data.clip, *out_data.skeleton))
+			if (!read_tracks(*out_data.clip, *out_data.skeleton, out_data.track_list))
 				return false;
 
 			return nothing_follows();
@@ -412,7 +413,7 @@ namespace acl
 			return false;
 		}
 
-		bool create_skeleton(std::unique_ptr<RigidSkeleton, Deleter<RigidSkeleton>>& skeleton)
+		bool create_skeleton(std::unique_ptr<RigidSkeleton, Deleter<RigidSkeleton>>& skeleton, track_array_qvvf& track_list)
 		{
 			sjson::ParserState before_bones = m_parser.save_state();
 
@@ -435,13 +436,19 @@ namespace acl
 			skeleton = make_unique<RigidSkeleton>(m_allocator, m_allocator, bones, num_bones);
 			deallocate_type_array(m_allocator, bones, num_allocated_bones);
 
-			return true;
-		}
+			track_list = track_array_qvvf(m_allocator, num_bones);
 
-		bool read_skeleton()
-		{
-			uint16_t num_bones;
-			return process_each_bone(nullptr, num_bones);
+			for (uint16_t transform_index = 0; transform_index < num_bones; ++transform_index)
+			{
+				const RigidBone& bone = skeleton->get_bone(transform_index);
+
+				track_qvvf& track = track_list[transform_index];
+				track_desc_transformf& desc = track.get_description();
+				desc.parent_index = bone.parent_index;
+				desc.shell_distance = bone.vertex_distance;
+			}
+
+			return true;
 		}
 
 		static double hex_to_double(const sjson::StringView& value)
@@ -863,8 +870,10 @@ namespace acl
 			return true;
 		}
 
-		bool read_tracks(AnimationClip& clip, const RigidSkeleton& skeleton)
+		bool read_tracks(AnimationClip& clip, const RigidSkeleton& skeleton, track_array_qvvf& track_list)
 		{
+			const uint32_t num_transforms = track_list.get_num_tracks();
+
 			std::unique_ptr<AnimationClip, Deleter<AnimationClip>> base_clip;
 
 			if (m_parser.try_array_begins("base_tracks"))
@@ -987,6 +996,28 @@ namespace acl
 
 			clip.set_additive_base(base_clip.release(), m_additive_format);
 
+			for (uint32_t transform_index = 0; transform_index < num_transforms; ++transform_index)
+			{
+				const AnimatedBone& bone = clip.get_animated_bone(safe_static_cast<uint16_t>(transform_index));
+
+				track_qvvf& track = track_list[transform_index];
+
+				track_desc_transformf desc = track.get_description();	// Copy our description
+				desc.output_index = bone.output_index;
+
+				track = track_qvvf::make_reserve(desc, m_allocator, m_num_samples, m_sample_rate);
+
+				for (uint32_t sample_index = 0; sample_index < m_num_samples; ++sample_index)
+				{
+					const rtm::quatf rotation = rtm::quat_normalize(rtm::quat_cast(bone.rotation_track.get_sample(sample_index)));
+					const rtm::vector4f translation = rtm::vector_cast(bone.translation_track.get_sample(sample_index));
+					const rtm::vector4f scale = rtm::vector_cast(bone.scale_track.get_sample(sample_index));
+
+					const rtm::qvvf transform = rtm::qvv_set(rotation, translation, scale);
+					track[sample_index] = transform;
+				}
+			}
+
 			return true;
 
 		error:

{'commit': 'diff --git a/includes/acl/io/clip_reader.h b/includes/acl/io/clip_reader.h\nindex 89a9b6c8..ebf8fa61 100644\n--- a/includes/acl/io/clip_reader.h\n+++ b/includes/acl/io/clip_reader.h\n@@ -61,6 +61,7 @@ namespace acl\n \t{\n \t\tstd::unique_ptr<AnimationClip, Deleter<AnimationClip>> clip;\n \t\tstd::unique_ptr<RigidSkeleton, Deleter<RigidSkeleton>> skeleton;\n+\t\ttrack_array_qvvf track_list;\n \n \t\tbool has_settings;\n \t\talgorithm_type8 algorithm_type;\n@@ -119,13 +120,13 @@ namespace acl\n \t\t\tif (!read_settings(&out_data.has_settings, &out_data.algorithm_type, &out_data.settings))\n \t\t\t\treturn false;\n \n-\t\t\tif (!create_skeleton(out_data.skeleton))\n+\t\t\tif (!create_skeleton(out_data.skeleton, out_data.track_list))\n \t\t\t\treturn false;\n \n \t\t\tif (!create_clip(out_data.clip, *out_data.skeleton))\n \t\t\t\treturn false;\n \n-\t\t\tif (!read_tracks(*out_data.clip, *out_data.skeleton))\n+\t\t\tif (!read_tracks(*out_data.clip, *out_data.skeleton, out_data.track_list))\n \t\t\t\treturn false;\n \n \t\t\treturn nothing_follows();\n@@ -412,7 +413,7 @@ namespace acl\n \t\t\treturn false;\n \t\t}\n \n-\t\tbool create_skeleton(std::unique_ptr<RigidSkeleton, Deleter<RigidSkeleton>>& skeleton)\n+\t\tbool create_skeleton(std::unique_ptr<RigidSkeleton, Deleter<RigidSkeleton>>& skeleton, track_array_qvvf& track_list)\n \t\t{\n \t\t\tsjson::ParserState before_bones = m_parser.save_state();\n \n@@ -435,13 +436,19 @@ namespace acl\n \t\t\tskeleton = make_unique<RigidSkeleton>(m_allocator, m_allocator, bones, num_bones);\n \t\t\tdeallocate_type_array(m_allocator, bones, num_allocated_bones);\n \n-\t\t\treturn true;\n-\t\t}\n+\t\t\ttrack_list = track_array_qvvf(m_allocator, num_bones);\n \n-\t\tbool read_skeleton()\n-\t\t{\n-\t\t\tuint16_t num_bones;\n-\t\t\treturn process_each_bone(nullptr, num_bones);\n+\t\t\tfor (uint16_t transform_index = 0; transform_index < num_bones; ++transform_index)\n+\t\t\t{\n+\t\t\t\tconst RigidBone& bone = skeleton->get_bone(transform_index);\n+\n+\t\t\t\ttrack_qvvf& track = track_list[transform_index];\n+\t\t\t\ttrack_desc_transformf& desc = track.get_description();\n+\t\t\t\tdesc.parent_index = bone.parent_index;\n+\t\t\t\tdesc.shell_distance = bone.vertex_distance;\n+\t\t\t}\n+\n+\t\t\treturn true;\n \t\t}\n \n \t\tstatic double hex_to_double(const sjson::StringView& value)\n@@ -863,8 +870,10 @@ namespace acl\n \t\t\treturn true;\n \t\t}\n \n-\t\tbool read_tracks(AnimationClip& clip, const RigidSkeleton& skeleton)\n+\t\tbool read_tracks(AnimationClip& clip, const RigidSkeleton& skeleton, track_array_qvvf& track_list)\n \t\t{\n+\t\t\tconst uint32_t num_transforms = track_list.get_num_tracks();\n+\n \t\t\tstd::unique_ptr<AnimationClip, Deleter<AnimationClip>> base_clip;\n \n \t\t\tif (m_parser.try_array_begins("base_tracks"))\n@@ -987,6 +996,28 @@ namespace acl\n \n \t\t\tclip.set_additive_base(base_clip.release(), m_additive_format);\n \n+\t\t\tfor (uint32_t transform_index = 0; transform_index < num_transforms; ++transform_index)\n+\t\t\t{\n+\t\t\t\tconst AnimatedBone& bone = clip.get_animated_bone(safe_static_cast<uint16_t>(transform_index));\n+\n+\t\t\t\ttrack_qvvf& track = track_list[transform_index];\n+\n+\t\t\t\ttrack_desc_transformf desc = track.get_description();\t// Copy our description\n+\t\t\t\tdesc.output_index = bone.output_index;\n+\n+\t\t\t\ttrack = track_qvvf::make_reserve(desc, m_allocator, m_num_samples, m_sample_rate);\n+\n+\t\t\t\tfor (uint32_t sample_index = 0; sample_index < m_num_samples; ++sample_index)\n+\t\t\t\t{\n+\t\t\t\t\tconst rtm::quatf rotation = rtm::quat_normalize(rtm::quat_cast(bone.rotation_track.get_sample(sample_index)));\n+\t\t\t\t\tconst rtm::vector4f translation = rtm::vector_cast(bone.translation_track.get_sample(sample_index));\n+\t\t\t\t\tconst rtm::vector4f scale = rtm::vector_cast(bone.scale_track.get_sample(sample_index));\n+\n+\t\t\t\t\tconst rtm::qvvf transform = rtm::qvv_set(rotation, translation, scale);\n+\t\t\t\t\ttrack[sample_index] = transform;\n+\t\t\t\t}\n+\t\t\t}\n+\n \t\t\treturn true;\n \n \t\terror:\n', 'commit_subject': 'feat(compression): read qvv track list from sjson files', 'repo_name': 'nfrechette/acl', 'committer': {'name': 'Nicholas Frechette', 'email': '4afa1ee64a5cb1edca81e75e933c58ad9445b064@gmail.com', 'time_sec': '1594343371', 'tz_offset': '-240', 'date': {'seconds': '1594343371'}}, 'commit_tree': '0c90778fc0a6ac97fef86230424a0d0b68b240fc', 'commit_message': 'feat(compression): read qvv track list from sjson files\n', 'language_name': 'Java', 'license': 'mit', 'hunks': '@@ -61,6 +61,7 @@ namespace acl\n \t{\n \t\tstd::unique_ptr<AnimationClip, Deleter<AnimationClip>> clip;\n \t\tstd::unique_ptr<RigidSkeleton, Deleter<RigidSkeleton>> skeleton;\n+\t\ttrack_array_qvvf track_list;\n \n \t\tbool has_settings;\n \t\talgorithm_type8 algorithm_type;\n@@ -119,13 +120,13 @@ namespace acl\n \t\t\tif (!read_settings(&out_data.has_settings, &out_data.algorithm_type, &out_data.settings))\n \t\t\t\treturn false;\n \n-\t\t\tif (!create_skeleton(out_data.skeleton))\n+\t\t\tif (!create_skeleton(out_data.skeleton, out_data.track_list))\n \t\t\t\treturn false;\n \n \t\t\tif (!create_clip(out_data.clip, *out_data.skeleton))\n \t\t\t\treturn false;\n \n-\t\t\tif (!read_tracks(*out_data.clip, *out_data.skeleton))\n+\t\t\tif (!read_tracks(*out_data.clip, *out_data.skeleton, out_data.track_list))\n \t\t\t\treturn false;\n \n \t\t\treturn nothing_follows();\n@@ -412,7 +413,7 @@ namespace acl\n \t\t\treturn false;\n \t\t}\n \n-\t\tbool create_skeleton(std::unique_ptr<RigidSkeleton, Deleter<RigidSkeleton>>& skeleton)\n+\t\tbool create_skeleton(std::unique_ptr<RigidSkeleton, Deleter<RigidSkeleton>>& skeleton, track_array_qvvf& track_list)\n \t\t{\n \t\t\tsjson::ParserState before_bones = m_parser.save_state();\n \n@@ -435,13 +436,19 @@ namespace acl\n \t\t\tskeleton = make_unique<RigidSkeleton>(m_allocator, m_allocator, bones, num_bones);\n \t\t\tdeallocate_type_array(m_allocator, bones, num_allocated_bones);\n \n-\t\t\treturn true;\n-\t\t}\n+\t\t\ttrack_list = track_array_qvvf(m_allocator, num_bones);\n \n-\t\tbool read_skeleton()\n-\t\t{\n-\t\t\tuint16_t num_bones;\n-\t\t\treturn process_each_bone(nullptr, num_bones);\n+\t\t\tfor (uint16_t transform_index = 0; transform_index < num_bones; ++transform_index)\n+\t\t\t{\n+\t\t\t\tconst RigidBone& bone = skeleton->get_bone(transform_index);\n+\n+\t\t\t\ttrack_qvvf& track = track_list[transform_index];\n+\t\t\t\ttrack_desc_transformf& desc = track.get_description();\n+\t\t\t\tdesc.parent_index = bone.parent_index;\n+\t\t\t\tdesc.shell_distance = bone.vertex_distance;\n+\t\t\t}\n+\n+\t\t\treturn true;\n \t\t}\n \n \t\tstatic double hex_to_double(const sjson::StringView& value)\n@@ -863,8 +870,10 @@ namespace acl\n \t\t\treturn true;\n \t\t}\n \n-\t\tbool read_tracks(AnimationClip& clip, const RigidSkeleton& skeleton)\n+\t\tbool read_tracks(AnimationClip& clip, const RigidSkeleton& skeleton, track_array_qvvf& track_list)\n \t\t{\n+\t\t\tconst uint32_t num_transforms = track_list.get_num_tracks();\n+\n \t\t\tstd::unique_ptr<AnimationClip, Deleter<AnimationClip>> base_clip;\n \n \t\t\tif (m_parser.try_array_begins("base_tracks"))\n@@ -987,6 +996,28 @@ namespace acl\n \n \t\t\tclip.set_additive_base(base_clip.release(), m_additive_format);\n \n+\t\t\tfor (uint32_t transform_index = 0; transform_index < num_transforms; ++transform_index)\n+\t\t\t{\n+\t\t\t\tconst AnimatedBone& bone = clip.get_animated_bone(safe_static_cast<uint16_t>(transform_index));\n+\n+\t\t\t\ttrack_qvvf& track = track_list[transform_index];\n+\n+\t\t\t\ttrack_desc_transformf desc = track.get_description();\t// Copy our description\n+\t\t\t\tdesc.output_index = bone.output_index;\n+\n+\t\t\t\ttrack = track_qvvf::make_reserve(desc, m_allocator, m_num_samples, m_sample_rate);\n+\n+\t\t\t\tfor (uint32_t sample_index = 0; sample_index < m_num_samples; ++sample_index)\n+\t\t\t\t{\n+\t\t\t\t\tconst rtm::quatf rotation = rtm::quat_normalize(rtm::quat_cast(bone.rotation_track.get_sample(sample_index)));\n+\t\t\t\t\tconst rtm::vector4f translation = rtm::vector_cast(bone.translation_track.get_sample(sample_index));\n+\t\t\t\t\tconst rtm::vector4f scale = rtm::vector_cast(bone.scale_track.get_sample(sample_index));\n+\n+\t\t\t\t\tconst rtm::qvvf transform = rtm::qvv_set(rotation, translation, scale);\n+\t\t\t\t\ttrack[sample_index] = transform;\n+\t\t\t\t}\n+\t\t\t}\n+\n \t\t\treturn true;\n \n \t\terror:\n', 'addition_count': 41, 'deletion_count': 10, 'src_file': 'a/includes/acl/io/clip_reader.h', 'tgt_file': 'b/includes/acl/io/clip_reader.h', 'file_extension': '.h', 'before_file': '#pragma once\n\n////////////////////////////////////////////////////////////////////////////////\n// The MIT License (MIT)\n//\n// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the "Software"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n////////////////////////////////////////////////////////////////////////////////\n\n#if defined(ACL_USE_SJSON)\n\n#include "acl/version.h"\n#include "acl/io/clip_reader_error.h"\n#include "acl/compression/compression_settings.h"\n#include "acl/compression/track_array.h"\n#include "acl/core/algorithm_types.h"\n#include "acl/core/impl/compiler_utils.h"\n#include "acl/core/iallocator.h"\n#include "acl/core/string.h"\n#include "acl/core/track_desc.h"\n#include "acl/core/unique_ptr.h"\n#include "acl/math/quatf.h"\n\n#include <rtm/quatd.h>\n#include <rtm/quatf.h>\n#include <rtm/vector4d.h>\n#include <rtm/vector4f.h>\n#include <rtm/qvvd.h>\n#include <rtm/qvvf.h>\n\n#include <sjson/parser.h>\n\n#include <cstdint>\n\nACL_IMPL_FILE_PRAGMA_PUSH\n\nnamespace acl\n{\n\tACL_IMPL_VERSION_NAMESPACE_BEGIN\n\n\t//////////////////////////////////////////////////////////////////////////\n\t// Enum to describe each type of raw content that an SJSON ACL file might contain.\n\tenum class sjson_file_type\n\t{\n\t\tstd::unique_ptr<AnimationClip, Deleter<AnimationClip>> clip;\n\t\tstd::unique_ptr<RigidSkeleton, Deleter<RigidSkeleton>> skeleton;\n\n\t\tbool has_settings;\n\t\talgorithm_type8 algorithm_type;\n\t// Raw transform tracks\n\tstruct sjson_raw_clip\n\t{\n\t\ttrack_array_qvvf track_list;\n\n\t\ttrack_array_qvvf additive_base_track_list;\n\t\tadditive_clip_format8 additive_format = additive_clip_format8::none;\n\n\t\ttrack_qvvf bind_pose;\n\n\t\tbool has_settings = false;\n\t\tcompression_settings settings;\n\t};\n\n\t//////////////////////////////////////////////////////////////////////////\n\t// Raw scalar tracks\n\tstruct sjson_raw_track_list\n\t{\n\t\ttrack_array track_list;\n\n\t\ttrack_qvvf bind_pose;\n\n\t\tbool has_settings = false;\n\t\tcompression_settings settings;\n\t};\n\n\t//////////////////////////////////////////////////////////////////////////\n\t// An SJSON ACL file reader.\n\tclass clip_reader\n\t{\n\tpublic:\n\t\tclip_reader(iallocator& allocator, const char* sjson_input, size_t input_length)\n\t\t\t: m_allocator(allocator)\n\t\t\t, m_parser(sjson_input, input_length)\n\t\t{\n\t\t}\n\n\t\t~clip_reader()\n\t\t{\n\t\t\tdeallocate_type_array(m_allocator, m_bone_names, m_num_bones);\n\t\t}\n\n\t\tsjson_file_type get_file_type()\n\t\t{\n\t\t\treset_state();\n\n\t\t\tif (!read_version())\n\t\t\t\treturn sjson_file_type::unknown;\n\n\t\t\tif (m_parser.object_begins("clip"))\n\t\t\t\treturn sjson_file_type::raw_clip;\n\n\t\t\tif (!read_settings(&out_data.has_settings, &out_data.algorithm_type, &out_data.settings))\n\t\t\t\treturn false;\n\n\t\t\tif (!create_skeleton(out_data.skeleton))\n\t\t\t\treturn false;\n\n\t\t\tif (!create_clip(out_data.clip, *out_data.skeleton))\n\t\t\t\treturn false;\n\n\t\t\tif (!read_tracks(*out_data.clip, *out_data.skeleton))\n\t\t\t\treturn false;\n\n\t\t\treturn nothing_follows();\n\t\t\tif (!read_raw_clip_header())\n\t\t\t\treturn false;\n\n\t\t\tif (!read_settings(&out_data.has_settings, &out_data.settings))\n\t\t\t\treturn false;\n\n\t\t\tif (!create_skeleton(out_data.track_list, out_data.bind_pose))\n\t\t\t\treturn false;\n\n\t\t\tif (!read_tracks(out_data.track_list, out_data.additive_base_track_list))\n\t\t\t\treturn false;\n\n\t\t\tout_data.additive_format = !out_data.additive_base_track_list.is_empty() ? m_additive_format : additive_clip_format8::none;\n\n\t\t\treturn nothing_follows();\n\t\t}\n\n\t\tbool read_raw_track_list(sjson_raw_track_list& out_data)\n\t\t{\n\t\t\treset_state();\n\n\t\t\tif (!read_version())\n\t\t\t\treturn false;\n\n\t\t\tif (!read_raw_track_list_header())\n\t\t\t\treturn false;\n\n\t\t\tif (!read_settings(&out_data.has_settings, &out_data.settings))\n\t\t\t\treturn false;\n\n\t\t\tif (!create_track_list(out_data.track_list, out_data.bind_pose))\n\t\t\t\treturn false;\n\n\t\t\treturn nothing_follows();\n\t\t}\n\n\t\tclip_reader_error get_error() const { return m_error; }\n\n\tprivate:\n\t\tclip_reader(const clip_reader&) = delete;\n\t\tclip_reader& operator=(const clip_reader&) = delete;\n\n\t\tiallocator& m_allocator;\n\t\tsjson::Parser m_parser;\n\t\tclip_reader_error m_error;\n\n\t\tuint32_t m_version\t\t\t\t\t\t\t\t= 0;\n\t\tuint32_t m_num_samples\t\t\t\t\t\t\t= 0;\n\t\tfloat m_sample_rate\t\t\t\t\t\t\t\t= 0.0F;\n\t\tsjson::StringView m_clip_name;\n\t\tbool m_is_binary_exact\t\t\t\t\t\t\t= false;\n\t\tadditive_clip_format8 m_additive_format\t\t\t= additive_clip_format8::none;\n\t\tsjson::StringView m_additive_base_name;\n\t\tuint32_t m_additive_base_num_samples\t\t\t= 0;\n\t\tfloat m_additive_base_sample_rate\t\t\t\t= 0.0F;\n\n\t\tbool m_has_settings\t\t\t\t\t\t\t\t= false;\n\t\tfloat m_constant_rotation_threshold_angle\t\t= 0.0F;\n\t\tfloat m_constant_translation_threshold\t\t\t= 0.0F;\n\t\tfloat m_constant_scale_threshold\t\t\t\t= 0.0F;\n\t\tfloat m_error_threshold\t\t\t\t\t\t\t= 0.0F;\n\n\t\tsjson::StringView* m_bone_names\t\t\t\t\t= nullptr;\n\t\tuint32_t m_num_bones\t\t\t\t\t\t\t= 0;\n\n\t\tstring convert_string(const sjson::StringView& str) const\n\t\t{\n\t\t\treturn string(m_allocator, str.c_str(), str.size());\n\t\t}\n\n\t\tvoid reset_state()\n\t\t{\n\t\t\tm_parser.reset_state();\n\t\t\tset_error(clip_reader_error::None);\n\t\t}\n\n\t\tbool read_version()\n\t\t{\n\t\t\tif (!m_parser.read("version", m_version))\n\t\t\t{\n\t\t\t\tm_error = m_parser.get_error();\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tif (m_version > 5)\n\t\t\t{\n\t\t\t\tset_error(clip_reader_error::UnsupportedVersion);\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool read_raw_clip_header()\n\t\t{\n\t\t\tsjson::StringView additive_format;\n\n\t\t\tif (!m_parser.object_begins("clip"))\n\t\t\t\tgoto error;\n\n\t\t\tm_parser.try_read("name", m_clip_name, "");\n\n\t\t\tdouble num_samples;\n\t\t\tif (!m_parser.read("num_samples", num_samples))\n\t\t\t\tgoto error;\n\n\t\t\tm_num_samples = static_cast<uint32_t>(num_samples);\n\t\t\tif (static_cast<double>(m_num_samples) != num_samples)\n\t\t\t{\n\t\t\t\tset_error(clip_reader_error::UnsignedIntegerExpected);\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tdouble sample_rate;\n\t\t\tif (!m_parser.read("sample_rate", sample_rate))\n\t\t\t\tgoto error;\n\n\t\t\tm_sample_rate = static_cast<float>(sample_rate);\n\t\t\tif (m_sample_rate <= 0.0F)\n\t\t\t{\n\t\t\t\tset_error(clip_reader_error::PositiveValueExpected);\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Version 1 had an error_threshold field, skip it\n\t\t\tdouble error_threshold;\n\t\t\tif (m_version == 1 && !m_parser.read("error_threshold", error_threshold))\n\t\t\t\tgoto error;\n\n\t\t\t// Optional value\n\t\t\tm_parser.try_read("is_binary_exact", m_is_binary_exact, false);\n\n\t\t\t// Optional value\n\t\t\tm_parser.try_read("additive_format", additive_format, "none");\n\t\t\tif (!get_additive_clip_format(additive_format.c_str(), m_additive_format))\n\t\t\t{\n\t\t\t\tset_error(clip_reader_error::InvalidAdditiveClipFormat);\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tm_parser.try_read("additive_base_name", m_additive_base_name, "");\n\t\t\tm_parser.try_read("additive_base_num_samples", num_samples, 1.0);\n\t\t\tm_additive_base_num_samples = static_cast<uint32_t>(num_samples);\n\t\t\tif (static_cast<double>(m_additive_base_num_samples) != num_samples || m_additive_base_num_samples == 0)\n\t\t\t{\n\t\t\t\tset_error(clip_reader_error::UnsignedIntegerExpected);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tm_parser.try_read("additive_base_sample_rate", sample_rate, 30.0);\n\t\t\tm_additive_base_sample_rate = static_cast<float>(sample_rate);\n\t\t\tif (m_additive_base_sample_rate <= 0.0F)\n\t\t\t{\n\t\t\t\tset_error(clip_reader_error::PositiveValueExpected);\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tif (!m_parser.object_ends())\n\t\t\t\tgoto error;\n\n\t\t\treturn true;\n\n\t\terror:\n\t\t\tm_error = m_parser.get_error();\n\t\t\treturn false;\n\t\t}\n\n\t\tbool read_raw_track_list_header()\n\t\t{\n\t\t\tif (!m_parser.object_begins("track_list"))\n\t\t\t\tgoto error;\n\n\t\t\tm_parser.try_read("name", m_clip_name, "");\n\n\t\t\tdouble num_samples;\n\t\t\tif (!m_parser.read("num_samples", num_samples))\n\t\t\t\tgoto error;\n\n\t\t\tm_num_samples = static_cast<uint32_t>(num_samples);\n\t\t\tif (static_cast<double>(m_num_samples) != num_samples)\n\t\t\t{\n\t\t\t\tset_error(clip_reader_error::UnsignedIntegerExpected);\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tdouble sample_rate;\n\t\t\tif (!m_parser.read("sample_rate", sample_rate))\n\t\t\t\tgoto error;\n\n\t\t\tm_sample_rate = static_cast<float>(sample_rate);\n\t\t\tif (m_sample_rate <= 0.0F)\n\t\t\t{\n\t\t\t\tset_error(clip_reader_error::PositiveValueExpected);\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Optional value\n\t\t\tm_parser.try_read("is_binary_exact", m_is_binary_exact, false);\n\n\t\t\tif (!m_parser.object_ends())\n\t\t\t\tgoto error;\n\n\t\t\treturn true;\n\n\t\terror:\n\t\t\tm_error = m_parser.get_error();\n\t\t\treturn false;\n\t\t}\n\n\t\tbool read_settings(bool* out_has_settings, compression_settings* out_settings)\n\t\t{\n\t\t\tm_has_settings = false;\n\n\t\t\tif (!m_parser.try_object_begins("settings"))\n\t\t\t{\n\t\t\t\tif (out_has_settings != nullptr)\n\t\t\t\t\t*out_has_settings = false;\n\n\t\t\t\t// Settings are optional, all good\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tcompression_settings default_settings;\n\n\t\t\tsjson::StringView algorithm_name;\n\t\t\tsjson::StringView compression_level;\n\t\t\tsjson::StringView rotation_format;\n\t\t\tsjson::StringView translation_format;\n\t\t\tsjson::StringView scale_format;\n\t\t\tbool rotation_range_reduction;\n\t\t\tbool translation_range_reduction;\n\t\t\tbool scale_range_reduction;\n\t\t\tdouble constant_rotation_threshold_angle;\n\t\t\tdouble constant_translation_threshold;\n\t\t\tdouble constant_scale_threshold;\n\t\t\tdouble error_threshold;\n\n\t\t\tdouble segmenting_ideal_num_samples = 0.0;\t// Legacy, no longer used\n\t\t\tdouble segmenting_max_num_samples = 0.0;\t// Legacy, no longer used\n\n\t\t\tm_parser.try_read("algorithm_name", algorithm_name, get_algorithm_name(algorithm_type8::uniformly_sampled));\n\t\t\tm_parser.try_read("level", compression_level, get_compression_level_name(default_settings.level));\n\t\t\tm_parser.try_read("rotation_format", rotation_format, get_rotation_format_name(default_settings.rotation_format));\n\t\t\tm_parser.try_read("translation_format", translation_format, get_vector_format_name(default_settings.translation_format));\n\t\t\tm_parser.try_read("scale_format", scale_format, get_vector_format_name(default_settings.scale_format));\n\t\t\tm_parser.try_read("rotation_range_reduction", rotation_range_reduction, false);\t\t\t// Legacy, no longer used\n\t\t\tm_parser.try_read("translation_range_reduction", translation_range_reduction, false);\t// Legacy, no longer used\n\t\t\tm_parser.try_read("scale_range_reduction", scale_range_reduction, false);\t\t\t\t// Legacy, no longer used\n\n\t\t\tif (m_parser.try_object_begins("segmenting"))\n\t\t\t{\n\t\t\t\tbool segmenting_enabled;\n\t\t\t\tbool segmenting_rotation_range_reduction;\n\t\t\t\tbool segmenting_translation_range_reduction;\n\t\t\t\tbool segmenting_scale_range_reduction;\n\t\t\t\tm_parser.try_read("enabled", segmenting_enabled, false);\t\t\t\t\t\t\t\t\t\t\t// Legacy, no longer used\n\t\t\t\tm_parser.try_read("ideal_num_samples", segmenting_ideal_num_samples, 0.0);\t\t\t\t\t\t\t// Legacy, no longer used\n\t\t\t\tm_parser.try_read("max_num_samples", segmenting_max_num_samples, 0.0);\t\t\t\t\t\t\t\t// Legacy, no longer used\n\t\t\t\tm_parser.try_read("rotation_range_reduction", segmenting_rotation_range_reduction, false);\t\t\t// Legacy, no longer used\n\t\t\t\tm_parser.try_read("translation_range_reduction", segmenting_translation_range_reduction, false);\t// Legacy, no longer used\n\t\t\t\tm_parser.try_read("scale_range_reduction", segmenting_scale_range_reduction, false);\t\t\t\t// Legacy, no longer used\n\n\t\t\t\tif (!m_parser.is_valid() || !m_parser.object_ends())\n\t\t\t\t\tgoto parsing_error;\n\t\t\t}\n\n\t\t\t// Skip deprecated values\n\t\t\tm_parser.try_read("constant_rotation_threshold_angle", constant_rotation_threshold_angle, 0.00284714461);\n\t\t\tm_parser.try_read("constant_translation_threshold", constant_translation_threshold, 0.001);\n\t\t\tm_parser.try_read("constant_scale_threshold", constant_scale_threshold, 0.00001);\n\t\t\tm_parser.try_read("error_threshold", error_threshold, 0.01);\n\n\t\t\tif (!m_parser.is_valid() || !m_parser.object_ends())\n\t\t\t\tgoto parsing_error;\n\n\t\t\tif (out_has_settings != nullptr)\n\t\t\t{\n\t\t\t\t*out_has_settings = true;\n\n\t\t\t\tif (!get_compression_level(compression_level.c_str(), out_settings->level))\n\t\t\t\t\tgoto invalid_value_error;\n\t\t\treturn false;\n\t\t}\n\n\t\tbool create_skeleton(std::unique_ptr<RigidSkeleton, Deleter<RigidSkeleton>>& skeleton)\n\t\t{\n\t\t\tsjson::ParserState before_bones = m_parser.save_state();\n\n\t\t\t\tif (!get_vector_format(scale_format.c_str(), out_settings->scale_format))\n\t\t\t\t\tgoto invalid_value_error;\n\n\t\t\t\tm_constant_rotation_threshold_angle = float(constant_rotation_threshold_angle);\n\t\t\t\tm_constant_translation_threshold = float(constant_translation_threshold);\n\t\t\t\tm_constant_scale_threshold = float(constant_scale_threshold);\n\t\t\t\tm_error_threshold = float(error_threshold);\n\t\t\t}\n\n\t\t\tm_has_settings = true;\n\t\t\treturn true;\n\n\t\tparsing_error:\n\t\t\tm_error = m_parser.get_error();\n\t\t\treturn false;\n\n\t\t\tskeleton = make_unique<RigidSkeleton>(m_allocator, m_allocator, bones, num_bones);\n\t\t\tdeallocate_type_array(m_allocator, bones, num_allocated_bones);\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool read_skeleton()\n\t\t{\n\t\t\tuint16_t num_bones;\n\t\t\treturn process_each_bone(nullptr, num_bones);\n\t\t}\n\n\t\tstatic double hex_to_double(const sjson::StringView& value)\n\t\t\ttrack_list = track_array_qvvf(m_allocator, num_bones);\n\t\t\ttrack_list.set_name(convert_string(m_clip_name));\n\t\t\tbind_pose = track_qvvf::make_reserve(track_desc_transformf{}, m_allocator, num_bones, 30.0F);\t// 1 sample per track\n\t\t\tbind_pose.set_name(string(m_allocator, "bind pose"));\n\n\t\t\tconst uint32_t num_allocated_bones = num_bones;\n\n\t\t\tif (!process_each_bone(&track_list, &bind_pose, num_bones))\n\t\t\t\treturn false;\n\n\t\t\t(void)num_allocated_bones;\n\t\t\tACL_ASSERT(num_bones == num_allocated_bones, "Number of bones read mismatch");\n\n\t\t\treturn true;\n\t\t}\n\n\t\tstatic double hex_to_double(const sjson::StringView& value)\n\t\t{\n\t\t\tunion UInt64ToDouble\n\t\t\t{\n\t\t\t\tuint64_t u64;\n\t\t\t\tdouble dbl;\n\n\t\t\t\tconstexpr explicit UInt64ToDouble(uint64_t u64_value) : u64(u64_value) {}\n\t\t\t};\n\n\t\t\tACL_ASSERT(value.size() <= 16, "Invalid binary exact double value");\n\t\t\tuint64_t value_u64 = acl_impl::strtoull(value.c_str(), nullptr, 16);\n\t\t\treturn UInt64ToDouble(value_u64).dbl;\n\t\t}\n\n\t\tstatic float hex_to_float(const sjson::StringView& value)\n\t\t{\n\t\t\tunion UInt32ToFloat\n\t\t\t{\n\t\t\t\tuint32_t u32;\n\t\t\t\tfloat flt;\n\n\t\t\t\tconstexpr explicit UInt32ToFloat(uint32_t u32_value) : u32(u32_value) {}\n\t\t\t};\n\n\t\t\tACL_ASSERT(value.size() <= 8, "Invalid binary exact float value");\n\t\t\tuint32_t value_u32 = safe_static_cast<uint32_t>(std::strtoul(value.c_str(), nullptr, 16));\n\t\t\treturn UInt32ToFloat(value_u32).flt;\n\t\t}\n\n\t\tstatic rtm::quatd hex_to_quat(const sjson::StringView values[4])\n\t\t{\n\t\t\treturn rtm::quat_set(hex_to_double(values[0]), hex_to_double(values[1]), hex_to_double(values[2]), hex_to_double(values[3]));\n\t\t}\n\n\t\tstatic rtm::vector4d hex_to_vector3(const sjson::StringView values[3])\n\t\t{\n\t\t\treturn rtm::vector_set(hex_to_double(values[0]), hex_to_double(values[1]), hex_to_double(values[2]));\n\t\t}\n\n\t\tstatic rtm::float4f hex_to_float4f(const sjson::StringView values[4], uint32_t num_components)\n\t\t{\n\t\t\tACL_ASSERT(num_components <= 4, "Invalid number of components");\n\n\t\t\trtm::float4f result = { 0.0F, 0.0F, 0.0F, 0.0F };\n\t\t\tfloat* result_ptr = &result.x;\n\n\t\t\tfor (uint32_t component_index = 0; component_index < num_components; ++component_index)\n\t\t\t\tresult_ptr[component_index] = hex_to_float(values[component_index]);\n\n\t\t\treturn result;\n\t\t}\n\n\t\tbool process_each_bone(track_array_qvvf* tracks, track_qvvf* bind_pose, uint32_t& num_bones)\n\t\t{\n\t\t\tbool counting = tracks == nullptr;\n\t\t\tnum_bones = 0;\n\n\t\t\tif (!m_parser.array_begins("bones"))\n\t\t\t\tgoto error;\n\n\t\t\tfor (uint32_t i = 0; !m_parser.try_array_ends(); ++i)\n\t\t\t{\n\t\t\t\tif (!m_parser.object_begins())\n\t\t\t\t\tgoto error;\n\n\t\t\t\tsjson::StringView name;\n\t\t\t\tif (!m_parser.read("name", name))\n\t\t\t\t\tgoto error;\n\n\t\t\t\tif (tracks != nullptr)\n\t\t\t\t\tm_bone_names[i] = name;\n\n\t\t\t\tsjson::StringView parent;\n\t\t\t\tif (!m_parser.read("parent", parent))\n\t\t\t\t\tgoto error;\n\n\t\t\t\tuint32_t parent_index = k_invalid_track_index;\n\t\t\t\tif (tracks != nullptr && !parent.empty())\n\t\t\t\t{\n\t\t\t\t\tparent_index = find_bone(parent);\n\t\t\t\t\tif (parent_index == k_invalid_track_index)\n\t\t\t\t\t{\n\t\t\t\t\t\tset_error(clip_reader_error::NoParentBoneWithThatName);\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfloat vertex_distance;\n\t\t\t\tif (!m_parser.read("vertex_distance", vertex_distance))\n\t\t\t\t\tgoto error;\n\n\t\t\t\trtm::qvvd bind_transform = rtm::qvv_identity();\n\t\t\t\tif (m_is_binary_exact)\n\t\t\t\t{\n\t\t\t\t\tsjson::StringView rotation[4];\n\t\t\t\t\tif (m_parser.try_read("bind_rotation", rotation, 4, nullptr) && !counting)\n\t\t\t\t\t\tbind_transform.rotation = hex_to_quat(rotation);\n\n\t\t\t\t\tsjson::StringView translation[3];\n\t\t\t\t\tif (m_parser.try_read("bind_translation", translation, 3, nullptr) && !counting)\n\t\t\t\t\t\tbind_transform.translation = hex_to_vector3(translation);\n\n\t\t\t\t\tsjson::StringView scale[3];\n\t\t\t\t\tif (m_parser.try_read("bind_scale", scale, 3, nullptr) && !counting)\n\t\t\t\t\t\tbind_transform.scale = hex_to_vector3(scale);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tdouble rotation[4] = { 0.0, 0.0, 0.0, 0.0 };\n\t\t\t\t\tif (m_parser.try_read("bind_rotation", rotation, 4, 0.0) && !counting)\n\t\t\t\t\t\tbind_transform.rotation = rtm::quat_load(&rotation[0]);\n\n\t\t\t\t\tdouble translation[3] = { 0.0, 0.0, 0.0 };\n\t\t\t\t\tif (m_parser.try_read("bind_translation", translation, 3, 0.0) && !counting)\n\t\t\t\t\t\tbind_transform.translation = rtm::vector_load3(&translation[0]);\n\n\t\t\t\t\tdouble scale[3] = { 0.0, 0.0, 0.0 };\n\t\t\t\t\tif (m_parser.try_read("bind_scale", scale, 3, 0.0) && !counting)\n\t\t\t\t\t\tbind_transform.scale = rtm::vector_load3(&scale[0]);\n\t\t\t\t}\n\n\t\t\t\tif (tracks != nullptr)\n\t\t\t\t{\n\t\t\t\t\ttrack_desc_transformf desc;\n\t\t\t\t\tdesc.parent_index = parent_index;\n\t\t\t\t\tdesc.shell_distance = vertex_distance;\n\n\t\t\t\t\tif (m_has_settings)\n\t\t\t\t\t{\n\t\t\t\t\t\tdesc.precision = m_error_threshold;\n\t\t\t\t\t\tdesc.constant_rotation_threshold_angle = m_constant_rotation_threshold_angle;\n\t\t\t\t\t\tdesc.constant_translation_threshold = m_constant_translation_threshold;\n\t\t\t\t\t\tdesc.constant_scale_threshold = m_constant_scale_threshold;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Create a dummy track for now to hold our arguments\n\t\t\t\t\t(*tracks)[i] = track_qvvf::make_ref(desc, nullptr, 0, 30.0F);\n\t\t\t\t\t(*tracks)[i].set_name(convert_string(name));\n\n\t\t\t\t\trtm::qvvf bind_transform_ = rtm::qvv_cast(bind_transform);\n\t\t\t\t\tbind_transform_.rotation = rtm::quat_normalize_deterministic(bind_transform_.rotation);\n\n\t\t\t\t\t(*tracks)[i].get_description().default_value = bind_transform_;\n\n\t\t\t\t\t(*bind_pose)[i] = bind_transform_;\n\t\t\t\t}\n\n\t\t\t\tif (!m_parser.object_ends())\n\t\t\t\t\tgoto error;\n\n\t\t\t\t++num_bones;\n\t\t\t}\n\n\t\t\treturn true;\n\n\t\terror:\n\t\t\tm_error = m_parser.get_error();\n\t\t\treturn false;\n\t\t}\n\n\t\tuint32_t find_bone(const sjson::StringView& name) const\n\t\t{\n\t\t\tfor (uint32_t i = 0; i < m_num_bones; ++i)\n\t\t\t{\n\t\t\t\tif (name == m_bone_names[i])\n\t\t\t\t\treturn i;\n\t\t\t}\n\n\t\t\treturn k_invalid_track_index;\n\t\t}\n\n\t\tfloat read_optional_float(const char* key, float default_value)\n\t\t{\n\t\t\tfloat value = default_value;\n\t\t\tif (m_is_binary_exact)\n\t\t\t{\n\t\t\t\tsjson::StringView value_str;\n\t\t\t\tif (m_parser.try_read(key, value_str, ""))\n\t\t\t\t\tvalue = hex_to_float(value_str);\n\t\t\t}\n\t\t\telse\n\t\t\t\tm_parser.try_read(key, value, default_value);\n\n\t\t\treturn value;\n\t\t}\n\n\t\tbool read_qvv_sample(rtm::qvvf& out_sample)\n\t\t{\n\t\t\trtm::qvvf sample = rtm::qvv_identity();\n\t\t\tsjson::StringView values_str[4];\n\t\t\tdouble values[4] = { 0.0, 0.0, 0.0, 0.0 };\n\n\t\t\t{\n\t\t\t\tif (!m_parser.array_begins())\n\t\t\t\t\treturn false;\n\n\t\t\t\trtm::float4f value;\n\t\t\t\tif (m_is_binary_exact)\n\t\t\t\t{\n\t\t\t\t\tif (!m_parser.read(values_str, 4))\n\t\t\t\t\t\treturn false;\n\n\t\t\t\t\tvalue = hex_to_float4f(values_str, 4);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tif (!m_parser.read(values, 4))\n\t\t\t\t\t\treturn false;\n\n\t\t\t\t\tvalue = { static_cast<float>(values[0]), static_cast<float>(values[1]), static_cast<float>(values[2]), static_cast<float>(values[3])};\n\t\t\t\t}\n\n\t\t\t\tsample.rotation = rtm::quat_load(&value);\n\t\t\t\tif (!rtm::quat_is_finite(sample.rotation))\n\t\t\t\t\treturn false;\n\n\t\t\t\tif (!m_parser.array_ends())\n\t\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tif (!m_parser.read_comma())\n\t\t\t\treturn false;\n\n\t\t\t{\n\t\t\t\tif (!m_parser.array_begins())\n\t\t\t\t\treturn false;\n\n\t\t\t\trtm::float4f value;\n\t\t\t\tif (m_is_binary_exact)\n\t\t\t\t{\n\t\t\t\t\tif (!m_parser.read(values_str, 3))\n\t\t\t\t\t\treturn false;\n\n\t\t\t\t\tvalue = hex_to_float4f(values_str, 4);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tif (!m_parser.read(values, 3))\n\t\t\t\t\t\treturn false;\n\n\t\t\t\t\tvalue = { static_cast<float>(values[0]), static_cast<float>(values[1]), static_cast<float>(values[2]), 0.0F };\n\t\t\t\t}\n\n\t\t\t\tsample.translation = vector_load(&value);\n\t\t\t\tif (!rtm::vector_is_finite3(sample.translation))\n\t\t\t\t\treturn false;\n\n\t\t\t\tif (!m_parser.array_ends())\n\t\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tif (!m_parser.read_comma())\n\t\t\t\treturn false;\n\n\t\t\t{\n\t\t\t\tif (!m_parser.array_begins())\n\t\t\t\t\treturn false;\n\n\t\t\t\trtm::float4f value;\n\t\t\t\tif (m_is_binary_exact)\n\t\t\t\t{\n\t\t\t\t\tif (!m_parser.read(values_str, 3))\n\t\t\t\t\t\treturn false;\n\n\t\t\t\t\tvalue = hex_to_float4f(values_str, 4);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tif (!m_parser.read(values, 3))\n\t\t\t\t\t\treturn false;\n\n\t\t\t\t\tvalue = { static_cast<float>(values[0]), static_cast<float>(values[1]), static_cast<float>(values[2]), 0.0F };\n\t\t\t\t}\n\n\t\t\t\tsample.scale = vector_load(&value);\n\t\t\t\tif (!rtm::vector_is_finite3(sample.scale))\n\t\t\t\t\treturn false;\n\n\t\t\t\tif (!m_parser.array_ends())\n\t\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tout_sample = sample;\n\t\t\treturn true;\n\t\t}\n\n\t\tbool process_track_list(track* tracks, track_qvvf* bind_pose, uint32_t& num_tracks)\n\t\t{\n\t\t\tconst bool counting = tracks == nullptr;\n\t\t\ttrack dummy;\n\t\t\ttrack_type8 track_list_type = track_type8::float1f;\n\n\t\t\tnum_tracks = 0;\n\n\t\t\tif (!m_parser.array_begins("tracks"))\n\t\t\t\tgoto error;\n\n\t\t\tfor (uint32_t i = 0; !m_parser.try_array_ends(); ++i)\n\t\t\t{\n\t\t\t\ttrack& track_ = counting ? dummy : tracks[i];\n\n\t\t\t\tif (!m_parser.object_begins())\n\t\t\t\t\tgoto error;\n\n\t\t\t\tsjson::StringView name;\n\t\t\t\tm_parser.try_read("name", name, "");\n\n\t\t\t\tsjson::StringView type;\n\t\t\t\tif (!m_parser.read("type", type))\n\t\t\t\t\tgoto error;\n\n\t\t\t\ttrack_type8 track_type;\n\t\t\t\tif (!get_track_type(type.c_str(), track_type))\n\t\t\t\t{\n\t\t\t\t\tm_error.error = clip_reader_error::InvalidTrackType;\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tif (num_tracks == 0)\n\t\t\t\t{\n\t\t\t\t\t// Found our first track, this is the type we\'ll use for every track\n\t\t\t\t\ttrack_list_type = track_type;\n\t\t\t\t}\n\t\t\t\telse if (track_type != track_list_type)\n\t\t\t\t{\n\t\t\t\t\t// Track type doesn\'t match the type of the first track\n\t\t\t\t\tm_error.error = clip_reader_error::InvalidTrackType;\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tconst uint32_t num_components = get_track_num_sample_elements(track_type);\n\t\t\t\tACL_ASSERT((num_components > 0 && num_components <= 4) || num_components == 12, "Must have between 1 and 4 components or 12");\n\n\t\t\t\ttrack_desc_scalarf scalar_desc;\n\t\t\t\ttrack_desc_transformf transform_desc;\n\n\t\t\t\tfloat precision = read_optional_float("precision", -1.0F);\n\n\t\t\t\t// Deprecated, no longer used\n\t\t\t\tread_optional_float("constant_threshold", -1.0F);\n\n\t\t\t\tuint32_t output_index;\n\t\t\t\tm_parser.try_read("output_index", output_index, i);\n\n\t\t\t\tm_parser.try_read("parent_index", transform_desc.parent_index, k_invalid_track_index);\n\n\t\t\t\ttransform_desc.shell_distance = read_optional_float("shell_distance", transform_desc.shell_distance);\n\t\t\t\ttransform_desc.constant_rotation_threshold_angle = read_optional_float("constant_rotation_threshold_angle", transform_desc.constant_rotation_threshold_angle);\n\t\t\t\ttransform_desc.constant_translation_threshold = read_optional_float("constant_translation_threshold", transform_desc.constant_translation_threshold);\n\t\t\t\ttransform_desc.constant_scale_threshold = read_optional_float("constant_scale_threshold", transform_desc.constant_scale_threshold);\n\n\t\t\t\tscalar_desc.output_index = output_index;\n\t\t\t\ttransform_desc.output_index = output_index;\n\n\t\t\t\tif (track_type == track_type8::qvvf)\n\t\t\t\t\ttransform_desc.precision = precision < 0.0F ? transform_desc.precision : precision;\n\t\t\t\telse\n\t\t\t\t\tscalar_desc.precision = precision < 0.0F ? scalar_desc.precision : precision;\n\n\t\t\t\trtm::qvvf bind_transform = rtm::qvv_identity();\n\t\t\t\tif (m_is_binary_exact)\n\t\t\t\t{\n\t\t\t\t\tsjson::StringView rotation[4];\n\t\t\t\t\tif (m_parser.try_read("bind_rotation", rotation, 4, nullptr) && !counting)\n\t\t\t\t\t{\n\t\t\t\t\t\tconst rtm::float4f value = hex_to_float4f(rotation, 4);\n\t\t\t\t\t\tbind_transform.rotation = rtm::quat_load(&value);\n\t\t\t\t\t}\n\n\t\t\t\t\tsjson::StringView translation[3];\n\t\t\t\t\tif (m_parser.try_read("bind_translation", translation, 3, nullptr) && !counting)\n\t\t\t\t\t{\n\t\t\t\t\t\tconst rtm::float4f value = hex_to_float4f(translation, 3);\n\t\t\t\t\t\tbind_transform.translation = rtm::vector_load(&value);\n\t\t\t\t\t}\n\n\t\t\t\t\tsjson::StringView scale[3];\n\t\t\t\t\tif (m_parser.try_read("bind_scale", scale, 3, nullptr) && !counting)\n\t\t\t\t\t{\n\t\t\t\t\t\tconst rtm::float4f value = hex_to_float4f(scale, 3);\n\t\t\t\t\t\tbind_transform.scale = rtm::vector_load(&value);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tdouble rotation[4] = { 0.0, 0.0, 0.0, 0.0 };\n\t\t\t\t\tif (m_parser.try_read("bind_rotation", rotation, 4, 0.0) && !counting)\n\t\t\t\t\t\tbind_transform.rotation = rtm::quat_normalize_deterministic(rtm::quat_cast(rtm::quat_load(&rotation[0])));\n\n\t\t\t\t\tdouble translation[3] = { 0.0, 0.0, 0.0 };\n\t\t\t\t\tif (m_parser.try_read("bind_translation", translation, 3, 0.0) && !counting)\n\t\t\t\t\t\tbind_transform.translation = rtm::vector_cast(rtm::vector_load3(&translation[0]));\n\n\t\t\t\t\tdouble scale[3] = { 0.0, 0.0, 0.0 };\n\t\t\t\t\tif (m_parser.try_read("bind_scale", scale, 3, 0.0) && !counting)\n\t\t\t\t\t\tbind_transform.scale = rtm::vector_cast(rtm::vector_load3(&scale[0]));\n\t\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool read_tracks(AnimationClip& clip, const RigidSkeleton& skeleton)\n\t\t{\n\t\t\tstd::unique_ptr<AnimationClip, Deleter<AnimationClip>> base_clip;\n\n\t\t\tif (m_parser.try_array_begins("base_tracks"))\n\t\t\t\t\tvoid*\t\t\tany;\n\t\t\t\t\tfloat*\t\t\tfloat1f;\n\t\t\t\t\trtm::float2f*\tfloat2f;\n\t\t\t\t\trtm::float3f*\tfloat3f;\n\t\t\t\t\trtm::float4f*\tfloat4f;\n\t\t\t\t\trtm::vector4f*\tvector4f;\n\t\t\t\t\trtm::qvvf*\t\tqvvf;\n\t\t\t\t};\n\t\t\t\ttrack_samples_ptr_union track_samples_typed = { nullptr };\n\n\t\t\t\tswitch (track_type)\n\t\t\t\t{\n\t\t\t\tcase track_type8::float1f:\n\t\t\t\t\ttrack_samples_typed.float1f = allocate_type_array<float>(m_allocator, m_num_samples);\n\t\t\t\t\tbreak;\n\t\t\t\tcase track_type8::float2f:\n\t\t\t\t\ttrack_samples_typed.float2f = allocate_type_array<rtm::float2f>(m_allocator, m_num_samples);\n\t\t\t\t\tbreak;\n\t\t\t\tcase track_type8::float3f:\n\t\t\t\t\ttrack_samples_typed.float3f = allocate_type_array<rtm::float3f>(m_allocator, m_num_samples);\n\t\t\t\t\tbreak;\n\t\t\t\tcase track_type8::float4f:\n\t\t\t\t\ttrack_samples_typed.float4f = allocate_type_array<rtm::float4f>(m_allocator, m_num_samples);\n\t\t\t\t\tbreak;\n\t\t\t\tcase track_type8::vector4f:\n\t\t\t\t\ttrack_samples_typed.vector4f = allocate_type_array<rtm::vector4f>(m_allocator, m_num_samples);\n\t\t\t\t\tbreak;\n\t\t\t\tcase track_type8::qvvf:\n\t\t\t\t\ttrack_samples_typed.qvvf = allocate_type_array<rtm::qvvf>(m_allocator, m_num_samples);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tACL_ASSERT(false, "Unsupported track type");\n\t\t\t\t\t// Initialize our value to itself, we\'ll never use it\n\t\t\t\t\t// This silences an MSVC static analysis warning\n\t\t\t\t\ttrack_samples_typed.any = &track_samples_typed;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tbool has_error = false;\n\t\t\t\tfor (uint32_t sample_index = 0; sample_index < m_num_samples; ++sample_index)\n\t\t\t\t{\n\t\t\t\t\tif (!m_parser.array_begins())\n\t\t\t\t\t{\n\t\t\t\t\t\thas_error = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (m_is_binary_exact)\n\t\t\t\t\t{\n\t\t\t\t\t\tswitch (track_type)\n\t\t\t\t\t\t{\n\t\t\t\t\t\tcase track_type8::float1f:\n\t\t\t\t\t\tcase track_type8::float2f:\n\t\t\t\t\t\tcase track_type8::float3f:\n\t\t\t\t\t\tcase track_type8::float4f:\n\t\t\t\t\t\tcase track_type8::vector4f:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tsjson::StringView values[4];\n\t\t\t\t\t\t\tif (m_parser.read(values, num_components))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tconst rtm::float4f value = hex_to_float4f(values, num_components);\n\t\t\t\t\t\t\t\tstd::memcpy(track_samples_typed.float1f + (size_t(sample_index) * num_components), &value, sizeof(float) * num_components);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\thas_error = true;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase track_type8::qvvf:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\trtm::qvvf sample;\n\t\t\t\t\t\t\tif (!read_qvv_sample(sample))\n\t\t\t\t\t\t\t\thas_error = true;\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\tstd::memcpy(track_samples_typed.qvvf + sample_index, &sample, sizeof(rtm::qvvf));\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\tACL_ASSERT(false, "Unsupported track type");\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (has_error)\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tswitch (track_type)\n\t\t\t\t\t\t{\n\t\t\t\t\t\tcase track_type8::float1f:\n\t\t\t\t\t\tcase track_type8::float2f:\n\t\t\t\t\t\tcase track_type8::float3f:\n\t\t\t\t\t\tcase track_type8::float4f:\n\t\t\t\t\t\tcase track_type8::vector4f:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdouble values[4] = { 0.0, 0.0, 0.0, 0.0 };\n\t\t\t\t\t\t\tif (m_parser.read(values, num_components))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tconst rtm::float4f value = { static_cast<float>(values[0]), static_cast<float>(values[1]), static_cast<float>(values[2]), static_cast<float>(values[3]) };\n\t\t\t\t\t\t\t\tstd::memcpy(track_samples_typed.float1f + (size_t(sample_index) * num_components), &value, sizeof(float) * num_components);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\thas_error = true;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase track_type8::qvvf:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\trtm::qvvf sample;\n\t\t\t\t\t\t\tif (!read_qvv_sample(sample))\n\t\t\t\t\t\t\t\thas_error = true;\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\tstd::memcpy(track_samples_typed.qvvf + sample_index, &sample, sizeof(rtm::qvvf));\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\tACL_ASSERT(false, "Unsupported track type");\n\t\t\t\t\t\t\tbreak;\n\n\t\t\tclip.set_additive_base(base_clip.release(), m_additive_format);\n\n\t\t\treturn true;\n\n\t\terror:\n\t\t\t\t\t{\n\t\t\t\t\tcase track_type8::float1f:\n\t\t\t\t\t\tdeallocate_type_array<float>(m_allocator, track_samples_typed.float1f, m_num_samples);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase track_type8::float2f:\n\t\t\t\t\t\tdeallocate_type_array<rtm::float2f>(m_allocator, track_samples_typed.float2f, m_num_samples);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase track_type8::float3f:\n\t\t\t\t\t\tdeallocate_type_array<rtm::float3f>(m_allocator, track_samples_typed.float3f, m_num_samples);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase track_type8::float4f:\n\t\t\t\t\t\tdeallocate_type_array<rtm::float4f>(m_allocator, track_samples_typed.float4f, m_num_samples);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase track_type8::vector4f:\n\t\t\t\t\t\tdeallocate_type_array<rtm::vector4f>(m_allocator, track_samples_typed.vector4f, m_num_samples);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase track_type8::qvvf:\n\t\t\t\t\t\tdeallocate_type_array<rtm::qvvf>(m_allocator, track_samples_typed.qvvf, m_num_samples);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tACL_ASSERT(false, "Unsupported track type");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tswitch (track_type)\n\t\t\t\t\t{\n\t\t\t\t\tcase track_type8::float1f:\n\t\t\t\t\t\ttrack_ = track_float1f::make_owner(scalar_desc, m_allocator, track_samples_typed.float1f, m_num_samples, m_sample_rate);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase track_type8::float2f:\n\t\t\t\t\t\ttrack_ = track_float2f::make_owner(scalar_desc, m_allocator, track_samples_typed.float2f, m_num_samples, m_sample_rate);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase track_type8::float3f:\n\t\t\t\t\t\ttrack_ = track_float3f::make_owner(scalar_desc, m_allocator, track_samples_typed.float3f, m_num_samples, m_sample_rate);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase track_type8::float4f:\n\t\t\t\t\t\ttrack_ = track_float4f::make_owner(scalar_desc, m_allocator, track_samples_typed.float4f, m_num_samples, m_sample_rate);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase track_type8::vector4f:\n\t\t\t\t\t\ttrack_ = track_vector4f::make_owner(scalar_desc, m_allocator, track_samples_typed.vector4f, m_num_samples, m_sample_rate);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase track_type8::qvvf:\n\t\t\t\t\t\ttrack_ = track_qvvf::make_owner(transform_desc, m_allocator, track_samples_typed.qvvf, m_num_samples, m_sample_rate);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tACL_ASSERT(false, "Unsupported track type");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\ttrack_.set_name(convert_string(name));\n\n\t\t\t\tnum_tracks++;\n\t\t\t}\n\n\t\t\treturn true;\n\n\t\terror:\n\t\t\tm_error = m_parser.get_error();\n\t\t\treturn false;\n\t\t}\n\n\t\tbool create_track_list(track_array& track_list, track_qvvf& bind_pose)\n\t\t{\n\t\t\tconst sjson::ParserState before_tracks = m_parser.save_state();\n\n\t\t\tuint32_t num_tracks;\n\t\t\tif (!process_track_list(nullptr, nullptr, num_tracks))\n\t\t\t\treturn false;\n\n\t\t\tm_parser.restore_state(before_tracks);\n\n\t\t\ttrack_list = track_array(m_allocator, num_tracks);\n\t\t\ttrack_list.set_name(convert_string(m_clip_name));\n\t\t\tbind_pose = track_qvvf::make_reserve(track_desc_transformf{}, m_allocator, num_tracks, 30.0F);\t// 1 sample per track\n\t\t\tbind_pose.set_name(string(m_allocator, "bind pose"));\n\n\t\t\tif (!process_track_list(track_list.begin(), &bind_pose, num_tracks))\n\t\t\t\treturn false;\n\n\t\t\tACL_ASSERT(num_tracks == track_list.get_num_tracks(), "Number of tracks read mismatch");\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool read_tracks(track_array_qvvf& track_list, track_array_qvvf& additive_base_track_list)\n\t\t{\n\t\t\tconst uint32_t num_transforms = track_list.get_num_tracks();\n\n\t\t\tif (m_parser.try_array_begins("base_tracks"))\n\t\t\t{\n\t\t\t\t// Copy our metadata from the actual clip\n\t\t\t\tadditive_base_track_list = track_array_qvvf(m_allocator, num_transforms);\n\t\t\t\tadditive_base_track_list.set_name(string(m_allocator, "additive base"));\n\t\t\t\tfor (uint32_t transform_index = 0; transform_index < num_transforms; ++transform_index)\n\t\t\t\t\tadditive_base_track_list[transform_index].get_description() = track_list[transform_index].get_description();\n\n\t\t\t\twhile (!m_parser.try_array_ends())\n\t\t\t\t{\n\t\t\t\t\tif (!m_parser.object_begins())\n\t\t\t\t\t\tgoto error;\n\n\t\t\t\t\tsjson::StringView name;\n\t\t\t\t\tif (!m_parser.read("name", name))\n\t\t\t\t\t\tgoto error;\n\n\t\t\t\t\tconst uint32_t bone_index = find_bone(name);\n\t\t\t\t\tif (bone_index == k_invalid_track_index)\n\t\t\t\t\t{\n\t\t\t\t\t\tset_error(clip_reader_error::NoBoneWithThatName);\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\n\t\t\t\t\ttrack_desc_transformf desc = additive_base_track_list[bone_index].get_description();\t// Copy our description\n\t\t\t\t\tdesc.output_index = bone_index;\n\n\t\t\t\t\ttrack_qvvf track = track_qvvf::make_reserve(desc, m_allocator, m_additive_base_num_samples, m_additive_base_sample_rate);\n\t\t\t\t\ttrack.set_name(convert_string(name));\n\n\t\t\t\t\tif (m_parser.try_array_begins("rotations"))\n\t\t\t\t\t{\n\t\t\t\t\t\tif (!read_track_rotations(track, m_additive_base_num_samples) || !m_parser.array_ends())\n\t\t\t\t\t\t\tgoto error;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tfor (uint32_t sample_index = 0; sample_index < m_additive_base_num_samples; ++sample_index)\n\t\t\t\t\t\t\ttrack[sample_index].rotation = rtm::quat_identity();\n\t\t\t\t\t}\n\n\t\t\t\t\tif (m_parser.try_array_begins("translations"))\n\t\t\t\t\t{\n\t\t\t\t\t\tif (!read_track_translations(track, m_additive_base_num_samples) || !m_parser.array_ends())\n\t\t\t\t\t\t\tgoto error;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tfor (uint32_t sample_index = 0; sample_index < m_additive_base_num_samples; ++sample_index)\n\t\t\t\t\t\t\ttrack[sample_index].translation = rtm::vector_zero();\n\t\t\t\t\t}\n\n\t\t\t\t\tif (m_parser.try_array_begins("scales"))\n\t\t\t\t\t{\n\t\t\t\t\t\tif (!read_track_scales(track, m_additive_base_num_samples) || !m_parser.array_ends())\n\t\t\t\t\t\t\tgoto error;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tfor (uint32_t sample_index = 0; sample_index < m_additive_base_num_samples; ++sample_index)\n\t\t\t\t\t\t\ttrack[sample_index].scale = rtm::vector_set(1.0F);\n\t\t\t\t\t}\n\n\t\t\t\t\tadditive_base_track_list[bone_index] = std::move(track);\n\n\t\t\t\t\tif (!m_parser.object_ends())\n\t\t\t\t\t\tgoto error;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!m_parser.array_begins("tracks"))\n\t\t\t\tgoto error;\n\n\t\t\twhile (!m_parser.try_array_ends())\n\t\t\t{\n\t\t\t\tif (!m_parser.object_begins())\n\t\t\t\t\tgoto error;\n\n\t\t\t\tsjson::StringView name;\n\t\t\t\tif (!m_parser.read("name", name))\n\t\t\t\t\tgoto error;\n\n\t\t\t\tconst uint32_t bone_index = find_bone(name);\n\t\t\t\tif (bone_index == k_invalid_track_index)\n\t\t\t\t{\n\t\t\t\t\tset_error(clip_reader_error::NoBoneWithThatName);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\ttrack_desc_transformf desc = track_list[bone_index].get_description();\t// Copy our description\n\t\t\t\tdesc.output_index = bone_index;\n\n\t\t\t\ttrack_qvvf track = track_qvvf::make_reserve(desc, m_allocator, m_num_samples, m_sample_rate);\n\t\t\t\ttrack.set_name(convert_string(name));\n\n\t\t\t\tif (m_parser.try_array_begins("rotations"))\n\t\t\t\t{\n\t\t\t\t\tif (!read_track_rotations(track, m_num_samples) || !m_parser.array_ends())\n\t\t\t\t\t\tgoto error;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tfor (uint32_t sample_index = 0; sample_index < m_num_samples; ++sample_index)\n\t\t\t\t\t\ttrack[sample_index].rotation = rtm::quat_identity();\n\t\t\t\t}\n\n\t\t\t\tif (m_parser.try_array_begins("translations"))\n\t\t\t\t{\n\t\t\t\t\tif (!read_track_translations(track, m_num_samples) || !m_parser.array_ends())\n\t\t\t\t\t\tgoto error;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tfor (uint32_t sample_index = 0; sample_index < m_num_samples; ++sample_index)\n\t\t\t\t\t\ttrack[sample_index].translation = rtm::vector_zero();\n\t\t\t\t}\n\n\t\t\t\tif (m_parser.try_array_begins("scales"))\n\t\t\t\t{\n\t\t\t\t\tif (!read_track_scales(track, m_num_samples) || !m_parser.array_ends())\n\t\t\t\t\t\tgoto error;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tfor (uint32_t sample_index = 0; sample_index < m_num_samples; ++sample_index)\n\t\t\t\t\t\ttrack[sample_index].scale = rtm::vector_set(1.0F);\n\t\t\t\t}\n\n\t\t\t\ttrack_list[bone_index] = std::move(track);\n\n\t\t\t\tif (!m_parser.object_ends())\n\t\t\t\t\tgoto error;\n\t\t\t}\n\n\t\t\treturn true;\n\n\t\terror:\n\t\t\tm_error = m_parser.get_error();\n\t\t\treturn false;\n\t\t}\n\n\t\tbool read_track_rotations(track_qvvf& track, uint32_t num_samples_expected)\n\t\t{\n\t\t\tfor (uint32_t i = 0; i < num_samples_expected; ++i)\n\t\t\t{\n\t\t\t\tif (!m_parser.array_begins())\n\t\t\t\t\treturn false;\n\n\t\t\t\trtm::quatd rotation;\n\n\t\t\t\tif (m_is_binary_exact)\n\t\t\t\t{\n\t\t\t\t\tsjson::StringView values[4];\n\t\t\t\t\tif (!m_parser.read(values, 4))\n\t\t\t\t\t\treturn false;\n\n\t\t\t\t\trotation = hex_to_quat(values);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tdouble values[4] = { 0.0, 0.0, 0.0, 0.0 };\n\t\t\t\t\tif (!m_parser.read(values, 4))\n\t\t\t\t\t\treturn false;\n\n\t\t\t\t\trotation = rtm::quat_load(values);\n\t\t\t\t}\n\n\t\t\t\tif (!m_parser.array_ends())\n\t\t\t\t\treturn false;\n\n\t\t\t\ttrack[i].rotation = rtm::quat_normalize_deterministic(rtm::quat_cast(rotation));\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool read_track_translations(track_qvvf& track, uint32_t num_samples_expected)\n\t\t{\n\t\t\tfor (uint32_t i = 0; i < num_samples_expected; ++i)\n\t\t\t{\n\t\t\t\tif (!m_parser.array_begins())\n\t\t\t\t\treturn false;\n\n\t\t\t\trtm::vector4d translation;\n\n\t\t\t\tif (m_is_binary_exact)\n\t\t\t\t{\n\t\t\t\t\tsjson::StringView values[3];\n\t\t\t\t\tif (!m_parser.read(values, 3))\n\t\t\t\t\t\treturn false;\n\n\t\t\t\t\ttranslation = hex_to_vector3(values);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tdouble values[3];\n\t\t\t\t\tif (!m_parser.read(values, 3))\n\t\t\t\t\t\treturn false;\n\n\t\t\t\t\ttranslation = rtm::vector_load3(values);\n\t\t\t\t}\n\n\t\t\t\tif (!m_parser.array_ends())\n\t\t\t\t\treturn false;\n\n\t\t\t\ttrack[i].translation = rtm::vector_cast(translation);\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool read_track_scales(track_qvvf& track, uint32_t num_samples_expected)\n\t\t{\n\t\t\tfor (uint32_t i = 0; i < num_samples_expected; ++i)\n\t\t\t{\n\t\t\t\tif (!m_parser.array_begins())\n\t\t\t\t\treturn false;\n\n\t\t\t\trtm::vector4d scale;\n\n\t\t\t\tif (m_is_binary_exact)\n\t\t\t\t{\n\t\t\t\t\tsjson::StringView values[3];\n\t\t\t\t\tif (!m_parser.read(values, 3))\n\t\t\t\t\t\treturn false;\n\n\t\t\t\t\tscale = hex_to_vector3(values);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tdouble values[3];\n\t\t\t\t\tif (!m_parser.read(values, 3))\n\t\t\t\t\t\treturn false;\n\n\t\t\t\t\tscale = rtm::vector_load3(values);\n\t\t\t\t}\n\n\t\t\t\tif (!m_parser.array_ends())\n\t\t\t\t\treturn false;\n\n\t\t\t\ttrack[i].scale = rtm::vector_cast(scale);\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool nothing_follows()\n\t\t{\n\t\t\tif (!m_parser.remainder_is_comments_and_whitespace())\n\t\t\t{\n\t\t\t\tm_error = m_parser.get_error();\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tvoid set_error(uint32_t reason)\n\t\t{\n\t\t\tm_parser.get_position(m_error.line, m_error.column);\n\t\t\tm_error.error = reason;\n\t\t}\n\t};\n\n\tACL_IMPL_VERSION_NAMESPACE_END\n}\n\nACL_IMPL_FILE_PRAGMA_POP\n\n#endif\t// #if defined(ACL_USE_SJSON)\n', 'message': 'feat(compression): read qvv track list from sjson files\n', 'author_name': 'Nicholas Frechette', 'lang': 'h'}