Initial commit
This commit is contained in:
563
src/IW4/Assets/Material.cpp
Normal file
563
src/IW4/Assets/Material.cpp
Normal file
@ -0,0 +1,563 @@
|
||||
// ======================= 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user