2
0
mirror of https://github.com/Laupetin/OpenAssetTools.git synced 2026-06-17 14:02:12 +00:00
Files
OpenAssetTools/src/ObjLoading/XAnim/FlatXAnimDataWriter.cpp
T
Jan 0d0f928267 feat: add binary xanim support for remaining games (#818)
* refactor: use generic loader for iw3 xanims

* refactor: use generic dumper for iw3 xanims

* chore: use templating on XAnimDumper

* chore: use templating on XAnimLoader

* feat: dump xanims for T5

* feat: load binary t5 xanims

* feat: load and dump t6 xanims

* feat: load and dump iw4,iw5 xanims

* chore: make sure iw3 and t5 notify about unsupported delta3D

* chore: also use CommonVec3U8 and CommonVec3U16 for non delta trans track
2026-06-06 14:47:51 +00:00

189 lines
7.2 KiB
C++

#include "FlatXAnimDataWriter.h"
#include <cassert>
#include <iterator>
using namespace xanim;
namespace
{
[[nodiscard]] int FloatBitsToInt(const float value)
{
union
{
int i;
float f;
};
f = value;
return i;
}
void WriteFloat3(FlatData& writeCursor, const std::array<float, 3>& value)
{
for (const float f : value)
writeCursor.m_data_int.emplace_back(FloatBitsToInt(f));
}
void WritePackedIndices(FlatData& writeCursor, const std::vector<uint16_t>& indices, const bool useByteIndices)
{
const auto indexCount = indices.size();
writeCursor.m_data_short.emplace_back(static_cast<int16_t>(indexCount - 1)); // storedSize
if (useByteIndices)
{
for (const auto index : indices)
{
assert(index <= std::numeric_limits<uint8_t>::max());
writeCursor.m_data_byte.emplace_back(static_cast<uint8_t>(index));
}
}
else if (indexCount >= 65)
{
// The linker moves 16-bit frame indices into the top-level indices pool only when
// the in-memory stored size is at least 64, i.e. frameCount >= 65.
std::ranges::copy(indices, std::back_inserter(writeCursor.m_indices));
// The game inserts checkpoint values in dataShort
// Those checkpoint values are copied from positions in the full index list: the first entry, then every 256th entry, and always the final entry.
// The final entry is included even when it does not land exactly on a 256-entry boundary.
const auto longTableSize = ((indexCount - 2) / 256u) + 1;
for (auto i = 0u; i < longTableSize; i++)
writeCursor.m_data_short.emplace_back(indices[256 * i]);
writeCursor.m_data_short.emplace_back(indices[indices.size() - 1]);
}
else
{
std::ranges::copy(indices, std::back_inserter(writeCursor.m_data_short));
}
}
void ProcessQuatTrack(FlatData& writeCursor, const QuatTrack& quatTrack, const bool useByteIndices)
{
switch (quatTrack.m_type)
{
case QuatType::NO_QUAT:
break;
case QuatType::HALF_QUAT:
WritePackedIndices(writeCursor, quatTrack.m_indices, useByteIndices);
assert(quatTrack.m_frames2.size() == quatTrack.m_indices.size());
writeCursor.m_random_data_short.reserve(writeCursor.m_random_data_short.size() + quatTrack.m_frames2.size() * 2);
for (const auto& quat2 : quatTrack.m_frames2)
{
writeCursor.m_random_data_short.emplace_back(quat2.value[0]);
writeCursor.m_random_data_short.emplace_back(quat2.value[1]);
}
break;
case QuatType::FULL_QUAT:
WritePackedIndices(writeCursor, quatTrack.m_indices, useByteIndices);
assert(quatTrack.m_frames.size() == quatTrack.m_indices.size());
writeCursor.m_random_data_short.reserve(writeCursor.m_random_data_short.size() + quatTrack.m_frames.size() * 4);
for (const auto& quat : quatTrack.m_frames)
{
writeCursor.m_random_data_short.emplace_back(quat.value[0]);
writeCursor.m_random_data_short.emplace_back(quat.value[1]);
writeCursor.m_random_data_short.emplace_back(quat.value[2]);
writeCursor.m_random_data_short.emplace_back(quat.value[3]);
}
break;
case QuatType::HALF_QUAT_NO_SIZE:
{
assert(quatTrack.m_frames2.size() == 1);
writeCursor.m_data_short.reserve(writeCursor.m_data_short.size() + 2);
const auto& quat2 = quatTrack.m_frames2[0];
writeCursor.m_data_short.emplace_back(quat2.value[0]);
writeCursor.m_data_short.emplace_back(quat2.value[1]);
break;
}
case QuatType::FULL_QUAT_NO_SIZE:
{
assert(quatTrack.m_frames.size() == 1);
writeCursor.m_data_short.reserve(writeCursor.m_data_short.size() + 4);
const auto& quat = quatTrack.m_frames[0];
writeCursor.m_data_short.emplace_back(quat.value[0]);
writeCursor.m_data_short.emplace_back(quat.value[1]);
writeCursor.m_data_short.emplace_back(quat.value[2]);
writeCursor.m_data_short.emplace_back(quat.value[3]);
break;
}
}
}
void ProcessTransTrack(FlatData& writeCursor, const TransTrack& transTrack, const size_t boneIndex, const bool useByteIndices)
{
assert(boneIndex <= std::numeric_limits<uint8_t>::max());
writeCursor.m_data_byte.emplace_back(static_cast<uint8_t>(boneIndex));
switch (transTrack.m_type)
{
case TransType::SMALL_TRANS:
WritePackedIndices(writeCursor, transTrack.m_indices, useByteIndices);
WriteFloat3(writeCursor, transTrack.m_mins);
WriteFloat3(writeCursor, transTrack.m_size);
assert(transTrack.m_frames_u8.size() == transTrack.m_indices.size());
writeCursor.m_random_data_byte.reserve(writeCursor.m_random_data_byte.size() + transTrack.m_frames_u8.size() * 3);
for (const auto& vec : transTrack.m_frames_u8)
{
writeCursor.m_random_data_byte.emplace_back(vec.value[0]);
writeCursor.m_random_data_byte.emplace_back(vec.value[1]);
writeCursor.m_random_data_byte.emplace_back(vec.value[2]);
}
break;
case TransType::FULL_TRANS:
WritePackedIndices(writeCursor, transTrack.m_indices, useByteIndices);
WriteFloat3(writeCursor, transTrack.m_mins);
WriteFloat3(writeCursor, transTrack.m_size);
assert(transTrack.m_frames_u16.size() == transTrack.m_indices.size());
writeCursor.m_random_data_short.reserve(writeCursor.m_random_data_short.size() + transTrack.m_frames_u16.size() * 3);
for (const auto& vec : transTrack.m_frames_u16)
{
writeCursor.m_random_data_short.emplace_back(vec.value[0]);
writeCursor.m_random_data_short.emplace_back(vec.value[1]);
writeCursor.m_random_data_short.emplace_back(vec.value[2]);
}
break;
case TransType::TRANS_NO_SIZE:
WriteFloat3(writeCursor, transTrack.m_constant);
break;
case TransType::NO_TRANS:
break;
}
}
} // namespace
namespace xanim
{
FlatData CreateFlatDataFromCommonXAnim(const CommonXAnimParts& parts)
{
FlatData writeCursor;
const auto useByteIndices = parts.m_num_frames < 256;
for (const auto& boneTrack : parts.m_bone_tracks)
ProcessQuatTrack(writeCursor, boneTrack.m_quat, useByteIndices);
const auto transBoneOrder = parts.GetBoneTrackOrderForTrans();
const auto boneCount = transBoneOrder.size();
for (size_t i = 0; i < boneCount; ++i)
{
const auto boneIndex = transBoneOrder[i];
ProcessTransTrack(writeCursor, parts.m_bone_tracks[boneIndex].m_trans, boneIndex, useByteIndices);
}
return writeCursor;
}
} // namespace xanim