mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2026-06-17 14:02:12 +00:00
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
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
#include "Game/IW3/Image/ImageLoaderExternalIW3.h"
|
||||
#include "Game/IW3/Techset/PixelShaderLoaderIW3.h"
|
||||
#include "Game/IW3/Techset/VertexShaderLoaderIW3.h"
|
||||
#include "Game/IW3/XAnim/XAnimLoaderIW3.h"
|
||||
#include "Game/IW3/XModel/LoaderXModelIW3.h"
|
||||
#include "LightDef/LightDefLoaderIW3.h"
|
||||
#include "Localize/AssetLoaderLocalizeIW3.h"
|
||||
@@ -18,7 +19,6 @@
|
||||
#include "RawFile/AssetLoaderRawFileIW3.h"
|
||||
#include "Sound/LoaderSoundCurveIW3.h"
|
||||
#include "StringTable/AssetLoaderStringTableIW3.h"
|
||||
#include "XAnim/XAnimLoaderIW3.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
|
||||
@@ -1,781 +0,0 @@
|
||||
#include "XAnimLoaderIW3.h"
|
||||
|
||||
#include "Utils/Alignment.h"
|
||||
#include "Utils/Logging/Log.h"
|
||||
#include "Utils/StreamUtils.h"
|
||||
#include "XAnim/XAnimCommon.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <format>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using namespace IW3;
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr uint16_t RAW_VERSION = 17;
|
||||
|
||||
constexpr uint8_t FLAG_LOOPED = 1u;
|
||||
constexpr uint8_t FLAG_DELTA = 2u;
|
||||
|
||||
// The linker decodes raw trans size[] with these exact float literals.
|
||||
// They correspond to 1.0f / 255.0f and 1.0f / 65535.0f, but we keep the
|
||||
// decompiled values to preserve binary-stable round trips.
|
||||
constexpr auto HALF_TRANS_SIZE_SCALE = 0.003921568859368563f;
|
||||
constexpr auto FULL_TRANS_SIZE_SCALE = 0.00001525902189314365f;
|
||||
enum class QuatType : uint8_t
|
||||
{
|
||||
NO_QUAT = 0,
|
||||
HALF_QUAT = 1,
|
||||
FULL_QUAT = 2,
|
||||
HALF_QUAT_NO_SIZE = 3,
|
||||
FULL_QUAT_NO_SIZE = 4,
|
||||
};
|
||||
|
||||
enum class TransType : uint8_t
|
||||
{
|
||||
SMALL_TRANS = 5,
|
||||
FULL_TRANS = 6,
|
||||
TRANS_NO_SIZE = 7,
|
||||
NO_TRANS = 8,
|
||||
};
|
||||
|
||||
struct QuatTrack
|
||||
{
|
||||
QuatType type = QuatType::NO_QUAT;
|
||||
std::vector<uint16_t> indices;
|
||||
std::vector<int16_t> values;
|
||||
};
|
||||
|
||||
struct TransTrack
|
||||
{
|
||||
TransType type = TransType::NO_TRANS;
|
||||
std::vector<uint16_t> indices;
|
||||
std::array<float, 3> mins{};
|
||||
std::array<float, 3> size{};
|
||||
std::vector<uint8_t> byteFrames;
|
||||
std::vector<uint16_t> shortFrames;
|
||||
std::array<float, 3> constant{};
|
||||
};
|
||||
|
||||
struct BoneTrack
|
||||
{
|
||||
std::string name;
|
||||
QuatTrack quat;
|
||||
TransTrack trans;
|
||||
};
|
||||
|
||||
struct FlatDataWriteCursor
|
||||
{
|
||||
std::vector<uint8_t> dataByte;
|
||||
std::vector<int16_t> dataShort;
|
||||
std::vector<int32_t> dataInt;
|
||||
std::vector<uint8_t> randomDataByte;
|
||||
std::vector<int16_t> randomDataShort;
|
||||
std::vector<uint16_t> indices;
|
||||
};
|
||||
|
||||
void PrintError(const XAnimParts& parts, const std::string& message)
|
||||
{
|
||||
con::error("Cannot load xanim \"{}\": {}", parts.name, message);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool UseByteIndices(const XAnimParts& parts)
|
||||
{
|
||||
return parts.numframes < 256;
|
||||
}
|
||||
|
||||
[[nodiscard]] int FloatBitsToInt(const float value)
|
||||
{
|
||||
union
|
||||
{
|
||||
int i;
|
||||
float f;
|
||||
};
|
||||
|
||||
f = value;
|
||||
return i;
|
||||
}
|
||||
|
||||
void WriteFloat3(FlatDataWriteCursor& writeCursor, const std::array<float, 3>& value)
|
||||
{
|
||||
for (const float f : value)
|
||||
writeCursor.dataInt.emplace_back(FloatBitsToInt(f));
|
||||
}
|
||||
|
||||
[[nodiscard]] float DecodeRawTransSize(const float value, const bool smallTrans)
|
||||
{
|
||||
const auto scale = smallTrans ? HALF_TRANS_SIZE_SCALE : FULL_TRANS_SIZE_SCALE;
|
||||
return value * scale;
|
||||
}
|
||||
|
||||
void ConsumeQuat(std::istream& stream, XQuat& quat)
|
||||
{
|
||||
quat.value[0] = stream::ReadValue<int16_t>(stream);
|
||||
quat.value[1] = stream::ReadValue<int16_t>(stream);
|
||||
quat.value[2] = stream::ReadValue<int16_t>(stream);
|
||||
|
||||
int32_t temp = 0x3FFF0001 - (quat.value[0] * quat.value[0] + quat.value[1] * quat.value[1] + quat.value[2] * quat.value[2]);
|
||||
if (temp <= 0)
|
||||
temp = 0;
|
||||
else
|
||||
temp = static_cast<int32_t>(std::floor(std::sqrt(static_cast<float>(temp)) + 0.5f));
|
||||
|
||||
assert(temp >= std::numeric_limits<int16_t>::min() && temp <= std::numeric_limits<int16_t>::max());
|
||||
quat.value[3] = static_cast<int16_t>(temp);
|
||||
}
|
||||
|
||||
void ConsumeQuat2(std::istream& stream, XQuat2& quat2)
|
||||
{
|
||||
quat2.value[0] = stream::ReadValue<int16_t>(stream);
|
||||
|
||||
int32_t temp = 0x3FFF0001 - quat2.value[0] * quat2.value[0];
|
||||
if (temp <= 0)
|
||||
temp = 0;
|
||||
else
|
||||
temp = static_cast<int32_t>(floor(std::sqrt(static_cast<float>(temp)) + 0.5f));
|
||||
|
||||
assert(temp >= std::numeric_limits<int16_t>::min() && temp <= std::numeric_limits<int16_t>::max());
|
||||
quat2.value[1] = static_cast<int16_t>(temp);
|
||||
}
|
||||
|
||||
void FlipQuat(XQuat& quat)
|
||||
{
|
||||
quat.value[0] = static_cast<int16_t>(-quat.value[0]);
|
||||
quat.value[1] = static_cast<int16_t>(-quat.value[1]);
|
||||
quat.value[2] = static_cast<int16_t>(-quat.value[2]);
|
||||
quat.value[3] = static_cast<int16_t>(-quat.value[3]);
|
||||
}
|
||||
|
||||
void FlipQuat2(XQuat2& quat)
|
||||
{
|
||||
quat.value[0] = static_cast<int16_t>(-quat.value[0]);
|
||||
quat.value[1] = static_cast<int16_t>(-quat.value[1]);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void LoadIndicesIfNeeded(std::istream& stream, T& indices, const uint16_t numIndices, const bool useByteIndices, const uint16_t numLoopFrames)
|
||||
{
|
||||
if (useByteIndices)
|
||||
{
|
||||
// The raw format omits indices when a track covers every loop frame in order.
|
||||
if (numIndices >= numLoopFrames)
|
||||
std::iota(&indices._1[0], &indices._1[numIndices], 0);
|
||||
else
|
||||
stream::Read(stream, indices._1, numIndices * sizeof(uint8_t));
|
||||
}
|
||||
else
|
||||
{
|
||||
// The raw format omits indices when a track covers every loop frame in order.
|
||||
if (numIndices >= numLoopFrames)
|
||||
std::iota(&indices._2[0], &indices._2[numIndices], 0);
|
||||
else
|
||||
stream::Read(stream, indices._2, numIndices * sizeof(uint16_t));
|
||||
}
|
||||
}
|
||||
|
||||
void LoadIndicesIfNeeded(
|
||||
std::istream& stream, std::vector<uint16_t>& indices, const uint16_t numIndices, const bool useByteIndices, const uint16_t numLoopFrames)
|
||||
{
|
||||
// The raw format omits indices when a track covers every loop frame in order.
|
||||
if (numIndices >= numLoopFrames)
|
||||
{
|
||||
indices.resize(numIndices);
|
||||
std::ranges::iota(indices, 0);
|
||||
}
|
||||
else if (useByteIndices)
|
||||
{
|
||||
indices.reserve(numIndices);
|
||||
for (auto i = 0u; i < numIndices; i++)
|
||||
indices.emplace_back(stream::ReadValue<uint8_t>(stream));
|
||||
}
|
||||
else
|
||||
{
|
||||
indices.resize(numIndices);
|
||||
stream::Read(stream, indices.data(), numIndices * sizeof(uint16_t));
|
||||
}
|
||||
}
|
||||
|
||||
void ReadTransTrack(std::istream& stream, TransTrack& transTrack, const uint16_t numLoopFrames, const bool useByteIndices)
|
||||
{
|
||||
const auto numTransIndices = stream::ReadValue<uint16_t>(stream);
|
||||
if (numTransIndices == 0)
|
||||
{
|
||||
transTrack.type = TransType::NO_TRANS;
|
||||
return;
|
||||
}
|
||||
|
||||
if (numTransIndices == 1)
|
||||
{
|
||||
transTrack.type = TransType::TRANS_NO_SIZE;
|
||||
for (auto& value : transTrack.constant)
|
||||
value = stream::ReadValue<float>(stream);
|
||||
return;
|
||||
}
|
||||
|
||||
LoadIndicesIfNeeded(stream, transTrack.indices, numTransIndices, useByteIndices, numLoopFrames);
|
||||
|
||||
const auto smallTrans = stream::ReadValue<bool>(stream);
|
||||
transTrack.type = smallTrans ? TransType::SMALL_TRANS : TransType::FULL_TRANS;
|
||||
|
||||
for (auto& value : transTrack.mins)
|
||||
value = stream::ReadValue<float>(stream);
|
||||
for (auto& value : transTrack.size)
|
||||
value = DecodeRawTransSize(stream::ReadValue<float>(stream), smallTrans);
|
||||
|
||||
if (smallTrans)
|
||||
{
|
||||
transTrack.byteFrames.resize(numTransIndices * 3);
|
||||
stream::Read(stream, transTrack.byteFrames.data(), numTransIndices * sizeof(uint8_t) * 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
transTrack.shortFrames.resize(numTransIndices * 3);
|
||||
stream::Read(stream, transTrack.shortFrames.data(), numTransIndices * sizeof(uint16_t) * 3);
|
||||
}
|
||||
}
|
||||
|
||||
void ReadQuatTrack(
|
||||
std::istream& stream, QuatTrack& quatTrack, const uint16_t numLoopFrames, const bool useByteIndices, const bool flipQuat, const bool halfQuat)
|
||||
{
|
||||
const auto numQuatIndices = stream::ReadValue<uint16_t>(stream);
|
||||
if (numQuatIndices == 0)
|
||||
{
|
||||
assert(halfQuat);
|
||||
quatTrack.type = QuatType::NO_QUAT;
|
||||
return;
|
||||
}
|
||||
|
||||
if (numQuatIndices == 1)
|
||||
{
|
||||
quatTrack.type = halfQuat ? QuatType::HALF_QUAT_NO_SIZE : QuatType::FULL_QUAT_NO_SIZE;
|
||||
if (halfQuat)
|
||||
{
|
||||
XQuat2 quat2;
|
||||
ConsumeQuat2(stream, quat2);
|
||||
if (flipQuat)
|
||||
FlipQuat2(quat2);
|
||||
|
||||
quatTrack.values.reserve(2);
|
||||
quatTrack.values.emplace_back(quat2.value[0]);
|
||||
quatTrack.values.emplace_back(quat2.value[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
XQuat quat;
|
||||
ConsumeQuat(stream, quat);
|
||||
if (flipQuat)
|
||||
FlipQuat(quat);
|
||||
|
||||
quatTrack.values.reserve(4);
|
||||
quatTrack.values.emplace_back(quat.value[0]);
|
||||
quatTrack.values.emplace_back(quat.value[1]);
|
||||
quatTrack.values.emplace_back(quat.value[2]);
|
||||
quatTrack.values.emplace_back(quat.value[3]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
LoadIndicesIfNeeded(stream, quatTrack.indices, numQuatIndices, useByteIndices, numLoopFrames);
|
||||
|
||||
if (halfQuat)
|
||||
{
|
||||
quatTrack.type = QuatType::HALF_QUAT;
|
||||
quatTrack.values.resize(numQuatIndices * 2);
|
||||
auto* quats2 = reinterpret_cast<XQuat2*>(quatTrack.values.data());
|
||||
for (auto quatIndexNum = 0u; quatIndexNum < numQuatIndices; quatIndexNum++)
|
||||
{
|
||||
auto& curFrame = quats2[quatIndexNum];
|
||||
ConsumeQuat2(stream, curFrame);
|
||||
|
||||
if (quatIndexNum > 0)
|
||||
{
|
||||
const auto& prevFrame = quats2[quatIndexNum - 1];
|
||||
if (prevFrame.value[0] * curFrame.value[0] + prevFrame.value[1] * curFrame.value[1] < 0)
|
||||
FlipQuat2(curFrame);
|
||||
}
|
||||
else if (flipQuat)
|
||||
FlipQuat2(curFrame);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
quatTrack.type = QuatType::FULL_QUAT;
|
||||
quatTrack.values.resize(numQuatIndices * 4);
|
||||
auto* quats = reinterpret_cast<XQuat*>(quatTrack.values.data());
|
||||
for (auto quatIndexNum = 0u; quatIndexNum < numQuatIndices; quatIndexNum++)
|
||||
{
|
||||
auto& curFrame = quats[quatIndexNum];
|
||||
ConsumeQuat(stream, curFrame);
|
||||
|
||||
if (quatIndexNum > 0)
|
||||
{
|
||||
const auto& prevFrame = quats[quatIndexNum - 1];
|
||||
const auto dot = prevFrame.value[0] * curFrame.value[0] + prevFrame.value[1] * curFrame.value[1] + prevFrame.value[2] * curFrame.value[2]
|
||||
+ prevFrame.value[3] * curFrame.value[3];
|
||||
if (dot < 0)
|
||||
FlipQuat(curFrame);
|
||||
}
|
||||
else if (flipQuat)
|
||||
FlipQuat(curFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyWriteCursorToParts(XAnimParts& parts, const FlatDataWriteCursor& writeCursor, MemoryManager& memory)
|
||||
{
|
||||
if (!writeCursor.dataByte.empty())
|
||||
{
|
||||
parts.dataByteCount = static_cast<uint16_t>(writeCursor.dataByte.size());
|
||||
parts.dataByte = memory.Alloc<uint8_t>(parts.dataByteCount);
|
||||
std::memcpy(parts.dataByte, writeCursor.dataByte.data(), parts.dataByteCount * sizeof(uint8_t));
|
||||
}
|
||||
|
||||
if (!writeCursor.dataShort.empty())
|
||||
{
|
||||
parts.dataShortCount = static_cast<uint16_t>(writeCursor.dataShort.size());
|
||||
parts.dataShort = memory.Alloc<int16_t>(parts.dataShortCount);
|
||||
std::memcpy(parts.dataShort, writeCursor.dataShort.data(), parts.dataShortCount * sizeof(int16_t));
|
||||
}
|
||||
|
||||
if (!writeCursor.dataInt.empty())
|
||||
{
|
||||
parts.dataIntCount = static_cast<uint16_t>(writeCursor.dataInt.size());
|
||||
parts.dataInt = memory.Alloc<int32_t>(parts.dataIntCount);
|
||||
std::memcpy(parts.dataInt, writeCursor.dataInt.data(), parts.dataIntCount * sizeof(int32_t));
|
||||
}
|
||||
|
||||
if (!writeCursor.randomDataByte.empty())
|
||||
{
|
||||
parts.randomDataByteCount = static_cast<uint16_t>(writeCursor.randomDataByte.size());
|
||||
parts.randomDataByte = memory.Alloc<uint8_t>(parts.randomDataByteCount);
|
||||
std::memcpy(parts.randomDataByte, writeCursor.randomDataByte.data(), parts.randomDataByteCount * sizeof(uint8_t));
|
||||
}
|
||||
|
||||
if (!writeCursor.randomDataShort.empty())
|
||||
{
|
||||
parts.randomDataShortCount = static_cast<unsigned int>(writeCursor.randomDataShort.size());
|
||||
parts.randomDataShort = memory.Alloc<int16_t>(parts.randomDataShortCount);
|
||||
std::memcpy(parts.randomDataShort, writeCursor.randomDataShort.data(), parts.randomDataShortCount * sizeof(int16_t));
|
||||
}
|
||||
|
||||
if (!writeCursor.indices.empty())
|
||||
{
|
||||
parts.indexCount = static_cast<unsigned int>(writeCursor.indices.size());
|
||||
parts.indices._2 = memory.Alloc<uint16_t>(parts.indexCount);
|
||||
std::memcpy(parts.indices._2, writeCursor.indices.data(), parts.indexCount * sizeof(uint16_t));
|
||||
}
|
||||
}
|
||||
|
||||
void WritePackedIndices(FlatDataWriteCursor& writeCursor, const std::vector<uint16_t>& indices, const bool useByteIndices)
|
||||
{
|
||||
const auto indexCount = indices.size();
|
||||
writeCursor.dataShort.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.dataByte.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.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.dataShort.emplace_back(indices[256 * i]);
|
||||
writeCursor.dataShort.emplace_back(indices[indices.size() - 1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::ranges::copy(indices, std::back_inserter(writeCursor.dataShort));
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessQuatTrack(FlatDataWriteCursor& writeCursor, const QuatTrack& quatTrack, XAnimParts& parts, const bool useByteIndices)
|
||||
{
|
||||
switch (quatTrack.type)
|
||||
{
|
||||
case QuatType::NO_QUAT:
|
||||
parts.boneCount[PART_TYPE_NO_QUAT]++;
|
||||
break;
|
||||
|
||||
case QuatType::HALF_QUAT:
|
||||
parts.boneCount[PART_TYPE_HALF_QUAT]++;
|
||||
WritePackedIndices(writeCursor, quatTrack.indices, useByteIndices);
|
||||
assert(quatTrack.values.size() == quatTrack.indices.size() * 2);
|
||||
std::ranges::copy(quatTrack.values, std::back_inserter(writeCursor.randomDataShort));
|
||||
break;
|
||||
|
||||
case QuatType::FULL_QUAT:
|
||||
parts.boneCount[PART_TYPE_FULL_QUAT]++;
|
||||
WritePackedIndices(writeCursor, quatTrack.indices, useByteIndices);
|
||||
assert(quatTrack.values.size() == quatTrack.indices.size() * 4);
|
||||
std::ranges::copy(quatTrack.values, std::back_inserter(writeCursor.randomDataShort));
|
||||
break;
|
||||
|
||||
case QuatType::HALF_QUAT_NO_SIZE:
|
||||
parts.boneCount[PART_TYPE_HALF_QUAT_NO_SIZE]++;
|
||||
assert(quatTrack.values.size() == 2);
|
||||
std::ranges::copy(quatTrack.values, std::back_inserter(writeCursor.dataShort));
|
||||
break;
|
||||
|
||||
case QuatType::FULL_QUAT_NO_SIZE:
|
||||
parts.boneCount[PART_TYPE_FULL_QUAT_NO_SIZE]++;
|
||||
assert(quatTrack.values.size() == 4);
|
||||
std::ranges::copy(quatTrack.values, std::back_inserter(writeCursor.dataShort));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessTransTrack(FlatDataWriteCursor& writeCursor, const TransTrack& transTrack, const size_t boneIndex, XAnimParts& parts, const bool useByteIndices)
|
||||
{
|
||||
assert(boneIndex <= std::numeric_limits<uint8_t>::max());
|
||||
writeCursor.dataByte.emplace_back(static_cast<uint8_t>(boneIndex));
|
||||
|
||||
switch (transTrack.type)
|
||||
{
|
||||
case TransType::SMALL_TRANS:
|
||||
parts.boneCount[PART_TYPE_SMALL_TRANS]++;
|
||||
WritePackedIndices(writeCursor, transTrack.indices, useByteIndices);
|
||||
WriteFloat3(writeCursor, transTrack.mins);
|
||||
WriteFloat3(writeCursor, transTrack.size);
|
||||
assert(transTrack.byteFrames.size() == transTrack.indices.size() * 3);
|
||||
std::ranges::copy(transTrack.byteFrames, std::back_inserter(writeCursor.randomDataByte));
|
||||
break;
|
||||
|
||||
case TransType::FULL_TRANS:
|
||||
parts.boneCount[PART_TYPE_TRANS]++;
|
||||
WritePackedIndices(writeCursor, transTrack.indices, useByteIndices);
|
||||
WriteFloat3(writeCursor, transTrack.mins);
|
||||
WriteFloat3(writeCursor, transTrack.size);
|
||||
assert(transTrack.shortFrames.size() == transTrack.indices.size() * 3);
|
||||
std::ranges::copy(transTrack.shortFrames, std::back_inserter(writeCursor.randomDataShort));
|
||||
break;
|
||||
|
||||
case TransType::TRANS_NO_SIZE:
|
||||
parts.boneCount[PART_TYPE_TRANS_NO_SIZE]++;
|
||||
WriteFloat3(writeCursor, transTrack.constant);
|
||||
break;
|
||||
|
||||
case TransType::NO_TRANS:
|
||||
parts.boneCount[PART_TYPE_NO_TRANS]++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
class XAnimLoader final : public AssetCreator<AssetXAnim>
|
||||
{
|
||||
public:
|
||||
XAnimLoader(MemoryManager& memory, ISearchPath& searchPath, ZoneScriptStrings& scriptStrings)
|
||||
: m_memory(memory),
|
||||
m_search_path(searchPath),
|
||||
m_script_strings(scriptStrings)
|
||||
{
|
||||
}
|
||||
|
||||
AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override
|
||||
{
|
||||
const auto file = m_search_path.Open(xanim::GetCompiledFileNameForAssetName(assetName));
|
||||
if (!file.IsOpen())
|
||||
return AssetCreationResult::NoAction();
|
||||
|
||||
auto* parts = m_memory.Alloc<XAnimParts>();
|
||||
parts->name = m_memory.Dup(assetName.c_str());
|
||||
|
||||
AssetRegistration<AssetXAnim> registration(assetName, parts);
|
||||
if (!LoadFromFile(*file.m_stream, *parts, registration))
|
||||
{
|
||||
con::error("Failed to load xanim \"{}\"", assetName);
|
||||
return AssetCreationResult::Failure();
|
||||
}
|
||||
|
||||
return AssetCreationResult::Success(context.AddAsset(std::move(registration)));
|
||||
}
|
||||
|
||||
private:
|
||||
void ReadNoteTracks(std::istream& stream, XAnimParts& parts, AssetRegistration<AssetXAnim>& registration) const
|
||||
{
|
||||
const auto numDiskNoteTracks = stream::ReadValue<uint8_t>(stream);
|
||||
assert(numDiskNoteTracks + 1 <= std::numeric_limits<uint8_t>::max());
|
||||
|
||||
uint8_t numNoteTracks;
|
||||
if (numDiskNoteTracks == std::numeric_limits<uint8_t>::max())
|
||||
{
|
||||
PrintError(parts, "Could not add \"end\" notify as maximum notify entries were reached");
|
||||
numNoteTracks = numDiskNoteTracks;
|
||||
}
|
||||
else
|
||||
numNoteTracks = numDiskNoteTracks + 1;
|
||||
|
||||
parts.notifyCount = numNoteTracks;
|
||||
parts.notify = m_memory.Alloc<XAnimNotifyInfo>(numNoteTracks);
|
||||
|
||||
for (auto notifyIndex = 0u; notifyIndex < numDiskNoteTracks; notifyIndex++)
|
||||
{
|
||||
auto& notify = parts.notify[notifyIndex];
|
||||
|
||||
const auto notifyName = stream::ReadCString(stream);
|
||||
notify.name = m_script_strings.AddOrGetScriptString(notifyName);
|
||||
registration.AddScriptString(notify.name);
|
||||
|
||||
const auto frame = stream::ReadValue<uint16_t>(stream);
|
||||
notify.time = parts.numframes > 0 ? static_cast<float>(frame) / static_cast<float>(parts.numframes) : 0;
|
||||
assert(notify.time >= 0.0f && notify.time <= 1.0f);
|
||||
}
|
||||
|
||||
if (numNoteTracks > numDiskNoteTracks)
|
||||
{
|
||||
const auto endScriptString = m_script_strings.AddOrGetScriptString("end");
|
||||
registration.AddScriptString(endScriptString);
|
||||
parts.notify[numDiskNoteTracks].name = endScriptString;
|
||||
parts.notify[numDiskNoteTracks].time = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void LoadDeltaQuats(std::istream& stream, XAnimDeltaPart& delta, const bool useByteIndices, const uint16_t numLoopFrames) const
|
||||
{
|
||||
const auto numQuatIndices = stream::ReadValue<uint16_t>(stream);
|
||||
if (numQuatIndices == 0)
|
||||
return;
|
||||
|
||||
if (numQuatIndices == 1)
|
||||
{
|
||||
delta.quat = static_cast<XAnimDeltaPartQuat*>(m_memory.AllocRaw(offsetof(XAnimDeltaPartQuat, u) + sizeof(XAnimDeltaPartQuatData::frame0)));
|
||||
delta.quat->size = 0;
|
||||
ConsumeQuat2(stream, delta.quat->u.frame0);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto indicesArraySize =
|
||||
useByteIndices ? numQuatIndices * sizeof(XAnimDynamicIndicesQuat::_1) : numQuatIndices * sizeof(XAnimDynamicIndicesQuat::_2);
|
||||
|
||||
delta.quat = static_cast<XAnimDeltaPartQuat*>(
|
||||
m_memory.AllocRaw(offsetof(XAnimDeltaPartQuat, u) + offsetof(XAnimDeltaPartQuatDataFrames, indices) + indicesArraySize));
|
||||
|
||||
auto& quatIndices = delta.quat->u.frames.indices;
|
||||
LoadIndicesIfNeeded(stream, quatIndices, numQuatIndices, useByteIndices, numLoopFrames);
|
||||
|
||||
delta.quat->size = static_cast<uint16_t>(numQuatIndices - 1);
|
||||
delta.quat->u.frames.frames = m_memory.Alloc<XQuat2>(numQuatIndices);
|
||||
|
||||
for (auto quatIndexNum = 0u; quatIndexNum < numQuatIndices; ++quatIndexNum)
|
||||
{
|
||||
auto& curFrame = delta.quat->u.frames.frames[quatIndexNum];
|
||||
ConsumeQuat2(stream, curFrame);
|
||||
|
||||
if (quatIndexNum > 0)
|
||||
{
|
||||
const auto& prevFrame = delta.quat->u.frames.frames[quatIndexNum - 1];
|
||||
if (prevFrame.value[0] * curFrame.value[0] + prevFrame.value[1] * curFrame.value[1] < 0)
|
||||
FlipQuat2(curFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoadDeltaTrans(std::istream& stream, XAnimDeltaPart& delta, const bool useByteIndices, const uint16_t numLoopFrames) const
|
||||
{
|
||||
const auto numTransIndices = stream::ReadValue<uint16_t>(stream);
|
||||
if (numTransIndices == 0)
|
||||
return;
|
||||
|
||||
if (numTransIndices == 1)
|
||||
{
|
||||
delta.trans = static_cast<XAnimPartTrans*>(m_memory.AllocRaw(offsetof(XAnimPartTrans, u) + sizeof(XAnimPartTransData::frame0)));
|
||||
delta.trans->size = 0;
|
||||
delta.trans->u.frame0.x = stream::ReadValue<float>(stream);
|
||||
delta.trans->u.frame0.y = stream::ReadValue<float>(stream);
|
||||
delta.trans->u.frame0.z = stream::ReadValue<float>(stream);
|
||||
return;
|
||||
}
|
||||
const auto indicesArraySize =
|
||||
useByteIndices ? numTransIndices * sizeof(XAnimDynamicIndicesTrans::_1) : numTransIndices * sizeof(XAnimDynamicIndicesTrans::_2);
|
||||
|
||||
delta.trans =
|
||||
static_cast<XAnimPartTrans*>(m_memory.AllocRaw(offsetof(XAnimPartTrans, u) + offsetof(XAnimPartTransFrames, indices) + indicesArraySize));
|
||||
|
||||
auto& frames = delta.trans->u.frames;
|
||||
LoadIndicesIfNeeded(stream, frames.indices, numTransIndices, useByteIndices, numLoopFrames);
|
||||
|
||||
const auto smallTrans = stream::ReadValue<bool>(stream);
|
||||
delta.trans->smallTrans = smallTrans ? 1 : 0;
|
||||
|
||||
frames.mins.x = stream::ReadValue<float>(stream);
|
||||
frames.mins.y = stream::ReadValue<float>(stream);
|
||||
frames.mins.z = stream::ReadValue<float>(stream);
|
||||
|
||||
frames.size.x = DecodeRawTransSize(stream::ReadValue<float>(stream), smallTrans);
|
||||
frames.size.y = DecodeRawTransSize(stream::ReadValue<float>(stream), smallTrans);
|
||||
frames.size.z = DecodeRawTransSize(stream::ReadValue<float>(stream), smallTrans);
|
||||
|
||||
delta.trans->size = static_cast<uint16_t>(numTransIndices - 1);
|
||||
if (smallTrans)
|
||||
{
|
||||
frames.frames._1 = m_memory.Alloc<ByteVec>(numTransIndices);
|
||||
stream::Read(stream, frames.frames._1, numTransIndices * sizeof(ByteVec));
|
||||
}
|
||||
else
|
||||
{
|
||||
frames.frames._2 = m_memory.Alloc<UShortVec>(numTransIndices);
|
||||
stream::Read(stream, frames.frames._2, numTransIndices * sizeof(UShortVec));
|
||||
}
|
||||
}
|
||||
|
||||
void LoadDeltaTrack(std::istream& stream, XAnimParts& parts, const bool useByteIndices, const uint16_t numLoopFrames) const
|
||||
{
|
||||
auto* delta = m_memory.Alloc<XAnimDeltaPart>();
|
||||
parts.deltaPart = delta;
|
||||
|
||||
LoadDeltaQuats(stream, *delta, useByteIndices, numLoopFrames);
|
||||
LoadDeltaTrans(stream, *delta, useByteIndices, numLoopFrames);
|
||||
}
|
||||
|
||||
bool LoadFromFile(std::istream& stream, XAnimParts& parts, AssetRegistration<AssetXAnim>& registration) const
|
||||
{
|
||||
const auto fileVersion = stream::ReadValue<uint16_t>(stream);
|
||||
if (fileVersion != RAW_VERSION)
|
||||
{
|
||||
PrintError(parts, std::format("Unsupported version number {} (expected {})", fileVersion, RAW_VERSION));
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto numFrames = stream::ReadValue<uint16_t>(stream);
|
||||
const auto boneCount = stream::ReadValue<uint16_t>(stream);
|
||||
const auto flags = stream::ReadValue<uint8_t>(stream);
|
||||
const auto assetType = stream::ReadValue<uint8_t>(stream);
|
||||
const auto framerate = stream::ReadValue<uint16_t>(stream);
|
||||
if (stream.fail())
|
||||
{
|
||||
PrintError(parts, "Truncated file");
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool isLooped = flags & FLAG_LOOPED;
|
||||
const bool hasDelta = flags & FLAG_DELTA;
|
||||
const uint16_t numLoopFrames = isLooped ? numFrames + 1u : numFrames;
|
||||
|
||||
parts.numframes = numLoopFrames - 1;
|
||||
parts.bLoop = isLooped;
|
||||
parts.bDelta = hasDelta;
|
||||
parts.assetType = assetType;
|
||||
parts.framerate = static_cast<float>(framerate);
|
||||
parts.frequency = parts.numframes > 0 ? parts.framerate / static_cast<float>(parts.numframes) : 0;
|
||||
|
||||
const auto useByteIndices = UseByteIndices(parts);
|
||||
|
||||
if (hasDelta)
|
||||
LoadDeltaTrack(stream, parts, useByteIndices, numLoopFrames);
|
||||
|
||||
std::vector<BoneTrack> boneTracks;
|
||||
if (boneCount > 0)
|
||||
{
|
||||
const auto bitmaskSize = utils::Align<size_t>(boneCount, 8u) / 8u;
|
||||
std::vector<uint8_t> flipQuatBits(bitmaskSize, 0);
|
||||
std::vector<uint8_t> halfQuatBits(bitmaskSize, 0);
|
||||
stream::Read(stream, flipQuatBits.data(), bitmaskSize);
|
||||
stream::Read(stream, halfQuatBits.data(), bitmaskSize);
|
||||
|
||||
boneTracks.resize(boneCount);
|
||||
for (size_t boneIndex = 0; boneIndex < boneCount; ++boneIndex)
|
||||
boneTracks[boneIndex].name = stream::ReadCString(stream);
|
||||
|
||||
for (size_t boneIndex = 0; boneIndex < boneCount; ++boneIndex)
|
||||
{
|
||||
auto& boneTrack = boneTracks[boneIndex];
|
||||
|
||||
const bool flipQuat = flipQuatBits[boneIndex / 8u] & static_cast<uint8_t>(1u << (boneIndex % 8u));
|
||||
const bool halfQuat = halfQuatBits[boneIndex / 8u] & static_cast<uint8_t>(1u << (boneIndex % 8u));
|
||||
|
||||
ReadQuatTrack(stream, boneTrack.quat, numLoopFrames, useByteIndices, flipQuat, halfQuat);
|
||||
ReadTransTrack(stream, boneTrack.trans, numLoopFrames, useByteIndices);
|
||||
}
|
||||
}
|
||||
|
||||
ReadNoteTracks(stream, parts, registration);
|
||||
|
||||
FlatDataWriteCursor writeCursor;
|
||||
|
||||
std::vector<size_t> boneOrder(boneCount);
|
||||
std::ranges::iota(boneOrder, 0);
|
||||
|
||||
std::ranges::sort(boneOrder,
|
||||
[&boneTracks](const size_t i0, const size_t i1)
|
||||
{
|
||||
const auto type0 = std::to_underlying(boneTracks[i0].quat.type);
|
||||
const auto type1 = std::to_underlying(boneTracks[i1].quat.type);
|
||||
if (type0 != type1)
|
||||
return type0 < type1;
|
||||
|
||||
return i0 < i1;
|
||||
});
|
||||
|
||||
// The parts bone indices are based on the quats order
|
||||
std::vector<size_t> boneTrackIndexToPartsBoneIndex(boneCount);
|
||||
parts.names = m_memory.Alloc<ScriptString>(boneCount);
|
||||
for (auto partsBoneIndex = 0u; partsBoneIndex < boneCount; ++partsBoneIndex)
|
||||
{
|
||||
const auto boneTrackIndex = boneOrder[partsBoneIndex];
|
||||
boneTrackIndexToPartsBoneIndex[boneTrackIndex] = partsBoneIndex;
|
||||
ProcessQuatTrack(writeCursor, boneTracks[boneTrackIndex].quat, parts, useByteIndices);
|
||||
|
||||
// Names are based on quats order so apply them here as well
|
||||
const auto scrString = m_script_strings.AddOrGetScriptString(boneTracks[boneTrackIndex].name);
|
||||
parts.names[partsBoneIndex] = scrString;
|
||||
registration.AddScriptString(scrString);
|
||||
}
|
||||
|
||||
// Trans are ordered differently
|
||||
std::ranges::sort(boneOrder,
|
||||
[&boneTracks, &boneTrackIndexToPartsBoneIndex](const size_t i0, const size_t i1)
|
||||
{
|
||||
const auto type0 = std::to_underlying(boneTracks[i0].trans.type);
|
||||
const auto type1 = std::to_underlying(boneTracks[i1].trans.type);
|
||||
if (type0 != type1)
|
||||
return type0 < type1;
|
||||
|
||||
return boneTrackIndexToPartsBoneIndex[i0] < boneTrackIndexToPartsBoneIndex[i1];
|
||||
});
|
||||
for (auto partsBoneIndex = 0u; partsBoneIndex < boneCount; ++partsBoneIndex)
|
||||
{
|
||||
const auto boneTrackIndex = boneOrder[partsBoneIndex];
|
||||
ProcessTransTrack(writeCursor, boneTracks[boneTrackIndex].trans, boneTrackIndexToPartsBoneIndex[boneTrackIndex], parts, useByteIndices);
|
||||
}
|
||||
|
||||
ApplyWriteCursorToParts(parts, writeCursor, m_memory);
|
||||
parts.boneCount[PART_TYPE_ALL] = static_cast<uint8_t>(boneCount);
|
||||
|
||||
assert(stream.peek() == std::char_traits<char>::eof());
|
||||
return true;
|
||||
}
|
||||
|
||||
MemoryManager& m_memory;
|
||||
ISearchPath& m_search_path;
|
||||
ZoneScriptStrings& m_script_strings;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace xanim
|
||||
{
|
||||
std::unique_ptr<AssetCreator<AssetXAnim>> CreateLoaderIW3(MemoryManager& memory, ISearchPath& searchPath, Zone& zone)
|
||||
{
|
||||
return std::make_unique<XAnimLoader>(memory, searchPath, zone.m_script_strings);
|
||||
}
|
||||
} // namespace xanim
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "Asset/IAssetCreator.h"
|
||||
#include "Game/IW3/IW3.h"
|
||||
#include "SearchPath/ISearchPath.h"
|
||||
#include "Utils/MemoryManager.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace xanim
|
||||
{
|
||||
std::unique_ptr<AssetCreator<IW3::AssetXAnim>> CreateLoaderIW3(MemoryManager& memory, ISearchPath& searchPath, Zone& zone);
|
||||
} // namespace xanim
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "Game/IW4/Image/ImageLoaderExternalIW4.h"
|
||||
#include "Game/IW4/Techset/PixelShaderLoaderIW4.h"
|
||||
#include "Game/IW4/Techset/VertexShaderLoaderIW4.h"
|
||||
#include "Game/IW4/XAnim/XAnimLoaderIW4.h"
|
||||
#include "Game/IW4/XModel/LoaderXModelIW4.h"
|
||||
#include "Leaderboard/LoaderLeaderboardIW4.h"
|
||||
#include "LightDef/LightDefLoaderIW4.h"
|
||||
@@ -125,8 +126,7 @@ namespace
|
||||
collection.AddAssetCreator(phys_preset::CreateRawLoaderIW4(memory, searchPath, zone));
|
||||
collection.AddAssetCreator(phys_preset::CreateGdtLoaderIW4(memory, gdt, zone));
|
||||
// collection.AddAssetCreator(std::make_unique<AssetLoaderPhysCollMap>(memory));
|
||||
// collection.AddAssetCreator(std::make_unique<AssetLoaderXAnim>(memory));
|
||||
// collection.AddAssetCreator(std::make_unique<AssetLoaderXModelSurfs>(memory));
|
||||
collection.AddAssetCreator(xanim::CreateLoaderIW4(memory, searchPath, zone));
|
||||
collection.AddAssetCreator(xmodel::CreateLoaderIW4(memory, searchPath, zone));
|
||||
collection.AddAssetCreator(material::CreateLoaderIW4(memory, searchPath));
|
||||
collection.AddAssetCreator(techset::CreateVertexShaderLoaderIW4(memory, searchPath));
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "Game/IW5/Image/ImageLoaderExternalIW5.h"
|
||||
#include "Game/IW5/Techset/PixelShaderLoaderIW5.h"
|
||||
#include "Game/IW5/Techset/VertexShaderLoaderIW5.h"
|
||||
#include "Game/IW5/XAnim/XAnimLoaderIW5.h"
|
||||
#include "Game/IW5/XModel/LoaderXModelIW5.h"
|
||||
#include "Leaderboard/LoaderLeaderboardIW5.h"
|
||||
#include "LightDef/LightDefLoaderIW5.h"
|
||||
@@ -132,8 +133,7 @@ namespace
|
||||
collection.AddAssetCreator(phys_preset::CreateRawLoaderIW5(memory, searchPath, zone));
|
||||
collection.AddAssetCreator(phys_preset::CreateGdtLoaderIW5(memory, gdt, zone));
|
||||
// collection.AddAssetCreator(std::make_unique<AssetLoaderPhysCollMap>(memory));
|
||||
// collection.AddAssetCreator(std::make_unique<AssetLoaderXAnim>(memory));
|
||||
// collection.AddAssetCreator(std::make_unique<AssetLoaderXModelSurfs>(memory));
|
||||
collection.AddAssetCreator(xanim::CreateLoaderIW5(memory, searchPath, zone));
|
||||
collection.AddAssetCreator(xmodel::CreateLoaderIW5(memory, searchPath, zone));
|
||||
collection.AddAssetCreator(material::CreateLoaderIW5(memory, searchPath));
|
||||
collection.AddAssetCreator(techset::CreateVertexShaderLoaderIW5(memory, searchPath));
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "Game/T5/T5.h"
|
||||
#include "Game/T5/Techset/PixelShaderLoaderT5.h"
|
||||
#include "Game/T5/Techset/VertexShaderLoaderT5.h"
|
||||
#include "Game/T5/XAnim/XAnimLoaderT5.h"
|
||||
#include "Game/T5/XModel/LoaderXModelT5.h"
|
||||
#include "LightDef/LightDefLoaderT5.h"
|
||||
#include "Localize/LoaderLocalizeT5.h"
|
||||
@@ -112,7 +113,7 @@ namespace
|
||||
collection.AddAssetCreator(phys_preset::CreateGdtLoaderT5(memory, gdt, zone));
|
||||
// collection.AddAssetCreator(std::make_unique<AssetLoaderPhysConstraints>(memory));
|
||||
// collection.AddAssetCreator(std::make_unique<AssetLoaderDestructibleDef>(memory));
|
||||
// collection.AddAssetCreator(std::make_unique<AssetLoaderXAnim>(memory));
|
||||
collection.AddAssetCreator(xanim::CreateLoaderT5(memory, searchPath, zone));
|
||||
collection.AddAssetCreator(xmodel::CreateLoaderT5(memory, searchPath, zone));
|
||||
collection.AddAssetCreator(material::CreateLoaderT5(memory, searchPath));
|
||||
// collection.AddAssetCreator(std::make_unique<AssetLoaderTechniqueSet>(memory));
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "Game/T6/T6.h"
|
||||
#include "Game/T6/Techset/PixelShaderLoaderT6.h"
|
||||
#include "Game/T6/Techset/VertexShaderLoaderT6.h"
|
||||
#include "Game/T6/XAnim/XAnimLoaderT6.h"
|
||||
#include "Game/T6/XModel/LoaderXModelT6.h"
|
||||
#include "Image/Dx12TextureLoader.h"
|
||||
#include "Image/IwiLoader.h"
|
||||
@@ -386,7 +387,7 @@ namespace T6
|
||||
collection.AddAssetCreator(phys_constraints::CreateRawLoaderT6(memory, searchPath, zone));
|
||||
collection.AddAssetCreator(phys_constraints::CreateGdtLoaderT6(memory, searchPath, gdt, zone));
|
||||
// collection.AddAssetCreator(std::make_unique<AssetLoaderDestructibleDef>(memory));
|
||||
// collection.AddAssetCreator(std::make_unique<AssetLoaderXAnim>(memory));
|
||||
collection.AddAssetCreator(xanim::CreateLoaderT6(memory, searchPath, zone));
|
||||
collection.AddAssetCreator(xmodel::CreateLoaderT6(memory, searchPath, zone));
|
||||
collection.AddAssetCreator(material::CreateLoaderT6(memory, searchPath));
|
||||
// collection.AddAssetCreator(std::make_unique<AssetLoaderTechniqueSet>(memory));
|
||||
|
||||
@@ -0,0 +1,546 @@
|
||||
#include "CompiledXAnimLoader.h"
|
||||
|
||||
#include "Utils/Alignment.h"
|
||||
#include "Utils/StreamUtils.h"
|
||||
#include "XAnim/BinaryXAnimCommon.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <format>
|
||||
#include <numeric>
|
||||
|
||||
using namespace xanim;
|
||||
|
||||
namespace
|
||||
{
|
||||
// The linker decodes raw trans size[] with these exact float literals.
|
||||
// They correspond to 1.0f / 255.0f and 1.0f / 65535.0f, but we keep the
|
||||
// decompiled values to preserve binary-stable round trips.
|
||||
constexpr auto HALF_TRANS_SIZE_SCALE = 0.003921568859368563f;
|
||||
constexpr auto FULL_TRANS_SIZE_SCALE = 0.00001525902189314365f;
|
||||
|
||||
std::expected<CompiledXAnimVersion, std::string> IdentifyVersion(std::istream& stream)
|
||||
{
|
||||
const auto fileVersion = stream::ReadValue<uint16_t>(stream);
|
||||
switch (static_cast<CompiledXAnimVersion>(fileVersion))
|
||||
{
|
||||
case CompiledXAnimVersion::VERSION_17:
|
||||
return CompiledXAnimVersion::VERSION_17;
|
||||
case CompiledXAnimVersion::VERSION_18:
|
||||
return CompiledXAnimVersion::VERSION_18;
|
||||
case CompiledXAnimVersion::VERSION_19:
|
||||
return CompiledXAnimVersion::VERSION_19;
|
||||
}
|
||||
|
||||
return std::unexpected(std::format("Version {} is not supported", fileVersion));
|
||||
}
|
||||
|
||||
CommonXQuat ConsumeQuat(std::istream& stream)
|
||||
{
|
||||
CommonXQuat quat{};
|
||||
quat.value[0] = stream::ReadValue<int16_t>(stream);
|
||||
quat.value[1] = stream::ReadValue<int16_t>(stream);
|
||||
quat.value[2] = stream::ReadValue<int16_t>(stream);
|
||||
|
||||
int32_t temp = 0x3FFF0001 - (quat.value[0] * quat.value[0] + quat.value[1] * quat.value[1] + quat.value[2] * quat.value[2]);
|
||||
if (temp <= 0)
|
||||
temp = 0;
|
||||
else
|
||||
temp = static_cast<int32_t>(std::floor(std::sqrt(static_cast<float>(temp)) + 0.5f));
|
||||
|
||||
assert(temp >= std::numeric_limits<int16_t>::min() && temp <= std::numeric_limits<int16_t>::max());
|
||||
quat.value[3] = static_cast<int16_t>(temp);
|
||||
|
||||
return quat;
|
||||
}
|
||||
|
||||
CommonXQuat2 ConsumeQuat2(std::istream& stream)
|
||||
{
|
||||
CommonXQuat2 quat2{};
|
||||
quat2.value[0] = stream::ReadValue<int16_t>(stream);
|
||||
|
||||
int32_t temp = 0x3FFF0001 - quat2.value[0] * quat2.value[0];
|
||||
if (temp <= 0)
|
||||
temp = 0;
|
||||
else
|
||||
temp = static_cast<int32_t>(floor(std::sqrt(static_cast<float>(temp)) + 0.5f));
|
||||
|
||||
assert(temp >= std::numeric_limits<int16_t>::min() && temp <= std::numeric_limits<int16_t>::max());
|
||||
quat2.value[1] = static_cast<int16_t>(temp);
|
||||
|
||||
return quat2;
|
||||
}
|
||||
|
||||
void FlipQuat(CommonXQuat& quat)
|
||||
{
|
||||
quat.value[0] = static_cast<int16_t>(-quat.value[0]);
|
||||
quat.value[1] = static_cast<int16_t>(-quat.value[1]);
|
||||
quat.value[2] = static_cast<int16_t>(-quat.value[2]);
|
||||
quat.value[3] = static_cast<int16_t>(-quat.value[3]);
|
||||
}
|
||||
|
||||
void FlipQuat2(CommonXQuat2& quat)
|
||||
{
|
||||
quat.value[0] = static_cast<int16_t>(-quat.value[0]);
|
||||
quat.value[1] = static_cast<int16_t>(-quat.value[1]);
|
||||
}
|
||||
|
||||
[[nodiscard]] float DecodeRawTransSize(const float value, const bool smallTrans)
|
||||
{
|
||||
const auto scale = smallTrans ? HALF_TRANS_SIZE_SCALE : FULL_TRANS_SIZE_SCALE;
|
||||
return value * scale;
|
||||
}
|
||||
|
||||
void LoadIndicesIfNeeded(
|
||||
std::istream& stream, std::vector<uint16_t>& indices, const uint16_t numIndices, const bool useByteIndices, const uint16_t numLoopFrames)
|
||||
{
|
||||
// The raw format omits indices when a track covers every loop frame in order.
|
||||
if (numIndices >= numLoopFrames)
|
||||
{
|
||||
indices.resize(numIndices);
|
||||
std::ranges::iota(indices, 0);
|
||||
}
|
||||
else if (useByteIndices)
|
||||
{
|
||||
indices.reserve(numIndices);
|
||||
for (auto i = 0u; i < numIndices; i++)
|
||||
indices.emplace_back(stream::ReadValue<uint8_t>(stream));
|
||||
}
|
||||
else
|
||||
{
|
||||
indices.resize(numIndices);
|
||||
stream::Read(stream, indices.data(), numIndices * sizeof(uint16_t));
|
||||
}
|
||||
}
|
||||
|
||||
std::expected<std::optional<CommonDeltaQuatTrack>, std::string>
|
||||
LoadDeltaQuatTrack(std::istream& stream, const bool useByteIndices, const uint16_t numLoopFrames)
|
||||
{
|
||||
const auto numQuatIndices = stream::ReadValue<uint16_t>(stream);
|
||||
if (numQuatIndices == 0)
|
||||
return std::nullopt;
|
||||
|
||||
CommonDeltaQuatTrack deltaQuatTrack;
|
||||
if (numQuatIndices == 1)
|
||||
{
|
||||
deltaQuatTrack.m_frames.emplace_back(ConsumeQuat(stream));
|
||||
return deltaQuatTrack;
|
||||
}
|
||||
|
||||
LoadIndicesIfNeeded(stream, deltaQuatTrack.m_indices, numQuatIndices, useByteIndices, numLoopFrames);
|
||||
|
||||
deltaQuatTrack.m_frames.reserve(numQuatIndices);
|
||||
for (auto quatIndexNum = 0u; quatIndexNum < numQuatIndices; ++quatIndexNum)
|
||||
{
|
||||
auto& curFrame = deltaQuatTrack.m_frames.emplace_back(ConsumeQuat(stream));
|
||||
|
||||
if (quatIndexNum > 0)
|
||||
{
|
||||
const auto& prevFrame = deltaQuatTrack.m_frames[quatIndexNum - 1];
|
||||
const auto dot = prevFrame.value[0] * curFrame.value[0] + prevFrame.value[1] * curFrame.value[1] + prevFrame.value[2] * curFrame.value[2]
|
||||
+ prevFrame.value[3] * curFrame.value[3];
|
||||
if (dot < 0)
|
||||
FlipQuat(curFrame);
|
||||
}
|
||||
}
|
||||
|
||||
return deltaQuatTrack;
|
||||
}
|
||||
|
||||
std::expected<std::optional<CommonDeltaQuatTrack>, std::string>
|
||||
LoadDeltaQuat2Track(std::istream& stream, const bool useByteIndices, const uint16_t numLoopFrames)
|
||||
{
|
||||
const auto numQuatIndices = stream::ReadValue<uint16_t>(stream);
|
||||
if (numQuatIndices == 0)
|
||||
return std::nullopt;
|
||||
|
||||
CommonDeltaQuatTrack deltaQuatTrack;
|
||||
if (numQuatIndices == 1)
|
||||
{
|
||||
deltaQuatTrack.m_frames2.emplace_back(ConsumeQuat2(stream));
|
||||
return deltaQuatTrack;
|
||||
}
|
||||
|
||||
LoadIndicesIfNeeded(stream, deltaQuatTrack.m_indices, numQuatIndices, useByteIndices, numLoopFrames);
|
||||
|
||||
deltaQuatTrack.m_frames2.reserve(numQuatIndices);
|
||||
for (auto quatIndexNum = 0u; quatIndexNum < numQuatIndices; ++quatIndexNum)
|
||||
{
|
||||
auto& curFrame = deltaQuatTrack.m_frames2.emplace_back(ConsumeQuat2(stream));
|
||||
|
||||
if (quatIndexNum > 0)
|
||||
{
|
||||
const auto& prevFrame = deltaQuatTrack.m_frames2[quatIndexNum - 1];
|
||||
if (prevFrame.value[0] * curFrame.value[0] + prevFrame.value[1] * curFrame.value[1] < 0)
|
||||
FlipQuat2(curFrame);
|
||||
}
|
||||
}
|
||||
|
||||
return deltaQuatTrack;
|
||||
}
|
||||
|
||||
std::expected<std::optional<CommonDeltaTransTrack>, std::string>
|
||||
LoadDeltaTransTrack(std::istream& stream, const bool useByteIndices, const uint16_t numLoopFrames)
|
||||
{
|
||||
const auto numTransIndices = stream::ReadValue<uint16_t>(stream);
|
||||
if (numTransIndices == 0)
|
||||
return std::nullopt;
|
||||
|
||||
CommonDeltaTransTrack deltaTransTrack;
|
||||
if (numTransIndices == 1)
|
||||
{
|
||||
deltaTransTrack.m_constant.emplace(std::array<float, 3>({
|
||||
stream::ReadValue<float>(stream),
|
||||
stream::ReadValue<float>(stream),
|
||||
stream::ReadValue<float>(stream),
|
||||
}));
|
||||
return deltaTransTrack;
|
||||
}
|
||||
|
||||
LoadIndicesIfNeeded(stream, deltaTransTrack.m_indices, numTransIndices, useByteIndices, numLoopFrames);
|
||||
|
||||
const auto smallTrans = stream::ReadValue<bool>(stream);
|
||||
|
||||
deltaTransTrack.m_mins[0] = stream::ReadValue<float>(stream);
|
||||
deltaTransTrack.m_mins[1] = stream::ReadValue<float>(stream);
|
||||
deltaTransTrack.m_mins[2] = stream::ReadValue<float>(stream);
|
||||
|
||||
deltaTransTrack.m_size[0] = DecodeRawTransSize(stream::ReadValue<float>(stream), smallTrans);
|
||||
deltaTransTrack.m_size[1] = DecodeRawTransSize(stream::ReadValue<float>(stream), smallTrans);
|
||||
deltaTransTrack.m_size[2] = DecodeRawTransSize(stream::ReadValue<float>(stream), smallTrans);
|
||||
|
||||
if (smallTrans)
|
||||
{
|
||||
deltaTransTrack.m_frames_u8.resize(numTransIndices);
|
||||
stream::Read(stream, deltaTransTrack.m_frames_u8.data(), numTransIndices * sizeof(CommonVec3U8));
|
||||
}
|
||||
else
|
||||
{
|
||||
deltaTransTrack.m_frames_u16.resize(numTransIndices);
|
||||
stream::Read(stream, deltaTransTrack.m_frames_u16.data(), numTransIndices * sizeof(CommonVec3U16));
|
||||
}
|
||||
|
||||
return deltaTransTrack;
|
||||
}
|
||||
|
||||
std::expected<std::unique_ptr<CommonXAnimDeltaTrack>, std::string>
|
||||
LoadDeltaTrack(std::istream& stream, const bool hasDelta3D, const bool useByteIndices, const uint16_t numLoopFrames)
|
||||
{
|
||||
auto delta = std::make_unique<CommonXAnimDeltaTrack>();
|
||||
|
||||
auto maybeLoadedDeltaQuat =
|
||||
hasDelta3D ? LoadDeltaQuatTrack(stream, useByteIndices, numLoopFrames) : LoadDeltaQuat2Track(stream, useByteIndices, numLoopFrames);
|
||||
if (!maybeLoadedDeltaQuat.has_value())
|
||||
return std::unexpected(std::move(maybeLoadedDeltaQuat).error());
|
||||
delta->m_quat = std::move(maybeLoadedDeltaQuat).value();
|
||||
|
||||
auto maybeLoadedDeltaTrans = LoadDeltaTransTrack(stream, useByteIndices, numLoopFrames);
|
||||
if (!maybeLoadedDeltaTrans.has_value())
|
||||
return std::unexpected(std::move(maybeLoadedDeltaTrans).error());
|
||||
delta->m_trans = std::move(maybeLoadedDeltaTrans).value();
|
||||
|
||||
return delta;
|
||||
}
|
||||
|
||||
QuatTrack ReadQuatTrack(std::istream& stream, const uint16_t numLoopFrames, const bool useByteIndices, const bool flipQuat, const bool halfQuat)
|
||||
{
|
||||
QuatTrack quatTrack;
|
||||
|
||||
const auto numQuatIndices = stream::ReadValue<uint16_t>(stream);
|
||||
if (numQuatIndices == 0)
|
||||
{
|
||||
assert(halfQuat);
|
||||
quatTrack.m_type = QuatType::NO_QUAT;
|
||||
return quatTrack;
|
||||
}
|
||||
|
||||
if (numQuatIndices == 1)
|
||||
{
|
||||
quatTrack.m_type = halfQuat ? QuatType::HALF_QUAT_NO_SIZE : QuatType::FULL_QUAT_NO_SIZE;
|
||||
if (halfQuat)
|
||||
{
|
||||
auto quat2 = ConsumeQuat2(stream);
|
||||
if (flipQuat)
|
||||
FlipQuat2(quat2);
|
||||
|
||||
quatTrack.m_frames2.emplace_back(quat2);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto quat = ConsumeQuat(stream);
|
||||
if (flipQuat)
|
||||
FlipQuat(quat);
|
||||
|
||||
quatTrack.m_frames.emplace_back(quat);
|
||||
}
|
||||
|
||||
return quatTrack;
|
||||
}
|
||||
|
||||
LoadIndicesIfNeeded(stream, quatTrack.m_indices, numQuatIndices, useByteIndices, numLoopFrames);
|
||||
|
||||
if (halfQuat)
|
||||
{
|
||||
quatTrack.m_type = QuatType::HALF_QUAT;
|
||||
quatTrack.m_frames2.reserve(numQuatIndices);
|
||||
for (auto quatIndexNum = 0u; quatIndexNum < numQuatIndices; quatIndexNum++)
|
||||
{
|
||||
auto& curFrame = quatTrack.m_frames2.emplace_back(ConsumeQuat2(stream));
|
||||
|
||||
if (quatIndexNum > 0)
|
||||
{
|
||||
const auto& prevFrame = quatTrack.m_frames2[quatIndexNum - 1];
|
||||
if (prevFrame.value[0] * curFrame.value[0] + prevFrame.value[1] * curFrame.value[1] < 0)
|
||||
FlipQuat2(curFrame);
|
||||
}
|
||||
else if (flipQuat)
|
||||
FlipQuat2(curFrame);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
quatTrack.m_type = QuatType::FULL_QUAT;
|
||||
quatTrack.m_frames.reserve(numQuatIndices);
|
||||
for (auto quatIndexNum = 0u; quatIndexNum < numQuatIndices; quatIndexNum++)
|
||||
{
|
||||
auto& curFrame = quatTrack.m_frames.emplace_back(ConsumeQuat(stream));
|
||||
|
||||
if (quatIndexNum > 0)
|
||||
{
|
||||
const auto& prevFrame = quatTrack.m_frames[quatIndexNum - 1];
|
||||
const auto dot = prevFrame.value[0] * curFrame.value[0] + prevFrame.value[1] * curFrame.value[1] + prevFrame.value[2] * curFrame.value[2]
|
||||
+ prevFrame.value[3] * curFrame.value[3];
|
||||
if (dot < 0)
|
||||
FlipQuat(curFrame);
|
||||
}
|
||||
else if (flipQuat)
|
||||
FlipQuat(curFrame);
|
||||
}
|
||||
}
|
||||
|
||||
return quatTrack;
|
||||
}
|
||||
|
||||
TransTrack ReadTransTrack(std::istream& stream, const uint16_t numLoopFrames, const bool useByteIndices)
|
||||
{
|
||||
TransTrack transTrack;
|
||||
|
||||
const auto numTransIndices = stream::ReadValue<uint16_t>(stream);
|
||||
if (numTransIndices == 0)
|
||||
{
|
||||
transTrack.m_type = TransType::NO_TRANS;
|
||||
return transTrack;
|
||||
}
|
||||
|
||||
if (numTransIndices == 1)
|
||||
{
|
||||
transTrack.m_type = TransType::TRANS_NO_SIZE;
|
||||
for (auto& value : transTrack.m_constant)
|
||||
value = stream::ReadValue<float>(stream);
|
||||
return transTrack;
|
||||
}
|
||||
|
||||
LoadIndicesIfNeeded(stream, transTrack.m_indices, numTransIndices, useByteIndices, numLoopFrames);
|
||||
|
||||
const auto smallTrans = stream::ReadValue<bool>(stream);
|
||||
transTrack.m_type = smallTrans ? TransType::SMALL_TRANS : TransType::FULL_TRANS;
|
||||
|
||||
for (auto& value : transTrack.m_mins)
|
||||
value = stream::ReadValue<float>(stream);
|
||||
for (auto& value : transTrack.m_size)
|
||||
value = DecodeRawTransSize(stream::ReadValue<float>(stream), smallTrans);
|
||||
|
||||
if (smallTrans)
|
||||
{
|
||||
static_assert(sizeof(decltype(transTrack.m_frames_u8)::value_type) == sizeof(uint8_t) * 3u);
|
||||
transTrack.m_frames_u8.resize(numTransIndices);
|
||||
stream::Read(stream, transTrack.m_frames_u8.data(), numTransIndices * sizeof(uint8_t) * 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(sizeof(decltype(transTrack.m_frames_u16)::value_type) == sizeof(int16_t) * 3u);
|
||||
transTrack.m_frames_u16.resize(numTransIndices);
|
||||
stream::Read(stream, transTrack.m_frames_u16.data(), numTransIndices * sizeof(uint16_t) * 3);
|
||||
}
|
||||
|
||||
return transTrack;
|
||||
}
|
||||
|
||||
void ReadNoteTracks(std::istream& stream, CommonXAnimParts& parts)
|
||||
{
|
||||
const auto numNoteTracks = stream::ReadValue<uint8_t>(stream);
|
||||
|
||||
parts.m_notifies.reserve(numNoteTracks + 1);
|
||||
for (auto notifyIndex = 0u; notifyIndex < numNoteTracks; notifyIndex++)
|
||||
{
|
||||
const auto notifyName = stream::ReadCString(stream);
|
||||
|
||||
const auto frame = stream::ReadValue<uint16_t>(stream);
|
||||
const auto time = parts.m_num_frames > 0 ? static_cast<float>(frame) / static_cast<float>(parts.m_num_frames) : 0;
|
||||
assert(time >= 0.0f && time <= 1.0f);
|
||||
parts.m_notifies.emplace_back(notifyName, time);
|
||||
}
|
||||
|
||||
// This notify is always automatically added
|
||||
parts.m_notifies.emplace_back("end", 1.0f);
|
||||
}
|
||||
|
||||
bool IsLooped(const uint8_t flags, const CompiledXAnimVersion version)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case CompiledXAnimVersion::VERSION_17:
|
||||
return (flags & binary17::FLAG_LOOPED) > 0;
|
||||
case CompiledXAnimVersion::VERSION_18:
|
||||
return (flags & binary18::FLAG_LOOPED) > 0;
|
||||
case CompiledXAnimVersion::VERSION_19:
|
||||
return (flags & binary19::FLAG_LOOPED) > 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HasDelta(const uint8_t flags, const CompiledXAnimVersion version)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case CompiledXAnimVersion::VERSION_17:
|
||||
return (flags & binary17::FLAG_DELTA) > 0;
|
||||
case CompiledXAnimVersion::VERSION_18:
|
||||
return (flags & binary18::FLAG_DELTA) > 0;
|
||||
case CompiledXAnimVersion::VERSION_19:
|
||||
return (flags & binary19::FLAG_DELTA) > 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HasDelta3D(const uint8_t flags, const CompiledXAnimVersion version)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case CompiledXAnimVersion::VERSION_18:
|
||||
return (flags & binary18::FLAG_DELTA_3D) > 0;
|
||||
case CompiledXAnimVersion::VERSION_19:
|
||||
return (flags & binary19::FLAG_T6_COMPATIBILITY) > 0 && (flags & binary19::FLAG_T6_DELTA_3D) > 0;
|
||||
case CompiledXAnimVersion::VERSION_17:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsLeftHandGripIk(const uint8_t flags, const CompiledXAnimVersion version)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case CompiledXAnimVersion::VERSION_19:
|
||||
if (flags & binary19::FLAG_T6_COMPATIBILITY)
|
||||
return (flags & binary19::FLAG_T6_LEFT_HAND_GRIP_IK) > 0;
|
||||
return (flags & binary19::FLAG_T5_LEFT_HAND_GRIP_IK) > 0;
|
||||
case CompiledXAnimVersion::VERSION_17:
|
||||
case CompiledXAnimVersion::VERSION_18:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsStreamable(const uint8_t flags, const CompiledXAnimVersion version)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case CompiledXAnimVersion::VERSION_19:
|
||||
if (flags & binary19::FLAG_T6_COMPATIBILITY)
|
||||
return false;
|
||||
return (flags & binary19::FLAG_T5_STREAMABLE) > 0;
|
||||
|
||||
case CompiledXAnimVersion::VERSION_17:
|
||||
case CompiledXAnimVersion::VERSION_18:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace xanim
|
||||
{
|
||||
std::expected<std::unique_ptr<CommonXAnimParts>, std::string> LoadCompiledXAnim(std::istream& stream)
|
||||
{
|
||||
auto maybeVersion = IdentifyVersion(stream);
|
||||
if (!maybeVersion)
|
||||
return std::unexpected(std::move(maybeVersion).error());
|
||||
|
||||
auto parts = std::make_unique<CommonXAnimParts>();
|
||||
|
||||
const auto version = maybeVersion.value();
|
||||
|
||||
const auto numFrames = stream::ReadValue<uint16_t>(stream);
|
||||
const auto boneCount = stream::ReadValue<uint16_t>(stream);
|
||||
const auto flags = stream::ReadValue<uint8_t>(stream);
|
||||
const auto assetType = stream::ReadValue<uint8_t>(stream);
|
||||
const auto framerate = stream::ReadValue<uint16_t>(stream);
|
||||
if (stream.fail())
|
||||
return std::unexpected("Truncated file");
|
||||
|
||||
const bool isLooped = IsLooped(flags, version);
|
||||
const bool hasDelta = HasDelta(flags, version);
|
||||
const bool hasDelta3D = HasDelta3D(flags, version);
|
||||
const bool leftHandGripIk = IsLeftHandGripIk(flags, version);
|
||||
const bool streamable = IsStreamable(flags, version);
|
||||
const uint16_t numLoopFrames = isLooped ? numFrames + 1u : numFrames;
|
||||
|
||||
parts->m_num_frames = numLoopFrames - 1;
|
||||
parts->m_looped = isLooped;
|
||||
parts->m_left_hand_grip_ik = leftHandGripIk;
|
||||
parts->m_streamable = streamable;
|
||||
parts->m_asset_type = assetType;
|
||||
parts->m_frame_rate = static_cast<float>(framerate);
|
||||
|
||||
if (version == CompiledXAnimVersion::VERSION_19 && streamable)
|
||||
parts->m_primed_length = stream::ReadValue<float>(stream);
|
||||
|
||||
const auto useByteIndices = parts->m_num_frames < 256;
|
||||
|
||||
if (hasDelta || hasDelta3D)
|
||||
{
|
||||
auto maybeBoneTrack = LoadDeltaTrack(stream, hasDelta3D, useByteIndices, numLoopFrames);
|
||||
if (!maybeBoneTrack.has_value())
|
||||
return std::unexpected(std::move(maybeBoneTrack).error());
|
||||
|
||||
parts->m_delta_track = std::move(maybeBoneTrack).value();
|
||||
}
|
||||
|
||||
if (boneCount > 0)
|
||||
{
|
||||
const auto bitmaskSize = utils::Align<size_t>(boneCount, 8u) / 8u;
|
||||
std::vector<uint8_t> flipQuatBits(bitmaskSize, 0);
|
||||
std::vector<uint8_t> halfQuatBits(bitmaskSize, 0);
|
||||
stream::Read(stream, flipQuatBits.data(), bitmaskSize);
|
||||
stream::Read(stream, halfQuatBits.data(), bitmaskSize);
|
||||
|
||||
parts->m_bone_tracks.resize(boneCount);
|
||||
for (size_t boneIndex = 0; boneIndex < boneCount; ++boneIndex)
|
||||
parts->m_bone_tracks[boneIndex].m_name = stream::ReadCString(stream);
|
||||
|
||||
for (size_t boneIndex = 0; boneIndex < boneCount; ++boneIndex)
|
||||
{
|
||||
auto& boneTrack = parts->m_bone_tracks[boneIndex];
|
||||
|
||||
const bool flipQuat = flipQuatBits[boneIndex / 8u] & static_cast<uint8_t>(1u << (boneIndex % 8u));
|
||||
const bool halfQuat = halfQuatBits[boneIndex / 8u] & static_cast<uint8_t>(1u << (boneIndex % 8u));
|
||||
|
||||
boneTrack.m_quat = ReadQuatTrack(stream, numLoopFrames, useByteIndices, flipQuat, halfQuat);
|
||||
boneTrack.m_trans = ReadTransTrack(stream, numLoopFrames, useByteIndices);
|
||||
}
|
||||
}
|
||||
parts->SortBoneTracksForQuats();
|
||||
|
||||
ReadNoteTracks(stream, *parts);
|
||||
|
||||
assert(stream.peek() == std::char_traits<char>::eof());
|
||||
return parts;
|
||||
}
|
||||
} // namespace xanim
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "XAnim/XAnimCommon.h"
|
||||
|
||||
#include <expected>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace xanim
|
||||
{
|
||||
std::expected<std::unique_ptr<CommonXAnimParts>, std::string> LoadCompiledXAnim(std::istream& stream);
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
#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
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "XAnim/XAnimCommon.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace xanim
|
||||
{
|
||||
class FlatData
|
||||
{
|
||||
public:
|
||||
std::vector<uint8_t> m_data_byte;
|
||||
std::vector<int16_t> m_data_short;
|
||||
std::vector<int32_t> m_data_int;
|
||||
std::vector<uint8_t> m_random_data_byte;
|
||||
std::vector<int16_t> m_random_data_short;
|
||||
std::vector<uint16_t> m_indices;
|
||||
};
|
||||
|
||||
FlatData CreateFlatDataFromCommonXAnim(const CommonXAnimParts& parts);
|
||||
} // namespace xanim
|
||||
@@ -0,0 +1,490 @@
|
||||
#options GAME(IW3, IW4, IW5, T5, T6)
|
||||
|
||||
#filename "Game/" + GAME + "/XAnim/XAnimLoader" + GAME + ".cpp"
|
||||
|
||||
#set LOADER_HEADER "\"XAnimLoader" + GAME + ".h\""
|
||||
|
||||
#if GAME == "IW3"
|
||||
#define FEATURE_IW3
|
||||
#elif GAME == "IW4"
|
||||
#define FEATURE_IW4
|
||||
#define HAS_DELTA_QUAT_3D
|
||||
#elif GAME == "IW5"
|
||||
#define FEATURE_IW5
|
||||
#define HAS_DELTA_QUAT_3D
|
||||
#elif GAME == "T5"
|
||||
#define FEATURE_T5
|
||||
#elif GAME == "T6"
|
||||
#define FEATURE_T6
|
||||
#define HAS_DELTA_QUAT_3D
|
||||
#endif
|
||||
|
||||
#if defined(FEATURE_IW4) || defined(FEATURE_IW5)
|
||||
#define SET_IS_LOOPED() parts.flags |= ANIM_LOOP
|
||||
#define SET_HAS_DELTA() parts.flags |= ANIM_DELTA
|
||||
#define SET_HAS_DELTA_3D() parts.flags |= ANIM_DELTA_3D
|
||||
#else
|
||||
#define SET_IS_LOOPED() parts.bLoop = true
|
||||
#define SET_HAS_DELTA() parts.bDelta = true
|
||||
#define SET_HAS_DELTA_3D() parts.bDelta3D = true
|
||||
#endif
|
||||
|
||||
// This file was templated.
|
||||
// See XAnimLoader.cpp.template.
|
||||
// Do not modify, changes will be lost.
|
||||
|
||||
#include LOADER_HEADER
|
||||
|
||||
#include "Utils/Logging/Log.h"
|
||||
#include "XAnim/CompiledXAnimLoader.h"
|
||||
#include "XAnim/FlatXAnimDataWriter.h"
|
||||
#include "XAnim/XAnimCommon.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <expected>
|
||||
#include <format>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using namespace GAME;
|
||||
|
||||
namespace
|
||||
{
|
||||
void ConvertNoteTracks(XAnimParts& parts,
|
||||
const xanim::CommonXAnimParts& commonParts,
|
||||
AssetRegistration<AssetXAnim>& registration,
|
||||
MemoryManager& memory,
|
||||
ZoneScriptStrings& scriptStrings)
|
||||
{
|
||||
if (commonParts.m_notifies.empty())
|
||||
return;
|
||||
|
||||
const auto numCommonNoteTracks = commonParts.m_notifies.size();
|
||||
const auto numNoteTracks = static_cast<uint8_t>(std::min<size_t>(numCommonNoteTracks, std::numeric_limits<uint8_t>::max()));
|
||||
|
||||
if (numNoteTracks < numCommonNoteTracks)
|
||||
con::error("XAnim {}: Could only fit {} of {} notetracks", parts.name, numNoteTracks, numCommonNoteTracks);
|
||||
|
||||
parts.notifyCount = numNoteTracks;
|
||||
parts.notify = memory.Alloc<XAnimNotifyInfo>(numNoteTracks);
|
||||
|
||||
for (auto notifyIndex = 0u; notifyIndex < numCommonNoteTracks; notifyIndex++)
|
||||
{
|
||||
const auto& commonNotify = commonParts.m_notifies[notifyIndex];
|
||||
auto& notify = parts.notify[notifyIndex];
|
||||
|
||||
notify.name = scriptStrings.AddOrGetScriptString(commonNotify.m_name);
|
||||
registration.AddScriptString(notify.name);
|
||||
|
||||
notify.time = commonNotify.m_time;
|
||||
}
|
||||
|
||||
#if defined(FEATURE_T5) || defined(FEATURE_T6)
|
||||
const auto loopBegin = std::ranges::find_if(commonParts.m_notifies, [](const xanim::CommonXAnimNotifyInfo& notify)
|
||||
{
|
||||
return notify.m_name == "loop_begin";
|
||||
});
|
||||
|
||||
if (loopBegin != commonParts.m_notifies.end())
|
||||
parts.loopEntryTime = loopBegin->m_time;
|
||||
else
|
||||
parts.loopEntryTime = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename T> void ConvertIndices(T& indices, const std::vector<uint16_t>& commonIndices, const bool useByteIndices)
|
||||
{
|
||||
if (useByteIndices)
|
||||
{
|
||||
const auto numIndices = commonIndices.size();
|
||||
for (size_t i = 0u; i < numIndices; i++)
|
||||
{
|
||||
assert(commonIndices[i] <= std::numeric_limits<uint8_t>::max());
|
||||
indices._1[i] = static_cast<uint8_t>(commonIndices[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memcpy(indices._2, commonIndices.data(), commonIndices.size() * sizeof(uint16_t));
|
||||
}
|
||||
}
|
||||
|
||||
void CountBoneTrackTypes(XAnimParts& parts, const xanim::BoneTrack& boneTrack)
|
||||
{
|
||||
switch (boneTrack.m_quat.m_type)
|
||||
{
|
||||
case xanim::QuatType::NO_QUAT:
|
||||
parts.boneCount[PART_TYPE_NO_QUAT]++;
|
||||
break;
|
||||
case xanim::QuatType::HALF_QUAT:
|
||||
parts.boneCount[PART_TYPE_HALF_QUAT]++;
|
||||
break;
|
||||
case xanim::QuatType::FULL_QUAT:
|
||||
parts.boneCount[PART_TYPE_FULL_QUAT]++;
|
||||
break;
|
||||
case xanim::QuatType::HALF_QUAT_NO_SIZE:
|
||||
parts.boneCount[PART_TYPE_HALF_QUAT_NO_SIZE]++;
|
||||
break;
|
||||
case xanim::QuatType::FULL_QUAT_NO_SIZE:
|
||||
parts.boneCount[PART_TYPE_FULL_QUAT_NO_SIZE]++;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (boneTrack.m_trans.m_type)
|
||||
{
|
||||
case xanim::TransType::SMALL_TRANS:
|
||||
parts.boneCount[PART_TYPE_SMALL_TRANS]++;
|
||||
break;
|
||||
case xanim::TransType::FULL_TRANS:
|
||||
parts.boneCount[PART_TYPE_TRANS]++;
|
||||
break;
|
||||
case xanim::TransType::TRANS_NO_SIZE:
|
||||
parts.boneCount[PART_TYPE_TRANS_NO_SIZE]++;
|
||||
break;
|
||||
case xanim::TransType::NO_TRANS:
|
||||
parts.boneCount[PART_TYPE_NO_TRANS]++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ConvertFlatData(XAnimParts& parts, const xanim::FlatData& flatData, MemoryManager& memory)
|
||||
{
|
||||
if (!flatData.m_data_byte.empty())
|
||||
{
|
||||
parts.dataByteCount = static_cast<uint16_t>(flatData.m_data_byte.size());
|
||||
parts.dataByte = memory.Alloc<uint8_t>(parts.dataByteCount);
|
||||
std::memcpy(parts.dataByte, flatData.m_data_byte.data(), parts.dataByteCount * sizeof(uint8_t));
|
||||
}
|
||||
|
||||
if (!flatData.m_data_short.empty())
|
||||
{
|
||||
parts.dataShortCount = static_cast<uint16_t>(flatData.m_data_short.size());
|
||||
parts.dataShort = memory.Alloc<int16_t>(parts.dataShortCount);
|
||||
std::memcpy(parts.dataShort, flatData.m_data_short.data(), parts.dataShortCount * sizeof(int16_t));
|
||||
}
|
||||
|
||||
if (!flatData.m_data_int.empty())
|
||||
{
|
||||
parts.dataIntCount = static_cast<uint16_t>(flatData.m_data_int.size());
|
||||
parts.dataInt = memory.Alloc<int32_t>(parts.dataIntCount);
|
||||
std::memcpy(parts.dataInt, flatData.m_data_int.data(), parts.dataIntCount * sizeof(int32_t));
|
||||
}
|
||||
|
||||
if (!flatData.m_random_data_byte.empty())
|
||||
{
|
||||
parts.randomDataByteCount = static_cast<uint16_t>(flatData.m_random_data_byte.size());
|
||||
parts.randomDataByte = memory.Alloc<uint8_t>(parts.randomDataByteCount);
|
||||
std::memcpy(parts.randomDataByte, flatData.m_random_data_byte.data(), parts.randomDataByteCount * sizeof(uint8_t));
|
||||
}
|
||||
|
||||
if (!flatData.m_random_data_short.empty())
|
||||
{
|
||||
parts.randomDataShortCount = static_cast<unsigned int>(flatData.m_random_data_short.size());
|
||||
parts.randomDataShort = memory.Alloc<int16_t>(parts.randomDataShortCount);
|
||||
std::memcpy(parts.randomDataShort, flatData.m_random_data_short.data(), parts.randomDataShortCount * sizeof(int16_t));
|
||||
}
|
||||
|
||||
if (!flatData.m_indices.empty())
|
||||
{
|
||||
parts.indexCount = static_cast<unsigned int>(flatData.m_indices.size());
|
||||
parts.indices._2 = memory.Alloc<uint16_t>(parts.indexCount);
|
||||
std::memcpy(parts.indices._2, flatData.m_indices.data(), parts.indexCount * sizeof(uint16_t));
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAS_DELTA_QUAT_3D
|
||||
#define DELTA_QUAT_2D_MEMBER quat2
|
||||
#define DELTA_QUAT_2D_STRUCT XAnimDeltaPartQuat2
|
||||
#define DELTA_QUAT_2D_DATA_STRUCT XAnimDeltaPartQuatData2
|
||||
#define DELTA_QUAT_2D_FRAMES_STRUCT XAnimDeltaPartQuatDataFrames2
|
||||
#else
|
||||
#define DELTA_QUAT_2D_MEMBER quat
|
||||
#define DELTA_QUAT_2D_STRUCT XAnimDeltaPartQuat
|
||||
#define DELTA_QUAT_2D_DATA_STRUCT XAnimDeltaPartQuatData
|
||||
#define DELTA_QUAT_2D_FRAMES_STRUCT XAnimDeltaPartQuatDataFrames
|
||||
#endif
|
||||
|
||||
#ifdef HAS_DELTA_QUAT_3D
|
||||
void ConvertCommonDeltaQuatPart(XAnimDeltaPart& deltaPart,
|
||||
const xanim::CommonDeltaQuatTrack& commonDeltaQuatTrack,
|
||||
MemoryManager& memory,
|
||||
const bool useByteIndices)
|
||||
{
|
||||
if (commonDeltaQuatTrack.m_frames.size() == 1)
|
||||
{
|
||||
deltaPart.quat = static_cast<XAnimDeltaPartQuat*>(memory.AllocRaw(offsetof(XAnimDeltaPartQuat, u) + sizeof(XAnimDeltaPartQuatData::frame0)));
|
||||
deltaPart.quat->size = 0;
|
||||
|
||||
const auto& commonFrame = commonDeltaQuatTrack.m_frames[0];
|
||||
auto& frame = deltaPart.quat->u.frame0;
|
||||
|
||||
frame.value[0] = commonFrame.value[0];
|
||||
frame.value[1] = commonFrame.value[1];
|
||||
frame.value[2] = commonFrame.value[2];
|
||||
frame.value[3] = commonFrame.value[3];
|
||||
return;
|
||||
}
|
||||
|
||||
const auto numQuatIndices = commonDeltaQuatTrack.m_indices.size();
|
||||
const auto indicesArraySize =
|
||||
useByteIndices ? numQuatIndices * sizeof(XAnimDynamicIndicesQuat::_1) : numQuatIndices * sizeof(XAnimDynamicIndicesQuat::_2);
|
||||
|
||||
deltaPart.quat = static_cast<XAnimDeltaPartQuat*>(
|
||||
memory.AllocRaw(offsetof(XAnimDeltaPartQuat, u) + offsetof(XAnimDeltaPartQuatDataFrames, indices) + indicesArraySize));
|
||||
|
||||
auto& frames = deltaPart.quat->u.frames;
|
||||
ConvertIndices(frames.indices, commonDeltaQuatTrack.m_indices, useByteIndices);
|
||||
|
||||
deltaPart.quat->size = static_cast<uint16_t>(numQuatIndices - 1);
|
||||
deltaPart.quat->u.frames.frames = memory.Alloc<XQuat>(numQuatIndices);
|
||||
|
||||
for (auto quatIndexNum = 0u; quatIndexNum < numQuatIndices; ++quatIndexNum)
|
||||
{
|
||||
const auto& commonFrame = commonDeltaQuatTrack.m_frames[quatIndexNum];
|
||||
auto& curFrame = deltaPart.quat->u.frames.frames[quatIndexNum];
|
||||
|
||||
curFrame.value[0] = commonFrame.value[0];
|
||||
curFrame.value[1] = commonFrame.value[1];
|
||||
curFrame.value[2] = commonFrame.value[2];
|
||||
curFrame.value[3] = commonFrame.value[3];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void ConvertCommonDeltaQuat2Part(XAnimDeltaPart& deltaPart,
|
||||
const xanim::CommonDeltaQuatTrack& commonDeltaQuatTrack,
|
||||
MemoryManager& memory,
|
||||
const bool useByteIndices)
|
||||
{
|
||||
if (commonDeltaQuatTrack.m_frames2.size() == 1)
|
||||
{
|
||||
deltaPart.DELTA_QUAT_2D_MEMBER = static_cast<DELTA_QUAT_2D_STRUCT*>(memory.AllocRaw(offsetof(DELTA_QUAT_2D_STRUCT, u) + sizeof(DELTA_QUAT_2D_DATA_STRUCT::frame0)));
|
||||
deltaPart.DELTA_QUAT_2D_MEMBER->size = 0;
|
||||
|
||||
const auto& commonFrame = commonDeltaQuatTrack.m_frames2[0];
|
||||
auto& frame = deltaPart.DELTA_QUAT_2D_MEMBER->u.frame0;
|
||||
|
||||
frame.value[0] = commonFrame.value[0];
|
||||
frame.value[1] = commonFrame.value[1];
|
||||
return;
|
||||
}
|
||||
|
||||
const auto numQuatIndices = commonDeltaQuatTrack.m_indices.size();
|
||||
const auto indicesArraySize =
|
||||
useByteIndices ? numQuatIndices * sizeof(XAnimDynamicIndicesQuat::_1) : numQuatIndices * sizeof(XAnimDynamicIndicesQuat::_2);
|
||||
|
||||
deltaPart.DELTA_QUAT_2D_MEMBER = static_cast<DELTA_QUAT_2D_STRUCT*>(
|
||||
memory.AllocRaw(offsetof(DELTA_QUAT_2D_STRUCT, u) + offsetof(DELTA_QUAT_2D_FRAMES_STRUCT, indices) + indicesArraySize));
|
||||
|
||||
auto& frames = deltaPart.DELTA_QUAT_2D_MEMBER->u.frames;
|
||||
ConvertIndices(frames.indices, commonDeltaQuatTrack.m_indices, useByteIndices);
|
||||
|
||||
deltaPart.DELTA_QUAT_2D_MEMBER->size = static_cast<uint16_t>(numQuatIndices - 1);
|
||||
deltaPart.DELTA_QUAT_2D_MEMBER->u.frames.frames = memory.Alloc<XQuat2>(numQuatIndices);
|
||||
|
||||
for (auto quatIndexNum = 0u; quatIndexNum < numQuatIndices; ++quatIndexNum)
|
||||
{
|
||||
const auto& commonFrame = commonDeltaQuatTrack.m_frames2[quatIndexNum];
|
||||
auto& curFrame = deltaPart.DELTA_QUAT_2D_MEMBER->u.frames.frames[quatIndexNum];
|
||||
|
||||
curFrame.value[0] = commonFrame.value[0];
|
||||
curFrame.value[1] = commonFrame.value[1];
|
||||
}
|
||||
}
|
||||
|
||||
void ConvertCommonDeltaTransPart(XAnimDeltaPart& deltaPart,
|
||||
const xanim::CommonDeltaTransTrack& commonDeltaTransTrack,
|
||||
MemoryManager& memory,
|
||||
const bool useByteIndices)
|
||||
{
|
||||
if (commonDeltaTransTrack.m_constant)
|
||||
{
|
||||
deltaPart.trans = static_cast<XAnimPartTrans*>(memory.AllocRaw(offsetof(XAnimPartTrans, u) + sizeof(XAnimPartTransData::frame0)));
|
||||
deltaPart.trans->size = 0;
|
||||
deltaPart.trans->u.frame0.x = (*commonDeltaTransTrack.m_constant)[0];
|
||||
deltaPart.trans->u.frame0.y = (*commonDeltaTransTrack.m_constant)[1];
|
||||
deltaPart.trans->u.frame0.z = (*commonDeltaTransTrack.m_constant)[2];
|
||||
return;
|
||||
}
|
||||
|
||||
const auto numTransIndices = commonDeltaTransTrack.m_indices.size();
|
||||
const auto indicesArraySize =
|
||||
useByteIndices ? numTransIndices * sizeof(XAnimDynamicIndicesTrans::_1) : numTransIndices * sizeof(XAnimDynamicIndicesTrans::_2);
|
||||
|
||||
deltaPart.trans =
|
||||
static_cast<XAnimPartTrans*>(memory.AllocRaw(offsetof(XAnimPartTrans, u) + offsetof(XAnimPartTransFrames, indices) + indicesArraySize));
|
||||
|
||||
auto& frames = deltaPart.trans->u.frames;
|
||||
ConvertIndices(frames.indices, commonDeltaTransTrack.m_indices, useByteIndices);
|
||||
|
||||
deltaPart.trans->size = static_cast<uint16_t>(numTransIndices - 1);
|
||||
frames.mins.x = commonDeltaTransTrack.m_mins[0];
|
||||
frames.mins.y = commonDeltaTransTrack.m_mins[1];
|
||||
frames.mins.z = commonDeltaTransTrack.m_mins[2];
|
||||
frames.size.x = commonDeltaTransTrack.m_size[0];
|
||||
frames.size.y = commonDeltaTransTrack.m_size[1];
|
||||
frames.size.z = commonDeltaTransTrack.m_size[2];
|
||||
|
||||
if (commonDeltaTransTrack.m_frames_u16.empty())
|
||||
{
|
||||
deltaPart.trans->smallTrans = 1;
|
||||
|
||||
static_assert(sizeof(ByteVec) == sizeof(xanim::CommonVec3U8));
|
||||
frames.frames._1 = memory.Alloc<ByteVec>(numTransIndices);
|
||||
std::memcpy(frames.frames._1, commonDeltaTransTrack.m_frames_u8.data(), numTransIndices * sizeof(ByteVec));
|
||||
}
|
||||
else
|
||||
{
|
||||
deltaPart.trans->smallTrans = 0;
|
||||
|
||||
static_assert(sizeof(UShortVec) == sizeof(xanim::CommonVec3U16));
|
||||
frames.frames._2 = memory.Alloc<UShortVec>(numTransIndices);
|
||||
std::memcpy(frames.frames._2, commonDeltaTransTrack.m_frames_u16.data(), numTransIndices * sizeof(UShortVec));
|
||||
}
|
||||
}
|
||||
|
||||
std::expected<void, std::string> ConvertCommonDeltaPart(XAnimDeltaPart& deltaPart,
|
||||
const xanim::CommonXAnimDeltaTrack& commonXAnimDeltaTrack,
|
||||
MemoryManager& memory,
|
||||
const bool useByteIndices)
|
||||
{
|
||||
#ifdef HAS_DELTA_QUAT_3D
|
||||
if (commonXAnimDeltaTrack.m_quat)
|
||||
{
|
||||
if (commonXAnimDeltaTrack.m_quat->Is3DTrack())
|
||||
ConvertCommonDeltaQuatPart(deltaPart, *commonXAnimDeltaTrack.m_quat, memory, useByteIndices);
|
||||
else
|
||||
ConvertCommonDeltaQuat2Part(deltaPart, *commonXAnimDeltaTrack.m_quat, memory, useByteIndices);
|
||||
}
|
||||
#else
|
||||
if (commonXAnimDeltaTrack.m_quat)
|
||||
{
|
||||
if (commonXAnimDeltaTrack.m_quat->Is3DTrack())
|
||||
return std::unexpected("XAnim uses delta3D which is unsupported in this game");
|
||||
|
||||
ConvertCommonDeltaQuat2Part(deltaPart, *commonXAnimDeltaTrack.m_quat, memory, useByteIndices);
|
||||
}
|
||||
#endif
|
||||
if (commonXAnimDeltaTrack.m_trans)
|
||||
ConvertCommonDeltaTransPart(deltaPart, *commonXAnimDeltaTrack.m_trans, memory, useByteIndices);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::expected<void, std::string> ConvertCommonXAnim(XAnimParts& parts,
|
||||
const xanim::CommonXAnimParts& commonParts,
|
||||
AssetRegistration<AssetXAnim>& registration,
|
||||
MemoryManager& memory,
|
||||
ZoneScriptStrings& scriptStrings)
|
||||
{
|
||||
parts.numframes = static_cast<decltype(XAnimParts::numframes)>(commonParts.m_num_frames);
|
||||
if (commonParts.m_looped)
|
||||
SET_IS_LOOPED();
|
||||
#if defined(FEATURE_T5) || defined(FEATURE_T6)
|
||||
parts.bLeftHandGripIK = commonParts.m_left_hand_grip_ik;
|
||||
#endif
|
||||
#if defined(FEATURE_T5)
|
||||
parts.bStreamable = commonParts.m_streamable;
|
||||
#endif
|
||||
parts.assetType = commonParts.m_asset_type;
|
||||
parts.framerate = commonParts.m_frame_rate;
|
||||
parts.frequency = parts.numframes > 0 ? parts.framerate / static_cast<float>(parts.numframes) : 0;
|
||||
#if defined(FEATURE_T5) || defined(FEATURE_T6)
|
||||
parts.primedLength = commonParts.m_primed_length;
|
||||
#endif
|
||||
|
||||
const auto useByteIndices = parts.numframes < 256;
|
||||
|
||||
if (commonParts.m_delta_track)
|
||||
{
|
||||
parts.deltaPart = memory.Alloc<XAnimDeltaPart>();
|
||||
#ifdef HAS_DELTA_QUAT_3D
|
||||
if (commonParts.m_delta_track->m_quat && commonParts.m_delta_track->m_quat->Is3DTrack())
|
||||
SET_HAS_DELTA_3D();
|
||||
else
|
||||
SET_HAS_DELTA();
|
||||
#else
|
||||
SET_HAS_DELTA();
|
||||
#endif
|
||||
auto result = ConvertCommonDeltaPart(*parts.deltaPart, *commonParts.m_delta_track, memory, useByteIndices);
|
||||
if (!result.has_value())
|
||||
return std::unexpected(std::move(result).error());
|
||||
}
|
||||
|
||||
parts.names = memory.Alloc<ScriptString>(commonParts.m_bone_tracks.size());
|
||||
for (size_t boneIndex = 0; boneIndex < commonParts.m_bone_tracks.size(); ++boneIndex)
|
||||
{
|
||||
const auto nameScrString = scriptStrings.AddOrGetScriptString(commonParts.m_bone_tracks[boneIndex].m_name);
|
||||
parts.names[boneIndex] = nameScrString;
|
||||
registration.AddScriptString(nameScrString);
|
||||
CountBoneTrackTypes(parts, commonParts.m_bone_tracks[boneIndex]);
|
||||
}
|
||||
parts.boneCount[PART_TYPE_ALL] = static_cast<uint8_t>(commonParts.m_bone_tracks.size());
|
||||
|
||||
ConvertNoteTracks(parts, commonParts, registration, memory, scriptStrings);
|
||||
|
||||
const auto flatData = xanim::CreateFlatDataFromCommonXAnim(commonParts);
|
||||
ConvertFlatData(parts, flatData, memory);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
class XAnimLoader final : public AssetCreator<AssetXAnim>
|
||||
{
|
||||
public:
|
||||
XAnimLoader(MemoryManager& memory, ISearchPath& searchPath, ZoneScriptStrings& scriptStrings)
|
||||
: m_memory(memory),
|
||||
m_search_path(searchPath),
|
||||
m_script_strings(scriptStrings)
|
||||
{
|
||||
}
|
||||
|
||||
AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override
|
||||
{
|
||||
const auto file = m_search_path.Open(xanim::GetCompiledFileNameForAssetName(assetName));
|
||||
if (!file.IsOpen())
|
||||
return AssetCreationResult::NoAction();
|
||||
|
||||
auto maybeCommonParts = xanim::LoadCompiledXAnim(*file.m_stream);
|
||||
if (!maybeCommonParts.has_value())
|
||||
{
|
||||
con::error("Failed to load xanim \"{}\": {}", assetName, maybeCommonParts.error());
|
||||
return AssetCreationResult::Failure();
|
||||
}
|
||||
const auto commonParts = std::move(maybeCommonParts).value();
|
||||
|
||||
auto* parts = m_memory.Alloc<XAnimParts>();
|
||||
parts->name = m_memory.Dup(assetName.c_str());
|
||||
|
||||
AssetRegistration<AssetXAnim> registration(assetName, parts);
|
||||
const auto conversionResult = ConvertCommonXAnim(*parts, *commonParts, registration, m_memory, m_script_strings);
|
||||
if (!conversionResult.has_value())
|
||||
{
|
||||
con::error("Failed to load xanim \"{}\": {}", assetName, conversionResult.error());
|
||||
return AssetCreationResult::Failure();
|
||||
}
|
||||
|
||||
return AssetCreationResult::Success(context.AddAsset(std::move(registration)));
|
||||
}
|
||||
|
||||
MemoryManager& m_memory;
|
||||
ISearchPath& m_search_path;
|
||||
ZoneScriptStrings& m_script_strings;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
#set METHOD_NAME "CreateLoader" + GAME
|
||||
|
||||
namespace xanim
|
||||
{
|
||||
std::unique_ptr<AssetCreator<AssetXAnim>> METHOD_NAME(MemoryManager& memory, ISearchPath& searchPath, Zone& zone)
|
||||
{
|
||||
return std::make_unique<XAnimLoader>(memory, searchPath, zone.m_script_strings);
|
||||
}
|
||||
} // namespace xanim
|
||||
@@ -0,0 +1,25 @@
|
||||
#options GAME(IW3, IW4, IW5, T5, T6)
|
||||
|
||||
#filename "Game/" + GAME + "/XAnim/XAnimLoader" + GAME + ".h"
|
||||
|
||||
#set GAME_HEADER "\"Game/" + GAME + "/" + GAME + ".h\""
|
||||
|
||||
// This file was templated.
|
||||
// See XAnimLoader.h.template.
|
||||
// Do not modify, changes will be lost.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Asset/IAssetCreator.h"
|
||||
#include GAME_HEADER
|
||||
#include "SearchPath/ISearchPath.h"
|
||||
#include "Utils/MemoryManager.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#set METHOD_NAME "CreateLoader" + GAME
|
||||
|
||||
namespace xanim
|
||||
{
|
||||
std::unique_ptr<AssetCreator<GAME::AssetXAnim>> METHOD_NAME(MemoryManager& memory, ISearchPath& searchPath, Zone& zone);
|
||||
} // namespace xanim
|
||||
Reference in New Issue
Block a user