diff --git a/common/fs.cc b/common/fs.cc index 1299c9a2..c38d8c5f 100644 --- a/common/fs.cc +++ b/common/fs.cc @@ -287,7 +287,7 @@ resolve_result where(const path &p, bool prefer_loose) } for (int32_t pass = 0; pass < 2; pass++) { - if (prefer_loose == !!pass) { + if (prefer_loose != !!pass) { // check absolute + relative if (exists(p)) { return {absrel_dir, p}; @@ -296,7 +296,7 @@ resolve_result where(const path &p, bool prefer_loose) for (int32_t archive_pass = 0; archive_pass < 2; archive_pass++) { // check directories & archives, depending on whether // we want loose first or not - for (auto &dir : (prefer_loose == !!archive_pass) ? directories : archives) { + for (auto &dir : (prefer_loose != !!archive_pass) ? directories : archives) { if (dir->contains(p)) { return {dir, p}; } diff --git a/common/imglib.cc b/common/imglib.cc index 529bad6a..d586bbc8 100644 --- a/common/imglib.cc +++ b/common/imglib.cc @@ -130,6 +130,8 @@ std::optional load_wal(const std::string &name, const fs::data &file, b texture tex; + tex.meta.extension = ext::WAL; + // note: this is a bit of a hack, but the name stored in // the .wal is ignored. it's extraneous and well-formed wals // will all match up anyways. @@ -173,6 +175,8 @@ std::optional load_mip(const std::string &name, const fs::data &file, b } texture tex; + + tex.meta.extension = ext::MIP; // note: this is a bit of a hack, but the name stored in // the mip is ignored. it's extraneous and well-formed mips @@ -294,6 +298,8 @@ std::optional load_tga(const std::string &name, const fs::data &file, b texture tex; + tex.meta.extension = ext::TGA; + tex.meta.name = name; tex.meta.width = columns; tex.meta.height = rows; diff --git a/include/common/imglib.hh b/include/common/imglib.hh index 2bf9c3c5..7ccc8c48 100644 --- a/include/common/imglib.hh +++ b/include/common/imglib.hh @@ -27,6 +27,20 @@ See file, 'COPYING', for details. namespace img { +enum class ext +{ + TGA, + WAL, + MIP +}; + +constexpr struct { const char *suffix; ext id; } extension_list[] = { + { ".tga", ext::TGA }, + { ".wal", ext::WAL }, + { ".mip", ext::MIP }, + { "", ext::MIP } +}; + extern std::vector palette; // Palette @@ -37,6 +51,8 @@ struct texture_meta std::string name; uint32_t width, height; + ext extension; + // This member is only set before insertion into the table // and not calculated by individual load functions. qvec3b averageColor; diff --git a/include/qbsp/map.hh b/include/qbsp/map.hh index 283e4f40..b0c21721 100644 --- a/include/qbsp/map.hh +++ b/include/qbsp/map.hh @@ -179,8 +179,10 @@ struct mapdata_t uint32_t brush_offset = 0; // Small cache for image meta in the current map std::unordered_map> meta_cache; - - const std::optional &load_image_meta(const char *name); + // load or fetch image meta associated with the specified name + const std::optional &load_image_meta(const std::string_view &name); + // load image data for the specified name + std::tuple, fs::resolve_result, fs::data> load_image_data(const std::string_view &name, bool meta_only); // whether we had attempted loading texture stuff bool textures_loaded = false; diff --git a/include/qbsp/qbsp.hh b/include/qbsp/qbsp.hh index 02ce064d..87033489 100644 --- a/include/qbsp/qbsp.hh +++ b/include/qbsp/qbsp.hh @@ -220,8 +220,9 @@ public: "func_detail_fence brushes are omitted from the compile"}; setting_bool expand{ this, "expand", false, &common_format_group, "write hull 1 expanded brushes to expanded.map for debugging"}; - setting_wadpathset wadpaths{this, {"wadpath", "xwadpath"}, &debugging_group, + setting_wadpathset wadpaths{this, {"wadpath", "xwadpath"}, &map_development_group, "add a path to the wad search paths; wads found in xwadpath's will not be embedded, otherwise they will be embedded (if not -notex)"}; + setting_set paths{this, "path", "\"/path/to/folder\" ", &map_development_group, "additional paths or archives to add to the search path, mostly for loose files"}; setting_bool notriggermodels{this, "notriggermodels", false, &common_format_group, "for supported game code only: triggers will not write a model\nout, and will instead just write out their mins/maxs."}; setting_set aliasdefs{this, "aliasdef", "\"path/to/file.def\" ", &map_development_group, "path to an alias definition file, which can transform entities in the .map into other entities."}; diff --git a/qbsp/map.cc b/qbsp/map.cc index 44edea13..b1ba9f8b 100644 --- a/qbsp/map.cc +++ b/qbsp/map.cc @@ -43,38 +43,61 @@ mapdata_t map; -const std::optional &mapdata_t::load_image_meta(const char *name) +std::tuple, fs::resolve_result, fs::data> mapdata_t::load_image_data(const std::string_view &name, bool meta_only) +{ + fs::path prefix; + + if (options.target_game->id == GAME_QUAKE_II) { + prefix = "textures"; + } + + for (auto &ext : img::extension_list) { + fs::path p = (prefix / name) += ext.suffix; + + if (auto pos = fs::where(p, options.filepriority.value() == settings::search_priority_t::LOOSE)) { + if (auto data = fs::load(pos)) { + std::optional texture; + + switch (ext.id) { + case img::ext::TGA: + texture = img::load_tga(name.data(), data, meta_only); + break; + case img::ext::WAL: + texture = img::load_wal(name.data(), data, meta_only); + break; + case img::ext::MIP: + texture = img::load_mip(name.data(), data, meta_only, options.target_game); + break; + } + + if (texture) { + return {texture, pos, data}; + } + } + } + } + + return {std::nullopt, {}, {}}; +} + +const std::optional &mapdata_t::load_image_meta(const std::string_view &name) { static std::optional nullmeta = std::nullopt; - auto it = meta_cache.find(name); + auto it = meta_cache.find(name.data()); if (it != meta_cache.end()) { return it->second; } - // FIXME: better method - if (options.target_game->id == GAME_QUAKE_II) { - fs::path p = fs::path("textures") / name += ".wal"; - fs::data wal = fs::load(p); + auto [texture, _0, _1] = load_image_data(name, true); - if (!wal) { - logging::print("WARNING: Couldn't locate texture for {}\n", name); - meta_cache.emplace(name, std::nullopt); - return nullmeta; - } - - return meta_cache.emplace(name, img::load_wal(name, wal, true)->meta).first->second; - } else { - fs::data mip = fs::load(name); - - if (!mip) { - logging::print("WARNING: Couldn't locate texture for {}\n", name); - meta_cache.emplace(name, std::nullopt); - return nullmeta; - } - - return meta_cache.emplace(name, img::load_mip(name, mip, true, options.target_game)->meta).first->second; + if (texture) { + return meta_cache.emplace(name, texture->meta).first->second; } + + logging::print("WARNING: Couldn't locate texture for {}\n", name); + meta_cache.emplace(name, std::nullopt); + return nullmeta; } static std::shared_ptr LoadTexturePath(const fs::path &path) @@ -96,6 +119,10 @@ static void EnsureTexturesLoaded() return; map.textures_loaded = true; + + for (auto &path : options.paths.values()) { + fs::addArchive(path, true); + } // Q2 doesn't need this if (options.target_game->id == GAME_QUAKE_II) { @@ -133,7 +160,7 @@ static void EnsureTexturesLoaded() defaultwad.replace_extension("wad"); if (fs::exists(defaultwad)) { - logging::print("Using default WAD: {}\n", defaultwad); + logging::print("INFO: Using default WAD: {}\n", defaultwad); LoadTexturePath(defaultwad); } } diff --git a/qbsp/qbsp.cc b/qbsp/qbsp.cc index 5663802b..e2267939 100644 --- a/qbsp/qbsp.cc +++ b/qbsp/qbsp.cc @@ -1131,18 +1131,14 @@ static void CreateHulls(void) static void LoadTextureData() { for (size_t i = 0; i < map.miptex.size(); i++) { - auto pos = fs::where(map.miptex[i].name, options.filepriority.value() == settings::search_priority_t::LOOSE); - - if (!pos) { - logging::print("WARNING: Texture {} not found\n", map.miptex[i].name); - continue; - } - - auto file = fs::load(pos); - auto tex = img::load_mip(map.miptex[i].name, file, true, options.target_game); + auto [tex, pos, file] = map.load_image_data(map.miptex[i].name, true); if (!tex) { - logging::print("WARNING: unable to load texture {} in archive {}\n", map.miptex[i].name, pos.archive->pathname); + if (pos.archive) { + logging::print("WARNING: unable to load texture {} in archive {}\n", map.miptex[i].name, pos.archive->pathname); + } else { + logging::print("WARNING: unable to find texture {}\n", map.miptex[i].name); + } continue; } @@ -1151,14 +1147,21 @@ static void LoadTextureData() miptex.width = tex->meta.width; miptex.height = tex->meta.height; - if (!pos.archive->external) { + // only mips can be embedded directly + if (!pos.archive->external && tex->meta.extension == img::ext::MIP) { miptex.data = std::move(file.value()); } else { // construct fake data that solely contains the header. miptex.data.resize(sizeof(dmiptex_t)); - + dmiptex_t header {}; - std::copy(miptex.name.begin(), miptex.name.end(), header.name.begin()); + if (miptex.name.size() >= 16) { + logging::print("WARNING: texture {} name too long for Quake miptex\n", miptex.name); + std::copy_n(miptex.name.begin(), 15, header.name.begin()); + } else { + std::copy(miptex.name.begin(), miptex.name.end(), header.name.begin()); + } + header.width = miptex.width; header.height = miptex.height; header.offsets = { -1, -1, -1, -1 };