#include #define IW4X_MODEL_VERSION 2 namespace Assets { void IXModel::load(Game::XAssetHeader* header, std::string name, Components::ZoneBuilder::Zone* builder) { Components::FileSystem::File modelFile(fmt::sprintf("xmodel/%s.iw4xModel", name.data())); if (modelFile.exists()) { Game::XModel* baseModel = Components::AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_XMODEL, "viewmodel_mp5k").model; // Allocate new model and copy the base data to it Game::XModel* model = builder->getAllocator()->allocate(); std::memcpy(model, baseModel, sizeof(Game::XModel)); Utils::Stream::Reader reader(builder->getAllocator(), modelFile.getBuffer()); if (reader.read<__int64>() != *reinterpret_cast<__int64*>("IW4xModl")) { Components::Logger::Error(0, "Reading model '%s' failed, header is invalid!", name.data()); } int version = reader.read(); if (version != IW4X_MODEL_VERSION) { Components::Logger::Error(0, "Reading model '%s' failed, expected version is %d, but it was %d!", name.data(), IW4X_MODEL_VERSION, version); } model->name = reader.readCString(); model->numBones = reader.readByte(); model->numRootBones = reader.readByte(); model->numSurfaces = reader.read(); model->numColSurfs = reader.read(); // Read bone names model->boneNames = builder->getAllocator()->allocateArray(model->numBones); for (int i = 0; i < model->numBones; ++i) { model->boneNames[i] = Game::SL_GetString(reader.readCString(), 0); } // Bone count int boneCount = (model->numBones - model->numRootBones); // Read bone data model->parentList = reader.readArray(boneCount); model->tagAngles = reader.readArray(boneCount); model->tagPositions = reader.readArray(boneCount); model->partClassification = reader.readArray(boneCount); model->animMatrix = reader.readArray(boneCount); // Prepare surfaces Game::XSurface* baseSurface = &baseModel->lods[0].surfaces[0].surfaces[0]; Game::XModelSurfs* surf = builder->getAllocator()->allocate(); std::memcpy(surf, baseModel->lods[0].surfaces, sizeof(Game::XModelSurfs)); surf->name = builder->getAllocator()->duplicateString(fmt::sprintf("%s_lod1", model->name)); surf->surfaces = builder->getAllocator()->allocateArray(model->numSurfaces); surf->numSurfaces = model->numSurfaces; // Reset surfaces in remaining lods for (unsigned int i = 1; i < 4; ++i) { ZeroMemory(&model->lods[i], sizeof(Game::XModelLodInfo)); } model->lods[0].dist = reader.read(); model->lods[0].numSurfs = reader.read(); model->lods[0].maxSurfs = reader.read(); model->lods[0].partBits[0] = reader.read(); model->lods[0].partBits[1] = reader.read(); model->lods[0].partBits[2] = reader.read(); model->lods[0].partBits[3] = reader.read(); model->lods[0].numSurfs = model->numSurfaces; // This is needed in case we have more than 1 LOD model->lods[0].surfaces = surf; model->lods[0].surfs = surf->surfaces; model->numLods = 1; // Read surfaces for (int i = 0; i < surf->numSurfaces; ++i) { Game::XSurface* surface = &surf->surfaces[i]; std::memcpy(surface, baseSurface, sizeof(Game::XSurface)); surface->tileMode = reader.read(); surface->deformed = reader.read(); surface->streamHandle = reader.read(); surface->partBits[0] = reader.read(); surface->partBits[1] = reader.read(); surface->partBits[2] = reader.read(); surface->partBits[3] = reader.read(); surface->baseTriIndex = reader.read(); surface->baseVertIndex = reader.read(); surface->numVertices = reader.read(); surface->numPrimitives = reader.read(); surface->numCT = reader.read(); surface->blendNum1 = reader.read(); surface->blendNum2 = reader.read(); surface->blendNum3 = reader.read(); surface->blendNum4 = reader.read(); surface->blendInfo = reinterpret_cast(reader.read(2, surface->blendNum1 + (3 * surface->blendNum2) + (5 * surface->blendNum3) + (7 * surface->blendNum4))); surface->vertexBuffer = reader.readArray(surface->numVertices); surface->indexBuffer = reader.readArray(surface->numPrimitives); // Read vert list if (reader.readByte()) { surface->ct = reader.readArray(surface->numCT); for (int j = 0; j < surface->numCT; ++j) { Game::XRigidVertList* vertList = &surface->ct[j]; vertList->entry = reader.readArray(); vertList->entry->node = reinterpret_cast(reader.read(16, vertList->entry->numNode)); vertList->entry->leaf = reader.readArray(vertList->entry->numLeaf); } } else { surface->ct = nullptr; } } // Read materials model->materials = builder->getAllocator()->allocateArray(model->numSurfaces); for (unsigned char i = 0; i < model->numSurfaces; ++i) { model->materials[i] = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader.readString(), builder).material; } // Read collision surfaces if (reader.readByte()) { model->colSurf = reader.readArray(model->numColSurfs); for (int i = 0; i < model->numColSurfs; ++i) { if (model->colSurf[i].tris) { model->colSurf[i].tris = reader.read(48, model->colSurf[i].count); } } } else { model->colSurf = nullptr; } // Read bone info if (reader.readByte()) { model->boneInfo = reader.readArray(model->numBones); } else { model->boneInfo = nullptr; } if (!reader.end()) { Components::Logger::Error(0, "Reading model '%s' failed, remaining raw data found!", name.data()); } header->model = model; } } 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->materials) { for (unsigned char i = 0; i < asset->numSurfaces; ++i) { if (asset->materials[i]) { builder->loadAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, asset->materials[i]->name); } } } for (int i = 0; i < 4; ++i) { if (asset->lods[i].surfaces) { // We're not supposed to include xmodelsurfs as standalone asset //builder->loadAsset(Game::XAssetType::ASSET_TYPE_XMODELSURFS, asset->lods[i].surfaces->name); IXModelSurfs().mark({ asset->lods[i].surfaces }, builder); } } if (asset->physPreset) { builder->loadAsset(Game::XAssetType::ASSET_TYPE_PHYSPRESET, asset->physPreset->name); } if (asset->physCollmap) { builder->loadAsset(Game::XAssetType::ASSET_TYPE_PHYS_COLLMAP, asset->physCollmap->name); } } 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(); 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(); 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) { buffer->save(asset->parentList, asset->numBones - asset->numRootBones); Utils::Stream::ClearPointer(&dest->parentList); } if (asset->tagAngles) { AssertSize(Game::XModelAngle, 8); buffer->align(Utils::Stream::ALIGN_2); buffer->saveArray(asset->tagAngles, asset->numBones - asset->numRootBones); Utils::Stream::ClearPointer(&dest->tagAngles); } if (asset->tagPositions) { AssertSize(Game::XModelTagPos, 12); buffer->align(Utils::Stream::ALIGN_4); buffer->saveArray(asset->tagPositions, asset->numBones - asset->numRootBones); Utils::Stream::ClearPointer(&dest->tagPositions); } if (asset->partClassification) { buffer->save(asset->partClassification, asset->numBones); Utils::Stream::ClearPointer(&dest->partClassification); } if (asset->animMatrix) { AssertSize(Game::DObjAnimMat, 32); buffer->align(Utils::Stream::ALIGN_4); buffer->saveArray(asset->animMatrix, asset->numBones); Utils::Stream::ClearPointer(&dest->animMatrix); } if (asset->materials) { buffer->align(Utils::Stream::ALIGN_4); Game::Material** destMaterials = buffer->dest(); buffer->saveArray(asset->materials, asset->numSurfaces); for (unsigned char i = 0; i < asset->numSurfaces; ++i) { if (asset->materials[i]) { destMaterials[i] = builder->requireAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, asset->materials[i]->name).material; } } Utils::Stream::ClearPointer(&dest->materials); } // Save_XModelLodInfoArray { AssertSize(Game::XModelLodInfo, 44); for (int i = 0; i < 4; ++i) { if (asset->lods[i].surfaces) { // Requiring this asset is not possible, it has to be loaded as part of the model //dest->lods[i].surfaces = builder->requireAsset(Game::XAssetType::ASSET_TYPE_XMODELSURFS, asset->lods[i].surfaces->name).surfaces; buffer->pushBlock(Game::XFILE_BLOCK_TEMP); buffer->align(Utils::Stream::ALIGN_4); IXModelSurfs().save({ asset->lods[i].surfaces }, builder); Utils::Stream::ClearPointer(&dest->lods[i].surfaces); buffer->popBlock(); } } } // Save_XModelCollSurfArray if (asset->colSurf) { AssertSize(Game::XModelCollSurf, 44); buffer->align(Utils::Stream::ALIGN_4); Game::XModelCollSurf* destColSurfs = buffer->dest(); buffer->saveArray(asset->colSurf, asset->numColSurfs); for (int i = 0; i < asset->numColSurfs; ++i) { Game::XModelCollSurf* destColSurf = &destColSurfs[i]; Game::XModelCollSurf* colSurf = &asset->colSurf[i]; if (colSurf->tris) { buffer->align(Utils::Stream::ALIGN_4); buffer->save(colSurf->tris, 48, colSurf->count); Utils::Stream::ClearPointer(&destColSurf->tris); } } Utils::Stream::ClearPointer(&dest->colSurf); } 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->requireAsset(Game::XAssetType::ASSET_TYPE_PHYSPRESET, asset->physPreset->name).physPreset; } if (asset->physCollmap) { dest->physCollmap = builder->requireAsset(Game::XAssetType::ASSET_TYPE_PHYS_COLLMAP, asset->physCollmap->name).physCollmap; } buffer->popBlock(); } }