2024-01-28 02:23:52 +01:00

1001 lines
30 KiB
C++

#include <STDInclude.hpp>
#include <stdint.h>
#include "IXModel.hpp"
namespace Assets
{
void IXModel::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
header->model = builder->getIW4OfApi()->read<Game::XModel>(Game::XAssetType::ASSET_TYPE_XMODEL, name);
if (header->model)
{
// ???
if (header->model->physCollmap)
{
Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_PHYSCOLLMAP, { header->model->physCollmap });
}
if (header->model->physPreset)
{
Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_PHYSPRESET, { header->model->physPreset });
}
for (size_t i = 0; i < header->model->numLods; i++)
{
const auto& info = header->model->lodInfo[i];
Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_XMODEL_SURFS, { info.modelSurfs });
}
}
}
void IXModel::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{
Game::XModel* asset = header.model;
if (asset->boneNames)
{
for (char i = 0; i < asset->numBones; ++i)
{
builder->addScriptString(asset->boneNames[i]);
}
}
if (asset->materialHandles)
{
for (unsigned char i = 0; i < asset->numsurfs; ++i)
{
if (asset->materialHandles[i])
{
builder->loadAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, asset->materialHandles[i]);
}
}
}
for (int i = 0; i < 4; ++i)
{
if (asset->lodInfo[i].modelSurfs)
{
builder->loadAsset(Game::XAssetType::ASSET_TYPE_XMODEL_SURFS, asset->lodInfo[i].modelSurfs);
}
}
if (asset->physPreset)
{
builder->loadAsset(Game::XAssetType::ASSET_TYPE_PHYSPRESET, asset->physPreset);
}
if (asset->physCollmap)
{
builder->loadAsset(Game::XAssetType::ASSET_TYPE_PHYSCOLLMAP, asset->physCollmap);
}
}
void IXModel::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{
AssertSize(Game::XModel, 304);
Utils::Stream* buffer = builder->getBuffer();
Game::XModel* asset = header.model;
Game::XModel* dest = buffer->dest<Game::XModel>();
buffer->save(asset);
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);
if (asset->name)
{
buffer->saveString(builder->getAssetName(this->getType(), asset->name));
Utils::Stream::ClearPointer(&dest->name);
}
if (asset->boneNames)
{
buffer->align(Utils::Stream::ALIGN_2);
unsigned short* destBoneNames = buffer->dest<unsigned short>();
buffer->saveArray(asset->boneNames, asset->numBones);
for (char i = 0; i < asset->numBones; ++i)
{
builder->mapScriptString(destBoneNames[i]);
}
Utils::Stream::ClearPointer(&dest->boneNames);
}
if (asset->parentList)
{
if (builder->hasPointer(asset->parentList))
{
dest->parentList = builder->getPointer(asset->parentList);
}
else
{
builder->storePointer(asset->parentList);
buffer->save(asset->parentList, asset->numBones - asset->numRootBones);
Utils::Stream::ClearPointer(&dest->parentList);
}
}
if (asset->quats)
{
if (builder->hasPointer(asset->quats))
{
dest->quats = builder->getPointer(asset->quats);
}
else
{
buffer->align(Utils::Stream::ALIGN_2);
builder->storePointer(asset->quats);
buffer->saveArray(asset->quats, (asset->numBones - asset->numRootBones) * 4);
Utils::Stream::ClearPointer(&dest->quats);
}
}
if (asset->trans)
{
if (builder->hasPointer(asset->trans))
{
dest->trans = builder->getPointer(asset->trans);
}
else
{
buffer->align(Utils::Stream::ALIGN_4);
builder->storePointer(asset->trans);
buffer->saveArray(asset->trans, (asset->numBones - asset->numRootBones) * 3);
Utils::Stream::ClearPointer(&dest->trans);
}
}
if (asset->partClassification)
{
if (builder->hasPointer(asset->partClassification))
{
dest->partClassification = builder->getPointer(asset->partClassification);
}
else
{
builder->storePointer(asset->partClassification);
buffer->save(asset->partClassification, asset->numBones);
Utils::Stream::ClearPointer(&dest->partClassification);
}
}
if (asset->baseMat)
{
AssertSize(Game::DObjAnimMat, 32);
if (builder->hasPointer(asset->baseMat))
{
dest->baseMat = builder->getPointer(asset->baseMat);
}
else
{
buffer->align(Utils::Stream::ALIGN_4);
builder->storePointer(asset->baseMat);
buffer->saveArray(asset->baseMat, asset->numBones);
Utils::Stream::ClearPointer(&dest->baseMat);
}
}
if (asset->materialHandles)
{
buffer->align(Utils::Stream::ALIGN_4);
Game::Material** destMaterials = buffer->dest<Game::Material*>();
buffer->saveArray(asset->materialHandles, asset->numsurfs);
for (unsigned char i = 0; i < asset->numsurfs; ++i)
{
if (asset->materialHandles[i])
{
destMaterials[i] = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, asset->materialHandles[i]).material;
}
}
Utils::Stream::ClearPointer(&dest->materialHandles);
}
// Save_XModelLodInfoArray
{
AssertSize(Game::XModelLodInfo, 44);
for (int i = 0; i < 4; ++i)
{
if (asset->lodInfo[i].modelSurfs)
{
dest->lodInfo[i].modelSurfs = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_XMODEL_SURFS, asset->lodInfo[i].modelSurfs).modelSurfs;
}
}
}
// Save_XModelCollSurfArray
if (asset->collSurfs)
{
AssertSize(Game::XModelCollSurf_s, 44);
buffer->align(Utils::Stream::ALIGN_4);
Game::XModelCollSurf_s* destColSurfs = buffer->dest<Game::XModelCollSurf_s>();
buffer->saveArray(asset->collSurfs, asset->numCollSurfs);
for (int i = 0; i < asset->numCollSurfs; ++i)
{
Game::XModelCollSurf_s* destCollSurf = &destColSurfs[i];
Game::XModelCollSurf_s* collSurf = &asset->collSurfs[i];
if (collSurf->collTris)
{
buffer->align(Utils::Stream::ALIGN_4);
buffer->save(collSurf->collTris, 48, collSurf->numCollTris);
Utils::Stream::ClearPointer(&destCollSurf->collTris);
}
}
Utils::Stream::ClearPointer(&dest->collSurfs);
}
if (asset->boneInfo)
{
AssertSize(Game::XBoneInfo, 28);
buffer->align(Utils::Stream::ALIGN_4);
buffer->saveArray(asset->boneInfo, asset->numBones);
Utils::Stream::ClearPointer(&dest->boneInfo);
}
if (asset->physPreset)
{
dest->physPreset = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_PHYSPRESET, asset->physPreset).physPreset;
}
if (asset->physCollmap)
{
dest->physCollmap = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_PHYSCOLLMAP, asset->physCollmap).physCollmap;
}
buffer->popBlock();
}
uint8_t IXModel::GetIndexOfBone(const Game::XModel* model, std::string name)
{
for (uint8_t i = 0; i < model->numBones; i++)
{
const auto bone = model->boneNames[i];
const auto boneName = Game::SL_ConvertToString(bone);
if (name == boneName)
{
return i;
}
}
return static_cast<uint8_t>(UCHAR_MAX);
};
uint8_t IXModel::GetParentIndexOfBone(const Game::XModel* model, uint8_t index)
{
const auto parentIndex = index - model->parentList[index - model->numRootBones];
return static_cast<uint8_t>(parentIndex);
};
void IXModel::SetParentIndexOfBone(Game::XModel* model, uint8_t boneIndex, uint8_t parentIndex)
{
if (boneIndex == SCHAR_MAX)
{
return;
}
model->parentList[boneIndex - model->numRootBones] = boneIndex - parentIndex;
};
std::string IXModel::GetParentOfBone(Game::XModel* model, uint8_t index)
{
assert(index > 0);
const auto parentIndex = GetParentIndexOfBone(model, index);
const auto boneName = Game::SL_ConvertToString(model->boneNames[parentIndex]);
return boneName;
};
uint8_t IXModel::GetHighestAffectingBoneIndex(const Game::XModelLodInfo* lod)
{
uint8_t highestBoneIndex = 0;
constexpr auto LENGTH = 6;
{
for (auto surfIndex = 0; surfIndex < lod->numsurfs; surfIndex++)
{
const auto surface = &lod->surfs[surfIndex];
auto vertsBlendOffset = 0;
int rebuiltPartBits[LENGTH]{};
std::unordered_set<uint8_t> affectingBones{};
const auto registerBoneAffectingSurface = [&](unsigned int offset) {
uint8_t index = static_cast<uint8_t>(surface->vertInfo.vertsBlend[offset] / sizeof(Game::DObjSkelMat));
highestBoneIndex = std::max(highestBoneIndex, index);
};
// 1 bone weight
for (unsigned int vertIndex = 0; vertIndex < surface->vertInfo.vertCount[0]; vertIndex++)
{
registerBoneAffectingSurface(vertsBlendOffset + 0);
vertsBlendOffset += 1;
}
// 2 bone weights
for (unsigned int vertIndex = 0; vertIndex < surface->vertInfo.vertCount[1]; vertIndex++)
{
registerBoneAffectingSurface(vertsBlendOffset + 0);
registerBoneAffectingSurface(vertsBlendOffset + 1);
vertsBlendOffset += 3;
}
// 3 bone weights
for (unsigned int vertIndex = 0; vertIndex < surface->vertInfo.vertCount[2]; vertIndex++)
{
registerBoneAffectingSurface(vertsBlendOffset + 0);
registerBoneAffectingSurface(vertsBlendOffset + 1);
registerBoneAffectingSurface(vertsBlendOffset + 3);
vertsBlendOffset += 5;
}
// 4 bone weights
for (unsigned int vertIndex = 0; vertIndex < surface->vertInfo.vertCount[3]; vertIndex++)
{
registerBoneAffectingSurface(vertsBlendOffset + 0);
registerBoneAffectingSurface(vertsBlendOffset + 1);
registerBoneAffectingSurface(vertsBlendOffset + 3);
registerBoneAffectingSurface(vertsBlendOffset + 5);
vertsBlendOffset += 7;
}
for (unsigned int vertListIndex = 0; vertListIndex < surface->vertListCount; vertListIndex++)
{
highestBoneIndex = std::max(highestBoneIndex, static_cast<uint8_t>(surface->vertList[vertListIndex].boneOffset / sizeof(Game::DObjSkelMat)));
}
}
}
return highestBoneIndex;
};
void IXModel::RebuildPartBits(Game::XModel* model)
{
constexpr auto LENGTH = 6;
for (auto i = 0; i < model->numLods; i++)
{
const auto lod = &model->lodInfo[i];
int lodPartBits[6]{};
for (auto surfIndex = 0; surfIndex < lod->numsurfs; surfIndex++)
{
const auto surface = &lod->surfs[surfIndex];
auto vertsBlendOffset = 0;
int rebuiltPartBits[6]{};
std::unordered_set<uint8_t> affectingBones{};
const auto registerBoneAffectingSurface = [&](unsigned int offset) {
uint8_t index = static_cast<uint8_t>(surface->vertInfo.vertsBlend[offset] / sizeof(Game::DObjSkelMat));
assert(index >= 0);
assert(index < model->numBones);
affectingBones.emplace(index);
};
// 1 bone weight
for (auto vertIndex = 0; vertIndex < surface->vertInfo.vertCount[0]; vertIndex++)
{
registerBoneAffectingSurface(vertsBlendOffset + 0);
vertsBlendOffset += 1;
}
// 2 bone weights
for (auto vertIndex = 0; vertIndex < surface->vertInfo.vertCount[1]; vertIndex++)
{
registerBoneAffectingSurface(vertsBlendOffset + 0);
registerBoneAffectingSurface(vertsBlendOffset + 1);
vertsBlendOffset += 3;
}
// 3 bone weights
for (auto vertIndex = 0; vertIndex < surface->vertInfo.vertCount[2]; vertIndex++)
{
registerBoneAffectingSurface(vertsBlendOffset + 0);
registerBoneAffectingSurface(vertsBlendOffset + 1);
registerBoneAffectingSurface(vertsBlendOffset + 3);
vertsBlendOffset += 5;
}
// 4 bone weights
for (auto vertIndex = 0; vertIndex < surface->vertInfo.vertCount[3]; vertIndex++)
{
registerBoneAffectingSurface(vertsBlendOffset + 0);
registerBoneAffectingSurface(vertsBlendOffset + 1);
registerBoneAffectingSurface(vertsBlendOffset + 3);
registerBoneAffectingSurface(vertsBlendOffset + 5);
vertsBlendOffset += 7;
}
for (auto vertListIndex = 0; vertListIndex < surface->vertListCount; vertListIndex++)
{
affectingBones.emplace(static_cast<uint8_t>(surface->vertList[vertListIndex].boneOffset / sizeof(Game::DObjSkelMat)));
}
// Actually rebuilding
for (const auto& boneIndex : affectingBones)
{
const auto bitPosition = 31 - boneIndex % 32;
const auto groupIndex = boneIndex / 32;
assert(groupIndex < 6);
assert(groupIndex >= 0);
rebuiltPartBits[groupIndex] |= 1 << bitPosition;
lodPartBits[groupIndex] |= 1 << bitPosition;
}
std::memcpy(surface->partBits, rebuiltPartBits, 6 * sizeof(int32_t));
}
std::memcpy(lod->partBits, lodPartBits, 6 * sizeof(int32_t));
std::memcpy(lod->modelSurfs->partBits, lodPartBits, 6 * sizeof(int32_t));
// here's a little lesson in trickery:
// We set the 192nd part bit to TRUE because it has no consequences
// but allows us to find out whether that surf was already converted in the past or not
lod->partBits[LENGTH - 1] |= 0x1;
lod->modelSurfs->partBits[LENGTH - 1] |= 0x1;
}
};
uint8_t IXModel::InsertBone(Game::XModel* model, const std::string& boneName, const std::string& parentName, Utils::Memory::Allocator& allocator)
{
assert(GetIndexOfBone(model, boneName) == UCHAR_MAX);
constexpr auto MAX_BONES = 192;
assert(model->numBones < MAX_BONES);
// Start with backing up parent links that we will have to restore
// We'll restore them at the end
std::map<std::string, std::string> parentsToRestore{};
for (int i = model->numRootBones; i < model->numBones; i++)
{
parentsToRestore[Game::SL_ConvertToString(model->boneNames[i])] = GetParentOfBone(model, i);
}
const uint8_t newBoneCount = model->numBones + 1;
const uint8_t newBoneCountMinusRoot = newBoneCount - model->numRootBones;
const auto parentIndex = GetIndexOfBone(model, parentName);
assert(parentIndex != UCHAR_MAX);
const uint8_t atPosition = parentIndex + 1;
const uint8_t newBoneIndex = atPosition;
const uint8_t newBoneIndexMinusRoot = atPosition - model->numRootBones;
Components::Logger::Print("Inserting bone {} at position {} (between {} and {})\n", boneName, atPosition, Game::SL_ConvertToString(model->boneNames[atPosition - 1]), Game::SL_ConvertToString(model->boneNames[atPosition + 1]));
// Reallocate
const auto newBoneNames = allocator.allocateArray<uint16_t>(newBoneCount);
const auto newMats = allocator.allocateArray<Game::DObjAnimMat>(newBoneCount);
const auto newBoneInfo = allocator.allocateArray<Game::XBoneInfo>(newBoneCount);
const auto newPartsClassification = allocator.allocateArray<uint8_t>(newBoneCount);
const auto newQuats = allocator.allocateArray<int16_t>(4 * newBoneCountMinusRoot);
const auto newTrans = allocator.allocateArray<float>(3 * newBoneCountMinusRoot);
const auto newParentList = allocator.allocateArray<uint8_t>(newBoneCountMinusRoot);
const uint8_t lengthOfFirstPart = atPosition;
const uint8_t lengthOfSecondPart = model->numBones - atPosition;
const uint8_t lengthOfFirstPartM1 = atPosition - model->numRootBones;
const uint8_t lengthOfSecondPartM1 = model->numBones - model->numRootBones - (atPosition - model->numRootBones);
const uint8_t atPositionM1 = atPosition - model->numRootBones;
// should be equal to model->numBones
int total = lengthOfFirstPart + lengthOfSecondPart;
assert(total = model->numBones);
// should be equal to model->numBones - model->numRootBones
int totalM1 = lengthOfFirstPartM1 + lengthOfSecondPartM1;
assert(totalM1 == model->numBones - model->numRootBones);
// Copy before
if (lengthOfFirstPart > 0)
{
std::memcpy(newBoneNames, model->boneNames, sizeof(uint16_t) * lengthOfFirstPart);
std::memcpy(newMats, model->baseMat, sizeof(Game::DObjAnimMat) * lengthOfFirstPart);
std::memcpy(newPartsClassification, model->partClassification, lengthOfFirstPart);
std::memcpy(newBoneInfo, model->boneInfo, sizeof(Game::XBoneInfo) * lengthOfFirstPart);
std::memcpy(newQuats, model->quats, sizeof(uint16_t) * 4 * lengthOfFirstPartM1);
std::memcpy(newTrans, model->trans, sizeof(float) * 3 * lengthOfFirstPartM1);
}
// Insert new bone
{
unsigned int name = Game::SL_GetString(boneName.data(), 0);
Game::XBoneInfo boneInfo{};
Game::DObjAnimMat mat{};
// It's ABSOLUTE!
mat = model->baseMat[parentIndex];
boneInfo = model->boneInfo[parentIndex];
// It's RELATIVE !
uint16_t quat[4]{};
quat[3] = SHRT_MAX; // 0 0 0 1
float trans[3]{};
mat.transWeight = 1.9999f; // Should be 1.9999 like everybody?
newMats[newBoneIndex] = mat;
newBoneInfo[newBoneIndex] = boneInfo;
newBoneNames[newBoneIndex] = static_cast<uint16_t>(name);
// TODO parts Classification
std::memcpy(&newQuats[newBoneIndexMinusRoot * 4], quat, ARRAYSIZE(quat) * sizeof(uint16_t));
std::memcpy(&newTrans[newBoneIndexMinusRoot * 3], trans, ARRAYSIZE(trans) * sizeof(float));
}
// Copy after
if (lengthOfSecondPart > 0)
{
std::memcpy(&newBoneNames[atPosition + 1], &model->boneNames[atPosition], sizeof(uint16_t) * lengthOfSecondPart);
std::memcpy(&newMats[atPosition + 1], &model->baseMat[atPosition], sizeof(Game::DObjAnimMat) * lengthOfSecondPart);
std::memcpy(&newPartsClassification[atPosition + 1], &model->partClassification[atPosition], lengthOfSecondPart);
std::memcpy(&newBoneInfo[atPosition + 1], &model->boneInfo[atPosition], sizeof(Game::XBoneInfo) * lengthOfSecondPart);
std::memcpy(&newQuats[(atPositionM1 + 1) * 4], &model->quats[atPositionM1 * 4], sizeof(uint16_t) * 4 * lengthOfSecondPartM1);
std::memcpy(&newTrans[(atPositionM1 + 1) * 3], &model->trans[atPositionM1 * 3], sizeof(float) * 3 * lengthOfSecondPartM1);
}
//Game::Z_VirtualFree(model->baseMat);
//Game::Z_VirtualFree(model->boneInfo);
//Game::Z_VirtualFree(model->boneNames);
//Game::Z_VirtualFree(model->quats);
//Game::Z_VirtualFree(model->trans);
//Game::Z_VirtualFree(model->parentList);
// Assign reallocated
model->baseMat = newMats;
model->boneInfo = newBoneInfo;
model->boneNames = newBoneNames;
model->quats = newQuats;
model->trans = newTrans;
model->parentList = newParentList;
model->numBones = newBoneCount;
// Update vertex weight
for (uint8_t lodIndex = 0; lodIndex < model->numLods; lodIndex++)
{
const auto lod = &model->lodInfo[lodIndex];
if ((lod->partBits[5] & 0x1) == 0x1)
{
// surface lod already converted (more efficient)
continue;
}
if (GetHighestAffectingBoneIndex(lod) >= model->numBones)
{
// surface lod already converted (more accurate)
continue;
}
for (int surfIndex = 0; surfIndex < lod->modelSurfs->numsurfs; surfIndex++)
{
auto vertsBlendOffset = 0u;
const auto surface = &lod->modelSurfs->surfs[surfIndex];
static_assert(sizeof(Game::DObjSkelMat) == 64);
{
const auto fixVertexBlendIndex = [&](unsigned int offset) {
int index = static_cast<int>(surface->vertInfo.vertsBlend[offset] / sizeof(Game::DObjSkelMat));
if (index >= atPosition)
{
index++;
if (index < 0 || index >= model->numBones)
{
Components::Logger::Print("Unexpected 'bone index' {} out of {} bones while working vertex blend of model {} lod {} surf {}\n", index, model->numBones, model->name, lodIndex, surfIndex);
assert(false);
}
surface->vertInfo.vertsBlend[offset] = static_cast<unsigned short>(index * sizeof(Game::DObjSkelMat));
}
};
// Fix bone offsets
if (surface->vertList)
{
for (auto vertListIndex = 0u; vertListIndex < surface->vertListCount; vertListIndex++)
{
const auto vertList = &surface->vertList[vertListIndex];
auto index = vertList->boneOffset / sizeof(Game::DObjSkelMat);
if (index >= atPosition)
{
index++;
if (index < 0 || index >= model->numBones)
{
Components::Logger::Print("Unexpected 'bone index' {} out of {} bones while working list blend of model {} lod {} surf {}\n", index, model->numBones, model->name, lodIndex, surfIndex);
assert(false);
}
vertList->boneOffset = static_cast<unsigned short>(index * sizeof(Game::DObjSkelMat));
}
}
}
// 1 bone weight
for (auto vertIndex = 0; vertIndex < surface->vertInfo.vertCount[0]; vertIndex++)
{
fixVertexBlendIndex(vertsBlendOffset + 0);
vertsBlendOffset += 1;
}
// 2 bone weights
for (auto vertIndex = 0; vertIndex < surface->vertInfo.vertCount[1]; vertIndex++)
{
fixVertexBlendIndex(vertsBlendOffset + 0);
fixVertexBlendIndex(vertsBlendOffset + 1);
vertsBlendOffset += 3;
}
// 3 bone weights
for (auto vertIndex = 0; vertIndex < surface->vertInfo.vertCount[2]; vertIndex++)
{
fixVertexBlendIndex(vertsBlendOffset + 0);
fixVertexBlendIndex(vertsBlendOffset + 1);
fixVertexBlendIndex(vertsBlendOffset + 3);
vertsBlendOffset += 5;
}
// 4 bone weights
for (auto vertIndex = 0; vertIndex < surface->vertInfo.vertCount[3]; vertIndex++)
{
fixVertexBlendIndex(vertsBlendOffset + 0);
fixVertexBlendIndex(vertsBlendOffset + 1);
fixVertexBlendIndex(vertsBlendOffset + 3);
fixVertexBlendIndex(vertsBlendOffset + 5);
vertsBlendOffset += 7;
}
}
}
}
SetParentIndexOfBone(model, atPosition, parentIndex);
// Restore parents
for (const auto& kv : parentsToRestore)
{
// Fix parents
const auto key = kv.first;
const auto beforeVal = kv.second;
const auto parentIndex = GetIndexOfBone(model, beforeVal);
const auto index = GetIndexOfBone(model, key);
SetParentIndexOfBone(model, index, parentIndex);
}
#if DEBUG
// check
for (const auto& kv : parentsToRestore)
{
const auto key = kv.first;
const auto beforeVal = kv.second;
const auto index = GetIndexOfBone(model, key);
const auto afterVal = GetParentOfBone(model, index);
if (beforeVal != afterVal)
{
printf("");
}
}
//
#endif
return atPosition; // Bone index of added bone
};
void IXModel::TransferWeights(Game::XModel* model, const uint8_t origin, const uint8_t destination)
{
const auto from = Game::SL_ConvertToString(model->boneNames[origin]);
const auto to = Game::SL_ConvertToString(model->boneNames[destination]);
Components::Logger::Print("Transferring bone weights from {} to {}\n", from, to);
const auto originalWeights = model->baseMat[origin].transWeight;
model->baseMat[origin].transWeight = model->baseMat[destination].transWeight;
model->baseMat[destination].transWeight = originalWeights;
for (int i = 0; i < model->numLods; i++)
{
const auto lod = &model->lodInfo[i];
if ((lod->partBits[5] & 0x1) == 0x1)
{
// surface lod already converted (more efficient)
continue;
}
for (int surfIndex = 0; surfIndex < lod->modelSurfs->numsurfs; surfIndex++)
{
auto vertsBlendOffset = 0u;
const auto surface = &lod->modelSurfs->surfs[surfIndex];
{
const auto transferVertexBlendIndex = [&](unsigned int offset) {
int index = static_cast<int>(surface->vertInfo.vertsBlend[offset] / sizeof(Game::DObjSkelMat));
if (index == origin)
{
index = destination;
if (index < 0 || index >= model->numBones)
{
assert(false);
}
surface->vertInfo.vertsBlend[offset] = static_cast<unsigned short>(index * sizeof(Game::DObjSkelMat));
}
};
// Fix bone offsets
if (surface->vertList)
{
for (auto vertListIndex = 0u; vertListIndex < surface->vertListCount; vertListIndex++)
{
const auto vertList = &surface->vertList[vertListIndex];
auto index = vertList->boneOffset / sizeof(Game::DObjSkelMat);
if (index == origin)
{
if (index < 0 || index >= model->numBones)
{
assert(false);
}
index = destination;
vertList->boneOffset = static_cast<unsigned short>(index * sizeof(Game::DObjSkelMat));
}
}
}
// 1 bone weight
for (auto vertIndex = 0; vertIndex < surface->vertInfo.vertCount[0]; vertIndex++)
{
transferVertexBlendIndex(vertsBlendOffset + 0);
vertsBlendOffset += 1;
}
// 2 bone weights
for (auto vertIndex = 0; vertIndex < surface->vertInfo.vertCount[1]; vertIndex++)
{
transferVertexBlendIndex(vertsBlendOffset + 0);
transferVertexBlendIndex(vertsBlendOffset + 1);
vertsBlendOffset += 3;
}
// 3 bone weights
for (auto vertIndex = 0; vertIndex < surface->vertInfo.vertCount[2]; vertIndex++)
{
transferVertexBlendIndex(vertsBlendOffset + 0);
transferVertexBlendIndex(vertsBlendOffset + 1);
transferVertexBlendIndex(vertsBlendOffset + 3);
vertsBlendOffset += 5;
}
// 4 bone weights
for (auto vertIndex = 0; vertIndex < surface->vertInfo.vertCount[3]; vertIndex++)
{
transferVertexBlendIndex(vertsBlendOffset + 0);
transferVertexBlendIndex(vertsBlendOffset + 1);
transferVertexBlendIndex(vertsBlendOffset + 3);
transferVertexBlendIndex(vertsBlendOffset + 5);
vertsBlendOffset += 7;
}
}
}
}
};
void IXModel::SetBoneTrans(Game::XModel* model, uint8_t boneIndex, bool baseMat, float x, float y, float z)
{
if (baseMat)
{
model->baseMat[boneIndex].trans[0] = x;
model->baseMat[boneIndex].trans[1] = y;
model->baseMat[boneIndex].trans[2] = z;
}
else
{
const auto index = boneIndex - model->numRootBones;
assert(index >= 0);
model->trans[index * 3 + 0] = x;
model->trans[index * 3 + 1] = y;
model->trans[index * 3 + 2] = z;
}
}
void IXModel::SetBoneQuaternion(Game::XModel* model, uint8_t boneIndex, bool baseMat, float x, float y, float z, float w)
{
if (baseMat)
{
model->baseMat[boneIndex].quat[0] = x;
model->baseMat[boneIndex].quat[1] = y;
model->baseMat[boneIndex].quat[2] = z;
model->baseMat[boneIndex].quat[3] = w;
}
else
{
const auto index = boneIndex - model->numRootBones;
assert(index >= 0);
model->quats[index * 4 + 0] = static_cast<uint16_t>(x * SHRT_MAX);
model->quats[index * 4 + 1] = static_cast<uint16_t>(y * SHRT_MAX);
model->quats[index * 4 + 2] = static_cast<uint16_t>(z * SHRT_MAX);
model->quats[index * 4 + 3] = static_cast<uint16_t>(w * SHRT_MAX);
}
}
void IXModel::ConvertPlayerModelFromSingleplayerToMultiplayer(Game::XModel* model, Utils::Memory::Allocator& allocator)
{
auto indexOfSpine = GetIndexOfBone(model, "j_spinelower");
if (indexOfSpine < UCHAR_MAX) // It has a spine so it must be some sort of humanoid
{
const auto nameOfParent = GetParentOfBone(model, indexOfSpine);
if (GetIndexOfBone(model, "torso_stabilizer") == UCHAR_MAX) // Singleplayer model is likely
{
Components::Logger::Print("Converting {}\n", model->name);
// No stabilizer - let's do surgery
// We're trying to get there:
// tag_origin
// j_main_root
// pelvis
// j_hip_le
// j_hip_ri
// tag_stowed_hip_rear
// torso_stabilizer
// j_spinelower
// back_low
// j_spineupper
// back_mid
// j_spine4
const auto root = GetIndexOfBone(model, "j_mainroot");
if (root < UCHAR_MAX) {
// Add pelvis
#if false
const uint8_t backLow = InsertBone(model, "back_low", "j_spinelower", allocator);
TransferWeights(model, GetIndexOfBone(model, "j_spinelower"), backLow);
SetParentIndexOfBone(model, GetIndexOfBone(model, "j_spineupper"), backLow);
#else
const uint8_t indexOfPelvis = InsertBone(model, "pelvis", "j_mainroot", allocator);
SetBoneQuaternion(model, indexOfPelvis, true, -0.494f, -0.506f, -0.506f, 0.494);
TransferWeights(model, root, indexOfPelvis);
SetParentIndexOfBone(model, GetIndexOfBone(model, "j_hip_le"), indexOfPelvis);
SetParentIndexOfBone(model, GetIndexOfBone(model, "j_hip_ri"), indexOfPelvis);
SetParentIndexOfBone(model, GetIndexOfBone(model, "tag_stowed_hip_rear"), indexOfPelvis);
// These two are optional
if (GetIndexOfBone(model, "j_coatfront_le") == UCHAR_MAX)
{
InsertBone(model, "j_coatfront_le", "pelvis", allocator);
}
if (GetIndexOfBone(model, "j_coatfront_ri") == UCHAR_MAX)
{
InsertBone(model, "j_coatfront_ri", "pelvis", allocator);
}
const uint8_t torsoStabilizer = InsertBone(model, "torso_stabilizer", "pelvis", allocator);
const uint8_t lowerSpine = GetIndexOfBone(model, "j_spinelower");
SetParentIndexOfBone(model, lowerSpine, torsoStabilizer);
const uint8_t backLow = InsertBone(model, "back_low", "j_spinelower", allocator);
TransferWeights(model, lowerSpine, backLow);
SetParentIndexOfBone(model, GetIndexOfBone(model, "j_spineupper"), backLow);
const uint8_t backMid = InsertBone(model, "back_mid", "j_spineupper", allocator);
TransferWeights(model, GetIndexOfBone(model, "j_spineupper"), backMid);
SetParentIndexOfBone(model, GetIndexOfBone(model, "j_spine4"), backMid);
assert(root == GetIndexOfBone(model, "j_mainroot"));
assert(indexOfPelvis == GetIndexOfBone(model, "pelvis"));
assert(backLow == GetIndexOfBone(model, "back_low"));
assert(backMid == GetIndexOfBone(model, "back_mid"));
// Twister bone
SetBoneQuaternion(model, lowerSpine, false, -0.492f, -0.507f, -0.507f, 0.492f);
SetBoneQuaternion(model, torsoStabilizer, false, 0.494f, 0.506f, 0.506f, 0.494f);
// This doesn't feel like it should be necessary
// It is, on singleplayer models unfortunately. Could we add an extra bone to compensate this?
// Or compensate it another way?
SetBoneTrans(model, GetIndexOfBone(model, "j_spinelower"), false, 0.07, 0.0f, 5.2f);
// These are often messed up on civilian models, but there is no obvious way to tell from code
const auto stowedBack = GetIndexOfBone(model, "tag_stowed_back");
if (stowedBack != UCHAR_MAX)
{
SetBoneTrans(model, stowedBack, false, -0.32f, -6.27f, -2.65F);
SetBoneQuaternion(model, stowedBack, false, -0.044, 0.088, -0.995, 0.025);
SetBoneTrans(model, stowedBack, true, -9.571f, -2.654f, 51.738f);
SetBoneQuaternion(model, stowedBack, true, -0.071f, 0.0f, -0.997f, 0.0f);
}
if (stowedBack != UCHAR_MAX)
{
const auto stowedRear = GetIndexOfBone(model, "tag_stowed_hip_rear");
SetBoneTrans(model, stowedRear, false, -0.75f, -6.45f, -4.99f);
SetBoneQuaternion(model, stowedRear, false, -0.553f, -0.062f, -0.049f, 0.830f);
SetBoneTrans(model, stowedBack, true, -9.866f, -4.989f, 36.315f);
SetBoneQuaternion(model, stowedRear, true, -0.054, -0.025f, -0.975f, 0.214f);
}
#endif
RebuildPartBits(model);
}
}
printf("");
}
}
}