Files
zonetool/src/IW4/Assets/Material.cpp
2020-12-16 20:44:46 +01:00

564 lines
14 KiB
C++

// ======================= ZoneTool =======================
// zonetool, a fastfile linker for various
// Call of Duty titles.
//
// Project: https://github.com/ZoneTool/zonetool
// Author: RektInator (https://github.com/RektInator)
// License: GNU GPL v3.0
// ========================================================
#include "stdafx.hpp"
namespace ZoneTool
{
namespace IW4
{
// watermap structures, same across most games.
struct WaterWritable
{
float floatTime;
};
struct complex_s
{
float real;
float imag;
};
struct water_t
{
WaterWritable writable;
complex_s* H0;
float* wTerm;
int M;
int N;
float Lx;
float Lz;
float gravity;
float windvel;
float winddir[2];
float amplitude;
float codeConstant[4];
GfxImage* image;
};
#define MATERIAL_DUMP_STRING(entry) \
matdata[#entry] = std::string(asset->entry);
#define MATERIAL_DUMP_INT(entry) \
matdata[#entry] = asset->entry;
#define MATERIAL_INT(entry) \
mat->entry = matdata[#entry].get<int>();
#define STATEBITENTRYNUM 48
#define MATERIAL_DUMP_BITS_ENTRY(entry,size) \
nlohmann::json carr##entry; \
for (int i = 0; i < size; i++) \
{ \
carr##entry[i] = asset->entry[i]; \
} \
matdata[#entry] = carr##entry;
#define MATERIAL_DUMP_CONST_ARRAY(entry,size) \
nlohmann::json carr##entry; \
for (int i = 0; i < size; i++) \
{ \
nlohmann::json cent##entry; \
cent##entry["name"] = asset->entry[i].name; \
cent##entry["nameHash"] = asset->entry[i].nameHash; \
nlohmann::json centliteral##entry; \
centliteral##entry[0] = asset->entry[i].literal[0]; \
centliteral##entry[1] = asset->entry[i].literal[1]; \
centliteral##entry[2] = asset->entry[i].literal[2]; \
centliteral##entry[3] = asset->entry[i].literal[3]; \
cent##entry["literal"] = centliteral##entry; \
carr##entry[i] = cent##entry; \
} \
matdata[#entry] = carr##entry;
#define MATERIAL_DUMP_STATE_MAP(entry,size) \
nlohmann::json carr##entry; \
for (int i = 0; i < size; i++) \
{ \
nlohmann::json cent##entry; \
cent##entry[0] = asset->entry[i].loadBits[0]; \
cent##entry[1] = asset->entry[i].loadBits[1]; \
carr##entry[i] = cent##entry; \
} \
matdata[#entry] = carr##entry;
// Legacy zonetool code: REFACTOR ME!
enum IWI_COMPRESSION_e
{
IWI_INVALID = 0x0,
IWI_ARGB = 0x1,
IWI_RGB8 = 0x2,
IWI_DXT1 = 0xB,
IWI_DXT3 = 0xC,
IWI_DXT5 = 0xD,
} IWI_COMPRESSION;
enum MaterialMapHashes
{
HASH_COLORMAP = 0xa0ab1041,
HASH_DETAILMAP = 0xeb529b4d,
HASH_SPECULARMAP = 0x34ecccb3,
HASH_NORMALMAP = 0x59d30d0f
};
enum MaterialSemantic
{
SEMANTIC_2D = 0x0,
SEMANTIC_FUNCTION = 0x1,
SEMANTIC_COLOR_MAP = 0x2,
SEMANTIC_NORMAL_MAP = 0x5,
SEMANTIC_SPECULAR_MAP = 0x8,
SEMANTIC_WATER_MAP = 0xB
};
typedef struct
{
char magic[3]; //IWi
char version; // 8
int flags;
short format; // see above
short xsize;
short ysize;
short depth;
int mipAddr4;
int mipAddr3;
int mipAddr2;
int mipAddr1;
} _IWI;
// Image parsing
_IWI* LoadIWIHeader(std::string name, ZoneMemory* mem)
{
auto path = "images\\" + name + ".iwi";
if (FileSystem::FileExists(path))
{
auto buf = mem->Alloc<_IWI>();
auto fp = FileSystem::FileOpen(path, "rb"s);
if (fp)
{
fread(buf, sizeof(_IWI), 1, fp);
}
FileSystem::FileClose(fp);
return buf;
} // ZONETOOL_ERROR("Image \"%s\" does not exist!", name.c_str());
return nullptr;
}
GfxImageLoadDef* GenerateLoadDef(GfxImage* image, short iwi_format, ZoneMemory* mem)
{
auto texture = mem->Alloc<GfxImageLoadDef>();
switch (iwi_format)
{
case IWI_ARGB:
texture->format = 21;
break;
case IWI_RGB8:
texture->format = 20;
break;
case IWI_DXT1:
texture->format = 0x31545844;
break;
case IWI_DXT3:
texture->format = 0x33545844;
break;
case IWI_DXT5:
texture->format = 0x35545844;
break;
}
texture->dimensions[0] = image->width;
texture->dimensions[1] = image->height;
texture->dimensions[2] = image->depth;
return texture;
}
GfxImage* Image_Parse(const char* name, char semantic, char category, char flags,
ZoneMemory* mem)
{
_IWI* buf = LoadIWIHeader(name, mem);
if (buf)
{
auto ret = mem->Alloc<GfxImage>();
ret->height = buf->xsize;
ret->width = buf->ysize;
ret->depth = buf->depth;
ret->dataLen1 = buf->mipAddr4 - 32;
ret->dataLen2 = buf->mipAddr4 - 32;
ret->name = strdup(name);
ret->semantic = semantic;
ret->category = category;
ret->flags = flags;
ret->mapType = 3; // hope that works lol
ret->texture = GenerateLoadDef(ret, buf->format, mem);
return ret;
}
return DB_FindXAssetHeader(image, name).gfximage;
}
MaterialImage* Material_ParseMaps(const std::string& material, nlohmann::json& matdata,
ZoneMemory* mem)
{
auto mat = mem->Alloc<MaterialImage>(matdata.size());
for (std::size_t i = 0; i < matdata.size(); i++)
{
mat[i].firstCharacter = matdata[i]["firstCharacter"].get<char>();
mat[i].secondLastCharacter = matdata[i]["lastCharacter"].get<char>();
mat[i].sampleState = matdata[i]["sampleState"].get<char>();
mat[i].semantic = matdata[i]["semantic"].get<char>();
mat[i].typeHash = matdata[i]["typeHash"].get<unsigned int>();
std::string img = matdata[i]["image"].get<std::string>();
mat[i].image = Image_Parse(img.data(), mat[i].semantic, 0, 0, mem);
if (img.empty())
{
MessageBoxA(nullptr, &va("Image name for material %s seems to be empty!", &material[0])[0], nullptr,
0);
}
}
return mat;
}
unsigned int R_HashString(const char* string)
{
unsigned int hash = 0;
while (*string)
{
hash = (*string | 0x20) ^ (33 * hash);
string++;
}
return hash;
}
__declspec(noinline) Material* IMaterial::parse(std::string name, ZoneMemory* mem)
{
auto path = "materials\\" + name;
auto file = FileSystem::FileOpen(path, "rb"s);
if (!file)
{
return nullptr;
}
ZONETOOL_INFO("Parsing material \"%s\"...", name.c_str());
auto size = FileSystem::FileSize(file);
auto bytes = FileSystem::ReadBytes(file, size);
FileSystem::FileClose(file);
nlohmann::json matdata = nlohmann::json::parse(bytes);
auto mat = mem->Alloc<Material>();
mat->name = mem->StrDup(name);
MATERIAL_INT(gameFlags);
MATERIAL_INT(sortKey);
MATERIAL_INT(animationX);
MATERIAL_INT(animationY);
mat->surfaceTypeBits = matdata["surfaceTypeBits"].get<unsigned int>();
mat->stateFlags = matdata["stateFlags"].get<char>();
mat->cameraRegion = matdata["cameraRegion"].get<unsigned short>();
std::string techset = matdata["techniqueSet->name"].get<std::string>();
if (!techset.empty())
{
mat->techniqueSet = DB_FindXAssetHeader(XAssetType::techset, techset.data()).techset;
}
auto maps = matdata["maps"];
if (!maps.empty())
{
mat->maps = Material_ParseMaps(mat->name, maps, mem);
}
mat->numMaps = maps.size();
auto constantTable = matdata["constantTable"];
if (!constantTable.empty())
{
auto constant_def = mem->Alloc<MaterialConstantDef>(constantTable.size());
for (auto i = 0; i < constantTable.size(); i++)
{
strncpy(constant_def[i].name, constantTable[i]["name"].get<std::string>().c_str(), 11);
constant_def[i].name[11] = '\0';
constant_def[i].nameHash = constantTable[i]["nameHash"].get<unsigned int>();
constant_def[i].literal[0] = constantTable[i]["literal"][0].get<float>();
constant_def[i].literal[1] = constantTable[i]["literal"][1].get<float>();
constant_def[i].literal[2] = constantTable[i]["literal"][2].get<float>();
constant_def[i].literal[3] = constantTable[i]["literal"][3].get<float>();
}
mat->constantTable = constant_def;
}
else
{
mat->constantTable = nullptr;
}
mat->constantCount = constantTable.size();
auto stateMap = matdata["stateMap"];
if (!stateMap.empty())
{
auto stateBits = mem->Alloc<GfxStateBits>(stateMap.size());
for (auto i = 0; i < stateMap.size(); i++)
{
stateBits[i].loadBits[0] = stateMap[i][0].get<unsigned int>();
stateBits[i].loadBits[1] = stateMap[i][1].get<unsigned int>();
}
mat->stateMap = stateBits;
}
else
{
mat->stateMap = nullptr;
}
mat->stateBitsCount = stateMap.size();
if (mat->techniqueSet)
{
auto statebits = ITechset::parse_statebits(mat->techniqueSet->name, mem);
memcpy(mat->stateBitsEntry, statebits, sizeof mat->stateBitsEntry);
}
return mat;
}
IMaterial::IMaterial()
{
}
IMaterial::~IMaterial()
{
}
void IMaterial::init(const std::string& name, ZoneMemory* mem)
{
this->name_ = name;
this->asset_ = this->parse(name, mem);
if (!this->asset_)
{
this->asset_ = DB_FindXAssetHeader(this->type(), this->name_.data()).material;
}
}
void IMaterial::prepare(ZoneBuffer* buf, ZoneMemory* mem)
{
}
void IMaterial::load_depending(IZone* zone)
{
auto data = this->asset_;
if (data->techniqueSet)
{
zone->add_asset_of_type(techset, data->techniqueSet->name);
}
for (char i = 0; i < data->numMaps; i++)
{
if (data->maps[i].image)
{
// use pointer rather than name here
zone->add_asset_of_type_by_pointer(image, data->maps[i].image);
}
}
}
std::string IMaterial::name()
{
return this->name_;
}
std::int32_t IMaterial::type()
{
return material;
}
void IMaterial::write(IZone* zone, ZoneBuffer* buf)
{
auto dest = buf->at<Material>();
auto data = this->asset_;
buf->write(data);
buf->push_stream(3);
START_LOG_STREAM;
dest->name = buf->write_str(this->name());
if (data->techniqueSet)
{
dest->techniqueSet = reinterpret_cast<MaterialTechniqueSet*>(zone->get_asset_pointer(
techset, data->techniqueSet->name));
}
if (data->maps)
{
buf->align(3);
auto destmaps = buf->write(data->maps, data->numMaps);
for (int i = 0; i < data->numMaps; i++)
{
if (data->maps[i].semantic == 11)
{
ZONETOOL_ERROR("Watermaps are not supported.");
destmaps[i].image = nullptr;
}
else
{
if (data->maps[i].image)
{
destmaps[i].image = reinterpret_cast<GfxImage*>(zone->get_asset_pointer(
image, data->maps[i].image->name));
}
}
}
ZoneBuffer::clear_pointer(&dest->maps);
}
if (data->constantTable)
{
dest->constantTable = buf->write_s(15, data->constantTable, data->constantCount);
}
if (data->stateMap)
{
dest->stateMap = buf->write_s(3, data->stateMap, data->stateBitsCount);
}
END_LOG_STREAM;
buf->pop_stream();
}
void IMaterial::dump(Material* asset)
{
if (asset && asset->techniqueSet)
{
ITechset::dump_statebits(asset->techniqueSet->name, asset->stateBitsEntry);
}
nlohmann::json matdata;
MATERIAL_DUMP_STRING(name);
if (asset->techniqueSet)
{
MATERIAL_DUMP_STRING(techniqueSet->name);
}
MATERIAL_DUMP_INT(gameFlags);
MATERIAL_DUMP_INT(sortKey);
MATERIAL_DUMP_INT(animationX);
MATERIAL_DUMP_INT(animationY);
MATERIAL_DUMP_INT(unknown);
MATERIAL_DUMP_INT(surfaceTypeBits);
MATERIAL_DUMP_INT(stateFlags);
MATERIAL_DUMP_INT(cameraRegion);
MATERIAL_DUMP_CONST_ARRAY(constantTable, asset->constantCount);
MATERIAL_DUMP_STATE_MAP(stateMap, asset->stateBitsCount);
nlohmann::json material_images;
for (int i = 0; i < asset->numMaps; i++)
{
nlohmann::json image;
// watermap
if (asset->maps[i].semantic == 11)
{
ZONETOOL_INFO("Dumping water data for image %s\n", asset->name);
water_t* waterData = reinterpret_cast<water_t*>(asset->maps[i].image);
image["image"] = waterData->image->name;
nlohmann::json waterdata;
waterdata["floatTime"] = waterData->writable.floatTime;
waterdata["codeConstant"][0] = waterData->codeConstant[0];
waterdata["codeConstant"][1] = waterData->codeConstant[1];
waterdata["codeConstant"][2] = waterData->codeConstant[2];
waterdata["codeConstant"][3] = waterData->codeConstant[3];
waterdata["M"] = waterData->M;
waterdata["N"] = waterData->N;
waterdata["Lx"] = waterData->Lx;
waterdata["Lz"] = waterData->Lz;
waterdata["gravity"] = waterData->gravity;
waterdata["windvel"] = waterData->windvel;
waterdata["winddir"][0] = waterData->winddir[0];
waterdata["winddir"][1] = waterData->winddir[1];
waterdata["amplitude"] = waterData->amplitude;
nlohmann::json waterComplexData;
nlohmann::json wTerm;
for (int i = 0; i < waterData->M * waterData->N; i++)
{
nlohmann::json complexdata;
nlohmann::json curWTerm;
complexdata["real"] = waterData->H0[i].real;
complexdata["imag"] = waterData->H0[i].imag;
curWTerm[i] = waterData->wTerm[i];
waterComplexData[i] = complexdata;
}
waterdata["complex"] = waterComplexData;
waterdata["wTerm"] = wTerm;
image["waterinfo"] = waterdata;
}
else
{
image["image"] = asset->maps[i].image->name;
}
image["semantic"] = asset->maps[i].semantic;
image["sampleState"] = asset->maps[i].sampleState;
image["lastCharacter"] = asset->maps[i].secondLastCharacter;
image["firstCharacter"] = asset->maps[i].firstCharacter;
image["typeHash"] = asset->maps[i].typeHash;
// add image data to material
material_images[i] = image;
}
matdata["maps"] = material_images;
std::string assetstr = matdata.dump(4);
auto assetPath = "materials\\"s + asset->name;
auto fileAsset = FileSystem::FileOpen(assetPath, "wb");
if (fileAsset)
{
fwrite(assetstr.c_str(), assetstr.size(), 1, fileAsset);
FileSystem::FileClose(fileAsset);
}
}
}
}