Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ae1ca84
.map support for sin (to convert, only)
Paril Nov 25, 2024
438202d
SiN BSP support
Paril Nov 25, 2024
79971de
--decompile support for SiN
Paril Dec 1, 2024
010a7fb
more sin
Paril Dec 2, 2024
25973ab
Merge remote-tracking branch 'remotes/eric-origin/brushbsp' into sin_…
Paril Dec 2, 2024
fe0f5a2
we sinnin boys
Paril Feb 6, 2025
f97c5b5
ho boy
Paril Aug 18, 2025
d265277
Merge branch 'brushbsp' into sin_support
Paril Aug 18, 2025
0802b4a
Merge branch 'main' into sin_support
Paril Nov 3, 2025
d366f1e
SiN decompile stuff
Paril Jan 1, 2026
e9b9d2b
fix groupname not persisting properly
Paril Jan 1, 2026
dbe31b9
Merge branch 'main' into sin_support_ericw3
ericwa Jan 3, 2026
2bff32d
Remove redundant second copy of stb_image_write
ericwa Dec 31, 2025
7fe626f
common: ReadQ2BSP: roll back some debug test code
ericwa Jan 1, 2026
8228dd0
Fix typo in e9b9d2b (sinheader -> q2header)
ericwa Jan 2, 2026
c25b15d
bspinfo: ExportObjFace: handle atlas.facenum_to_lightmap_uvs not cont…
ericwa Jan 2, 2026
a815bf5
Merge branch 'main' into sin_support_ericw
ericwa Jan 17, 2026
bdcfeb4
merge main into sin_support_ericw
ericwa Jan 25, 2026
c3ef9b5
various Sinnities
Paril Jan 26, 2026
7026cad
qbsp: add a -sinbsp switch, (very) wip
ericwa Jan 26, 2026
f298bd8
Merge branch 'main' into sin_support_ericw
ericwa Feb 28, 2026
2c45be0
Merge remote-tracking branch 'Paril/sin_support' into sin_support_ericw
ericwa Feb 28, 2026
067c51e
fix test failures from last merge
ericwa Feb 28, 2026
951edc5
lightpreview: add Face panel
Paril Mar 1, 2026
9a0cb54
lightpreview: setup for making bmodel faces clickable
ericwa Mar 3, 2026
b0d34be
lightpreview: make bmodel faces clickable only when visable
ericwa Mar 3, 2026
7ac19ff
Merge branch 'main' into sin_support_ericw
ericwa Mar 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 121 additions & 8 deletions bsputil/bsputil.cc
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include <common/fs.hh>
#include <common/settings.hh>
#include <common/ostream.hh>
#include <common/mapfile.hh>

#include <map>
#include <set>
Expand Down Expand Up @@ -73,7 +74,7 @@ bsputil_settings::bsputil_settings()
[&](const std::string &name, parser_base_t &parser, settings::source src) {
return this->load_setting<settings::setting_bool>(name, parser, src, "");
},
nullptr, "Extract BSP texutres to the given wad file"},
nullptr, "Extract BSP textures to the given wad file"},
replace_textures{this, "replace-textures",
[&](const std::string &name, parser_base_t &parser, settings::source src) {
return this->load_setting<settings::setting_string>(name, parser, src, "");
Expand Down Expand Up @@ -130,6 +131,7 @@ bsputil_settings::bsputil_settings()
return this->load_setting(name, src);
},
nullptr, "Decompile to the given .map file"},
decompile_suffix{this, "decompile-suffix", ".decompile", "\"str\"", nullptr, "Suffix to add to decompiled map name"},
decompile_geomonly{this, "decompile-geomonly",
[&](const std::string &name, parser_base_t &parser, settings::source src) {
return this->load_setting(name, src);
Expand All @@ -145,6 +147,11 @@ bsputil_settings::bsputil_settings()
return this->load_setting<settings::setting_int32>(name, parser, src, 0);
},
nullptr, "Decompile specific hull"},
reorder_entities{this, "resort-entities",
[&](const std::string &name, parser_base_t &parser, settings::source src) {
return this->load_setting(name, src);
},
nullptr, "Re-sort BSP entities in a deterministic way, for diffing"},
extract_bspx_lump{this, "extract-bspx-lump",
[&](const std::string &name, parser_base_t &parser, settings::source src) {
auto lump = std::make_shared<settings::setting_string>(nullptr, name, "");
Expand Down Expand Up @@ -808,6 +815,30 @@ struct planelist_t
}
};

#include "../3rdparty/stb_image_write.h"

static void ExportTextures(const fs::path &source, const gamedef_t *game, const mbsp_t &bsp)
{
for (auto &tex : bsp.texinfo) {
// temp, just for us
auto tex_name = (source.parent_path() / "textures" / tex.texturename).replace_extension(".tga");

if (tex_name.string().find_first_of(' ') != std::string::npos)
continue;

auto swl_meta = std::get<0>(img::load_texture(tex.texturename, false, game, bsputil_options));

if (!swl_meta)
continue;

fs::create_directories(tex_name.parent_path());

if (!fs::exists(tex_name)) {
stbi_write_tga(tex_name.string().c_str(), swl_meta->width, swl_meta->height, 4, swl_meta->pixels.data());
}
}
}

int bsputil_main(int _argc, const char **_argv)
{
logging::preinitialize();
Expand Down Expand Up @@ -1395,6 +1426,12 @@ int bsputil_main(int _argc, const char **_argv)
} else if (operation->primary_name() == "extract-textures") {
mbsp_t &bsp = std::get<mbsp_t>(bspdata.bsp);

if (bspdata.loadversion->game->id == GAME_QUAKE_II ||
bspdata.loadversion->game->id == GAME_SIN) {
ExportTextures(source, bspdata.loadversion->game, bsp);
continue;
}

source.replace_extension(".wad");
logging::print("-> writing {}... ", source);

Expand All @@ -1421,6 +1458,34 @@ int bsputil_main(int _argc, const char **_argv)
mbsp_t &bsp = std::get<mbsp_t>(bspdata.bsp);
CheckBSPFile(&bsp);
CheckBSPFacesPlanar(&bsp);
} else if (operation->primary_name() == "resort-entities") {
mbsp_t &bsp = std::get<mbsp_t>(bspdata.bsp);

mapfile::map_file_t map = mapfile::parse(bsp.dentdata, parser_source_location{});

// sort each entity pair
for (auto &ent : map.entities) {
std::sort(ent.epairs.begin(), ent.epairs.end(), [](const keyvalue_t &a, const keyvalue_t &b) {
if (a.first == b.first) {
return a.second < b.second;
}

return a.first < b.first;
});
}

// sort pairs by length
std::sort(map.entities.begin(), map.entities.end(), [](const mapfile::map_entity_t &a, const mapfile::map_entity_t &b) {
return a.epairs.get_pairs().size() < b.epairs.get_pairs().size();
});

std::stringstream s;
map.write(s);

bsp.dentdata = s.str();

ConvertBSPFormat(&bspdata, bspdata.loadversion);
WriteBSPFile(source.replace_filename(source.filename().generic_string() + "_sorted"), &bspdata);
} else if (operation->primary_name() == "modelinfo") {
mbsp_t &bsp = std::get<mbsp_t>(bspdata.bsp);
PrintModelInfo(&bsp);
Expand Down Expand Up @@ -1470,9 +1535,62 @@ int bsputil_main(int _argc, const char **_argv)

// generate output filename
if (hull) {
source.replace_extension(fmt::format(".decompile.hull{}.map", hullnum));
source.replace_extension(fmt::format("{}.hull{}.map", bsputil_options.decompile_suffix.value(), hullnum));
} else {
source.replace_extension(".decompile.map");
source.replace_extension(fmt::format("{}.map", bsputil_options.decompile_suffix.value()));
}

decomp_options options;
options.geometryOnly = geomOnly;
options.ignoreBrushes = ignoreBrushes;
options.hullnum = hullnum;

if (bspdata.loadversion->game->id == GAME_SIN) {
source.replace_extension(".srf");

logging::print("-> writing surface file {}...\n", source);

std::ofstream f;

mbsp_t &bsp = std::get<mbsp_t>(bspdata.bsp);

std::map<std::string_view, std::string_view, natural_case_insensitive_less> written;

for (auto &tex : bsp.texinfo) {
if (tex.nexttexinfo != -1) {
auto &next = bsp.texinfo[tex.nexttexinfo];

if (auto exists = written.find(tex.texturename); exists != written.end()) {
if (!string_iequals(next.texturename, exists->second)) {
logging::print("WARNING: animation for {} is specified twice but doesn't lead to {}, leads to {} instead\n", tex.texturename, exists->second, next.texturename);
continue;
} else {
// already written
continue;
}
}

if (options.sin_srfName.empty()) {
options.sin_srfName = fs::path(source.filename()).generic_string();
f.open(source);

if (!f)
Error("couldn't open {} for writing\n", source);
}

f << tex.texturename << " date 0 anim " << next.texturename << std::endl;
written.insert(std::make_pair(tex.texturename, next.texturename));
}
}

if (!options.sin_srfName.empty()) {
f.close();

if (!f)
Error("{}", strerror(errno));
}

source.replace_extension(".map");
}

logging::print("-> writing {}...\n", source);
Expand All @@ -1484,11 +1602,6 @@ int bsputil_main(int _argc, const char **_argv)

mbsp_t &bsp = std::get<mbsp_t>(bspdata.bsp);

decomp_options options;
options.geometryOnly = geomOnly;
options.ignoreBrushes = ignoreBrushes;
options.hullnum = hullnum;

DecompileBSP(&bsp, options, f);

f.close();
Expand Down
2 changes: 2 additions & 0 deletions common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ add_library(common STATIC
bspfile_generic.cc
bspfile_q1.cc
bspfile_q2.cc
bspfile_sin.cc
bsputils.cc
bspxfile.cc
cmdlib.cc
Expand Down Expand Up @@ -32,6 +33,7 @@ add_library(common STATIC
../include/common/bspfile_generic.hh
../include/common/bspfile_q1.hh
../include/common/bspfile_q2.hh
../include/common/bspfile_sin.hh
../include/common/bsputils.hh
../include/common/bspxfile.hh
../include/common/cmdlib.hh
Expand Down
Loading