[MapDump] Introduce proper map dumping

This commit is contained in:
Maurice Heumann 2019-12-28 11:43:41 +01:00
parent 15f38141d3
commit dd42a06151
11 changed files with 504 additions and 178 deletions

View File

@ -40,7 +40,6 @@
| `--disable-bitmessage` | Disable use of BitMessage completely. |
| `--disable-base128` | Disable base128 encoding for minidumps. |
| `--no-new-structure` | Do not use new virtual path structure (separating headers and source files). |
| `--enable-dxsdk` | Enable DirectX SDK (required for GfxMap exporting). |
## Disclaimer

View File

@ -74,11 +74,6 @@ newoption {
description = "Upload minidumps even for Debug builds."
}
newoption {
trigger = "enable-dxsdk",
description = "Enable DirectX SDK (required for GfxMap exporting)."
}
newaction {
trigger = "version",
description = "Returns the version string for the current commit of the source code.",
@ -329,11 +324,6 @@ workspace "iw4x"
if _OPTIONS["force-exception-handler"] then
defines { "FORCE_EXCEPTION_HANDLER" }
end
--if _OPTIONS["enable-dxsdk"] then
defines { "ENABLE_DXSDK" }
-- includedirs { "%DXSDK_DIR%Include" }
-- libdirs { "%DXSDK_DIR%Lib/x86" }
--end
-- Pre-compiled header
pchheader "STDInclude.hpp" -- must be exactly same as used in #include directives

View File

@ -60,6 +60,7 @@ namespace Components
Loader::Register(new Console());
Loader::Register(new Friends());
Loader::Register(new IPCPipe());
Loader::Register(new MapDump());
Loader::Register(new ModList());
Loader::Register(new Monitor());
Loader::Register(new Network());

View File

@ -93,6 +93,7 @@ namespace Components
#include "Modules/Logger.hpp"
#include "Modules/Friends.hpp"
#include "Modules/IPCPipe.hpp"
#include "Modules/MapDump.hpp"
#include "Modules/Session.hpp"
#include "Modules/ClanTags.hpp"
#include "Modules/Download.hpp"

View File

@ -0,0 +1,451 @@
#include "STDInclude.hpp"
namespace Components
{
class MapDumper
{
public:
MapDumper(Game::GfxWorld* world) : world_(world)
{
}
void dump()
{
if (!this->world_) return;
Logger::Print("Exporting '%s'...\n", this->world_->baseName);
this->parseVertices();
this->parseFaces();
this->parseStaticModels();
this->write();
}
private:
struct Vertex
{
Game::vec3_t coordinate;
Game::vec2_t texture;
Game::vec3_t normal;
};
struct Face
{
int a{};
int b{};
int c{};
};
struct FaceList
{
std::vector<Face> indices{};
};
class File
{
public:
File() {}
File(const std::string& file)
{
Utils::IO::WriteFile(file, {});
this->stream_ = std::ofstream(file, std::ofstream::out);
}
void append(const std::string& str)
{
this->stream_.write(str.data(), str.size());
}
private:
std::ofstream stream_{};
};
Game::GfxWorld* world_{};
std::vector<Vertex> vertices_{};
std::unordered_map<Game::Material*, FaceList> faces_{};
std::vector<Game::Material*> facesOrder_{};
File object_{};
File material_{};
void transformAxes(Game::vec3_t& vec) const
{
std::swap(vec[0], vec[1]);
std::swap(vec[1], vec[2]);
}
void parseVertices()
{
Logger::Print("Parsing vertices...\n");
for (unsigned int i = 0; i < this->world_->draw.vertexCount; ++i)
{
const auto* vertex = &this->world_->draw.vd.vertices[i];
Vertex v{};
v.coordinate[0] = vertex->xyz[0];
v.coordinate[1] = vertex->xyz[1];
v.coordinate[2] = vertex->xyz[2];
this->transformAxes(v.coordinate);
v.texture[0] = vertex->texCoord[0];
v.texture[1] = -vertex->texCoord[1];
Game::Vec3UnpackUnitVec(vertex->normal, &v.normal);
this->transformAxes(v.normal);
this->vertices_.push_back(v);
}
}
void parseFaces()
{
Logger::Print("Parsing faces...\n");
for (unsigned int i = 0; i < this->world_->dpvs.staticSurfaceCount; ++i)
{
const auto* surface = &this->world_->dpvs.surfaces[i];
const unsigned int vertOffset = surface->tris.firstVertex + 1;
const unsigned int indexOffset = surface->tris.baseIndex;
auto& f = this->getFaceList(surface->material);
for (unsigned short j = 0; j < surface->tris.triCount; ++j)
{
Face face{};
face.a = this->world_->draw.indices[indexOffset + j * 3 + 0] + vertOffset;
face.b = this->world_->draw.indices[indexOffset + j * 3 + 1] + vertOffset;
face.c = this->world_->draw.indices[indexOffset + j * 3 + 2] + vertOffset;
f.indices.push_back(face);
}
}
}
FaceList& getFaceList(Game::Material* material)
{
auto& faceList = this->faces_[material];
if (this->facesOrder_.size() < this->faces_.size())
{
this->facesOrder_.push_back(material);
}
return faceList;
}
void performWorldTransformation(const Game::GfxPackedPlacement& placement, Vertex& v) const
{
Game::MatrixVecMultiply(placement.axis, v.normal, v.normal);
Game::Vec3Normalize(v.normal);
Game::MatrixVecMultiply(placement.axis, v.coordinate, v.coordinate);
v.coordinate[0] = v.coordinate[0] * placement.scale + placement.origin[0];
v.coordinate[1] = v.coordinate[1] * placement.scale + placement.origin[1];
v.coordinate[2] = v.coordinate[2] * placement.scale + placement.origin[2];
}
std::vector<Vertex> parseSurfaceVertices(const Game::XSurface* surface, const Game::GfxPackedPlacement& placement)
{
std::vector<Vertex> vertices;
for (unsigned short j = 0; j < surface->vertCount; j++)
{
const auto *vertex = &surface->verts0[j];
Vertex v{};
v.coordinate[0] = vertex->xyz[0];
v.coordinate[1] = vertex->xyz[1];
v.coordinate[2] = vertex->xyz[2];
// Why...
Game::Vec2UnpackTexCoords(vertex->texCoord, &v.texture);
std::swap(v.texture[0], v.texture[1]);
v.texture[1] *= -1;
Game::Vec3UnpackUnitVec(vertex->normal, &v.normal);
this->performWorldTransformation(placement, v);
this->transformAxes(v.coordinate);
this->transformAxes(v.normal);
vertices.push_back(v);
}
return vertices;
}
std::vector<Face> parseSurfaceFaces(const Game::XSurface* surface) const
{
std::vector<Face> faces;
for (unsigned short j = 0; j < surface->triCount; ++j)
{
Face face{};
face.a = surface->triIndices[j * 3 + 0];
face.b = surface->triIndices[j * 3 + 1];
face.c = surface->triIndices[j * 3 + 2];
faces.push_back(face);
}
return faces;
}
void removeVertex(const int index, std::vector<Face>& faces, std::vector<Vertex>& vertices) const
{
vertices.erase(vertices.begin() + index);
for (auto &face : faces)
{
if (face.a > index) --face.a;
if (face.b > index) --face.b;
if (face.c > index) --face.c;
}
}
void filterSurfaceVertices(std::vector<Face>& faces, std::vector<Vertex>& vertices) const
{
for (auto i = 0; i < int(vertices.size()); ++i)
{
auto referenced = false;
for (const auto &face : faces)
{
if (face.a == i || face.b == i || face.c == i)
{
referenced = true;
break;
}
}
if (!referenced)
{
this->removeVertex(i--, faces, vertices);
}
}
}
void parseStaticModel(Game::GfxStaticModelDrawInst* model)
{
for (unsigned char i = 0; i < model->model->numsurfs; ++i)
{
this->getFaceList(model->model->materialHandles[i]);
}
const auto* lod = &model->model->lodInfo[model->model->numLods - 1];
const auto baseIndex = this->vertices_.size() + 1;
const auto surfIndex = lod->surfIndex;
assert(lod->modelSurfs->numsurfs <= model->model->numsurfs);
for (unsigned short i = 0; i < lod->modelSurfs->numsurfs; ++i)
{
// TODO: Something is still wrong about the models. Probably baseTriIndex and baseVertIndex might help
const auto* surface = &lod->modelSurfs->surfs[i];
auto faces = this->parseSurfaceFaces(surface);
auto vertices = this->parseSurfaceVertices(surface, model->placement);
this->filterSurfaceVertices(faces, vertices);
auto& f = this->getFaceList(model->model->materialHandles[i + surfIndex]);
for (const auto& vertex : vertices)
{
this->vertices_.push_back(vertex);
}
for (auto face : faces)
{
face.a += baseIndex;
face.b += baseIndex;
face.c += baseIndex;
f.indices.push_back(std::move(face));
}
}
}
void parseStaticModels()
{
Logger::Print("Parsing static models...\n");
for (unsigned i = 0u; i < this->world_->dpvs.smodelCount; ++i)
{
this->parseStaticModel(this->world_->dpvs.smodelDrawInsts + i);
}
}
void write()
{
this->object_ = File(Utils::String::VA("raw/mapdump/%s/%s.obj", this->world_->baseName, this->world_->baseName));
this->material_ = File(Utils::String::VA("raw/mapdump/%s/%s.mtl", this->world_->baseName, this->world_->baseName));
this->object_.append("# Generated by IW4x\n");
this->object_.append("# Credit to SE2Dev for his D3DBSP Tool\n");
this->object_.append(Utils::String::VA("o %s\n", this->world_->baseName));
this->object_.append(Utils::String::VA("mtllib %s.mtl\n\n", this->world_->baseName));
this->material_.append("# IW4x MTL File\n");
this->material_.append("# Credit to SE2Dev for his D3DBSP Tool\n");
this->writeVertices();
this->writeFaces();
Logger::Print("Writing files...\n");
this->object_ = {};
this->material_ = {};
}
void writeVertices()
{
Logger::Print("Writing vertices...\n");
this->object_.append("# Vertices\n");
for (const auto& vertex : this->vertices_)
{
this->object_.append(Utils::String::VA("v %.6f %.6f %.6f\n", vertex.coordinate[0], vertex.coordinate[1], vertex.coordinate[2]));
}
Logger::Print("Writing texture coordinates...\n");
this->object_.append("\n# Texture coordinates\n");
for (const auto& vertex : this->vertices_)
{
this->object_.append(Utils::String::VA("vt %.6f %.6f\n", vertex.texture[0], vertex.texture[1]));
}
Logger::Print("Writing normals...\n");
this->object_.append("\n# Normals\n");
for (const auto& vertex : this->vertices_)
{
this->object_.append(Utils::String::VA("vn %.6f %.6f %.6f\n", vertex.normal[0], vertex.normal[1], vertex.normal[2]));
}
this->object_.append("\n");
}
void writeMaterial(Game::Material* material)
{
std::string name = material->info.name;
const auto pos = name.find_last_of('/');
if (pos != std::string::npos)
{
name = name.substr(pos + 1);
}
this->object_.append(Utils::String::VA("usemtl %s\n", name.data()));
this->object_.append("s off\n");
Game::GfxImage *image = nullptr;
for (char l = 0; l < material->textureCount; ++l)
{
if (material->textureTable[l].nameStart == 'c' && material->textureTable[l].nameEnd == 'p')
{
image = material->textureTable[l].u.image; // Hopefully our colorMap
}
}
if (!image)
{
Logger::Print("Failed to get color map for material: %s\n", material->info.name);
return;
}
// TODO: This is still wrong.
if (image->mapType == 5 && false)
{
for (auto i = 0; i < 6; ++i)
{
IDirect3DSurface9* surface = nullptr;
image->texture.cubemap->GetCubeMapSurface(D3DCUBEMAP_FACES(i), 0, &surface);
if (surface)
{
std::string _name = Utils::String::VA("raw/mapdump/%s/textures/%s_%i.png", this->world_->baseName, image->name, i);
D3DXSaveSurfaceToFileA(_name.data(), D3DXIFF_PNG, surface, nullptr, nullptr);
surface->Release();
}
}
}
else
{
std::string _name = Utils::String::VA("raw/mapdump/%s/textures/%s.png", this->world_->baseName, image->name);
D3DXSaveTextureToFileA(_name.data(), D3DXIFF_PNG, image->texture.map, nullptr);
}
this->material_.append(Utils::String::VA("\nnewmtl %s\n", name.data()));
this->material_.append("Ka 1.0000 1.0000 1.0000\n");
this->material_.append("Kd 1.0000 1.0000 1.0000\n");
this->material_.append("illum 1\n");
this->material_.append(Utils::String::VA("map_Ka textures/%s.png\n", image->name));
this->material_.append(Utils::String::VA("map_Kd textures/%s.png\n", image->name));
}
void writeFaces()
{
Logger::Print("Writing faces...\n");
Utils::IO::CreateDir(Utils::String::VA("raw/mapdump/%s/textures", this->world_->baseName));
this->material_.append(Utils::String::VA("# Material count: %d\n", this->faces_.size()));
this->object_.append("# Faces\n");
for (const auto& material : this->facesOrder_)
{
this->writeMaterial(material);
const auto& faces = this->getFaceList(material);
for (const auto& index : faces.indices)
{
const int a = index.a;
const int b = index.b;
const int c = index.c;
this->object_.append(Utils::String::VA("f %d/%d/%d %d/%d/%d %d/%d/%d\n", a, a, a, b, b, b, c, c, c));
}
this->object_.append("\n");
}
}
};
MapDump::MapDump()
{
Command::Add("dumpmap", [](Command::Params*)
{
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled())
{
Logger::Print("DirectX needs to be enabled, please start a client to use this command!\n");
return;
}
Game::GfxWorld* world = nullptr;
Game::DB_EnumXAssets(Game::XAssetType::ASSET_TYPE_GFXWORLD, [](Game::XAssetHeader header, void* world)
{
*reinterpret_cast<Game::GfxWorld**>(world) = header.gfxWorld;
}, &world, false);
if (world)
{
MapDumper dumper(world);
dumper.dump();
Logger::Print("Map '%s' exported!\n", world->baseName);
}
else
{
Logger::Print("No map loaded, unable to dump anything!\n");
}
});
}
}

View File

@ -0,0 +1,10 @@
#pragma once
namespace Components
{
class MapDump : public Component
{
public:
MapDump();
};
}

View File

@ -524,140 +524,6 @@ namespace Components
}
}
#if defined(DEBUG) && defined(ENABLE_DXSDK)
// Credit to SE2Dev, as we shouldn't share the code, keep that in debug mode!
void Maps::ExportMap(Game::GfxWorld* world)
{
Utils::Memory::Allocator allocator;
if (!world) return;
Logger::Print("Exporting '%s'...\n", world->baseName);
std::string mtl;
mtl.append("# IW4x MTL File\n");
mtl.append("# Credit to SE2Dev for his D3DBSP Tool\n");
std::string map;
map.append("# Generated by IW4x\n");
map.append("# Credit to SE2Dev for his D3DBSP Tool\n");
map.append(Utils::String::VA("o %s\n", world->baseName));
map.append(Utils::String::VA("mtllib %s.mtl\n\n", world->baseName));
Logger::Print("Writing vertices...\n");
for (unsigned int i = 0; i < world->draw.vertexCount; ++i)
{
float x = world->draw.vd.vertices[i].xyz[1];
float y = world->draw.vd.vertices[i].xyz[2];
float z = world->draw.vd.vertices[i].xyz[0];
map.append(Utils::String::VA("v %.6f %.6f %.6f\n", x, y, z));
}
map.append("\n");
Logger::Print("Writing texture coordinates...\n");
for (unsigned int i = 0; i < world->draw.vertexCount; ++i)
{
map.append(Utils::String::VA("vt %.6f %.6f\n", world->draw.vd.vertices[i].texCoord[0], -world->draw.vd.vertices[i].texCoord[1]));
}
Logger::Print("Writing normals...\n");
for (unsigned int i = 0; i < world->draw.vertexCount; ++i)
{
Game::vec3_t normal;
Game::Vec3UnpackUnitVec(world->draw.vd.vertices[i].normal, &normal);
map.append(Utils::String::VA("vn %.6f %.6f %.6f\n", normal[0], normal[1], normal[2]));
}
map.append("\n");
Logger::Print("Searching materials...\n");
int materialCount = 0;
Game::Material** materials = allocator.allocateArray<Game::Material*>(world->dpvs.staticSurfaceCount);
for (unsigned int i = 0; i < world->dpvs.staticSurfaceCount; ++i)
{
bool isNewMat = true;
for (int j = 0; j < materialCount; ++j)
{
if (world->dpvs.surfaces[i].material == materials[j])
{
isNewMat = false;
break;
}
}
if (isNewMat)
{
materials[materialCount++] = world->dpvs.surfaces[i].material;
}
}
Utils::IO::CreateDir(Utils::String::VA("raw/mapdump/%s/textures", world->baseName));
mtl.append(Utils::String::VA("# Material Count: %d\n", materialCount));
Logger::Print("Exporting materials and faces...\n");
for (int m = 0; m < materialCount; ++m)
{
std::string name = materials[m]->info.name;
auto pos = name.find_last_of("/");
if (pos != std::string::npos)
{
name = name.substr(pos + 1);
}
map.append(Utils::String::VA("\nusemtl %s\n", name.data()));
map.append("s off\n");
Game::GfxImage* image = materials[m]->textureTable[0].u.image;
for (char l = 0; l < materials[m]->textureCount; ++l)
{
if (materials[m]->textureTable[l].nameStart == 'c')
{
if (materials[m]->textureTable[l].nameEnd == 'p')
{
image = materials[m]->textureTable[l].u.image; // Hopefully our colorMap
}
}
}
std::string _name = Utils::String::VA("raw/mapdump/%s/textures/%s.png", world->baseName, image->name);
D3DXSaveTextureToFile(std::wstring(_name.begin(), _name.end()).data(), D3DXIFF_PNG, image->texture.map, NULL);
mtl.append(Utils::String::VA("\nnewmtl %s\n", name.data()));
mtl.append("Ka 1.0000 1.0000 1.0000\n");
mtl.append("Kd 1.0000 1.0000 1.0000\n");
mtl.append("illum 1\n");
mtl.append(Utils::String::VA("map_Ka textures/%s.png\n", image->name));
mtl.append(Utils::String::VA("map_Kd textures/%s.png\n", image->name));
for (unsigned int i = 0; i < world->dpvs.staticSurfaceCount; ++i)
{
if (world->dpvs.surfaces[i].material != materials[m])
continue;
int vertOffset = world->dpvs.surfaces[i].tris.firstVertex + 1;//+1 cus obj starts at 1
int indexOffset = world->dpvs.surfaces[i].tris.baseIndex;
for (unsigned short j = 0; j < world->dpvs.surfaces[i].tris.triCount; ++j)
{
int a = world->draw.indices[indexOffset + j * 3 + 0] + vertOffset;
int b = world->draw.indices[indexOffset + j * 3 + 1] + vertOffset;
int c = world->draw.indices[indexOffset + j * 3 + 2] + vertOffset;
map.append(Utils::String::VA("f %d/%d/%d %d/%d/%d %d/%d/%d\n", a, a, a, b, b, b, c, c, c));
}
}
}
Logger::Print("Writing final files...\n");
Utils::IO::WriteFile(Utils::String::VA("raw/mapdump/%s/%s.mtl", world->baseName, world->baseName), mtl);
Utils::IO::WriteFile(Utils::String::VA("raw/mapdump/%s/%s.obj", world->baseName, world->baseName), map);
}
#endif
void Maps::AddDlc(Maps::DLC dlc)
{
for (auto& pack : Maps::DlcPacks)
@ -1001,33 +867,6 @@ namespace Components
//Maps::AddDependency("co_hunted", "mp_storm");
//Maps::AddDependency("mp_shipment", "mp_shipment_long");
#if defined(DEBUG) && defined(ENABLE_DXSDK)
Command::Add("dumpmap", [](Command::Params*)
{
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled())
{
Logger::Print("DirectX needs to be enabled, please start a client to use this command!\n");
return;
}
Game::GfxWorld* world = nullptr;
Game::DB_EnumXAssets(Game::XAssetType::ASSET_TYPE_GFXWORLD, [](Game::XAssetHeader header, void* world)
{
*reinterpret_cast<Game::GfxWorld**>(world) = header.gfxWorld;
}, &world, false);
if (world)
{
Maps::ExportMap(world);
Logger::Print("Map '%s' exported!\n", world->baseName);
}
else
{
Logger::Print("No map loaded, unable to dump anything!\n");
}
});
#endif
// Allow hiding specific smodels
Utils::Hook(0x50E67C, Maps::HideModelStub, HOOK_CALL).install()->quick();

View File

@ -100,10 +100,6 @@ namespace Components
static void AddDlc(DLC dlc);
static void UpdateDlcStatus();
#if defined(DEBUG) && defined(ENABLE_DXSDK)
static void ExportMap(Game::GfxWorld* world);
#endif
static void PrepareUsermap(const char* mapname);
static void SpawnServerStub();
static void LoadMapLoadscreenStub();

View File

@ -683,6 +683,43 @@ namespace Game
return atoi(StringTable_Lookup(rankTable, 0, maxrank, 7));
}
void Vec3Normalize(vec3_t& vec)
{
const auto length = std::sqrt(std::pow(vec[0], 2) + std::pow(vec[1], 2) + std::pow(vec[2], 2));
vec[0] /= length;
vec[1] /= length;
vec[2] /= length;
}
void Vec2UnpackTexCoords(const PackedTexCoords in, vec2_t* out)
{
unsigned int v3; // xmm1_4
if (LOWORD(in.packed))
v3 = ((in.packed & 0x8000) << 16) | (((((in.packed & 0x3FFF) << 14) - (~(LOWORD(in.packed) << 14) & 0x10000000)) ^ 0x80000001) >> 1);
else
v3 = 0;
(*out)[0] = *reinterpret_cast<float*>(&v3);
if (HIWORD(in.packed))
v3 = ((HIWORD(in.packed) & 0x8000) << 16) | (((((HIWORD(in.packed) & 0x3FFF) << 14)
- (~(HIWORD(in.packed) << 14) & 0x10000000)) ^ 0x80000001) >> 1);
else
v3 = 0;
(*out)[1] = *reinterpret_cast<float*>(&v3);
}
void MatrixVecMultiply(const float (& mulMat)[3][3], const vec3_t& mulVec, vec3_t& solution)
{
vec3_t res;
res[0] = mulMat[0][0] * mulVec[0] + mulMat[1][0] * mulVec[1] + mulMat[2][0] * mulVec[2];
res[1] = mulMat[0][1] * mulVec[0] + mulMat[1][1] * mulVec[1] + mulMat[2][1] * mulVec[2];
res[2] = mulMat[0][2] * mulVec[0] + mulMat[1][2] * mulVec[1] + mulMat[2][2] * mulVec[2];
std::memmove(&solution[0], &res[0], sizeof(res));
}
void SortWorldSurfaces(GfxWorld* world)
{
DWORD* specular1 = reinterpret_cast<DWORD*>(0x69F105C);

View File

@ -885,6 +885,10 @@ namespace Game
void Image_Setup(GfxImage* image, unsigned int width, unsigned int height, unsigned int depth, unsigned int flags, _D3DFORMAT format);
void Vec3Normalize(vec3_t& vec);
void Vec2UnpackTexCoords(const PackedTexCoords in, vec2_t* out);
void MatrixVecMultiply(const float(&mulMat)[3][3], const vec3_t& mulVec, vec3_t& solution);
void SortWorldSurfaces(GfxWorld* world);
void R_AddDebugLine(float* color, float* v1, float* v2);
void R_AddDebugString(float *color, float *pos, float scale, const char *str);

View File

@ -45,10 +45,8 @@
#pragma warning(pop)
#ifdef ENABLE_DXSDK
#include <d3dx9tex.h>
#pragma comment(lib, "D3dx9.lib")
#endif
// Usefull for debugging
template <size_t S> class Sizer { };