Merge branch 'type-cleanup' into brushbsp
# Conflicts: # include/common/bspfile.hh
This commit is contained in:
commit
c1dc3cb7d8
|
|
@ -69,7 +69,7 @@ static void ExportWad(std::ofstream &wadfile, mbsp_t *bsp)
|
|||
/* Count up the valid lumps */
|
||||
numvalid = 0;
|
||||
for (auto &texture : texdata.textures) {
|
||||
if (!texture.data.empty()) {
|
||||
if (texture.data.size() > sizeof(dmiptex_t)) {
|
||||
numvalid++;
|
||||
}
|
||||
}
|
||||
|
|
@ -85,7 +85,7 @@ static void ExportWad(std::ofstream &wadfile, mbsp_t *bsp)
|
|||
/* Miptex data will follow the lump headers */
|
||||
filepos = sizeof(header) + numvalid * sizeof(lump);
|
||||
for (auto &miptex : texdata.textures) {
|
||||
if (miptex.data.empty())
|
||||
if (miptex.data.size() <= sizeof(dmiptex_t))
|
||||
continue;
|
||||
|
||||
lump.filepos = filepos;
|
||||
|
|
@ -99,7 +99,7 @@ static void ExportWad(std::ofstream &wadfile, mbsp_t *bsp)
|
|||
wadfile <= lump;
|
||||
}
|
||||
for (auto &miptex : texdata.textures) {
|
||||
if (!miptex.data.empty()) {
|
||||
if (miptex.data.size() > sizeof(dmiptex_t)) {
|
||||
miptex.stream_write(wadfile);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
47
common/fs.cc
47
common/fs.cc
|
|
@ -275,38 +275,33 @@ std::shared_ptr<archive_like> addArchive(const path &p, bool external)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
resolve_result where(const path &p)
|
||||
resolve_result where(const path &p, bool prefer_loose)
|
||||
{
|
||||
// check absolute + relative first
|
||||
if (exists(p)) {
|
||||
return {absrel_dir, p};
|
||||
}
|
||||
|
||||
// check direct archive loading
|
||||
// check direct archive loading first; it can't ever
|
||||
// be loose, so there's no sense for it to be in the
|
||||
// loop below
|
||||
if (auto paths = splitArchivePath(p)) {
|
||||
auto arch = addArchive(paths.archive);
|
||||
|
||||
if (arch) {
|
||||
if (auto arch = addArchive(paths.archive)) {
|
||||
return {arch, paths.filename};
|
||||
}
|
||||
}
|
||||
|
||||
// absolute doesn't make sense for other load types
|
||||
if (p.is_absolute()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// check directories
|
||||
for (auto &dir : directories) {
|
||||
if (dir->contains(p)) {
|
||||
return {dir, p};
|
||||
}
|
||||
}
|
||||
|
||||
// check archives
|
||||
for (auto &arch : archives) {
|
||||
if (arch->contains(p)) {
|
||||
return {arch, p};
|
||||
for (int32_t pass = 0; pass < 2; pass++) {
|
||||
if (prefer_loose != !!pass) {
|
||||
// check absolute + relative
|
||||
if (exists(p)) {
|
||||
return {absrel_dir, p};
|
||||
}
|
||||
} else if (!p.is_absolute()) { // absolute doesn't make sense for other load types
|
||||
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) {
|
||||
if (dir->contains(p)) {
|
||||
return {dir, p};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
156
common/imglib.cc
156
common/imglib.cc
|
|
@ -119,7 +119,7 @@ struct q2_miptex_t
|
|||
auto stream_data() { return std::tie(name, width, height, offsets, animname, flags, contents, value); }
|
||||
};
|
||||
|
||||
std::optional<texture> load_wal(const std::string &name, const fs::data &file, bool meta_only)
|
||||
std::optional<texture> load_wal(const std::string_view &name, const fs::data &file, bool meta_only, const gamedef_t *game)
|
||||
{
|
||||
imemstream stream(file->data(), file->size(), std::ios_base::in | std::ios_base::binary);
|
||||
stream >> endianness<std::endian::little>;
|
||||
|
|
@ -130,6 +130,8 @@ std::optional<texture> 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.
|
||||
|
|
@ -157,9 +159,9 @@ Quake/Half Life MIP
|
|||
============================================================================
|
||||
*/
|
||||
|
||||
std::optional<texture> load_mip(const std::string &name, const fs::data &file, bool meta_only, const gamedef_t *game)
|
||||
std::optional<texture> load_mip(const std::string_view &name, const fs::data &file, bool meta_only, const gamedef_t *game)
|
||||
{
|
||||
memstream stream(file->data(), file->size(), std::ios_base::in | std::ios_base::binary);
|
||||
imemstream stream(file->data(), file->size());
|
||||
stream >> endianness<std::endian::little>;
|
||||
|
||||
// read header
|
||||
|
|
@ -173,6 +175,8 @@ std::optional<texture> 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
|
||||
|
|
@ -269,7 +273,7 @@ struct targa_t
|
|||
LoadTGA
|
||||
=============
|
||||
*/
|
||||
std::optional<texture> load_tga(const std::string &name, const fs::data &file, bool meta_only)
|
||||
std::optional<texture> load_tga(const std::string_view &name, const fs::data &file, bool meta_only, const gamedef_t *game)
|
||||
{
|
||||
imemstream stream(file->data(), file->size(), std::ios_base::in | std::ios_base::binary);
|
||||
stream >> endianness<std::endian::little>;
|
||||
|
|
@ -294,6 +298,8 @@ std::optional<texture> 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;
|
||||
|
|
@ -395,9 +401,9 @@ breakOut:;
|
|||
// texture cache
|
||||
std::unordered_map<std::string, texture, case_insensitive_hash, case_insensitive_equal> textures;
|
||||
|
||||
const texture *find(const std::string &str)
|
||||
const texture *find(const std::string_view &str)
|
||||
{
|
||||
auto it = textures.find(str);
|
||||
auto it = textures.find(str.data());
|
||||
|
||||
if (it == textures.end()) {
|
||||
return nullptr;
|
||||
|
|
@ -422,128 +428,40 @@ qvec3b calculate_average(const std::vector<qvec4b> &pixels)
|
|||
return avg /= n;
|
||||
}
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
Load (Quake 2) / Convert (Quake, Hexen 2) textures from paletted to RGBA (mxd)
|
||||
==============================================================================
|
||||
*/
|
||||
static void AddTextureName(const char *textureName)
|
||||
std::tuple<std::optional<img::texture>, fs::resolve_result, fs::data> load_texture(const std::string_view &name, bool meta_only, const gamedef_t *game, const settings::common_settings &options)
|
||||
{
|
||||
if (textures.find(textureName) != textures.end()) {
|
||||
return;
|
||||
fs::path prefix;
|
||||
|
||||
if (game->id == GAME_QUAKE_II) {
|
||||
prefix = "textures";
|
||||
}
|
||||
|
||||
auto &tex = textures.emplace(textureName, texture{}).first->second;
|
||||
for (auto &ext : img::extension_list) {
|
||||
fs::path p = (prefix / name) += ext.suffix;
|
||||
|
||||
static constexpr struct
|
||||
{
|
||||
const char *name;
|
||||
decltype(load_wal) *loader;
|
||||
} supportedExtensions[] = {{"tga", load_tga}};
|
||||
if (auto pos = fs::where(p, options.filepriority.value() == settings::search_priority_t::LOOSE)) {
|
||||
if (auto data = fs::load(pos)) {
|
||||
std::optional<img::texture> texture;
|
||||
|
||||
// find wal first, since we'll use it for metadata
|
||||
auto wal = fs::load("textures" / fs::path(textureName) += ".wal");
|
||||
switch (ext.id) {
|
||||
case img::ext::TGA:
|
||||
texture = img::load_tga(name.data(), data, meta_only, game);
|
||||
break;
|
||||
case img::ext::WAL:
|
||||
texture = img::load_wal(name.data(), data, meta_only, game);
|
||||
break;
|
||||
case img::ext::MIP:
|
||||
texture = img::load_mip(name.data(), data, meta_only, game);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!wal) {
|
||||
logging::funcprint("WARNING: can't find .wal for {}\n", textureName);
|
||||
} else {
|
||||
auto walTex = load_wal(textureName, wal, false);
|
||||
|
||||
if (walTex) {
|
||||
tex = std::move(*walTex);
|
||||
}
|
||||
}
|
||||
|
||||
// now check for replacements
|
||||
for (auto &ext : supportedExtensions) {
|
||||
auto replacement = fs::load(("textures" / fs::path(textureName) += ".") += ext.name);
|
||||
|
||||
if (!replacement) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto replacementTex = ext.loader(textureName, replacement, false);
|
||||
|
||||
if (replacementTex) {
|
||||
tex.meta.width = replacementTex->meta.width;
|
||||
tex.meta.height = replacementTex->meta.height;
|
||||
tex.pixels = std::move(replacementTex->pixels);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
tex.meta.averageColor = calculate_average(tex.pixels);
|
||||
}
|
||||
|
||||
// Load all of the referenced textures from the BSP texinfos into
|
||||
// the texture cache.
|
||||
static void LoadTextures(const mbsp_t *bsp)
|
||||
{
|
||||
// gather all loadable textures...
|
||||
for (auto &texinfo : bsp->texinfo) {
|
||||
AddTextureName(texinfo.texture.data());
|
||||
}
|
||||
|
||||
// gather textures used by _project_texture.
|
||||
// FIXME: I'm sure we can resolve this so we don't parse entdata twice.
|
||||
auto entdicts = EntData_Parse(bsp->dentdata);
|
||||
for (auto &entdict : entdicts) {
|
||||
if (entdict.get("classname").find("light") == 0) {
|
||||
const auto &tex = entdict.get("_project_texture");
|
||||
if (!tex.empty()) {
|
||||
AddTextureName(tex.c_str());
|
||||
if (texture) {
|
||||
return {texture, pos, data};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load all of the paletted textures from the BSP into
|
||||
// the texture cache.
|
||||
// TODO: doesn't handle external wads...
|
||||
static void ConvertTextures(const mbsp_t *bsp)
|
||||
{
|
||||
if (!bsp->dtex.textures.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &miptex : bsp->dtex.textures) {
|
||||
if (textures.find(miptex.name) != textures.end()) {
|
||||
logging::funcprint("WARNING: Texture {} duplicated\n", miptex.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add empty to keep texture index in case of load problems...
|
||||
auto &tex = textures.emplace(miptex.name, texture{}).first->second;
|
||||
|
||||
// FIXME: fs::load
|
||||
if (miptex.data.empty()) {
|
||||
logging::funcprint("WARNING: Texture {} is external\n", miptex.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto loaded_tex = img::load_mip(miptex.name, miptex.data, false, bsp->loadversion->game);
|
||||
|
||||
if (!loaded_tex) {
|
||||
logging::funcprint("WARNING: Texture {} is invalid\n", miptex.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
tex = std::move(loaded_tex.value());
|
||||
|
||||
tex.meta.averageColor = calculate_average(tex.pixels);
|
||||
}
|
||||
}
|
||||
|
||||
void load_textures(const mbsp_t *bsp)
|
||||
{
|
||||
logging::print("--- {} ---\n", __func__);
|
||||
|
||||
if (bsp->loadversion->game->id == GAME_QUAKE_II) {
|
||||
LoadTextures(bsp);
|
||||
} else if (bsp->dtex.textures.size() > 0) {
|
||||
ConvertTextures(bsp);
|
||||
} else {
|
||||
logging::print("WARNING: failed to load or convert textures.\n");
|
||||
}
|
||||
return {std::nullopt, {}, {}};
|
||||
}
|
||||
} // namespace img
|
||||
|
|
|
|||
|
|
@ -112,10 +112,9 @@ struct mvis_t
|
|||
}
|
||||
};
|
||||
|
||||
constexpr size_t MIPLEVELS = 4;
|
||||
|
||||
// structured data from BSP. this is the header of the miptex used
|
||||
// in Quake-like formats.
|
||||
constexpr size_t MIPLEVELS = 4;
|
||||
struct dmiptex_t
|
||||
{
|
||||
std::array<char, 16> name;
|
||||
|
|
@ -142,7 +141,7 @@ struct miptex_t
|
|||
data.resize(len);
|
||||
stream.read(reinterpret_cast<char *>(data.data()), len);
|
||||
|
||||
memstream miptex_stream(data.data(), len, std::ios_base::in | std::ios_base::binary);
|
||||
imemstream miptex_stream(data.data(), len);
|
||||
|
||||
dmiptex_t dtex;
|
||||
miptex_stream >= dtex;
|
||||
|
|
@ -200,7 +199,9 @@ struct dmiptexlump_t
|
|||
next_offset = offsets[i + 1];
|
||||
}
|
||||
|
||||
tex.stream_read(stream, next_offset - offset);
|
||||
if (next_offset > offset) {
|
||||
tex.stream_read(stream, next_offset - offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -793,13 +793,13 @@ protected:
|
|||
|
||||
struct memstream : virtual membuf, std::ostream, std::istream
|
||||
{
|
||||
inline memstream(void *base, size_t size, std::ios_base::openmode which = std::ios_base::in | std::ios_base::out)
|
||||
inline memstream(void *base, size_t size, std::ios_base::openmode which = std::ios_base::in | std::ios_base::out | std::ios_base::binary)
|
||||
: membuf(base, size, which), std::ostream(static_cast<std::streambuf *>(this)),
|
||||
std::istream(static_cast<std::streambuf *>(this))
|
||||
{
|
||||
}
|
||||
|
||||
inline memstream(const void *base, size_t size, std::ios_base::openmode which = std::ios_base::in)
|
||||
inline memstream(const void *base, size_t size, std::ios_base::openmode which = std::ios_base::in | std::ios_base::binary)
|
||||
: membuf(base, size, which), std::ostream(nullptr), std::istream(static_cast<std::streambuf *>(this))
|
||||
{
|
||||
}
|
||||
|
|
@ -807,7 +807,7 @@ struct memstream : virtual membuf, std::ostream, std::istream
|
|||
|
||||
struct omemstream : virtual membuf, std::ostream
|
||||
{
|
||||
inline omemstream(void *base, size_t size, std::ios_base::openmode which = std::ios_base::in | std::ios_base::out)
|
||||
inline omemstream(void *base, size_t size, std::ios_base::openmode which = std::ios_base::in | std::ios_base::out | std::ios_base::binary)
|
||||
: membuf(base, size, which), std::ostream(static_cast<std::streambuf *>(this))
|
||||
{
|
||||
}
|
||||
|
|
@ -815,7 +815,7 @@ struct omemstream : virtual membuf, std::ostream
|
|||
|
||||
struct imemstream : virtual membuf, std::istream
|
||||
{
|
||||
inline imemstream(const void *base, size_t size, std::ios_base::openmode which = std::ios_base::in)
|
||||
inline imemstream(const void *base, size_t size, std::ios_base::openmode which = std::ios_base::in | std::ios_base::binary)
|
||||
: membuf(base, size, which), std::istream(static_cast<std::streambuf *>(this))
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,16 +77,16 @@ struct resolve_result
|
|||
// - registered archives in reverse order (ie, "c:/quake/pak1/maps/start.map", "c:/quake/pak0/maps/start.map")
|
||||
// returns the archive that it is contained in, and the filename.
|
||||
// the filename is only different from p if p is an archive path.
|
||||
resolve_result where(const path &p);
|
||||
resolve_result where(const path &p, bool prefer_loose = false);
|
||||
|
||||
// attempt to load the specified resolve result.
|
||||
data load(const resolve_result &pos);
|
||||
|
||||
// attempt to load the specified file from the specified path.
|
||||
// shortcut to load(where(p))
|
||||
inline data load(const path &p)
|
||||
inline data load(const path &p, bool prefer_loose = false)
|
||||
{
|
||||
return load(where(p));
|
||||
return load(where(p, prefer_loose));
|
||||
}
|
||||
|
||||
struct archive_components
|
||||
|
|
|
|||
|
|
@ -27,6 +27,13 @@ See file, 'COPYING', for details.
|
|||
|
||||
namespace img
|
||||
{
|
||||
enum class ext
|
||||
{
|
||||
TGA,
|
||||
WAL,
|
||||
MIP
|
||||
};
|
||||
|
||||
extern std::vector<qvec3b> palette;
|
||||
|
||||
// Palette
|
||||
|
|
@ -37,6 +44,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;
|
||||
|
|
@ -58,17 +67,25 @@ extern std::unordered_map<std::string, texture, case_insensitive_hash, case_inse
|
|||
|
||||
qvec3b calculate_average(const std::vector<qvec4b> &pixels);
|
||||
|
||||
const texture *find(const std::string &str);
|
||||
const texture *find(const std::string_view &str);
|
||||
|
||||
// Load wal
|
||||
std::optional<texture> load_wal(const std::string &name, const fs::data &file, bool meta_only);
|
||||
std::optional<texture> load_wal(const std::string_view &name, const fs::data &file, bool meta_only, const gamedef_t *game);
|
||||
|
||||
// Load TGA
|
||||
std::optional<texture> load_tga(const std::string &name, const fs::data &file, bool meta_only);
|
||||
std::optional<texture> load_tga(const std::string_view &name, const fs::data &file, bool meta_only, const gamedef_t *game);
|
||||
|
||||
// Load Quake/Half Life mip (raw data)
|
||||
std::optional<texture> load_mip(const std::string &name, const fs::data &file, bool meta_only, const gamedef_t *game);
|
||||
std::optional<texture> load_mip(const std::string_view &name, const fs::data &file, bool meta_only, const gamedef_t *game);
|
||||
|
||||
// Pull in texture data from the BSP into the textures map
|
||||
void load_textures(const mbsp_t *bsp);
|
||||
// list of supported extensions and their loaders
|
||||
constexpr struct { const char *suffix; ext id; decltype(load_wal) *loader; } extension_list[] = {
|
||||
{ ".tga", ext::TGA, load_tga },
|
||||
{ ".wal", ext::WAL, load_wal },
|
||||
{ ".mip", ext::MIP, load_mip },
|
||||
{ "", ext::MIP, load_mip }
|
||||
};
|
||||
|
||||
// Attempt to load a texture from the specified name.
|
||||
std::tuple<std::optional<texture>, fs::resolve_result, fs::data> load_texture(const std::string_view &name, bool meta_only, const gamedef_t *game, const settings::common_settings &options);
|
||||
}; // namespace img
|
||||
|
|
|
|||
|
|
@ -825,6 +825,12 @@ public:
|
|||
// global groups
|
||||
extern setting_group performance_group, logging_group, game_group;
|
||||
|
||||
enum class search_priority_t
|
||||
{
|
||||
LOOSE,
|
||||
ARCHIVE
|
||||
};
|
||||
|
||||
class common_settings : public virtual setting_container
|
||||
{
|
||||
public:
|
||||
|
|
@ -841,6 +847,8 @@ public:
|
|||
setting_bool noprogress{this, "noprogress", false, &logging_group, "don't output progress messages"};
|
||||
setting_redirect quiet{this, {"quiet", "noverbose"}, {&nopercent, &nostat, &noprogress}, &logging_group, "suppress non-important messages (equivalent to -nopercent -nostat -noprogress)"};
|
||||
setting_string basedir{this, "basedir", "", "dir_name", &game_group, "override the default game base directory"};
|
||||
setting_enum<search_priority_t> filepriority{this, "filepriority", search_priority_t::LOOSE, { { "loose", search_priority_t::LOOSE }, { "archive", search_priority_t::ARCHIVE } }, &game_group, "which types of archives (folders/loose files or packed archives) are higher priority and chosen first for path searching" };
|
||||
setting_set paths{this, "path", "\"/path/to/folder\" <multiple allowed>", &game_group, "additional paths or archives to add to the search path, mostly for loose files"};
|
||||
setting_bool q2rtx{this, "q2rtx", false, &game_group, "adjust settings to best support Q2RTX"};
|
||||
|
||||
virtual void setParameters(int argc, const char **argv);
|
||||
|
|
|
|||
|
|
@ -158,8 +158,8 @@ struct mapdata_t
|
|||
uint32_t brush_offset = 0;
|
||||
// Small cache for image meta in the current map
|
||||
std::unordered_map<std::string, std::optional<img::texture_meta>> meta_cache;
|
||||
|
||||
const std::optional<img::texture_meta> &load_image_meta(const char *name);
|
||||
// load or fetch image meta associated with the specified name
|
||||
const std::optional<img::texture_meta> &load_image_meta(const std::string_view &name);
|
||||
// whether we had attempted loading texture stuff
|
||||
bool textures_loaded = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -241,7 +241,7 @@ 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_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\" <multiple allowed>", &map_development_group, "path to an alias definition file, which can transform entities in the .map into other entities."};
|
||||
|
|
|
|||
123
light/light.cc
123
light/light.cc
|
|
@ -1012,6 +1012,127 @@ static inline void WriteNormals(const mbsp_t &bsp, bspdata_t &bspdata)
|
|||
bspdata.bspx.transfer("FACENORMALS", data);
|
||||
}
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
Load (Quake 2) / Convert (Quake, Hexen 2) textures from paletted to RGBA (mxd)
|
||||
==============================================================================
|
||||
*/
|
||||
static void AddTextureName(const char *textureName, const mbsp_t *bsp)
|
||||
{
|
||||
if (img::find(textureName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &tex = img::textures.emplace(textureName, img::texture{}).first->second;
|
||||
|
||||
// find wal first, since we'll use it for metadata
|
||||
auto wal = fs::load("textures" / fs::path(textureName) += ".wal");
|
||||
|
||||
if (!wal) {
|
||||
logging::funcprint("WARNING: can't find .wal for {}\n", textureName);
|
||||
} else {
|
||||
auto walTex = img::load_wal(textureName, wal, false, bsp->loadversion->game);
|
||||
|
||||
if (walTex) {
|
||||
tex = std::move(*walTex);
|
||||
}
|
||||
}
|
||||
|
||||
// now check for replacements
|
||||
auto [replacement_tex, _0, _1] = img::load_texture(textureName, false, bsp->loadversion->game, options);
|
||||
|
||||
// FIXME: I think this is fundamentally wrong; we need the
|
||||
// original texture's size for texcoords
|
||||
if (replacement_tex) {
|
||||
tex.meta.width = replacement_tex->meta.width;
|
||||
tex.meta.height = replacement_tex->meta.height;
|
||||
tex.pixels = std::move(replacement_tex->pixels);
|
||||
}
|
||||
|
||||
tex.meta.averageColor = img::calculate_average(tex.pixels);
|
||||
}
|
||||
|
||||
// Load all of the referenced textures from the BSP texinfos into
|
||||
// the texture cache.
|
||||
static void LoadTextures(const mbsp_t *bsp)
|
||||
{
|
||||
// gather all loadable textures...
|
||||
for (auto &texinfo : bsp->texinfo) {
|
||||
AddTextureName(texinfo.texture.data(), bsp);
|
||||
}
|
||||
|
||||
// gather textures used by _project_texture.
|
||||
// FIXME: I'm sure we can resolve this so we don't parse entdata twice.
|
||||
auto entdicts = EntData_Parse(bsp->dentdata);
|
||||
for (auto &entdict : entdicts) {
|
||||
if (entdict.get("classname").find("light") == 0) {
|
||||
const auto &tex = entdict.get("_project_texture");
|
||||
if (!tex.empty()) {
|
||||
AddTextureName(tex.c_str(), bsp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load all of the paletted textures from the BSP into
|
||||
// the texture cache.
|
||||
static void ConvertTextures(const mbsp_t *bsp)
|
||||
{
|
||||
if (!bsp->dtex.textures.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &miptex : bsp->dtex.textures) {
|
||||
if (img::find(miptex.name)) {
|
||||
logging::funcprint("WARNING: Texture {} duplicated\n", miptex.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add empty to keep texture index in case of load problems...
|
||||
auto &tex = img::textures.emplace(miptex.name, img::texture{}).first->second;
|
||||
|
||||
// try to load it externally first
|
||||
auto [texture, _0, _1] = img::load_texture(miptex.name, false, bsp->loadversion->game, options);
|
||||
|
||||
if (texture) {
|
||||
tex = std::move(texture.value());
|
||||
} else {
|
||||
if (miptex.data.size() <= sizeof(dmiptex_t)) {
|
||||
logging::funcprint("WARNING: can't find texture {}\n", miptex.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto loaded_tex = img::load_mip(miptex.name, miptex.data, false, bsp->loadversion->game);
|
||||
|
||||
if (!loaded_tex) {
|
||||
logging::funcprint("WARNING: Texture {} is invalid\n", miptex.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
tex = std::move(loaded_tex.value());
|
||||
}
|
||||
|
||||
tex.meta.averageColor = img::calculate_average(tex.pixels);
|
||||
}
|
||||
}
|
||||
|
||||
void load_textures(const mbsp_t *bsp)
|
||||
{
|
||||
logging::print("--- {} ---\n", __func__);
|
||||
|
||||
for (auto &path : options.paths.values()) {
|
||||
fs::addArchive(path, true);
|
||||
}
|
||||
|
||||
if (bsp->loadversion->game->id == GAME_QUAKE_II) {
|
||||
LoadTextures(bsp);
|
||||
} else if (bsp->dtex.textures.size() > 0) {
|
||||
ConvertTextures(bsp);
|
||||
} else {
|
||||
logging::print("WARNING: failed to load or convert textures.\n");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ==================
|
||||
* main
|
||||
|
|
@ -1075,7 +1196,7 @@ int light_main(int argc, const char **argv)
|
|||
}
|
||||
}
|
||||
|
||||
img::load_textures(&bsp);
|
||||
load_textures(&bsp);
|
||||
|
||||
CacheTextures(bsp);
|
||||
|
||||
|
|
|
|||
40
qbsp/map.cc
40
qbsp/map.cc
|
|
@ -43,38 +43,24 @@
|
|||
|
||||
mapdata_t map;
|
||||
|
||||
const std::optional<img::texture_meta> &mapdata_t::load_image_meta(const char *name)
|
||||
const std::optional<img::texture_meta> &mapdata_t::load_image_meta(const std::string_view &name)
|
||||
{
|
||||
static std::optional<img::texture_meta> 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] = img::load_texture(name, true, options.target_game, options);
|
||||
|
||||
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<fs::archive_like> LoadTexturePath(const fs::path &path)
|
||||
|
|
@ -96,6 +82,10 @@ static void EnsureTexturesLoaded(const mapentity_t *entity)
|
|||
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) {
|
||||
|
|
@ -113,7 +103,7 @@ static void EnsureTexturesLoaded(const mapentity_t *entity)
|
|||
if (wadstring.empty()) {
|
||||
logging::print("WARNING: No wad or _wad key exists in the worldmodel\n");
|
||||
} else {
|
||||
memstream stream(wadstring.data(), wadstring.size(), std::ios_base::in | std::ios_base::binary);
|
||||
imemstream stream(wadstring.data(), wadstring.size());
|
||||
std::string wad;
|
||||
|
||||
while (std::getline(stream, wad, ';')) {
|
||||
|
|
@ -133,7 +123,7 @@ static void EnsureTexturesLoaded(const mapentity_t *entity)
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
68
qbsp/qbsp.cc
68
qbsp/qbsp.cc
|
|
@ -939,41 +939,49 @@ 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);
|
||||
|
||||
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);
|
||||
|
||||
if (!tex) {
|
||||
logging::print("WARNING: unable to load texture {} in archive {}\n", map.miptex[i].name, pos.archive->pathname);
|
||||
continue;
|
||||
}
|
||||
|
||||
// always fill the name even if we can't find it
|
||||
auto &miptex = map.bsp.dtex.textures[i];
|
||||
miptex.name = map.miptex[i].name;
|
||||
miptex.width = tex->meta.width;
|
||||
miptex.height = tex->meta.height;
|
||||
|
||||
if (!pos.archive->external) {
|
||||
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());
|
||||
header.width = miptex.width;
|
||||
header.height = miptex.height;
|
||||
header.offsets = { -1, -1, -1, -1 };
|
||||
{
|
||||
auto [tex, pos, file] = img::load_texture(map.miptex[i].name, true, options.target_game, options);
|
||||
|
||||
omemstream stream(miptex.data.data(), miptex.data.size());
|
||||
stream <= header;
|
||||
if (!tex) {
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
miptex.width = tex->meta.width;
|
||||
miptex.height = tex->meta.height;
|
||||
|
||||
// only mips can be embedded directly
|
||||
if (!pos.archive->external && tex->meta.extension == img::ext::MIP) {
|
||||
miptex.data = std::move(file.value());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fall back to when we can't load the image.
|
||||
// construct fake data that solely contains the header.
|
||||
miptex.data.resize(sizeof(dmiptex_t));
|
||||
|
||||
dmiptex_t header {};
|
||||
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 };
|
||||
|
||||
omemstream stream(miptex.data.data(), miptex.data.size());
|
||||
stream <= header;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue