mirror of
https://github.com/open-goal/jak-project
synced 2026-05-23 15:02:01 -04:00
a3af37989b
There are art groups that are present in multiple levels, and that means that also their textures are present in multiple levels. With texture replacement, currently we need to make replacements for all instances if we want it replaced everywhere, but this is not ideal, especially when you make changes to your replacement texture and now you have to put it in each folder again. I added a way to replace all instances of a texture, by letting texture-replacer people put their replacements into an '_all' folder. I set up the logic in such a way that if you have a replacement for the texture in its corresponding folder, it will take priority over a replacement that you placed into the '_all' folder. I personally found this very useful for replacing guard textures. The guards appear in a lot of levels. But ideally you want them to look the same everywhere. And that is why I looked into this and made a PR. Oh and I changed what is printed in the 'Replacing ' part because it was printing the path to our replacement, which didn't look nicely when several textures got replaced by the same replacement from the '_all' folder. So now it will print the original texture's page and name, I think this information is more useful anyway.
224 lines
7.6 KiB
C++
224 lines
7.6 KiB
C++
#include "TextureDB.h"
|
|
|
|
#include "common/log/log.h"
|
|
#include "common/util/Assert.h"
|
|
|
|
#include "third-party/fmt/core.h"
|
|
#define STBI_WINDOWS_UTF8
|
|
#include "third-party/stb_image/stb_image.h"
|
|
|
|
namespace decompiler {
|
|
|
|
TextureDB::TextureDB() {
|
|
std::vector<u32> data(16 * 16, 0xffffffff);
|
|
add_texture(kPlaceholderWhiteTexturePage, kPlaceholderWhiteTextureId, data, 16, 16,
|
|
"placeholder-white", "placeholder", {}, 1, 0);
|
|
}
|
|
|
|
void TextureDB::add_texture(u32 tpage,
|
|
u32 texid,
|
|
const std::vector<u32>& data,
|
|
u16 w,
|
|
u16 h,
|
|
const std::string& tex_name,
|
|
const std::string& tpage_name,
|
|
const std::vector<std::string>& level_names,
|
|
u32 num_mips,
|
|
u32 dest) {
|
|
auto existing_tpage_name = tpage_names.find(tpage);
|
|
if (existing_tpage_name == tpage_names.end()) {
|
|
tpage_names[tpage] = tpage_name;
|
|
} else {
|
|
ASSERT(existing_tpage_name->second == tpage_name);
|
|
}
|
|
|
|
u32 combo_id = (tpage << 16) | texid;
|
|
auto existing_tex = textures.find(combo_id);
|
|
if (existing_tex != textures.end()) {
|
|
ASSERT(existing_tex->second.name == tex_name);
|
|
ASSERT(existing_tex->second.w == w);
|
|
ASSERT(existing_tex->second.h == h);
|
|
ASSERT(existing_tex->second.rgba_bytes == data);
|
|
ASSERT(existing_tex->second.page == tpage);
|
|
ASSERT(existing_tex->second.num_mips == num_mips);
|
|
ASSERT(existing_tex->second.dest == dest);
|
|
} else {
|
|
auto& new_tex = textures[combo_id];
|
|
new_tex.rgba_bytes = data;
|
|
new_tex.name = tex_name;
|
|
new_tex.w = w;
|
|
new_tex.h = h;
|
|
new_tex.page = tpage;
|
|
new_tex.num_mips = num_mips;
|
|
new_tex.dest = dest;
|
|
}
|
|
for (const auto& level_name : level_names) {
|
|
texture_ids_per_level[level_name].insert(combo_id);
|
|
}
|
|
}
|
|
|
|
void TextureDB::add_index_texture(u32 tpage,
|
|
u32 texid,
|
|
const std::vector<u8>& index_data,
|
|
const std::array<math::Vector4<u8>, 256>& clut,
|
|
u16 w,
|
|
u16 h,
|
|
const std::string& tex_name,
|
|
const std::string& tpage_name,
|
|
const std::vector<std::string>& level_names) {
|
|
auto existing_tpage_name = tpage_names.find(tpage);
|
|
if (existing_tpage_name == tpage_names.end()) {
|
|
tpage_names[tpage] = tpage_name;
|
|
} else {
|
|
ASSERT(existing_tpage_name->second == tpage_name);
|
|
}
|
|
|
|
u32 combo_id = (tpage << 16) | texid;
|
|
auto existing_tex = index_textures_by_combo_id.find(combo_id);
|
|
if (existing_tex != index_textures_by_combo_id.end()) {
|
|
ASSERT(existing_tex->second.name == tex_name);
|
|
ASSERT(existing_tex->second.w == w);
|
|
ASSERT(existing_tex->second.h == h);
|
|
ASSERT(existing_tex->second.index_data == index_data);
|
|
ASSERT(existing_tex->second.combo_id == combo_id);
|
|
ASSERT(existing_tex->second.color_table == clut);
|
|
ASSERT(existing_tex->second.tpage_name == tpage_name);
|
|
for (auto& ln : level_names) {
|
|
existing_tex->second.level_names.push_back(ln);
|
|
}
|
|
} else {
|
|
auto& new_tex = index_textures_by_combo_id[combo_id];
|
|
new_tex.index_data = index_data;
|
|
new_tex.color_table = clut;
|
|
new_tex.name = tex_name;
|
|
new_tex.w = w;
|
|
new_tex.h = h;
|
|
new_tex.tpage_name = tpage_name;
|
|
new_tex.combo_id = combo_id;
|
|
new_tex.level_names = level_names;
|
|
}
|
|
}
|
|
|
|
void TextureDB::merge_textures(const fs::path& base_path) {
|
|
for (auto& tex : textures) {
|
|
fs::path full_path = base_path / tpage_names.at(tex.second.page) / (tex.second.name + ".png");
|
|
if (fs::exists(full_path)) {
|
|
lg::info("Merging {}", full_path.string().c_str());
|
|
int w, h;
|
|
auto merge_data = stbi_load(full_path.string().c_str(), &w, &h, 0, 4); // rgba channels
|
|
if (!merge_data) {
|
|
lg::warn("failed to load PNG file: {}", full_path.string().c_str());
|
|
continue;
|
|
} else if (w != tex.second.w || h != tex.second.h) {
|
|
lg::warn("merge texture does not match the same dimensions: {}, {} != {} || {} != {}",
|
|
full_path.string().c_str(), w, tex.second.w, h, tex.second.h);
|
|
stbi_image_free(merge_data);
|
|
continue;
|
|
}
|
|
// Merge any non-transparent pixels into the existing texture
|
|
for (int i = 0; i < w * h * 4; i += 4) {
|
|
const auto merge_pixel_a = merge_data[i + 3];
|
|
if (merge_pixel_a != 0) {
|
|
u32 merge_pixel;
|
|
memcpy(&merge_pixel, &merge_data[i], sizeof(u32));
|
|
tex.second.rgba_bytes.at(i / 4) = merge_pixel;
|
|
}
|
|
}
|
|
stbi_image_free(merge_data);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TextureDB::replace_textures(const fs::path& path) {
|
|
fs::path base_path(path);
|
|
for (auto& tex : textures) {
|
|
fs::path full_path = base_path / tpage_names.at(tex.second.page) / (tex.second.name + ".png");
|
|
if (!fs::exists(full_path)) {
|
|
full_path = base_path / "_all" / (tex.second.name + ".png");
|
|
if (!fs::exists(full_path))
|
|
continue;
|
|
}
|
|
lg::info("Replacing {}", tpage_names.at(tex.second.page) + "/" + (tex.second.name));
|
|
int w, h;
|
|
auto data = stbi_load(full_path.string().c_str(), &w, &h, 0, 4); // rgba channels
|
|
if (!data) {
|
|
lg::warn("failed to load PNG file: {}", full_path.string().c_str());
|
|
continue;
|
|
}
|
|
tex.second.rgba_bytes.resize(w * h);
|
|
memcpy(tex.second.rgba_bytes.data(), data, w * h * 4);
|
|
tex.second.w = w;
|
|
tex.second.h = h;
|
|
stbi_image_free(data);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Generate a table of offsets
|
|
*/
|
|
std::string TextureDB::generate_texture_dest_adjustment_table() const {
|
|
// group textures by page
|
|
std::map<u32, std::vector<u32>> textures_by_page;
|
|
for (const auto& [texture_id, texture] : textures) {
|
|
textures_by_page[texture.page].push_back(texture_id);
|
|
}
|
|
|
|
std::string result = "{\n";
|
|
// loop over pages (this overlap trick only applies within a page)
|
|
for (const auto& [tpage, texture_ids_in_page] : textures_by_page) {
|
|
// organize by tbp offset
|
|
std::map<u32, std::vector<u32>> textures_by_tbp_offset;
|
|
std::set<u32> all_used_tbp_offsets;
|
|
u32 max_tbp_offset = 0;
|
|
for (auto tid : texture_ids_in_page) {
|
|
u32 tbp = textures.at(tid).dest;
|
|
textures_by_tbp_offset[tbp].push_back(tid);
|
|
all_used_tbp_offsets.insert(tbp);
|
|
max_tbp_offset = std::max(max_tbp_offset, tbp);
|
|
}
|
|
|
|
// find tbp's with overlaps:
|
|
bool needs_remap = false;
|
|
for (const auto& [tbp, tex_ids] : textures_by_tbp_offset) {
|
|
if (tex_ids.size() > 1) {
|
|
needs_remap = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (needs_remap) {
|
|
result += fmt::format("{{{},{{\n", tpage);
|
|
for (const auto& [tbp, tex_ids] : textures_by_tbp_offset) {
|
|
if (tex_ids.size() > 1) {
|
|
int offset = 1;
|
|
for (size_t id_id = 1; id_id < tex_ids.size(); id_id++) {
|
|
auto id = tex_ids[id_id];
|
|
|
|
bool ok = false;
|
|
int tries = 50;
|
|
// make sure we don't overlap again.
|
|
while (!ok && tries > 0) {
|
|
tries--;
|
|
if (!all_used_tbp_offsets.count(tbp + offset)) {
|
|
ok = true;
|
|
break;
|
|
}
|
|
offset++;
|
|
}
|
|
|
|
ASSERT(ok);
|
|
all_used_tbp_offsets.insert(tbp + offset);
|
|
result += fmt::format("{{{}, {}}},", id & 0xffff, offset);
|
|
offset++;
|
|
}
|
|
}
|
|
}
|
|
result.pop_back();
|
|
result += "}},\n";
|
|
}
|
|
}
|
|
result += "}\n";
|
|
return result;
|
|
}
|
|
} // namespace decompiler
|