From 8032daad8979dd71f5bf30551ec88237b1fbc66e Mon Sep 17 00:00:00 2001 From: "Guilherme M. Miranda" Date: Thu, 15 Jan 2026 02:50:36 -0300 Subject: [PATCH 01/14] prep, clean up nomenclature --- src/core.hpp | 12 ++++---- src/level.cpp | 82 +++++++++++++++++++++++++-------------------------- 2 files changed, 46 insertions(+), 48 deletions(-) diff --git a/src/core.hpp b/src/core.hpp index 3055c83..38639c1 100644 --- a/src/core.hpp +++ b/src/core.hpp @@ -760,7 +760,7 @@ using raw_blockmap_header_t = struct raw_blockmap_header_s // // Vanilla BSP // -using raw_node_t = struct raw_node_s +using raw_node_vanilla_t = struct raw_node_vanilla_s { int16_t x, y; // starting point int16_t dx, dy; // offset to ending point @@ -768,13 +768,13 @@ using raw_node_t = struct raw_node_s uint16_t right, left; // children: Node or SSector (if high bit is set) } PACKEDATTR; -using raw_subsec_t = struct raw_subsec_s +using raw_subsec_vanilla_t = struct raw_subsec_vanilla_s { uint16_t num; // number of Segs in this Sub-Sector uint16_t first; // first Seg } PACKEDATTR; -using raw_seg_t = struct raw_seg_s +using raw_seg_vanilla_t = struct raw_seg_vanilla_s { uint16_t start; // from this vertex... uint16_t end; // ... to this vertex @@ -788,7 +788,7 @@ using raw_seg_t = struct raw_seg_s // DeepSea BSP // * compared to vanilla, some types were raise to 32bit // -using raw_node_deep_t = struct raw_node_deep_s +using raw_node_deepbspv4_t = struct raw_node_deepbspv4_s { int16_t x, y; // starting point int16_t dx, dy; // offset to ending point @@ -796,13 +796,13 @@ using raw_node_deep_t = struct raw_node_deep_s uint32_t right, left; // children: Node or SSector (if high bit is set) } PACKEDATTR; -using raw_subsec_deep_t = struct raw_subsec_deep_s +using raw_subsec_deepbspv4_t = struct raw_subsec_deepbspv4_s { uint16_t num; // number of Segs in this Sub-Sector uint32_t first; // first Seg } PACKEDATTR; -using raw_seg_deep_t = struct raw_seg_deep_s +using raw_seg_deepbspv4_t = struct raw_seg_deepbspv4_s { uint32_t start; // from this vertex... uint32_t end; // ... to this vertex diff --git a/src/level.cpp b/src/level.cpp index 478c164..9c5f0ee 100644 --- a/src/level.cpp +++ b/src/level.cpp @@ -1831,16 +1831,16 @@ static inline uint32_t VertexIndex_XNOD(const vertex_t *v) return static_cast(v->index); } -static void PutSegs(void) +static void PutSegs_Vanilla(void) { // this size is worst-case scenario - size_t size = lev_segs.size() * sizeof(raw_seg_t); + size_t size = lev_segs.size() * sizeof(raw_seg_vanilla_t); Lump_c *lump = CreateLevelLump("SEGS", size); for (size_t i = 0; i < lev_segs.size(); i++) { - raw_seg_t raw; + raw_seg_vanilla_t raw; const seg_t *seg = lev_segs[i]; @@ -1876,15 +1876,15 @@ static void PutSegs(void) } } -static void PutSubsecs(void) +static void PutSubsecs_Vanilla(void) { - size_t size = lev_subsecs.size() * sizeof(raw_subsec_t); + size_t size = lev_subsecs.size() * sizeof(raw_subsec_vanilla_t); Lump_c *lump = CreateLevelLump("SSECTORS", size); for (size_t i = 0; i < lev_subsecs.size(); i++) { - raw_subsec_t raw; + raw_subsec_vanilla_t raw; const subsec_t *sub = lev_subsecs[i]; @@ -1910,21 +1910,21 @@ static void PutSubsecs(void) static size_t node_cur_index; -static void PutOneNode(node_t *node, Lump_c *lump) +static void PutOneNode_Vanilla(node_t *node, Lump_c *lump) { if (node->r.node) { - PutOneNode(node->r.node, lump); + PutOneNode_Vanilla(node->r.node, lump); } if (node->l.node) { - PutOneNode(node->l.node, lump); + PutOneNode_Vanilla(node->l.node, lump); } node->index = node_cur_index++; - raw_node_t raw; + raw_node_vanilla_t raw; // note that x/y/dx/dy are always integral in non-UDMF maps raw.x = GetLittleEndian(static_cast(floor(node->x))); @@ -1977,12 +1977,10 @@ static void PutOneNode(node_t *node, Lump_c *lump) } } -static void PutNodes(node_t *root) +static void PutNodes_Vanilla(node_t *root) { - size_t struct_size = sizeof(raw_node_t); - // this can be bigger than the actual size, but never smaller - size_t max_size = (lev_nodes.size() + 1) * struct_size; + size_t max_size = (lev_nodes.size() + 1) * sizeof(raw_node_vanilla_t); Lump_c *lump = CreateLevelLump("NODES", max_size); @@ -1990,7 +1988,7 @@ static void PutNodes(node_t *root) if (root != nullptr) { - PutOneNode(root, lump); + PutOneNode_Vanilla(root, lump); } lump->Finish(); @@ -2066,7 +2064,7 @@ void SortSegs(void) /* ----- ZDoom format writing --------------------------- */ -static void PutXnodVertices(Lump_c *lump) +static void PutVertices_Xnod(Lump_c *lump) { size_t orgverts = GetLittleEndian(num_old_vert); size_t newverts = GetLittleEndian(num_new_vert); @@ -2100,7 +2098,7 @@ static void PutXnodVertices(Lump_c *lump) } } -static void PutXnodSubsecs(Lump_c *lump) +static void PutSubsecs_Xnod(Lump_c *lump) { size_t raw_num = GetLittleEndian(lev_subsecs.size()); lump->Write(&raw_num, 4); @@ -2137,7 +2135,7 @@ static void PutXnodSubsecs(Lump_c *lump) } } -static void PutXnodSegs(Lump_c *lump) +static void PutSegs_Xnod(Lump_c *lump) { size_t raw_num = GetLittleEndian(lev_segs.size()); lump->Write(&raw_num, 4); @@ -2161,7 +2159,7 @@ static void PutXnodSegs(Lump_c *lump) } } -static void PutXgl2Segs(Lump_c *lump) +static void PutSegs_Xgl2(Lump_c *lump) { size_t raw_num = GetLittleEndian(lev_segs.size()); lump->Write(&raw_num, 4); @@ -2191,18 +2189,18 @@ static void PutXgl2Segs(Lump_c *lump) } } -static void PutOneXnodNode(Lump_c *lump, node_t *node, bool xgl3) +static void PutOneNode_Xnod(Lump_c *lump, node_t *node, bool xgl3) { raw_xgl3_node_t raw; if (node->r.node) { - PutOneXnodNode(lump, node->r.node, xgl3); + PutOneNode_Xnod(lump, node->r.node, xgl3); } if (node->l.node) { - PutOneXnodNode(lump, node->l.node, xgl3); + PutOneNode_Xnod(lump, node->l.node, xgl3); } node->index = node_cur_index++; @@ -2281,7 +2279,7 @@ static void PutOneXnodNode(Lump_c *lump, node_t *node, bool xgl3) } } -static void PutXnodNodes(Lump_c *lump, node_t *root) +static void PutNodes_Xnod(Lump_c *lump, node_t *root) { size_t raw_num = GetLittleEndian(lev_nodes.size()); lump->Write(&raw_num, 4); @@ -2290,7 +2288,7 @@ static void PutXnodNodes(Lump_c *lump, node_t *root) if (root) { - PutOneXnodNode(lump, root, false); + PutOneNode_Xnod(lump, root, false); } if (node_cur_index != lev_nodes.size()) @@ -2299,7 +2297,7 @@ static void PutXnodNodes(Lump_c *lump, node_t *root) } } -static void PutXgl3Nodes(Lump_c *lump, node_t *root) +static void PutNodes_Xgl3(Lump_c *lump, node_t *root) { size_t raw_num = GetLittleEndian(lev_nodes.size()); lump->Write(&raw_num, 4); @@ -2308,7 +2306,7 @@ static void PutXgl3Nodes(Lump_c *lump, node_t *root) if (root) { - PutOneXnodNode(lump, root, true); + PutOneNode_Xnod(lump, root, true); } if (node_cur_index != lev_nodes.size()) @@ -2333,7 +2331,7 @@ static size_t CalcXnodNodesSize(void) return size; } -void SaveXnodFormat(node_t *root_node) +void SaveFormat_Xnod(node_t *root_node) { SortSegs(); @@ -2343,16 +2341,16 @@ void SaveXnodFormat(node_t *root_node) lump->Write(XNOD_MAGIC, 4); - PutXnodVertices(lump); - PutXnodSubsecs(lump); - PutXnodSegs(lump); - PutXnodNodes(lump, root_node); + PutVertices_Xnod(lump); + PutSubsecs_Xnod(lump); + PutSegs_Xnod(lump); + PutNodes_Xnod(lump, root_node); lump->Finish(); lump = nullptr; } -static void SaveXgl3Format(Lump_c *lump, node_t *root_node) +static void SaveFormat_Xgl3(Lump_c *lump, node_t *root_node) { SortSegs(); @@ -2360,10 +2358,10 @@ static void SaveXgl3Format(Lump_c *lump, node_t *root_node) lump->Write(XGL3_MAGIC, 4); - PutXnodVertices(lump); - PutXnodSubsecs(lump); - PutXgl2Segs(lump); - PutXgl3Nodes(lump, root_node); + PutVertices_Xnod(lump); + PutSubsecs_Xnod(lump); + PutSegs_Xgl2(lump); + PutNodes_Xgl3(lump, root_node); lump->Finish(); lump = nullptr; @@ -2494,7 +2492,7 @@ build_result_e SaveLevel(node_t *root_node) // leave SEGS empty CreateLevelLump("SEGS")->Finish(); Lump_c *lump = CreateLevelLump("SSECTORS"); - SaveXgl3Format(lump, root_node); + SaveFormat_Xgl3(lump, root_node); CreateLevelLump("NODES")->Finish(); } else if (lev_force_xnod && num_real_lines > 0) @@ -2503,7 +2501,7 @@ build_result_e SaveLevel(node_t *root_node) CreateLevelLump("SSECTORS")->Finish(); // remove all the mini-segs from subsectors NormaliseBspTree(); - SaveXnodFormat(root_node); + SaveFormat_Xnod(root_node); } else { @@ -2519,9 +2517,9 @@ build_result_e SaveLevel(node_t *root_node) PutVertices(); - PutSegs(); - PutSubsecs(); - PutNodes(root_node); + PutSegs_Vanilla(); + PutSubsecs_Vanilla(); + PutNodes_Vanilla(root_node); } PutBlockmap(); @@ -2559,7 +2557,7 @@ build_result_e SaveUDMF(node_t *root_node) } else { - SaveXgl3Format(lump, root_node); + SaveFormat_Xgl3(lump, root_node); } // -Elf- From 822f95dfc790bae19d4ca3bf5024ee120716b34e Mon Sep 17 00:00:00 2001 From: "Guilherme M. Miranda" Date: Thu, 15 Jan 2026 18:34:58 -0300 Subject: [PATCH 02/14] initial work --- src/core.hpp | 17 +++++++++++++---- src/elfbsp.cpp | 12 ++++++------ src/elfbsp.hpp | 17 ++++++----------- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/core.hpp b/src/core.hpp index 38639c1..0d8a61b 100644 --- a/src/core.hpp +++ b/src/core.hpp @@ -730,15 +730,24 @@ using raw_hexen_thing_t = struct raw_hexen_thing_s // BSP TREE STRUCTURES //------------------------------------------------------------------------ +// +// We do not write ZIP-compressed ZDoom nodes +// +using bsp_type_t = enum bsp_type_e : uint8_t +{ + BSP_VANILLA, + BSP_DEEPBSPV4, + BSP_XNOD, + BSP_XGLN, + BSP_XGL2, + BSP_XGL3, +}; + static constexpr const char *DEEP_MAGIC = "xNd4\0\0\0\0"; static constexpr const char *XNOD_MAGIC = "XNOD"; -static constexpr const char *ZNOD_MAGIC = "ZNOD"; static constexpr const char *XGLN_MAGIC = "XGLN"; -static constexpr const char *ZGLN_MAGIC = "ZGLN"; static constexpr const char *XGL2_MAGIC = "XGL2"; -static constexpr const char *ZGL2_MAGIC = "ZGL2"; static constexpr const char *XGL3_MAGIC = "XGL3"; -static constexpr const char *ZGL3_MAGIC = "ZGL3"; // // Vanilla blockmap diff --git a/src/elfbsp.cpp b/src/elfbsp.cpp index 4b649e0..0da3fa1 100644 --- a/src/elfbsp.cpp +++ b/src/elfbsp.cpp @@ -436,10 +436,10 @@ void ParseShortArgument(const char *arg) config.fast = true; continue; case 'x': - config.force_xnod = true; + config.bsp_type = BSP_XNOD; continue; case 's': - config.ssect_xgl3 = true; + config.bsp_type = BSP_XGL3; continue; case 'm': @@ -469,7 +469,7 @@ void ParseShortArgument(const char *arg) FatalError("illegal value for '-c' option\n"); } - config.split_cost = static_cast(val); + config.split_cost = static_cast(val); continue; default: @@ -531,11 +531,11 @@ int ParseLongArgument(const char *name, int argc, char *argv[]) } else if (strcmp(name, "--xnod") == 0) { - config.force_xnod = true; + config.bsp_type = BSP_XNOD; } else if (strcmp(name, "--ssect") == 0) { - config.ssect_xgl3 = true; + config.bsp_type = BSP_XGL3; } else if (strcmp(name, "--cost") == 0) { @@ -551,7 +551,7 @@ int ParseLongArgument(const char *name, int argc, char *argv[]) FatalError("illegal value for '--cost' option\n"); } - config.split_cost = static_cast(val); + config.split_cost = static_cast(val); used = 1; } else if (strcmp(name, "--output") == 0) diff --git a/src/elfbsp.hpp b/src/elfbsp.hpp index e31f4d7..14b9cd9 100644 --- a/src/elfbsp.hpp +++ b/src/elfbsp.hpp @@ -41,23 +41,18 @@ extern buildinfo_t config; struct buildinfo_s { - // use a faster method to pick nodes + size_t total_warnings = 0; + size_t total_minor_issues = 0; + double split_cost = static_cast(SPLIT_COST_DEFAULT); + + bsp_type_t bsp_type = BSP_VANILLA; + bool fast = false; bool backup = false; bool no_reject = false; - bool force_xnod = false; - bool ssect_xgl3 = false; - - size_t split_cost = SPLIT_COST_DEFAULT; - - // this affects how some messages are shown bool verbose = false; - // from here on, various bits of internal state - size_t total_warnings = 0; - size_t total_minor_issues = 0; - inline void Print_Verbose(const char *fmt, ...) { if (!verbose) From 799e044bb5f178dc4558b2984d1953c65976e415 Mon Sep 17 00:00:00 2001 From: "Guilherme M. Miranda" Date: Wed, 21 Jan 2026 02:36:19 -0300 Subject: [PATCH 03/14] minor tweaks --- src/core.hpp | 22 ++++++++++++---------- src/level.cpp | 39 +++++++++++++++++---------------------- 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/src/core.hpp b/src/core.hpp index d049c61..33bc9ef 100644 --- a/src/core.hpp +++ b/src/core.hpp @@ -172,11 +172,6 @@ using long_angle_t = uint32_t; using short_angle_t = uint16_t; using lump_t = char[8]; -using vec2_fixed_t = struct -{ - fixed_t x, y; -}; - // misc constants static constexpr long_angle_t LONG_ANGLE_45 = 0x20000000; static constexpr long_angle_t LONG_ANGLE_1 = (LONG_ANGLE_45 / 45); @@ -745,11 +740,18 @@ using bsp_type_t = enum bsp_type_e : uint8_t BSP_XGL3, }; -static constexpr const char *DEEP_MAGIC = "xNd4\0\0\0\0"; -static constexpr const char *XNOD_MAGIC = "XNOD"; -static constexpr const char *XGLN_MAGIC = "XGLN"; -static constexpr const char *XGL2_MAGIC = "XGL2"; -static constexpr const char *XGL3_MAGIC = "XGL3"; +// Obviously, vanilla did not include any magic headers +static constexpr const char *BSP_MAGIC_DEEPBSPV4 = "xNd4\0\0\0\0"; +static constexpr const char *BSP_MAGIC_XNOD = "XNOD"; +static constexpr const char *BSP_MAGIC_XGLN = "XGLN"; +static constexpr const char *BSP_MAGIC_XGL2 = "XGL2"; +static constexpr const char *BSP_MAGIC_XGL3 = "XGL3"; + +// Upper-most bit is used for distinguishing tree children as either nodes or sub-sectors +// All known non-vanilla formats are know to use 32bit indexes +static constexpr size_t LIMIT_VANILLA_NODE = INT16_MAX; +static constexpr size_t LIMIT_VANILLA_SUBSEC = INT16_MAX; +static constexpr size_t LIMIT_VANILLA_SEG = UINT16_MAX; // // Vanilla blockmap diff --git a/src/level.cpp b/src/level.cpp index 5e9fc42..88096a0 100644 --- a/src/level.cpp +++ b/src/level.cpp @@ -773,7 +773,6 @@ size_t lev_current_idx; size_t lev_current_start; map_format_e lev_format; -bool lev_force_xnod; bool lev_overflows; @@ -2005,7 +2004,7 @@ static void PutNodes_Vanilla(node_t *root) } } -static void CheckLimits(void) +static void CheckBinaryFormatLimits(void) { // this could potentially be 65536, since there are no reserved values // for sectors, but there may be source ports or tools treating 0xFFFF @@ -2031,12 +2030,12 @@ static void CheckLimits(void) MarkOverflow(LIMIT_LINEDEFS); } - if (!(config.force_xnod || config.ssect_xgl3)) + if (config.bsp_type < BSP_XNOD) { if (num_old_vert > 32767 || num_new_vert > 32767 || lev_segs.size() > 32767 || lev_nodes.size() > 32767) { + config.bsp_type = BSP_XNOD; Warning("Forcing XNOD format nodes due to overflows.\n"); - lev_force_xnod = true; } } } @@ -2331,15 +2330,11 @@ static size_t CalcXnodNodesSize(void) return size; } -void SaveFormat_Xnod(node_t *root_node) +void SaveFormat_Xnod(Lump_c *lump, node_t *root_node) { SortSegs(); - size_t max_size = CalcXnodNodesSize(); - - Lump_c *lump = CreateLevelLump("NODES", max_size); - - lump->Write(XNOD_MAGIC, 4); + lump->Write(BSP_MAGIC_XNOD, 4); PutVertices_Xnod(lump); PutSubsecs_Xnod(lump); @@ -2356,7 +2351,7 @@ static void SaveFormat_Xgl3(Lump_c *lump, node_t *root_node) // WISH : compute a max_size - lump->Write(XGL3_MAGIC, 4); + lump->Write(BSP_MAGIC_XGL3, 4); PutVertices_Xnod(lump); PutSubsecs_Xnod(lump); @@ -2465,7 +2460,7 @@ static void AddMissingLump(const char *name, const char *after) cur_wad->AddLump(name)->Finish(); } -build_result_e SaveLevel(node_t *root_node) +build_result_e SaveBinaryFormatLevel(node_t *root_node) { // Note: root_node may be nullptr @@ -2479,15 +2474,12 @@ build_result_e SaveLevel(node_t *root_node) AddMissingLump("REJECT", "SECTORS"); AddMissingLump("BLOCKMAP", "REJECT"); - // user preferences - lev_force_xnod |= config.force_xnod; - // check for overflows... // this sets the force_xxx vars if certain limits are breached - CheckLimits(); + CheckBinaryFormatLimits(); /* --- Normal nodes --- */ - if (config.ssect_xgl3 && num_real_lines > 0) + if (config.bsp_type == BSP_XGL3 && num_real_lines > 0) { // leave SEGS empty CreateLevelLump("SEGS")->Finish(); @@ -2495,13 +2487,16 @@ build_result_e SaveLevel(node_t *root_node) SaveFormat_Xgl3(lump, root_node); CreateLevelLump("NODES")->Finish(); } - else if (lev_force_xnod && num_real_lines > 0) + else if (config.bsp_type == BSP_XNOD && num_real_lines > 0) { CreateLevelLump("SEGS")->Finish(); CreateLevelLump("SSECTORS")->Finish(); + // remove all the mini-segs from subsectors NormaliseBspTree(); - SaveFormat_Xnod(root_node); + + Lump_c *lump = CreateLevelLump("NODES", CalcXnodNodesSize()); + SaveFormat_Xnod(lump, root_node); } else { @@ -2538,7 +2533,7 @@ build_result_e SaveLevel(node_t *root_node) return BUILD_OK; } -build_result_e SaveUDMF(node_t *root_node) +build_result_e SaveTextMapLevel(node_t *root_node) { cur_wad->BeginWrite(); @@ -2699,10 +2694,10 @@ build_result_e BuildLevel(size_t lev_idx) { case MAPF_Doom: case MAPF_Hexen: - ret = SaveLevel(root_node); + ret = SaveBinaryFormatLevel(root_node); break; case MAPF_UDMF: - ret = SaveUDMF(root_node); + ret = SaveTextMapLevel(root_node); break; default: break; From a5752927fcfc28155f1f957868cdafaf581bb96b Mon Sep 17 00:00:00 2001 From: "Guilherme M. Miranda" Date: Sat, 31 Jan 2026 02:05:52 -0300 Subject: [PATCH 04/14] formatting --- .clang-format | 2 +- src/elfbsp.cpp | 108 ++++++++++++++++++++++++------------------------- src/level.cpp | 102 +++++++++++++++++++++++----------------------- src/parse.cpp | 52 ++++++++++++------------ src/wad.cpp | 30 +++++++------- 5 files changed, 147 insertions(+), 147 deletions(-) diff --git a/.clang-format b/.clang-format index 663918b..38842ae 100644 --- a/.clang-format +++ b/.clang-format @@ -39,7 +39,7 @@ BreakStringLiterals: true ColumnLimit: 128 IncludeBlocks: Preserve IndentCaseBlocks: true -IndentCaseLabels: true +IndentCaseLabels: false IndentPPDirectives: BeforeHash IndentWidth: 2 IndentWrappedFunctionNames: false diff --git a/src/elfbsp.cpp b/src/elfbsp.cpp index e055877..b9d3ea0 100644 --- a/src/elfbsp.cpp +++ b/src/elfbsp.cpp @@ -436,68 +436,68 @@ void ParseShortArgument(const char *arg) switch (c) { - case 'h': - opt_help = true; - continue; - case 'b': - config.backup = true; - continue; + case 'h': + opt_help = true; + continue; + case 'b': + config.backup = true; + continue; - case 'v': - config.verbose = true; - continue; - case 'f': - config.fast = true; - continue; - case 'x': - config.bsp_type = BSP_XNOD; - continue; - case 's': - config.bsp_type = BSP_XGL3; - continue; - case 'a': - config.analysis = true; - continue; + case 'v': + config.verbose = true; + continue; + case 'f': + config.fast = true; + continue; + case 'x': + config.bsp_type = std::max(config.bsp_type, BSP_XNOD); + continue; + case 's': + config.bsp_type = std::max(config.bsp_type, BSP_XGL3); + continue; + case 'a': + config.analysis = true; + continue; - case 'm': - case 'o': - PrintLine(LOG_ERROR, "cannot use option '-%c' like that", c); - return; + case 'm': + case 'o': + PrintLine(LOG_ERROR, "cannot use option '-%c' like that", c); + return; - case 'c': - if (*arg == 0 || !isdigit(*arg)) - { - PrintLine(LOG_ERROR, "missing value for '-c' option"); - } + case 'c': + if (*arg == 0 || !isdigit(*arg)) + { + PrintLine(LOG_ERROR, "missing value for '-c' option"); + } - // we only accept one or two digits here - val = *arg - '0'; - arg++; + // we only accept one or two digits here + val = *arg - '0'; + arg++; - if (*arg && isdigit(*arg)) - { - val = (val * 10) + (*arg - '0'); - arg++; - } + if (*arg && isdigit(*arg)) + { + val = (val * 10) + (*arg - '0'); + arg++; + } - if (val < SPLIT_COST_MIN || val > SPLIT_COST_MAX) - { - PrintLine(LOG_ERROR, "illegal value for '-c' option"); - } + if (val < SPLIT_COST_MIN || val > SPLIT_COST_MAX) + { + PrintLine(LOG_ERROR, "illegal value for '-c' option"); + } - config.split_cost = val; - continue; + config.split_cost = val; + continue; - default: - if (isprint(c) && !isspace(c)) - { - PrintLine(LOG_ERROR, "unknown short option: '-%c'", c); - } - else - { - PrintLine(LOG_ERROR, "illegal short option (ascii code %d)", static_cast(c)); - } - return; + default: + if (isprint(c) && !isspace(c)) + { + PrintLine(LOG_ERROR, "unknown short option: '-%c'", c); + } + else + { + PrintLine(LOG_ERROR, "illegal short option (ascii code %d)", static_cast(c)); + } + return; } } } diff --git a/src/level.cpp b/src/level.cpp index b0cdc8f..0752e95 100644 --- a/src/level.cpp +++ b/src/level.cpp @@ -1544,23 +1544,23 @@ static void ParseUDMF_Block(lexer_c &lex, int cur_type) switch (cur_type) { - case UDMF_VERTEX: - vertex = NewVertex(); - break; - case UDMF_THING: - thing = NewThing(); - break; - case UDMF_SECTOR: - sector = NewSector(); - break; - case UDMF_SIDEDEF: - side = NewSidedef(); - break; - case UDMF_LINEDEF: - line = NewLinedef(); - break; - default: - break; + case UDMF_VERTEX: + vertex = NewVertex(); + break; + case UDMF_THING: + thing = NewThing(); + break; + case UDMF_SECTOR: + sector = NewSector(); + break; + case UDMF_SIDEDEF: + side = NewSidedef(); + break; + case UDMF_LINEDEF: + line = NewLinedef(); + break; + default: + break; } for (;;) @@ -1604,24 +1604,24 @@ static void ParseUDMF_Block(lexer_c &lex, int cur_type) switch (cur_type) { - case UDMF_VERTEX: - ParseVertexField(vertex, key, tok, value); - break; - case UDMF_THING: - ParseThingField(thing, key, tok, value); - break; - case UDMF_SECTOR: - ParseSectorField(sector, key, tok, value); - break; - case UDMF_SIDEDEF: - ParseSidedefField(side, key, tok, value); - break; - case UDMF_LINEDEF: - ParseLinedefField(line, key, tok, value); - break; + case UDMF_VERTEX: + ParseVertexField(vertex, key, tok, value); + break; + case UDMF_THING: + ParseThingField(thing, key, tok, value); + break; + case UDMF_SECTOR: + ParseSectorField(sector, key, tok, value); + break; + case UDMF_SIDEDEF: + ParseSidedefField(side, key, tok, value); + break; + case UDMF_LINEDEF: + ParseLinedefField(line, key, tok, value); + break; - default: /* just skip it */ - break; + default: /* just skip it */ + break; } } @@ -2424,14 +2424,14 @@ void LoadLevel(void) // -JL- Find sectors containing polyobjs switch (lev_format) { - case MapFormat_Hexen: - DetectPolyobjSectors(false); - break; - case MapFormat_UDMF: - DetectPolyobjSectors(true); - break; - default: - break; + case MapFormat_Hexen: + DetectPolyobjSectors(false); + break; + case MapFormat_UDMF: + DetectPolyobjSectors(true); + break; + default: + break; } } @@ -2736,15 +2736,15 @@ build_result_e BuildLevel(size_t lev_idx, const char *filename) switch (lev_format) { - case MapFormat_Doom: - case MapFormat_Hexen: - ret = SaveBinaryFormatLevel(root_node); - break; - case MapFormat_UDMF: - ret = SaveTextMapLevel(root_node); - break; - default: - break; + case MapFormat_Doom: + case MapFormat_Hexen: + ret = SaveBinaryFormatLevel(root_node); + break; + case MapFormat_UDMF: + ret = SaveTextMapLevel(root_node); + break; + default: + break; } } else diff --git a/src/parse.cpp b/src/parse.cpp index 07425a6..f4d6bd8 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -402,31 +402,31 @@ void lexer_c::ParseEscape(std::string &s) switch (ch) { - case 'a': - s.push_back('\a'); - break; // bell - case 'b': - s.push_back('\b'); - break; // backspace - case 'f': - s.push_back('\f'); - break; // form feed - case 'n': - s.push_back('\n'); - break; // newline - case 't': - s.push_back('\t'); - break; // tab - case 'r': - s.push_back('\r'); - break; // carriage return - case 'v': - s.push_back('\v'); - break; // vertical tab - - // the default is to reproduce the same character - default: - s.push_back(ch); - break; + case 'a': + s.push_back('\a'); + break; // bell + case 'b': + s.push_back('\b'); + break; // backspace + case 'f': + s.push_back('\f'); + break; // form feed + case 'n': + s.push_back('\n'); + break; // newline + case 't': + s.push_back('\t'); + break; // tab + case 'r': + s.push_back('\r'); + break; // carriage return + case 'v': + s.push_back('\v'); + break; // vertical tab + + // the default is to reproduce the same character + default: + s.push_back(ch); + break; } } diff --git a/src/wad.cpp b/src/wad.cpp index 1288803..fb5adb4 100644 --- a/src/wad.cpp +++ b/src/wad.cpp @@ -652,21 +652,21 @@ void Wad_file::ProcessNamespaces(void) switch (active) { - case 'P': - patches.push_back(k); - break; - case 'S': - sprites.push_back(k); - break; - case 'F': - flats.push_back(k); - break; - case 'T': - tx_tex.push_back(k); - break; - - default: - PrintLine(LOG_ERROR, "ProcessNamespaces: active = 0x%02x", active); + case 'P': + patches.push_back(k); + break; + case 'S': + sprites.push_back(k); + break; + case 'F': + flats.push_back(k); + break; + case 'T': + tx_tex.push_back(k); + break; + + default: + PrintLine(LOG_ERROR, "ProcessNamespaces: active = 0x%02x", active); } } } From 1758ac048c797ded230f7e0172c290afb91c1906 Mon Sep 17 00:00:00 2001 From: "Guilherme M. Miranda" Date: Sat, 31 Jan 2026 02:06:17 -0300 Subject: [PATCH 05/14] dummy commit --- src/elfbsp.cpp | 20 ++-- src/level.cpp | 295 ++++++++++++++++++++++++++++++++++++++++--------- src/node.cpp | 9 +- 3 files changed, 253 insertions(+), 71 deletions(-) diff --git a/src/elfbsp.cpp b/src/elfbsp.cpp index b9d3ea0..e114389 100644 --- a/src/elfbsp.cpp +++ b/src/elfbsp.cpp @@ -53,7 +53,7 @@ static std::vector analysis_csv; buildinfo_t config; //------------------------------------------------------------------------ -void AnalysisSetupFile(const char * filepath) +void AnalysisSetupFile(const char *filepath) { auto csv_path = std::string(filepath); @@ -70,11 +70,11 @@ void AnalysisSetupFile(const char * filepath) analysis_csv.push_back(line); } -void AnalysisPushLine(size_t level_index, bool is_fast, double split_cost, size_t segs, size_t subsecs, size_t nodes, int32_t left_size, - int32_t right_size) +void AnalysisPushLine(size_t level_index, bool is_fast, double split_cost, size_t segs, size_t subsecs, size_t nodes, + int32_t left_size, int32_t right_size) { - std::string line = - std::format("{},{},{},{},{},{},{},{}", GetLevelName(level_index), is_fast, split_cost, segs, subsecs, nodes, left_size, right_size); + std::string line = std::format("{},{},{},{},{},{},{},{}", GetLevelName(level_index), is_fast, split_cost, segs, subsecs, + nodes, left_size, right_size); analysis_csv.push_back(line); } @@ -100,7 +100,7 @@ void WriteAnalysis(const char *filename) return; } - for (const auto& line : analysis_csv) + for (const auto &line : analysis_csv) { csv_file << line << '\n'; } @@ -145,7 +145,7 @@ bool CheckMapInMapList(const size_t lev_idx) const char *name = GetLevelName(lev_idx); - for (auto & map : map_list) + for (auto &map : map_list) { if (CheckMapInRange(&map, name)) { @@ -603,11 +603,11 @@ int32_t ParseLongArgument(const char *name, const int32_t argc, const char *argv } else if (strcmp(name, "--xnod") == 0) { - config.bsp_type = BSP_XNOD; + config.bsp_type = std::max(config.bsp_type, BSP_XNOD); } else if (strcmp(name, "--ssect") == 0) { - config.bsp_type = BSP_XGL3; + config.bsp_type = std::max(config.bsp_type, BSP_XGL3); } else if (strcmp(name, "--cost") == 0) { @@ -787,7 +787,7 @@ int32_t main(const int32_t argc, const char *argv[]) } } - for (const auto & wad : wad_list) + for (const auto &wad : wad_list) { VisitFile(wad); } diff --git a/src/level.cpp b/src/level.cpp index 0752e95..b553a06 100644 --- a/src/level.cpp +++ b/src/level.cpp @@ -1836,6 +1836,10 @@ static inline uint32_t VertexIndex_XNOD(const vertex_t *v) return static_cast(v->index); } +// +// Vanilla nodes +// + static void PutSegs_Vanilla(void) { // this size is worst-case scenario @@ -1914,18 +1918,16 @@ static void PutSubsecs_Vanilla(void) lump->Finish(); } -static size_t node_cur_index; - -static void PutOneNode_Vanilla(node_t *node, Lump_c *lump) +static void PutOneNode_Vanilla(node_t *node, size_t &node_cur_index, Lump_c *lump) { if (node->r.node) { - PutOneNode_Vanilla(node->r.node, lump); + PutOneNode_Vanilla(node->r.node, node_cur_index, lump); } if (node->l.node) { - PutOneNode_Vanilla(node->l.node, lump); + PutOneNode_Vanilla(node->l.node, node_cur_index, lump); } node->index = node_cur_index++; @@ -1987,14 +1989,12 @@ static void PutNodes_Vanilla(node_t *root) { // this can be bigger than the actual size, but never smaller size_t max_size = (lev_nodes.size() + 1) * sizeof(raw_node_vanilla_t); - + size_t node_cur_index = 0; Lump_c *lump = CreateLevelLump("NODES", max_size); - node_cur_index = 0; - if (root != nullptr) { - PutOneNode_Vanilla(root, lump); + PutOneNode_Vanilla(root, node_cur_index, lump); } lump->Finish(); @@ -2011,6 +2011,161 @@ static void PutNodes_Vanilla(node_t *root) } } +// +// DeepBSPV4 format +// + +static void PutSegs_DeepBSPV4(void) +{ + // this size is worst-case scenario + size_t size = lev_segs.size() * sizeof(raw_seg_deepbspv4_t); + Lump_c *lump = CreateLevelLump("SEGS", size); + + for (size_t i = 0; i < lev_segs.size(); i++) + { + raw_seg_deepbspv4_t raw; + + const seg_t *seg = lev_segs[i]; + + raw.start = GetLittleEndian(static_cast(seg->start->index)); + raw.end = GetLittleEndian(static_cast(seg->end->index)); + raw.angle = GetLittleEndian(VanillaSegAngle(seg)); + raw.linedef = GetLittleEndian(static_cast(seg->linedef->index)); + raw.flip = GetLittleEndian(seg->side); + raw.dist = GetLittleEndian(VanillaSegDist(seg)); + + lump->Write(&raw, sizeof(raw)); + + if (HAS_BIT(config.debug, DEBUG_BSP)) + { + PrintLine(LOG_DEBUG, "[%s] %zu Vert %04X->%04X Line %04X %s Angle %04X (%1.1f,%1.1f) -> (%1.1f,%1.1f)", __func__, + seg->index, GetLittleEndian(raw.start), GetLittleEndian(raw.end), GetLittleEndian(raw.linedef), + seg->side ? "L" : "R", GetLittleEndian(raw.angle), seg->start->x, seg->start->y, seg->end->x, seg->end->y); + } + } + + lump->Finish(); +} + +static void PutSubsecs_DeepBSPV4(void) +{ + size_t size = lev_subsecs.size() * sizeof(raw_subsec_deepbspv4_t); + + Lump_c *lump = CreateLevelLump("SSECTORS", size); + + for (size_t i = 0; i < lev_subsecs.size(); i++) + { + raw_subsec_deepbspv4_t raw; + + const subsec_t *sub = lev_subsecs[i]; + + raw.first = GetLittleEndian(static_cast(sub->seg_list->index)); + raw.num = GetLittleEndian(static_cast(sub->seg_count)); + + lump->Write(&raw, sizeof(raw)); + + if (HAS_BIT(config.debug, DEBUG_BSP)) + { + PrintLine(LOG_DEBUG, "[%s] %zu First %04X Num %04X", __func__, sub->index, GetLittleEndian(raw.first), + GetLittleEndian(raw.num)); + } + } + + lump->Finish(); +} + +static void PutOneNode_DeepBSPV4(node_t *node, size_t node_cur_index, Lump_c *lump) +{ + if (node->r.node) + { + PutOneNode_DeepBSPV4(node->r.node, node_cur_index, lump); + } + + if (node->l.node) + { + PutOneNode_DeepBSPV4(node->l.node, node_cur_index, lump); + } + + node->index = node_cur_index++; + + raw_node_deepbspv4_t raw; + + // note that x/y/dx/dy are always integral in non-UDMF maps + raw.x = GetLittleEndian(static_cast(floor(node->x))); + raw.y = GetLittleEndian(static_cast(floor(node->y))); + raw.dx = GetLittleEndian(static_cast(floor(node->dx))); + raw.dy = GetLittleEndian(static_cast(floor(node->dy))); + + raw.b1.minx = GetLittleEndian(static_cast(node->r.bounds.minx)); + raw.b1.miny = GetLittleEndian(static_cast(node->r.bounds.miny)); + raw.b1.maxx = GetLittleEndian(static_cast(node->r.bounds.maxx)); + raw.b1.maxy = GetLittleEndian(static_cast(node->r.bounds.maxy)); + + raw.b2.minx = GetLittleEndian(static_cast(node->l.bounds.minx)); + raw.b2.miny = GetLittleEndian(static_cast(node->l.bounds.miny)); + raw.b2.maxx = GetLittleEndian(static_cast(node->l.bounds.maxx)); + raw.b2.maxy = GetLittleEndian(static_cast(node->l.bounds.maxy)); + + if (node->r.node) + { + raw.right = GetLittleEndian(static_cast(node->r.node->index)); + } + else if (node->r.subsec) + { + raw.right = GetLittleEndian(static_cast(node->r.subsec->index | 0x8000)); + } + else + { + PrintLine(LOG_ERROR, "Bad right child in node %zu", node->index); + } + + if (node->l.node) + { + raw.left = GetLittleEndian(static_cast(node->l.node->index)); + } + else if (node->l.subsec) + { + raw.left = GetLittleEndian(static_cast(node->l.subsec->index | 0x8000)); + } + else + { + PrintLine(LOG_ERROR, "Bad left child in node %zu", node->index); + } + + lump->Write(&raw, sizeof(raw)); + + if (HAS_BIT(config.debug, DEBUG_BSP)) + { + PrintLine(LOG_DEBUG, "[%s] %zu Left %04X Right %04X (%1.1f,%1.1f) -> (%1.1f,%1.1f)", __func__, node->index, + GetLittleEndian(raw.left), GetLittleEndian(raw.right), node->x, node->y, node->x + node->dx, node->y + node->dy); + } +} + +static void PutNodes_DeepBSPV4(node_t *root) +{ + // this can be bigger than the actual size, but never smaller + // 8 bytes for BSP_MAGIC_DEEPBSPV4 header + size_t max_size = 8 + (lev_nodes.size() + 1) * sizeof(raw_node_deepbspv4_t); + size_t node_cur_index = 0; + Lump_c *lump = CreateLevelLump("NODES", max_size); + + if (root != nullptr) + { + PutOneNode_DeepBSPV4(root, node_cur_index, lump); + } + + lump->Finish(); + + if (node_cur_index != lev_nodes.size()) + { + PrintLine(LOG_ERROR, "PutNodes miscounted (%zu != %zu)", node_cur_index, lev_nodes.size()); + } +} + +// +// Check Limits +// + static void CheckBinaryFormatLimits(void) { // this could potentially be 65536, since there are no reserved values @@ -2043,7 +2198,7 @@ static void CheckBinaryFormatLimits(void) { PrintLine(LOG_NORMAL, "WARNING: Forcing XNOD format nodes due to overflows."); config.total_warnings++; - config.bsp_type = BSP_XNOD; + config.bsp_type = std::max(config.bsp_type, BSP_XNOD); } } } @@ -2197,18 +2352,18 @@ static void PutSegs_Xgl2(Lump_c *lump) } } -static void PutOneNode_Xnod(Lump_c *lump, node_t *node, bool xgl3) +static void PutOneNode_Xnod(Lump_c *lump, node_t *node, size_t &node_cur_index, bool xgl3) { raw_xgl3_node_t raw; if (node->r.node) { - PutOneNode_Xnod(lump, node->r.node, xgl3); + PutOneNode_Xnod(lump, node->r.node, node_cur_index, xgl3); } if (node->l.node) { - PutOneNode_Xnod(lump, node->l.node, xgl3); + PutOneNode_Xnod(lump, node->l.node, node_cur_index, xgl3); } node->index = node_cur_index++; @@ -2289,14 +2444,13 @@ static void PutOneNode_Xnod(Lump_c *lump, node_t *node, bool xgl3) static void PutNodes_Xnod(Lump_c *lump, node_t *root) { + size_t node_cur_index = 0; size_t raw_num = GetLittleEndian(lev_nodes.size()); lump->Write(&raw_num, 4); - node_cur_index = 0; - if (root) { - PutOneNode_Xnod(lump, root, false); + PutOneNode_Xnod(lump, root, node_cur_index, false); } if (node_cur_index != lev_nodes.size()) @@ -2307,14 +2461,13 @@ static void PutNodes_Xnod(Lump_c *lump, node_t *root) static void PutNodes_Xgl3(Lump_c *lump, node_t *root) { + size_t node_cur_index = 0; size_t raw_num = GetLittleEndian(lev_nodes.size()); lump->Write(&raw_num, 4); - node_cur_index = 0; - if (root) { - PutOneNode_Xnod(lump, root, true); + PutOneNode_Xnod(lump, root, node_cur_index, true); } if (node_cur_index != lev_nodes.size()) @@ -2491,42 +2644,75 @@ build_result_e SaveBinaryFormatLevel(node_t *root_node) CheckBinaryFormatLimits(); /* --- Normal nodes --- */ - if (config.bsp_type == BSP_XGL3 && num_real_lines > 0) - { - // leave SEGS empty - CreateLevelLump("SEGS")->Finish(); - Lump_c *lump = CreateLevelLump("SSECTORS"); - SaveFormat_Xgl3(lump, root_node); - CreateLevelLump("NODES")->Finish(); - } - else if (config.bsp_type == BSP_XNOD && num_real_lines > 0) - { - CreateLevelLump("SEGS")->Finish(); - CreateLevelLump("SSECTORS")->Finish(); - - // remove all the mini-segs from subsectors - NormaliseBspTree(); - - Lump_c *lump = CreateLevelLump("NODES", CalcXnodNodesSize()); - SaveFormat_Xnod(lump, root_node); - } - else + switch (config.bsp_type) { - // remove all the mini-segs from subsectors - NormaliseBspTree(); - - // reduce vertex precision for classic DOOM nodes. - // some segs can become "degenerate" after this, and these - // are removed from subsectors. - RoundOffBspTree(); - - SortSegs(); - - PutVertices(); - - PutSegs_Vanilla(); - PutSubsecs_Vanilla(); - PutNodes_Vanilla(root_node); + case BSP_XGL3: + { + // leave SEGS empty + CreateLevelLump("SEGS")->Finish(); + Lump_c *lump = CreateLevelLump("SSECTORS"); + SaveFormat_Xgl3(lump, root_node); + CreateLevelLump("NODES")->Finish(); + break; + } + case BSP_XGL2: + { + // leave SEGS empty + CreateLevelLump("SEGS")->Finish(); + Lump_c *lump = CreateLevelLump("SSECTORS"); + SaveFormat_Xgl2(lump, root_node); + CreateLevelLump("NODES")->Finish(); + break; + } + case BSP_XGLN: + { + // leave SEGS empty + CreateLevelLump("SEGS")->Finish(); + Lump_c *lump = CreateLevelLump("SSECTORS"); + SaveFormat_Xgln(lump, root_node); + CreateLevelLump("NODES")->Finish(); + break; + } + case BSP_XNOD: + { + CreateLevelLump("SEGS")->Finish(); + CreateLevelLump("SSECTORS")->Finish(); + // remove all the mini-segs from subsectors + NormaliseBspTree(); + Lump_c *lump = CreateLevelLump("NODES", CalcXnodNodesSize()); + SaveFormat_Xnod(lump, root_node); + break; + } + case BSP_DEEPBSPV4: + { + // remove all the mini-segs from subsectors + NormaliseBspTree(); + // reduce vertex precision for classic DOOM nodes. + // some segs can become "degenerate" after this, and these + // are removed from subsectors. + RoundOffBspTree(); + SortSegs(); + PutVertices(); + PutSegs_DeepBSPV4(); + PutSubsecs_DeepBSPV4(); + PutNodes_DeepBSPV4(root_node); + break; + } + case BSP_VANILLA: + { + // remove all the mini-segs from subsectors + NormaliseBspTree(); + // reduce vertex precision for classic DOOM nodes. + // some segs can become "degenerate" after this, and these + // are removed from subsectors. + RoundOffBspTree(); + SortSegs(); + PutVertices(); + PutSegs_Vanilla(); + PutSubsecs_Vanilla(); + PutNodes_Vanilla(root_node); + break; + } } PutBlockmap(); @@ -2754,7 +2940,6 @@ build_result_e BuildLevel(size_t lev_idx, const char *filename) FreeLevel(); - if (config.analysis) { WriteAnalysis(filename); diff --git a/src/node.cpp b/src/node.cpp index d6996a6..2e23984 100644 --- a/src/node.cpp +++ b/src/node.cpp @@ -1864,12 +1864,12 @@ void RoundOff(subsec_t *subsec) seg_t *new_head = nullptr; seg_t *new_tail = nullptr; - seg_t *seg; + seg_t *seg = nullptr; seg_t *last_real_degen = nullptr; int real_total = 0; - int degen_total = 0; + if (HAS_BIT(config.debug, DEBUG_SUBSEC)) { PrintLine(LOG_DEBUG, "[%s] Rounding off %zu", __func__, subsec->index); @@ -1889,10 +1889,7 @@ void RoundOff(subsec_t *subsec) last_real_degen = seg; } - if (HAS_BIT(config.debug, DEBUG_SUBSEC)) - { - degen_total++; - } + degen_total++; continue; } From e2f72dd61692087d763495007e8f9ae15742a386 Mon Sep 17 00:00:00 2001 From: "Guilherme M. Miranda" Date: Sat, 31 Jan 2026 06:00:38 -0300 Subject: [PATCH 06/14] split up bsp tree writing; added xgln,xgl2; fixed xgl3 writing --- CMakeLists.txt | 1 + src/bsp.cpp | 936 +++++++++++++++++++++++++++++++++++++++++++++++++ src/core.hpp | 8 + src/elfbsp.cpp | 8 +- src/level.cpp | 842 +------------------------------------------- src/local.hpp | 16 +- src/misc.cpp | 7 +- 7 files changed, 977 insertions(+), 841 deletions(-) create mode 100644 src/bsp.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1af2432..bae86c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,7 @@ if(SAN_COUNT GREATER 0) endif() add_executable(elfbsp + src/bsp.cpp src/elfbsp.cpp src/level.cpp src/node.cpp diff --git a/src/bsp.cpp b/src/bsp.cpp new file mode 100644 index 0000000..59e34ee --- /dev/null +++ b/src/bsp.cpp @@ -0,0 +1,936 @@ +#include + +#include "core.hpp" +#include "local.hpp" + +// +// Utility +// + +struct Compare_seg_pred +{ + inline bool operator()(const seg_t *A, const seg_t *B) const + { + return A->index < B->index; + } +}; + +void SortSegs(void) +{ + // sort segs into ascending index + std::sort(lev_segs.begin(), lev_segs.end(), Compare_seg_pred()); + + // remove unwanted segs + while (lev_segs.size() > 0 && lev_segs.back()->index == SEG_IS_GARBAGE) + { + UtilFree(lev_segs.back()); + lev_segs.pop_back(); + } +} + +static inline uint16_t VanillaSegDist(const seg_t *seg) +{ + double lx = seg->side ? seg->linedef->end->x : seg->linedef->start->x; + double ly = seg->side ? seg->linedef->end->y : seg->linedef->start->y; + + // use the "true" starting coord (as stored in the wad) + double sx = round(seg->start->x); + double sy = round(seg->start->y); + + return static_cast(floor(hypot(sx - lx, sy - ly) + 0.5)); +} + +static inline short_angle_t VanillaSegAngle(const seg_t *seg) +{ + // compute the "true" delta + double dx = round(seg->end->x) - round(seg->start->x); + double dy = round(seg->end->y) - round(seg->start->y); + + double angle = ComputeAngle(dx, dy); + + if (angle < 0) + { + angle += 360.0; + } + + short_angle_t result = short_angle_t(floor(angle * 65536.0 / 360.0 + 0.5)); + + // TODO: make sure this works better -- Hexen, UDMF + // -Elf- ZokumBSP + // 1080 => Additive degrees stored in tag + // 1081 => Set to degrees stored in tag + // 1082 => Additive BAM stored in tag + // 1083 => Set to BAM stored in tag + if (seg->linedef->special == Special_RotateDegrees) + { + result += DegreesToShortBAM(static_cast(seg->linedef->tag)); + } + else if (seg->linedef->special == Special_RotateDegreesHard) + { + result = DegreesToShortBAM(static_cast(seg->linedef->tag)); + } + else if (seg->linedef->special == Special_RotateAngleT) + { + result += static_cast(seg->linedef->tag); + } + else if (seg->linedef->special == Special_RotateAngleTHard) + { + result = static_cast(seg->linedef->tag); + } + + return result; +} + +static void PutVertices_Vanilla(void) +{ + // this size is worst-case scenario + size_t size = lev_vertices.size() * sizeof(raw_vertex_t); + Lump_c *lump = CreateLevelLump("VERTEXES", size); + + size_t count = 0; + for (size_t i = 0; i < lev_vertices.size(); i++) + { + raw_vertex_t raw; + + const vertex_t *vert = lev_vertices[i]; + + if (vert->is_new) + { + continue; + } + + raw.x = GetLittleEndian(static_cast(floor(vert->x))); + raw.y = GetLittleEndian(static_cast(floor(vert->y))); + + lump->Write(&raw, sizeof(raw)); + + count++; + } + + lump->Finish(); + + if (count != num_old_vert) + { + PrintLine(LOG_ERROR, "PutVertices miscounted (%zu != %zu)", count, num_old_vert); + } + + if (count > 65534) + { + PrintLine(LOG_NORMAL, "FAILURE: Number of vertices has overflowed."); + lev_overflows = true; + } +} + +static inline uint16_t VertexIndex16Bit(const vertex_t *v) +{ + if (v->is_new) + { + return static_cast(v->index | 0x8000U); + } + + return static_cast(v->index); +} + +static inline uint32_t VertexIndex_XNOD(const vertex_t *v) +{ + if (v->is_new) + { + return static_cast(num_old_vert + v->index); + } + + return static_cast(v->index); +} + +// +// Vanilla format +// + +static void PutSegs_Vanilla(void) +{ + // this size is worst-case scenario + size_t size = lev_segs.size() * sizeof(raw_seg_vanilla_t); + + Lump_c *lump = CreateLevelLump("SEGS", size); + + for (size_t i = 0; i < lev_segs.size(); i++) + { + raw_seg_vanilla_t raw; + + const seg_t *seg = lev_segs[i]; + + raw.start = GetLittleEndian(VertexIndex16Bit(seg->start)); + raw.end = GetLittleEndian(VertexIndex16Bit(seg->end)); + raw.angle = GetLittleEndian(VanillaSegAngle(seg)); + raw.linedef = GetLittleEndian(static_cast(seg->linedef->index)); + raw.flip = GetLittleEndian(seg->side); + raw.dist = GetLittleEndian(VanillaSegDist(seg)); + + // -Elf- ZokumBSP + if ((seg->linedef->dont_render_back && seg->side) || (seg->linedef->dont_render_front && !seg->side)) + { + raw = {}; + } + + lump->Write(&raw, sizeof(raw)); + + if (HAS_BIT(config.debug, DEBUG_BSP)) + { + PrintLine(LOG_DEBUG, "[%s] %zu Vert %04X->%04X Line %04X %s Angle %04X (%1.1f,%1.1f) -> (%1.1f,%1.1f)", __func__, + seg->index, GetLittleEndian(raw.start), GetLittleEndian(raw.end), GetLittleEndian(raw.linedef), + seg->side ? "L" : "R", GetLittleEndian(raw.angle), seg->start->x, seg->start->y, seg->end->x, seg->end->y); + } + } + + lump->Finish(); + + if (lev_segs.size() > 65534) + { + PrintLine(LOG_NORMAL, "FAILURE: Number of segs has overflowed."); + lev_overflows = true; + } +} + +static void PutSubsecs_Vanilla(void) +{ + size_t size = lev_subsecs.size() * sizeof(raw_subsec_vanilla_t); + + Lump_c *lump = CreateLevelLump("SSECTORS", size); + + for (size_t i = 0; i < lev_subsecs.size(); i++) + { + raw_subsec_vanilla_t raw; + + const subsec_t *sub = lev_subsecs[i]; + + raw.first = GetLittleEndian(static_cast(sub->seg_list->index)); + raw.num = GetLittleEndian(static_cast(sub->seg_count)); + + lump->Write(&raw, sizeof(raw)); + + if (HAS_BIT(config.debug, DEBUG_BSP)) + { + PrintLine(LOG_DEBUG, "[%s] %zu First %04X Num %04X", __func__, sub->index, GetLittleEndian(raw.first), + GetLittleEndian(raw.num)); + } + } + + if (lev_subsecs.size() > 32767) + { + PrintLine(LOG_NORMAL, "FAILURE: Number of subsectors has overflowed."); + lev_overflows = true; + } + + lump->Finish(); +} + +static void PutOneNode_Vanilla(node_t *node, size_t &node_cur_index, Lump_c *lump) +{ + if (node->r.node) + { + PutOneNode_Vanilla(node->r.node, node_cur_index, lump); + } + + if (node->l.node) + { + PutOneNode_Vanilla(node->l.node, node_cur_index, lump); + } + + node->index = node_cur_index++; + + raw_node_vanilla_t raw; + + // note that x/y/dx/dy are always integral in non-UDMF maps + raw.x = GetLittleEndian(static_cast(floor(node->x))); + raw.y = GetLittleEndian(static_cast(floor(node->y))); + raw.dx = GetLittleEndian(static_cast(floor(node->dx))); + raw.dy = GetLittleEndian(static_cast(floor(node->dy))); + + raw.b1.minx = GetLittleEndian(static_cast(node->r.bounds.minx)); + raw.b1.miny = GetLittleEndian(static_cast(node->r.bounds.miny)); + raw.b1.maxx = GetLittleEndian(static_cast(node->r.bounds.maxx)); + raw.b1.maxy = GetLittleEndian(static_cast(node->r.bounds.maxy)); + + raw.b2.minx = GetLittleEndian(static_cast(node->l.bounds.minx)); + raw.b2.miny = GetLittleEndian(static_cast(node->l.bounds.miny)); + raw.b2.maxx = GetLittleEndian(static_cast(node->l.bounds.maxx)); + raw.b2.maxy = GetLittleEndian(static_cast(node->l.bounds.maxy)); + + if (node->r.node) + { + raw.right = GetLittleEndian(static_cast(node->r.node->index)); + } + else if (node->r.subsec) + { + raw.right = GetLittleEndian(static_cast(node->r.subsec->index | 0x8000)); + } + else + { + PrintLine(LOG_ERROR, "Bad right child in node %zu", node->index); + } + + if (node->l.node) + { + raw.left = GetLittleEndian(static_cast(node->l.node->index)); + } + else if (node->l.subsec) + { + raw.left = GetLittleEndian(static_cast(node->l.subsec->index | 0x8000)); + } + else + { + PrintLine(LOG_ERROR, "Bad left child in node %zu", node->index); + } + + lump->Write(&raw, sizeof(raw)); + + if (HAS_BIT(config.debug, DEBUG_BSP)) + { + PrintLine(LOG_DEBUG, "[%s] %zu Left %04X Right %04X (%1.1f,%1.1f) -> (%1.1f,%1.1f)", __func__, node->index, + GetLittleEndian(raw.left), GetLittleEndian(raw.right), node->x, node->y, node->x + node->dx, node->y + node->dy); + } +} + +static void PutNodes_Vanilla(node_t *root) +{ + // this can be bigger than the actual size, but never smaller + size_t max_size = (lev_nodes.size() + 1) * sizeof(raw_node_vanilla_t); + size_t node_cur_index = 0; + Lump_c *lump = CreateLevelLump("NODES", max_size); + + if (root != nullptr) + { + PutOneNode_Vanilla(root, node_cur_index, lump); + } + + lump->Finish(); + + if (node_cur_index != lev_nodes.size()) + { + PrintLine(LOG_ERROR, "PutNodes miscounted (%zu != %zu)", node_cur_index, lev_nodes.size()); + } + + if (node_cur_index > 32767) + { + PrintLine(LOG_NORMAL, "FAILURE: Number of nodes has overflowed."); + lev_overflows = true; + } +} + +// +// DeepBSPV4 format +// + +static void PutSegs_DeepBSPV4(void) +{ + // this size is worst-case scenario + size_t size = lev_segs.size() * sizeof(raw_seg_deepbspv4_t); + Lump_c *lump = CreateLevelLump("SEGS", size); + + for (size_t i = 0; i < lev_segs.size(); i++) + { + raw_seg_deepbspv4_t raw; + + const seg_t *seg = lev_segs[i]; + + raw.start = GetLittleEndian(static_cast(seg->start->index)); + raw.end = GetLittleEndian(static_cast(seg->end->index)); + raw.angle = GetLittleEndian(VanillaSegAngle(seg)); + raw.linedef = GetLittleEndian(static_cast(seg->linedef->index)); + raw.flip = GetLittleEndian(seg->side); + raw.dist = GetLittleEndian(VanillaSegDist(seg)); + + lump->Write(&raw, sizeof(raw)); + + if (HAS_BIT(config.debug, DEBUG_BSP)) + { + PrintLine(LOG_DEBUG, "[%s] %zu Vert %08X->%08X Line %04X %s Angle %04X (%1.1f,%1.1f) -> (%1.1f,%1.1f)", __func__, + seg->index, GetLittleEndian(raw.start), GetLittleEndian(raw.end), GetLittleEndian(raw.linedef), + seg->side ? "L" : "R", GetLittleEndian(raw.angle), seg->start->x, seg->start->y, seg->end->x, seg->end->y); + } + } + + lump->Finish(); +} + +static void PutSubsecs_DeepBSPV4(void) +{ + size_t size = lev_subsecs.size() * sizeof(raw_subsec_deepbspv4_t); + + Lump_c *lump = CreateLevelLump("SSECTORS", size); + + for (size_t i = 0; i < lev_subsecs.size(); i++) + { + raw_subsec_deepbspv4_t raw; + + const subsec_t *sub = lev_subsecs[i]; + + raw.first = GetLittleEndian(static_cast(sub->seg_list->index)); + raw.num = GetLittleEndian(static_cast(sub->seg_count)); + + lump->Write(&raw, sizeof(raw)); + + if (HAS_BIT(config.debug, DEBUG_BSP)) + { + PrintLine(LOG_DEBUG, "[%s] %zu First %08X Num %04X", __func__, sub->index, GetLittleEndian(raw.first), + GetLittleEndian(raw.num)); + } + } + + lump->Finish(); +} + +static void PutOneNode_DeepBSPV4(node_t *node, size_t node_cur_index, Lump_c *lump) +{ + if (node->r.node) + { + PutOneNode_DeepBSPV4(node->r.node, node_cur_index, lump); + } + + if (node->l.node) + { + PutOneNode_DeepBSPV4(node->l.node, node_cur_index, lump); + } + + node->index = node_cur_index++; + + raw_node_deepbspv4_t raw; + + // note that x/y/dx/dy are always integral in non-UDMF maps + raw.x = GetLittleEndian(static_cast(floor(node->x))); + raw.y = GetLittleEndian(static_cast(floor(node->y))); + raw.dx = GetLittleEndian(static_cast(floor(node->dx))); + raw.dy = GetLittleEndian(static_cast(floor(node->dy))); + + raw.b1.minx = GetLittleEndian(static_cast(node->r.bounds.minx)); + raw.b1.miny = GetLittleEndian(static_cast(node->r.bounds.miny)); + raw.b1.maxx = GetLittleEndian(static_cast(node->r.bounds.maxx)); + raw.b1.maxy = GetLittleEndian(static_cast(node->r.bounds.maxy)); + + raw.b2.minx = GetLittleEndian(static_cast(node->l.bounds.minx)); + raw.b2.miny = GetLittleEndian(static_cast(node->l.bounds.miny)); + raw.b2.maxx = GetLittleEndian(static_cast(node->l.bounds.maxx)); + raw.b2.maxy = GetLittleEndian(static_cast(node->l.bounds.maxy)); + + if (node->r.node) + { + raw.right = GetLittleEndian(static_cast(node->r.node->index)); + } + else if (node->r.subsec) + { + raw.right = GetLittleEndian(static_cast(node->r.subsec->index | 0x8000)); + } + else + { + PrintLine(LOG_ERROR, "Bad right child in node %zu", node->index); + } + + if (node->l.node) + { + raw.left = GetLittleEndian(static_cast(node->l.node->index)); + } + else if (node->l.subsec) + { + raw.left = GetLittleEndian(static_cast(node->l.subsec->index | 0x8000)); + } + else + { + PrintLine(LOG_ERROR, "Bad left child in node %zu", node->index); + } + + lump->Write(&raw, sizeof(raw)); + + if (HAS_BIT(config.debug, DEBUG_BSP)) + { + PrintLine(LOG_DEBUG, "[%s] %zu Left %08X Right %08X (%1.1f,%1.1f) -> (%1.1f,%1.1f)", __func__, node->index, + GetLittleEndian(raw.left), GetLittleEndian(raw.right), node->x, node->y, node->x + node->dx, node->y + node->dy); + } +} + +static void PutNodes_DeepBSPV4(node_t *root) +{ + // this can be bigger than the actual size, but never smaller + // 8 bytes for BSP_MAGIC_DEEPBSPV4 header + size_t max_size = 8 + (lev_nodes.size() + 1) * sizeof(raw_node_deepbspv4_t); + size_t node_cur_index = 0; + + Lump_c *lump = CreateLevelLump("NODES", max_size); + lump->Write(BSP_MAGIC_DEEPBSPV4, 4); + + if (root != nullptr) + { + PutOneNode_DeepBSPV4(root, node_cur_index, lump); + } + + lump->Finish(); + + if (node_cur_index != lev_nodes.size()) + { + PrintLine(LOG_ERROR, "PutNodes miscounted (%zu != %zu)", node_cur_index, lev_nodes.size()); + } +} + +// +// ZDoom format -- XNOD +// + +static void PutVertices_Xnod(Lump_c *lump) +{ + size_t orgverts = GetLittleEndian(num_old_vert); + size_t newverts = GetLittleEndian(num_new_vert); + + lump->Write(&orgverts, 4); + lump->Write(&newverts, 4); + + size_t count = 0; + for (size_t i = 0; i < lev_vertices.size(); i++) + { + raw_xnod_vertex_t raw; + + const vertex_t *vert = lev_vertices[i]; + + if (!vert->is_new) + { + continue; + } + + raw.x = GetLittleEndian(static_cast(floor(vert->x * 65536.0))); + raw.y = GetLittleEndian(static_cast(floor(vert->y * 65536.0))); + + lump->Write(&raw, sizeof(raw)); + + count++; + } + + if (count != num_new_vert) + { + PrintLine(LOG_ERROR, "PutZVertices miscounted (%zu != %zu)", count, num_new_vert); + } +} + +static void PutSubsecs_Xnod(Lump_c *lump) +{ + size_t raw_num = GetLittleEndian(lev_subsecs.size()); + lump->Write(&raw_num, 4); + + size_t cur_seg_index = 0; + for (size_t i = 0; i < lev_subsecs.size(); i++) + { + const subsec_t *sub = lev_subsecs[i]; + + raw_num = GetLittleEndian(sub->seg_count); + lump->Write(&raw_num, 4); + + // sanity check the seg index values + size_t count = 0; + for (const seg_t *seg = sub->seg_list; seg; seg = seg->next, cur_seg_index++) + { + if (cur_seg_index != seg->index) + { + PrintLine(LOG_ERROR, "PutZSubsecs: seg index mismatch in sub %zu (%zu != %zu)", i, cur_seg_index, seg->index); + } + + count++; + } + + if (count != sub->seg_count) + { + PrintLine(LOG_ERROR, "PutZSubsecs: miscounted segs in sub %zu (%zu != %zu)", i, count, sub->seg_count); + } + } + + if (cur_seg_index != lev_segs.size()) + { + PrintLine(LOG_ERROR, "PutZSubsecs miscounted segs (%zu != %zu)", cur_seg_index, lev_segs.size()); + } +} + +static void PutSegs_Xnod(Lump_c *lump) +{ + size_t raw_num = GetLittleEndian(lev_segs.size()); + lump->Write(&raw_num, 4); + + for (size_t i = 0; i < lev_segs.size(); i++) + { + const seg_t *seg = lev_segs[i]; + + if (seg->index != i) + { + PrintLine(LOG_ERROR, "PutZSegs: seg index mismatch (%zu != %zu)", seg->index, i); + } + + raw_xnod_seg_t raw = {}; + + raw.start = GetLittleEndian(VertexIndex_XNOD(seg->start)); + raw.end = GetLittleEndian(VertexIndex_XNOD(seg->end)); + raw.linedef = GetLittleEndian(static_cast(seg->linedef->index)); + raw.side = seg->side; + lump->Write(&raw, sizeof(raw)); + } +} + +static void PutOneNode_Xnod(Lump_c *lump, node_t *node, size_t &node_cur_index) +{ + raw_xnod_node_t raw; + + if (node->r.node) + { + PutOneNode_Xnod(lump, node->r.node, node_cur_index); + } + + if (node->l.node) + { + PutOneNode_Xnod(lump, node->l.node, node_cur_index); + } + + node->index = node_cur_index++; + + raw.x = GetLittleEndian(static_cast(floor(node->x))); + raw.y = GetLittleEndian(static_cast(floor(node->y))); + raw.dx = GetLittleEndian(static_cast(floor(node->dx))); + raw.dy = GetLittleEndian(static_cast(floor(node->dy))); + + raw.b1.minx = GetLittleEndian(static_cast(node->r.bounds.minx)); + raw.b1.miny = GetLittleEndian(static_cast(node->r.bounds.miny)); + raw.b1.maxx = GetLittleEndian(static_cast(node->r.bounds.maxx)); + raw.b1.maxy = GetLittleEndian(static_cast(node->r.bounds.maxy)); + + raw.b2.minx = GetLittleEndian(static_cast(node->l.bounds.minx)); + raw.b2.miny = GetLittleEndian(static_cast(node->l.bounds.miny)); + raw.b2.maxx = GetLittleEndian(static_cast(node->l.bounds.maxx)); + raw.b2.maxy = GetLittleEndian(static_cast(node->l.bounds.maxy)); + + if (node->r.node) + { + raw.right = GetLittleEndian(static_cast(node->r.node->index)); + } + else if (node->r.subsec) + { + raw.right = GetLittleEndian(static_cast(node->r.subsec->index | 0x80000000U)); + } + else + { + PrintLine(LOG_ERROR, "Bad right child in ZDoom node %zu", node->index); + } + + if (node->l.node) + { + raw.left = GetLittleEndian(static_cast(node->l.node->index)); + } + else if (node->l.subsec) + { + raw.left = GetLittleEndian(static_cast(node->l.subsec->index | 0x80000000U)); + } + else + { + PrintLine(LOG_ERROR, "Bad left child in ZDoom node %zu", node->index); + } + + lump->Write(&raw, sizeof(raw_xnod_node_t)); + + if (HAS_BIT(config.debug, DEBUG_BSP)) + { + PrintLine(LOG_DEBUG, "[%s] %zu Left %08X Right %08X (%f,%f) -> (%f,%f)", __func__, node->index, + GetLittleEndian(raw.left), GetLittleEndian(raw.right), node->x, node->y, node->x + node->dx, node->y + node->dy); + } +} + +static void PutNodes_Xnod(Lump_c *lump, node_t *root) +{ + size_t node_cur_index = 0; + size_t raw_num = GetLittleEndian(lev_nodes.size()); + lump->Write(&raw_num, 4); + + if (root) + { + PutOneNode_Xnod(lump, root, node_cur_index); + } + + if (node_cur_index != lev_nodes.size()) + { + PrintLine(LOG_ERROR, "PutZNodes miscounted (%zu != %zu)", node_cur_index, lev_nodes.size()); + } +} + +static size_t CalcXnodNodesSize(void) +{ + // compute size of the ZDoom format nodes. + // it does not need to be exact, but it *does* need to be bigger + // (or equal) to the actual size of the lump. + + size_t size = 32; // header + a bit extra + + size += 8 + lev_vertices.size() * sizeof(raw_xnod_vertex_t); + size += 4 + lev_subsecs.size() * sizeof(raw_xnod_subsec_t); + size += 4 + lev_segs.size() * sizeof(raw_xnod_seg_t); + size += 4 + lev_nodes.size() * sizeof(raw_xnod_node_t); + + return size; +} + +// +// ZDoom format -- XGLN, XGL2, XGL3 +// + +static void PutSegs_Xgln(Lump_c *lump) +{ + size_t raw_num = GetLittleEndian(lev_segs.size()); + lump->Write(&raw_num, 4); + + for (size_t i = 0; i < lev_segs.size(); i++) + { + const seg_t *seg = lev_segs[i]; + + if (seg->index != i) + { + PrintLine(LOG_ERROR, "PutXGL3Segs: seg index mismatch (%zu != %zu)", seg->index, i); + } + + raw_xgln_seg_t raw = {}; + + raw.vertex = GetLittleEndian(VertexIndex_XNOD(seg->start)); + raw.partner = GetLittleEndian(static_cast(seg->partner ? seg->partner->index : NO_INDEX)); + raw.linedef = GetLittleEndian(static_cast(seg->linedef ? seg->linedef->index : NO_INDEX)); + raw.side = seg->side; + + lump->Write(&raw, sizeof(raw)); + + if (HAS_BIT(config.debug, DEBUG_BSP)) + { + PrintLine(LOG_DEBUG, "[%s] SEG[%zu] v1=%d partner=%d line=%d side=%d", __func__, i, raw.vertex, raw.partner, raw.linedef, + raw.side); + } + } +} + +static void PutSegs_Xgl2(Lump_c *lump) +{ + size_t raw_num = GetLittleEndian(lev_segs.size()); + lump->Write(&raw_num, 4); + + for (size_t i = 0; i < lev_segs.size(); i++) + { + const seg_t *seg = lev_segs[i]; + + if (seg->index != i) + { + PrintLine(LOG_ERROR, "PutXGL3Segs: seg index mismatch (%zu != %zu)", seg->index, i); + } + + raw_xgl2_seg_t raw = {}; + + raw.vertex = GetLittleEndian(VertexIndex_XNOD(seg->start)); + raw.partner = GetLittleEndian(static_cast(seg->partner ? seg->partner->index : NO_INDEX)); + raw.linedef = GetLittleEndian(static_cast(seg->linedef ? seg->linedef->index : NO_INDEX)); + raw.side = seg->side; + + lump->Write(&raw, sizeof(raw)); + + if (HAS_BIT(config.debug, DEBUG_BSP)) + { + PrintLine(LOG_DEBUG, "[%s] SEG[%zu] v1=%d partner=%d line=%d side=%d", __func__, i, raw.vertex, raw.partner, raw.linedef, + raw.side); + } + } +} + +static void PutOneNode_Xgl3(Lump_c *lump, node_t *node, size_t &node_cur_index) +{ + raw_xgl3_node_t raw; + + if (node->r.node) + { + PutOneNode_Xgl3(lump, node->r.node, node_cur_index); + } + + if (node->l.node) + { + PutOneNode_Xgl3(lump, node->l.node, node_cur_index); + } + + node->index = node_cur_index++; + + raw.x = GetLittleEndian(static_cast(floor(node->x) * 65536.0)); + raw.y = GetLittleEndian(static_cast(floor(node->y) * 65536.0)); + raw.dx = GetLittleEndian(static_cast(floor(node->dx * 65536.0))); + raw.dy = GetLittleEndian(static_cast(floor(node->dy * 65536.0))); + + raw.b1.minx = GetLittleEndian(static_cast(node->r.bounds.minx)); + raw.b1.miny = GetLittleEndian(static_cast(node->r.bounds.miny)); + raw.b1.maxx = GetLittleEndian(static_cast(node->r.bounds.maxx)); + raw.b1.maxy = GetLittleEndian(static_cast(node->r.bounds.maxy)); + + raw.b2.minx = GetLittleEndian(static_cast(node->l.bounds.minx)); + raw.b2.miny = GetLittleEndian(static_cast(node->l.bounds.miny)); + raw.b2.maxx = GetLittleEndian(static_cast(node->l.bounds.maxx)); + raw.b2.maxy = GetLittleEndian(static_cast(node->l.bounds.maxy)); + + if (node->r.node) + { + raw.right = GetLittleEndian(static_cast(node->r.node->index)); + } + else if (node->r.subsec) + { + raw.right = GetLittleEndian(static_cast(node->r.subsec->index | 0x80000000U)); + } + else + { + PrintLine(LOG_ERROR, "Bad right child in ZDoom node %zu", node->index); + } + + if (node->l.node) + { + raw.left = GetLittleEndian(static_cast(node->l.node->index)); + } + else if (node->l.subsec) + { + raw.left = GetLittleEndian(static_cast(node->l.subsec->index | 0x80000000U)); + } + else + { + PrintLine(LOG_ERROR, "Bad left child in ZDoom node %zu", node->index); + } + + lump->Write(&raw, sizeof(raw_xgl3_node_t)); + + if (HAS_BIT(config.debug, DEBUG_BSP)) + { + PrintLine(LOG_DEBUG, "[%s] %zu Left %08X Right %08X (%f,%f) -> (%f,%f)", __func__, node->index, + GetLittleEndian(raw.left), GetLittleEndian(raw.right), node->x, node->y, node->x + node->dx, node->y + node->dy); + } +} + +static void PutNodes_Xgl3(Lump_c *lump, node_t *root) +{ + size_t node_cur_index = 0; + size_t raw_num = GetLittleEndian(lev_nodes.size()); + lump->Write(&raw_num, 4); + + if (root) + { + PutOneNode_Xgl3(lump, root, node_cur_index); + } + + if (node_cur_index != lev_nodes.size()) + { + PrintLine(LOG_ERROR, "PutZNodes miscounted (%zu != %zu)", node_cur_index, lev_nodes.size()); + } +} + +// +// Lump writing procedures +// + +void SaveFormat_Vanilla(node_t *root_node) +{ + // remove all the mini-segs from subsectors + NormaliseBspTree(); + // reduce vertex precision for classic DOOM nodes. + // some segs can become "degenerate" after this, and these + // are removed from subsectors. + RoundOffBspTree(); + SortSegs(); + PutVertices_Vanilla(); + PutSegs_Vanilla(); + PutSubsecs_Vanilla(); + PutNodes_Vanilla(root_node); +} + +void SaveFormat_DeepBSPV4(node_t *root_node) +{ + // remove all the mini-segs from subsectors + NormaliseBspTree(); + // reduce vertex precision for classic DOOM nodes. + // some segs can become "degenerate" after this, and these + // are removed from subsectors. + RoundOffBspTree(); + SortSegs(); + PutVertices_Vanilla(); + PutSegs_DeepBSPV4(); + PutSubsecs_DeepBSPV4(); + PutNodes_DeepBSPV4(root_node); +} + +void SaveFormat_Xnod(node_t *root_node) +{ + CreateLevelLump("SEGS")->Finish(); + CreateLevelLump("SSECTORS")->Finish(); + // remove all the mini-segs from subsectors + NormaliseBspTree(); + SortSegs(); + + Lump_c *lump = CreateLevelLump("NODES", CalcXnodNodesSize()); + lump->Write(BSP_MAGIC_XNOD, 4); + PutVertices_Xnod(lump); + PutSubsecs_Xnod(lump); + PutSegs_Xnod(lump); + PutNodes_Xnod(lump, root_node); + + lump->Finish(); + lump = nullptr; +} + +void SaveFormat_Xgln(node_t *root_node) +{ + // leave SEGS empty + CreateLevelLump("SEGS")->Finish(); + + SortSegs(); + + // WISH : compute a max_size + Lump_c *lump = CreateLevelLump("SSECTORS"); + lump->Write(BSP_MAGIC_XGLN, 4); + PutVertices_Xnod(lump); + PutSubsecs_Xnod(lump); + PutSegs_Xgln(lump); + PutNodes_Xnod(lump, root_node); + + lump->Finish(); + lump = nullptr; + + // leave NODES empty + CreateLevelLump("NODES")->Finish(); +} + +void SaveFormat_Xgl2(node_t *root_node) +{ + // leave SEGS empty + CreateLevelLump("SEGS")->Finish(); + + SortSegs(); + + // WISH : compute a max_size + Lump_c *lump = CreateLevelLump("SSECTORS"); + lump->Write(BSP_MAGIC_XGL2, 4); + PutVertices_Xnod(lump); + PutSubsecs_Xnod(lump); + PutSegs_Xgl2(lump); + PutNodes_Xnod(lump, root_node); + + lump->Finish(); + lump = nullptr; + + // leave NODES empty + CreateLevelLump("NODES")->Finish(); +} + +void SaveFormat_Xgl3(node_t *root_node) +{ + // leave SEGS empty + CreateLevelLump("SEGS")->Finish(); + + SortSegs(); + + // WISH : compute a max_size + Lump_c *lump = CreateLevelLump("SSECTORS"); + lump->Write(BSP_MAGIC_XGL3, 4); + PutVertices_Xnod(lump); + PutSubsecs_Xnod(lump); + PutSegs_Xgl2(lump); + PutNodes_Xgl3(lump, root_node); + + lump->Finish(); + lump = nullptr; + + // leave NODES empty + CreateLevelLump("NODES")->Finish(); +} diff --git a/src/core.hpp b/src/core.hpp index e850713..d54e1c0 100644 --- a/src/core.hpp +++ b/src/core.hpp @@ -159,6 +159,12 @@ template constexpr T GetBigEndian(T value) } } +template +constexpr void RaiseValue(T& var, T value) +{ + var = std::max(var, value); +} + // sized types using byte = uint8_t; using args_t = uint8_t[5]; @@ -1430,6 +1436,8 @@ using build_result_t = enum build_result_e BUILD_LumpOverflow }; +extern bool lev_overflows; + // attempt to open a wad. on failure, the FatalError method in the // buildinfo_t interface is called. void OpenWad(const char *filename); diff --git a/src/elfbsp.cpp b/src/elfbsp.cpp index e114389..6de85a8 100644 --- a/src/elfbsp.cpp +++ b/src/elfbsp.cpp @@ -450,10 +450,10 @@ void ParseShortArgument(const char *arg) config.fast = true; continue; case 'x': - config.bsp_type = std::max(config.bsp_type, BSP_XNOD); + RaiseValue(config.bsp_type, BSP_XNOD); continue; case 's': - config.bsp_type = std::max(config.bsp_type, BSP_XGL3); + RaiseValue(config.bsp_type, BSP_XGL3); continue; case 'a': config.analysis = true; @@ -603,11 +603,11 @@ int32_t ParseLongArgument(const char *name, const int32_t argc, const char *argv } else if (strcmp(name, "--xnod") == 0) { - config.bsp_type = std::max(config.bsp_type, BSP_XNOD); + RaiseValue(config.bsp_type, BSP_XNOD); } else if (strcmp(name, "--ssect") == 0) { - config.bsp_type = std::max(config.bsp_type, BSP_XGL3); + RaiseValue(config.bsp_type, BSP_XGL3); } else if (strcmp(name, "--cost") == 0) { diff --git a/src/level.cpp b/src/level.cpp index b553a06..c171634 100644 --- a/src/level.cpp +++ b/src/level.cpp @@ -780,7 +780,7 @@ size_t lev_current_start; map_format_e lev_format; -bool lev_overflows; +bool lev_overflows = false; // objects of loaded level, and stuff we've built std::vector lev_vertices; @@ -1367,63 +1367,6 @@ static void GetLinedefsHexen(void) } } -static inline uint16_t VanillaSegDist(const seg_t *seg) -{ - double lx = seg->side ? seg->linedef->end->x : seg->linedef->start->x; - double ly = seg->side ? seg->linedef->end->y : seg->linedef->start->y; - - // use the "true" starting coord (as stored in the wad) - double sx = round(seg->start->x); - double sy = round(seg->start->y); - - return static_cast(floor(hypot(sx - lx, sy - ly) + 0.5)); -} - -static inline short_angle_t VanillaSegAngle(const seg_t *seg) -{ - // compute the "true" delta - double dx = round(seg->end->x) - round(seg->start->x); - double dy = round(seg->end->y) - round(seg->start->y); - - double angle = ComputeAngle(dx, dy); - - if (angle < 0) - { - angle += 360.0; - } - - short_angle_t result = short_angle_t(floor(angle * 65536.0 / 360.0 + 0.5)); - - if (lev_format != MapFormat_Doom) - { - return result; - } - - // -Elf- ZokumBSP - // 1080 => Additive degrees stored in tag - // 1081 => Set to degrees stored in tag - // 1082 => Additive BAM stored in tag - // 1083 => Set to BAM stored in tag - if (seg->linedef->special == Special_RotateDegrees) - { - result += DegreesToShortBAM(static_cast(seg->linedef->tag)); - } - else if (seg->linedef->special == Special_RotateDegreesHard) - { - result = DegreesToShortBAM(static_cast(seg->linedef->tag)); - } - else if (seg->linedef->special == Special_RotateAngleT) - { - result += static_cast(seg->linedef->tag); - } - else if (seg->linedef->special == Special_RotateAngleTHard) - { - result = static_cast(seg->linedef->tag); - } - - return result; -} - /* ----- UDMF reading routines ------------------------- */ static constexpr uint32_t UDMF_THING = 1; @@ -1770,403 +1713,11 @@ void ParseUDMF(void) /* ----- writing routines ------------------------------ */ -static void MarkOverflow(int flags) -{ - // flags are ignored - lev_overflows = true; -} - -static void PutVertices(void) -{ - // this size is worst-case scenario - size_t size = lev_vertices.size() * sizeof(raw_vertex_t); - Lump_c *lump = CreateLevelLump("VERTEXES", size); - - size_t count = 0; - for (size_t i = 0; i < lev_vertices.size(); i++) - { - raw_vertex_t raw; - - const vertex_t *vert = lev_vertices[i]; - - if (vert->is_new) - { - continue; - } - - raw.x = GetLittleEndian(static_cast(floor(vert->x))); - raw.y = GetLittleEndian(static_cast(floor(vert->y))); - - lump->Write(&raw, sizeof(raw)); - - count++; - } - - lump->Finish(); - - if (count != num_old_vert) - { - PrintLine(LOG_ERROR, "PutVertices miscounted (%zu != %zu)", count, num_old_vert); - } - - if (count > 65534) - { - PrintLine(LOG_NORMAL, "FAILURE: Number of vertices has overflowed."); - MarkOverflow(LIMIT_VERTEXES); - } -} - -static inline uint16_t VertexIndex16Bit(const vertex_t *v) -{ - if (v->is_new) - { - return static_cast(v->index | 0x8000U); - } - - return static_cast(v->index); -} - -static inline uint32_t VertexIndex_XNOD(const vertex_t *v) -{ - if (v->is_new) - { - return static_cast(num_old_vert + v->index); - } - - return static_cast(v->index); -} - -// -// Vanilla nodes -// - -static void PutSegs_Vanilla(void) -{ - // this size is worst-case scenario - size_t size = lev_segs.size() * sizeof(raw_seg_vanilla_t); - - Lump_c *lump = CreateLevelLump("SEGS", size); - - for (size_t i = 0; i < lev_segs.size(); i++) - { - raw_seg_vanilla_t raw; - - const seg_t *seg = lev_segs[i]; - - raw.start = GetLittleEndian(VertexIndex16Bit(seg->start)); - raw.end = GetLittleEndian(VertexIndex16Bit(seg->end)); - raw.angle = GetLittleEndian(VanillaSegAngle(seg)); - raw.linedef = GetLittleEndian(static_cast(seg->linedef->index)); - raw.flip = GetLittleEndian(seg->side); - raw.dist = GetLittleEndian(VanillaSegDist(seg)); - - // -Elf- ZokumBSP - if ((seg->linedef->dont_render_back && seg->side) || (seg->linedef->dont_render_front && !seg->side)) - { - raw = {}; - } - - lump->Write(&raw, sizeof(raw)); - - if (HAS_BIT(config.debug, DEBUG_BSP)) - { - PrintLine(LOG_DEBUG, "[%s] %zu Vert %04X->%04X Line %04X %s Angle %04X (%1.1f,%1.1f) -> (%1.1f,%1.1f)", __func__, - seg->index, GetLittleEndian(raw.start), GetLittleEndian(raw.end), GetLittleEndian(raw.linedef), - seg->side ? "L" : "R", GetLittleEndian(raw.angle), seg->start->x, seg->start->y, seg->end->x, seg->end->y); - } - } - - lump->Finish(); - - if (lev_segs.size() > 65534) - { - PrintLine(LOG_NORMAL, "FAILURE: Number of segs has overflowed."); - MarkOverflow(LIMIT_SEGS); - } -} - -static void PutSubsecs_Vanilla(void) -{ - size_t size = lev_subsecs.size() * sizeof(raw_subsec_vanilla_t); - - Lump_c *lump = CreateLevelLump("SSECTORS", size); - - for (size_t i = 0; i < lev_subsecs.size(); i++) - { - raw_subsec_vanilla_t raw; - - const subsec_t *sub = lev_subsecs[i]; - - raw.first = GetLittleEndian(static_cast(sub->seg_list->index)); - raw.num = GetLittleEndian(static_cast(sub->seg_count)); - - lump->Write(&raw, sizeof(raw)); - - if (HAS_BIT(config.debug, DEBUG_BSP)) - { - PrintLine(LOG_DEBUG, "[%s] %zu First %04X Num %04X", __func__, sub->index, GetLittleEndian(raw.first), - GetLittleEndian(raw.num)); - } - } - - if (lev_subsecs.size() > 32767) - { - PrintLine(LOG_NORMAL, "FAILURE: Number of subsectors has overflowed."); - MarkOverflow(LIMIT_SSECTORS); - } - - lump->Finish(); -} - -static void PutOneNode_Vanilla(node_t *node, size_t &node_cur_index, Lump_c *lump) -{ - if (node->r.node) - { - PutOneNode_Vanilla(node->r.node, node_cur_index, lump); - } - - if (node->l.node) - { - PutOneNode_Vanilla(node->l.node, node_cur_index, lump); - } - - node->index = node_cur_index++; - - raw_node_vanilla_t raw; - - // note that x/y/dx/dy are always integral in non-UDMF maps - raw.x = GetLittleEndian(static_cast(floor(node->x))); - raw.y = GetLittleEndian(static_cast(floor(node->y))); - raw.dx = GetLittleEndian(static_cast(floor(node->dx))); - raw.dy = GetLittleEndian(static_cast(floor(node->dy))); - - raw.b1.minx = GetLittleEndian(static_cast(node->r.bounds.minx)); - raw.b1.miny = GetLittleEndian(static_cast(node->r.bounds.miny)); - raw.b1.maxx = GetLittleEndian(static_cast(node->r.bounds.maxx)); - raw.b1.maxy = GetLittleEndian(static_cast(node->r.bounds.maxy)); - - raw.b2.minx = GetLittleEndian(static_cast(node->l.bounds.minx)); - raw.b2.miny = GetLittleEndian(static_cast(node->l.bounds.miny)); - raw.b2.maxx = GetLittleEndian(static_cast(node->l.bounds.maxx)); - raw.b2.maxy = GetLittleEndian(static_cast(node->l.bounds.maxy)); - - if (node->r.node) - { - raw.right = GetLittleEndian(static_cast(node->r.node->index)); - } - else if (node->r.subsec) - { - raw.right = GetLittleEndian(static_cast(node->r.subsec->index | 0x8000)); - } - else - { - PrintLine(LOG_ERROR, "Bad right child in node %zu", node->index); - } - - if (node->l.node) - { - raw.left = GetLittleEndian(static_cast(node->l.node->index)); - } - else if (node->l.subsec) - { - raw.left = GetLittleEndian(static_cast(node->l.subsec->index | 0x8000)); - } - else - { - PrintLine(LOG_ERROR, "Bad left child in node %zu", node->index); - } - - lump->Write(&raw, sizeof(raw)); - - if (HAS_BIT(config.debug, DEBUG_BSP)) - { - PrintLine(LOG_DEBUG, "[%s] %zu Left %04X Right %04X (%1.1f,%1.1f) -> (%1.1f,%1.1f)", __func__, node->index, - GetLittleEndian(raw.left), GetLittleEndian(raw.right), node->x, node->y, node->x + node->dx, node->y + node->dy); - } -} - -static void PutNodes_Vanilla(node_t *root) -{ - // this can be bigger than the actual size, but never smaller - size_t max_size = (lev_nodes.size() + 1) * sizeof(raw_node_vanilla_t); - size_t node_cur_index = 0; - Lump_c *lump = CreateLevelLump("NODES", max_size); - - if (root != nullptr) - { - PutOneNode_Vanilla(root, node_cur_index, lump); - } - - lump->Finish(); - - if (node_cur_index != lev_nodes.size()) - { - PrintLine(LOG_ERROR, "PutNodes miscounted (%zu != %zu)", node_cur_index, lev_nodes.size()); - } - - if (node_cur_index > 32767) - { - PrintLine(LOG_NORMAL, "FAILURE: Number of nodes has overflowed."); - MarkOverflow(LIMIT_NODES); - } -} - -// -// DeepBSPV4 format -// - -static void PutSegs_DeepBSPV4(void) -{ - // this size is worst-case scenario - size_t size = lev_segs.size() * sizeof(raw_seg_deepbspv4_t); - Lump_c *lump = CreateLevelLump("SEGS", size); - - for (size_t i = 0; i < lev_segs.size(); i++) - { - raw_seg_deepbspv4_t raw; - - const seg_t *seg = lev_segs[i]; - - raw.start = GetLittleEndian(static_cast(seg->start->index)); - raw.end = GetLittleEndian(static_cast(seg->end->index)); - raw.angle = GetLittleEndian(VanillaSegAngle(seg)); - raw.linedef = GetLittleEndian(static_cast(seg->linedef->index)); - raw.flip = GetLittleEndian(seg->side); - raw.dist = GetLittleEndian(VanillaSegDist(seg)); - - lump->Write(&raw, sizeof(raw)); - - if (HAS_BIT(config.debug, DEBUG_BSP)) - { - PrintLine(LOG_DEBUG, "[%s] %zu Vert %04X->%04X Line %04X %s Angle %04X (%1.1f,%1.1f) -> (%1.1f,%1.1f)", __func__, - seg->index, GetLittleEndian(raw.start), GetLittleEndian(raw.end), GetLittleEndian(raw.linedef), - seg->side ? "L" : "R", GetLittleEndian(raw.angle), seg->start->x, seg->start->y, seg->end->x, seg->end->y); - } - } - - lump->Finish(); -} - -static void PutSubsecs_DeepBSPV4(void) -{ - size_t size = lev_subsecs.size() * sizeof(raw_subsec_deepbspv4_t); - - Lump_c *lump = CreateLevelLump("SSECTORS", size); - - for (size_t i = 0; i < lev_subsecs.size(); i++) - { - raw_subsec_deepbspv4_t raw; - - const subsec_t *sub = lev_subsecs[i]; - - raw.first = GetLittleEndian(static_cast(sub->seg_list->index)); - raw.num = GetLittleEndian(static_cast(sub->seg_count)); - - lump->Write(&raw, sizeof(raw)); - - if (HAS_BIT(config.debug, DEBUG_BSP)) - { - PrintLine(LOG_DEBUG, "[%s] %zu First %04X Num %04X", __func__, sub->index, GetLittleEndian(raw.first), - GetLittleEndian(raw.num)); - } - } - - lump->Finish(); -} - -static void PutOneNode_DeepBSPV4(node_t *node, size_t node_cur_index, Lump_c *lump) -{ - if (node->r.node) - { - PutOneNode_DeepBSPV4(node->r.node, node_cur_index, lump); - } - - if (node->l.node) - { - PutOneNode_DeepBSPV4(node->l.node, node_cur_index, lump); - } - - node->index = node_cur_index++; - - raw_node_deepbspv4_t raw; - - // note that x/y/dx/dy are always integral in non-UDMF maps - raw.x = GetLittleEndian(static_cast(floor(node->x))); - raw.y = GetLittleEndian(static_cast(floor(node->y))); - raw.dx = GetLittleEndian(static_cast(floor(node->dx))); - raw.dy = GetLittleEndian(static_cast(floor(node->dy))); - - raw.b1.minx = GetLittleEndian(static_cast(node->r.bounds.minx)); - raw.b1.miny = GetLittleEndian(static_cast(node->r.bounds.miny)); - raw.b1.maxx = GetLittleEndian(static_cast(node->r.bounds.maxx)); - raw.b1.maxy = GetLittleEndian(static_cast(node->r.bounds.maxy)); - - raw.b2.minx = GetLittleEndian(static_cast(node->l.bounds.minx)); - raw.b2.miny = GetLittleEndian(static_cast(node->l.bounds.miny)); - raw.b2.maxx = GetLittleEndian(static_cast(node->l.bounds.maxx)); - raw.b2.maxy = GetLittleEndian(static_cast(node->l.bounds.maxy)); - - if (node->r.node) - { - raw.right = GetLittleEndian(static_cast(node->r.node->index)); - } - else if (node->r.subsec) - { - raw.right = GetLittleEndian(static_cast(node->r.subsec->index | 0x8000)); - } - else - { - PrintLine(LOG_ERROR, "Bad right child in node %zu", node->index); - } - - if (node->l.node) - { - raw.left = GetLittleEndian(static_cast(node->l.node->index)); - } - else if (node->l.subsec) - { - raw.left = GetLittleEndian(static_cast(node->l.subsec->index | 0x8000)); - } - else - { - PrintLine(LOG_ERROR, "Bad left child in node %zu", node->index); - } - - lump->Write(&raw, sizeof(raw)); - - if (HAS_BIT(config.debug, DEBUG_BSP)) - { - PrintLine(LOG_DEBUG, "[%s] %zu Left %04X Right %04X (%1.1f,%1.1f) -> (%1.1f,%1.1f)", __func__, node->index, - GetLittleEndian(raw.left), GetLittleEndian(raw.right), node->x, node->y, node->x + node->dx, node->y + node->dy); - } -} - -static void PutNodes_DeepBSPV4(node_t *root) -{ - // this can be bigger than the actual size, but never smaller - // 8 bytes for BSP_MAGIC_DEEPBSPV4 header - size_t max_size = 8 + (lev_nodes.size() + 1) * sizeof(raw_node_deepbspv4_t); - size_t node_cur_index = 0; - Lump_c *lump = CreateLevelLump("NODES", max_size); - - if (root != nullptr) - { - PutOneNode_DeepBSPV4(root, node_cur_index, lump); - } - - lump->Finish(); - - if (node_cur_index != lev_nodes.size()) - { - PrintLine(LOG_ERROR, "PutNodes miscounted (%zu != %zu)", node_cur_index, lev_nodes.size()); - } -} - // // Check Limits // -static void CheckBinaryFormatLimits(void) +static void CheckBinaryFormatLimits(bsp_type_t &bsp_type) { // this could potentially be 65536, since there are no reserved values // for sectors, but there may be source ports or tools treating 0xFFFF @@ -2175,355 +1726,37 @@ static void CheckBinaryFormatLimits(void) if (lev_sectors.size() > 65535) { PrintLine(LOG_NORMAL, "FAILURE: Map has too many sectors."); - MarkOverflow(LIMIT_SECTORS); + lev_overflows = true; } // the sidedef 0xFFFF is reserved to mean "no side" in DOOM map format if (lev_sidedefs.size() > 65535) { PrintLine(LOG_NORMAL, "FAILURE: Map has too many sidedefs."); - MarkOverflow(LIMIT_SIDEDEFS); + lev_overflows = true; } // the linedef 0xFFFF is reserved for minisegs in GL nodes if (lev_linedefs.size() > 65535) { PrintLine(LOG_NORMAL, "FAILURE: Map has too many linedefs."); - MarkOverflow(LIMIT_LINEDEFS); + lev_overflows = true; } +} +void RaiseBSPFormat(bsp_type_t &bsp_type) +{ if (config.bsp_type < BSP_XNOD) { if (num_old_vert > 32767 || num_new_vert > 32767 || lev_segs.size() > 32767 || lev_nodes.size() > 32767) { - PrintLine(LOG_NORMAL, "WARNING: Forcing XNOD format nodes due to overflows."); + PrintLine(LOG_NORMAL, "WARNING: Forcing DeepBSPV4 format nodes due to overflows."); config.total_warnings++; - config.bsp_type = std::max(config.bsp_type, BSP_XNOD); - } - } -} - -struct Compare_seg_pred -{ - inline bool operator()(const seg_t *A, const seg_t *B) const - { - return A->index < B->index; - } -}; - -void SortSegs(void) -{ - // sort segs into ascending index - std::sort(lev_segs.begin(), lev_segs.end(), Compare_seg_pred()); - - // remove unwanted segs - while (lev_segs.size() > 0 && lev_segs.back()->index == SEG_IS_GARBAGE) - { - UtilFree(lev_segs.back()); - lev_segs.pop_back(); - } -} - -/* ----- ZDoom format writing --------------------------- */ - -static void PutVertices_Xnod(Lump_c *lump) -{ - size_t orgverts = GetLittleEndian(num_old_vert); - size_t newverts = GetLittleEndian(num_new_vert); - - lump->Write(&orgverts, 4); - lump->Write(&newverts, 4); - - size_t count = 0; - for (size_t i = 0; i < lev_vertices.size(); i++) - { - raw_xnod_vertex_t raw; - - const vertex_t *vert = lev_vertices[i]; - - if (!vert->is_new) - { - continue; - } - - raw.x = GetLittleEndian(static_cast(floor(vert->x * 65536.0))); - raw.y = GetLittleEndian(static_cast(floor(vert->y * 65536.0))); - - lump->Write(&raw, sizeof(raw)); - - count++; - } - - if (count != num_new_vert) - { - PrintLine(LOG_ERROR, "PutZVertices miscounted (%zu != %zu)", count, num_new_vert); - } -} - -static void PutSubsecs_Xnod(Lump_c *lump) -{ - size_t raw_num = GetLittleEndian(lev_subsecs.size()); - lump->Write(&raw_num, 4); - - size_t cur_seg_index = 0; - for (size_t i = 0; i < lev_subsecs.size(); i++) - { - const subsec_t *sub = lev_subsecs[i]; - - raw_num = GetLittleEndian(sub->seg_count); - lump->Write(&raw_num, 4); - - // sanity check the seg index values - size_t count = 0; - for (const seg_t *seg = sub->seg_list; seg; seg = seg->next, cur_seg_index++) - { - if (cur_seg_index != seg->index) - { - PrintLine(LOG_ERROR, "PutZSubsecs: seg index mismatch in sub %zu (%zu != %zu)", i, cur_seg_index, seg->index); - } - - count++; + RaiseValue(config.bsp_type, BSP_DEEPBSPV4); } - - if (count != sub->seg_count) - { - PrintLine(LOG_ERROR, "PutZSubsecs: miscounted segs in sub %zu (%zu != %zu)", i, count, sub->seg_count); - } - } - - if (cur_seg_index != lev_segs.size()) - { - PrintLine(LOG_ERROR, "PutZSubsecs miscounted segs (%zu != %zu)", cur_seg_index, lev_segs.size()); - } -} - -static void PutSegs_Xnod(Lump_c *lump) -{ - size_t raw_num = GetLittleEndian(lev_segs.size()); - lump->Write(&raw_num, 4); - - for (size_t i = 0; i < lev_segs.size(); i++) - { - const seg_t *seg = lev_segs[i]; - - if (seg->index != i) - { - PrintLine(LOG_ERROR, "PutZSegs: seg index mismatch (%zu != %zu)", seg->index, i); - } - - raw_xnod_seg_t raw = {}; - - raw.start = GetLittleEndian(VertexIndex_XNOD(seg->start)); - raw.end = GetLittleEndian(VertexIndex_XNOD(seg->end)); - raw.linedef = GetLittleEndian(static_cast(seg->linedef->index)); - raw.side = seg->side; - lump->Write(&raw, sizeof(raw)); } } -static void PutSegs_Xgl2(Lump_c *lump) -{ - size_t raw_num = GetLittleEndian(lev_segs.size()); - lump->Write(&raw_num, 4); - - for (size_t i = 0; i < lev_segs.size(); i++) - { - const seg_t *seg = lev_segs[i]; - - if (seg->index != i) - { - PrintLine(LOG_ERROR, "PutXGL3Segs: seg index mismatch (%zu != %zu)", seg->index, i); - } - - raw_xgl2_seg_t raw = {}; - - raw.vertex = GetLittleEndian(VertexIndex_XNOD(seg->start)); - raw.partner = GetLittleEndian(static_cast(seg->partner ? seg->partner->index : NO_INDEX)); - raw.linedef = GetLittleEndian(static_cast(seg->linedef ? seg->linedef->index : NO_INDEX)); - raw.side = seg->side; - - lump->Write(&raw, sizeof(raw)); - - if (HAS_BIT(config.debug, DEBUG_BSP)) - { - PrintLine(LOG_DEBUG, "[%s] SEG[%zu] v1=%d partner=%d line=%d side=%d", __func__, i, raw.vertex, raw.partner, raw.linedef, - raw.side); - } - } -} - -static void PutOneNode_Xnod(Lump_c *lump, node_t *node, size_t &node_cur_index, bool xgl3) -{ - raw_xgl3_node_t raw; - - if (node->r.node) - { - PutOneNode_Xnod(lump, node->r.node, node_cur_index, xgl3); - } - - if (node->l.node) - { - PutOneNode_Xnod(lump, node->l.node, node_cur_index, xgl3); - } - - node->index = node_cur_index++; - - if (xgl3) - { - int32_t x = GetLittleEndian(static_cast(floor(node->x * 65536.0))); - int32_t y = GetLittleEndian(static_cast(floor(node->y * 65536.0))); - int32_t dx = GetLittleEndian(static_cast(floor(node->dx * 65536.0))); - int32_t dy = GetLittleEndian(static_cast(floor(node->dy * 65536.0))); - - lump->Write(&x, 4); - lump->Write(&y, 4); - lump->Write(&dx, 4); - lump->Write(&dy, 4); - } - else - { - raw.x = GetLittleEndian(static_cast(floor(node->x))); - raw.y = GetLittleEndian(static_cast(floor(node->y))); - raw.dx = GetLittleEndian(static_cast(floor(node->dx))); - raw.dy = GetLittleEndian(static_cast(floor(node->dy))); - - lump->Write(&raw.x, 2); - lump->Write(&raw.y, 2); - lump->Write(&raw.dx, 2); - lump->Write(&raw.dy, 2); - } - - raw.b1.minx = GetLittleEndian(static_cast(node->r.bounds.minx)); - raw.b1.miny = GetLittleEndian(static_cast(node->r.bounds.miny)); - raw.b1.maxx = GetLittleEndian(static_cast(node->r.bounds.maxx)); - raw.b1.maxy = GetLittleEndian(static_cast(node->r.bounds.maxy)); - - raw.b2.minx = GetLittleEndian(static_cast(node->l.bounds.minx)); - raw.b2.miny = GetLittleEndian(static_cast(node->l.bounds.miny)); - raw.b2.maxx = GetLittleEndian(static_cast(node->l.bounds.maxx)); - raw.b2.maxy = GetLittleEndian(static_cast(node->l.bounds.maxy)); - - lump->Write(&raw.b1, sizeof(raw.b1)); - lump->Write(&raw.b2, sizeof(raw.b2)); - - if (node->r.node) - { - raw.right = GetLittleEndian(static_cast(node->r.node->index)); - } - else if (node->r.subsec) - { - raw.right = GetLittleEndian(static_cast(node->r.subsec->index | 0x80000000U)); - } - else - { - PrintLine(LOG_ERROR, "Bad right child in ZDoom node %zu", node->index); - } - - if (node->l.node) - { - raw.left = GetLittleEndian(static_cast(node->l.node->index)); - } - else if (node->l.subsec) - { - raw.left = GetLittleEndian(static_cast(node->l.subsec->index | 0x80000000U)); - } - else - { - PrintLine(LOG_ERROR, "Bad left child in ZDoom node %zu", node->index); - } - - lump->Write(&raw.right, 4); - lump->Write(&raw.left, 4); - - if (HAS_BIT(config.debug, DEBUG_BSP)) - { - PrintLine(LOG_DEBUG, "[%s] %zu Left %08X Right %08X (%f,%f) -> (%f,%f)", __func__, node->index, - GetLittleEndian(raw.left), GetLittleEndian(raw.right), node->x, node->y, node->x + node->dx, node->y + node->dy); - } -} - -static void PutNodes_Xnod(Lump_c *lump, node_t *root) -{ - size_t node_cur_index = 0; - size_t raw_num = GetLittleEndian(lev_nodes.size()); - lump->Write(&raw_num, 4); - - if (root) - { - PutOneNode_Xnod(lump, root, node_cur_index, false); - } - - if (node_cur_index != lev_nodes.size()) - { - PrintLine(LOG_ERROR, "PutZNodes miscounted (%zu != %zu)", node_cur_index, lev_nodes.size()); - } -} - -static void PutNodes_Xgl3(Lump_c *lump, node_t *root) -{ - size_t node_cur_index = 0; - size_t raw_num = GetLittleEndian(lev_nodes.size()); - lump->Write(&raw_num, 4); - - if (root) - { - PutOneNode_Xnod(lump, root, node_cur_index, true); - } - - if (node_cur_index != lev_nodes.size()) - { - PrintLine(LOG_ERROR, "PutZNodes miscounted (%zu != %zu)", node_cur_index, lev_nodes.size()); - } -} - -static size_t CalcXnodNodesSize(void) -{ - // compute size of the ZDoom format nodes. - // it does not need to be exact, but it *does* need to be bigger - // (or equal) to the actual size of the lump. - - size_t size = 32; // header + a bit extra - - size += 8 + lev_vertices.size() * sizeof(raw_xnod_vertex_t); - size += 4 + lev_subsecs.size() * sizeof(raw_xnod_subsec_t); - size += 4 + lev_segs.size() * sizeof(raw_xnod_seg_t); - size += 4 + lev_nodes.size() * sizeof(raw_xnod_node_t); - - return size; -} - -void SaveFormat_Xnod(Lump_c *lump, node_t *root_node) -{ - SortSegs(); - - lump->Write(BSP_MAGIC_XNOD, 4); - - PutVertices_Xnod(lump); - PutSubsecs_Xnod(lump); - PutSegs_Xnod(lump); - PutNodes_Xnod(lump, root_node); - - lump->Finish(); - lump = nullptr; -} - -static void SaveFormat_Xgl3(Lump_c *lump, node_t *root_node) -{ - SortSegs(); - - // WISH : compute a max_size - - lump->Write(BSP_MAGIC_XGL3, 4); - - PutVertices_Xnod(lump); - PutSubsecs_Xnod(lump); - PutSegs_Xgl2(lump); - PutNodes_Xgl3(lump, root_node); - - lump->Finish(); - lump = nullptr; -} - /* ----- whole-level routines --------------------------- */ void LoadLevel(void) @@ -2640,77 +1873,40 @@ build_result_e SaveBinaryFormatLevel(node_t *root_node) AddMissingLump("BLOCKMAP", "REJECT"); // check for overflows... - // this sets the force_xxx vars if certain limits are breached - CheckBinaryFormatLimits(); + CheckBinaryFormatLimits(config.bsp_type); + RaiseBSPFormat(config.bsp_type); /* --- Normal nodes --- */ switch (config.bsp_type) { case BSP_XGL3: { - // leave SEGS empty - CreateLevelLump("SEGS")->Finish(); - Lump_c *lump = CreateLevelLump("SSECTORS"); - SaveFormat_Xgl3(lump, root_node); - CreateLevelLump("NODES")->Finish(); + SaveFormat_Xgl3(root_node); break; } case BSP_XGL2: { - // leave SEGS empty - CreateLevelLump("SEGS")->Finish(); - Lump_c *lump = CreateLevelLump("SSECTORS"); - SaveFormat_Xgl2(lump, root_node); - CreateLevelLump("NODES")->Finish(); + SaveFormat_Xgl2(root_node); break; } case BSP_XGLN: { - // leave SEGS empty - CreateLevelLump("SEGS")->Finish(); - Lump_c *lump = CreateLevelLump("SSECTORS"); - SaveFormat_Xgln(lump, root_node); - CreateLevelLump("NODES")->Finish(); + SaveFormat_Xgln(root_node); break; } case BSP_XNOD: { - CreateLevelLump("SEGS")->Finish(); - CreateLevelLump("SSECTORS")->Finish(); - // remove all the mini-segs from subsectors - NormaliseBspTree(); - Lump_c *lump = CreateLevelLump("NODES", CalcXnodNodesSize()); - SaveFormat_Xnod(lump, root_node); + SaveFormat_Xnod(root_node); break; } case BSP_DEEPBSPV4: { - // remove all the mini-segs from subsectors - NormaliseBspTree(); - // reduce vertex precision for classic DOOM nodes. - // some segs can become "degenerate" after this, and these - // are removed from subsectors. - RoundOffBspTree(); - SortSegs(); - PutVertices(); - PutSegs_DeepBSPV4(); - PutSubsecs_DeepBSPV4(); - PutNodes_DeepBSPV4(root_node); + SaveFormat_DeepBSPV4(root_node); break; } case BSP_VANILLA: { - // remove all the mini-segs from subsectors - NormaliseBspTree(); - // reduce vertex precision for classic DOOM nodes. - // some segs can become "degenerate" after this, and these - // are removed from subsectors. - RoundOffBspTree(); - SortSegs(); - PutVertices(); - PutSegs_Vanilla(); - PutSubsecs_Vanilla(); - PutNodes_Vanilla(root_node); + SaveFormat_Vanilla(root_node); break; } } @@ -2750,7 +1946,7 @@ build_result_e SaveTextMapLevel(node_t *root_node) } else { - SaveFormat_Xgl3(lump, root_node); + SaveFormat_Xgl3(root_node); } // -Elf- diff --git a/src/local.hpp b/src/local.hpp index 2b65216..de82b16 100644 --- a/src/local.hpp +++ b/src/local.hpp @@ -409,15 +409,6 @@ void FreeNodes(void); Lump_c *CreateLevelLump(const char *name, size_t max_size = NO_INDEX); Lump_c *FindLevelLump(const char *name); -/* limit flags, to show what went wrong */ -static constexpr uint32_t LIMIT_VERTEXES = BIT(0); -static constexpr uint32_t LIMIT_SECTORS = BIT(1); -static constexpr uint32_t LIMIT_SIDEDEFS = BIT(2); -static constexpr uint32_t LIMIT_LINEDEFS = BIT(3); -static constexpr uint32_t LIMIT_SEGS = BIT(4); -static constexpr uint32_t LIMIT_SSECTORS = BIT(5); -static constexpr uint32_t LIMIT_NODES = BIT(6); - //------------------------------------------------------------------------ // ANALYZE : Analyzing level structures //------------------------------------------------------------------------ @@ -555,3 +546,10 @@ void NormaliseBspTree(void); // vertices to integer coordinates (for example, removing segs whose // rounded coordinates degenerate to the same point). void RoundOffBspTree(void); + +void SaveFormat_Vanilla(node_t *root_node); +void SaveFormat_DeepBSPV4(node_t *root_node); +void SaveFormat_Xnod(node_t *root_node); +void SaveFormat_Xgln(node_t *root_node); +void SaveFormat_Xgl2(node_t *root_node); +void SaveFormat_Xgl3(node_t *root_node); diff --git a/src/misc.cpp b/src/misc.cpp index a6e9624..93bc81f 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -402,12 +402,9 @@ void PruneVerticesAtEnd(void) size_t unused = old_num - lev_vertices.size(); - if (unused > 0) + if (unused > 0 && config.verbose) { - if (config.verbose) - { - PrintLine(LOG_NORMAL, "Pruned %zu unused vertices at end", unused); - } + PrintLine(LOG_NORMAL, "Pruned %zu unused vertices at end", unused); } num_old_vert = lev_vertices.size(); From 98454cbe58d306eb494b221037fc332f6da8ed44 Mon Sep 17 00:00:00 2001 From: "Guilherme M. Miranda" Date: Sun, 1 Feb 2026 07:51:12 -0300 Subject: [PATCH 07/14] get deepbspv4 nodes working, xgl nodes are not --- src/bsp.cpp | 64 +++++++++++++++++++++++--------------------------- src/core.hpp | 14 +++++++---- src/elfbsp.cpp | 32 +++++++++++++++---------- 3 files changed, 59 insertions(+), 51 deletions(-) diff --git a/src/bsp.cpp b/src/bsp.cpp index 59e34ee..c4b0c0e 100644 --- a/src/bsp.cpp +++ b/src/bsp.cpp @@ -165,12 +165,6 @@ static void PutSegs_Vanilla(void) raw.flip = GetLittleEndian(seg->side); raw.dist = GetLittleEndian(VanillaSegDist(seg)); - // -Elf- ZokumBSP - if ((seg->linedef->dont_render_back && seg->side) || (seg->linedef->dont_render_front && !seg->side)) - { - raw = {}; - } - lump->Write(&raw, sizeof(raw)); if (HAS_BIT(config.debug, DEBUG_BSP)) @@ -183,7 +177,7 @@ static void PutSegs_Vanilla(void) lump->Finish(); - if (lev_segs.size() > 65534) + if (lev_segs.size() > LIMIT_VANILLA_SEG) { PrintLine(LOG_NORMAL, "FAILURE: Number of segs has overflowed."); lev_overflows = true; @@ -214,7 +208,7 @@ static void PutSubsecs_Vanilla(void) } } - if (lev_subsecs.size() > 32767) + if (lev_subsecs.size() > LIMIT_VANILLA_SUBSEC) { PrintLine(LOG_NORMAL, "FAILURE: Number of subsectors has overflowed."); lev_overflows = true; @@ -223,16 +217,16 @@ static void PutSubsecs_Vanilla(void) lump->Finish(); } -static void PutOneNode_Vanilla(node_t *node, size_t &node_cur_index, Lump_c *lump) +static void PutOneNode_Vanilla(Lump_c *lump, node_t *node, size_t &node_cur_index) { if (node->r.node) { - PutOneNode_Vanilla(node->r.node, node_cur_index, lump); + PutOneNode_Vanilla(lump, node->r.node, node_cur_index); } if (node->l.node) { - PutOneNode_Vanilla(node->l.node, node_cur_index, lump); + PutOneNode_Vanilla(lump, node->l.node, node_cur_index); } node->index = node_cur_index++; @@ -261,7 +255,7 @@ static void PutOneNode_Vanilla(node_t *node, size_t &node_cur_index, Lump_c *lum } else if (node->r.subsec) { - raw.right = GetLittleEndian(static_cast(node->r.subsec->index | 0x8000)); + raw.right = GetLittleEndian(static_cast(node->r.subsec->index | NF_SUBSECTOR_VANILLA)); } else { @@ -274,7 +268,7 @@ static void PutOneNode_Vanilla(node_t *node, size_t &node_cur_index, Lump_c *lum } else if (node->l.subsec) { - raw.left = GetLittleEndian(static_cast(node->l.subsec->index | 0x8000)); + raw.left = GetLittleEndian(static_cast(node->l.subsec->index | NF_SUBSECTOR_VANILLA)); } else { @@ -290,16 +284,16 @@ static void PutOneNode_Vanilla(node_t *node, size_t &node_cur_index, Lump_c *lum } } -static void PutNodes_Vanilla(node_t *root) +static void PutNodes_Vanilla(node_t *root_node) { // this can be bigger than the actual size, but never smaller size_t max_size = (lev_nodes.size() + 1) * sizeof(raw_node_vanilla_t); size_t node_cur_index = 0; Lump_c *lump = CreateLevelLump("NODES", max_size); - if (root != nullptr) + if (root_node != nullptr) { - PutOneNode_Vanilla(root, node_cur_index, lump); + PutOneNode_Vanilla(lump, root_node, node_cur_index); } lump->Finish(); @@ -379,16 +373,16 @@ static void PutSubsecs_DeepBSPV4(void) lump->Finish(); } -static void PutOneNode_DeepBSPV4(node_t *node, size_t node_cur_index, Lump_c *lump) +static void PutOneNode_DeepBSPV4(Lump_c *lump, node_t *node, size_t &node_cur_index) { if (node->r.node) { - PutOneNode_DeepBSPV4(node->r.node, node_cur_index, lump); + PutOneNode_DeepBSPV4(lump, node->r.node, node_cur_index); } if (node->l.node) { - PutOneNode_DeepBSPV4(node->l.node, node_cur_index, lump); + PutOneNode_DeepBSPV4(lump, node->l.node, node_cur_index); } node->index = node_cur_index++; @@ -413,11 +407,11 @@ static void PutOneNode_DeepBSPV4(node_t *node, size_t node_cur_index, Lump_c *lu if (node->r.node) { - raw.right = GetLittleEndian(static_cast(node->r.node->index)); + raw.right = GetLittleEndian(static_cast(node->r.node->index)); } else if (node->r.subsec) { - raw.right = GetLittleEndian(static_cast(node->r.subsec->index | 0x8000)); + raw.right = GetLittleEndian(static_cast(node->r.subsec->index | NF_SUBSECTOR)); } else { @@ -426,11 +420,11 @@ static void PutOneNode_DeepBSPV4(node_t *node, size_t node_cur_index, Lump_c *lu if (node->l.node) { - raw.left = GetLittleEndian(static_cast(node->l.node->index)); + raw.left = GetLittleEndian(static_cast(node->l.node->index)); } else if (node->l.subsec) { - raw.left = GetLittleEndian(static_cast(node->l.subsec->index | 0x8000)); + raw.left = GetLittleEndian(static_cast(node->l.subsec->index | NF_SUBSECTOR)); } else { @@ -446,19 +440,19 @@ static void PutOneNode_DeepBSPV4(node_t *node, size_t node_cur_index, Lump_c *lu } } -static void PutNodes_DeepBSPV4(node_t *root) +static void PutNodes_DeepBSPV4(node_t *root_node) { // this can be bigger than the actual size, but never smaller // 8 bytes for BSP_MAGIC_DEEPBSPV4 header - size_t max_size = 8 + (lev_nodes.size() + 1) * sizeof(raw_node_deepbspv4_t); + // size_t max_size = 8 + (lev_nodes.size() + 1) * sizeof(raw_node_deepbspv4_t); size_t node_cur_index = 0; - Lump_c *lump = CreateLevelLump("NODES", max_size); - lump->Write(BSP_MAGIC_DEEPBSPV4, 4); + Lump_c *lump = CreateLevelLump("NODES"); + lump->Write(BSP_MAGIC_DEEPBSPV4, 8); - if (root != nullptr) + if (root_node != nullptr) { - PutOneNode_DeepBSPV4(root, node_cur_index, lump); + PutOneNode_DeepBSPV4(lump, root_node, node_cur_index); } lump->Finish(); @@ -605,7 +599,7 @@ static void PutOneNode_Xnod(Lump_c *lump, node_t *node, size_t &node_cur_index) } else if (node->r.subsec) { - raw.right = GetLittleEndian(static_cast(node->r.subsec->index | 0x80000000U)); + raw.right = GetLittleEndian(static_cast(node->r.subsec->index | NF_SUBSECTOR)); } else { @@ -618,7 +612,7 @@ static void PutOneNode_Xnod(Lump_c *lump, node_t *node, size_t &node_cur_index) } else if (node->l.subsec) { - raw.left = GetLittleEndian(static_cast(node->l.subsec->index | 0x80000000U)); + raw.left = GetLittleEndian(static_cast(node->l.subsec->index | NF_SUBSECTOR)); } else { @@ -661,8 +655,8 @@ static size_t CalcXnodNodesSize(void) size += 8 + lev_vertices.size() * sizeof(raw_xnod_vertex_t); size += 4 + lev_subsecs.size() * sizeof(raw_xnod_subsec_t); - size += 4 + lev_segs.size() * sizeof(raw_xnod_seg_t); - size += 4 + lev_nodes.size() * sizeof(raw_xnod_node_t); + size += 4 + lev_segs.size() * sizeof(raw_xgl2_seg_t); + size += 4 + lev_nodes.size() * sizeof(raw_xgl3_node_t); return size; } @@ -770,7 +764,7 @@ static void PutOneNode_Xgl3(Lump_c *lump, node_t *node, size_t &node_cur_index) } else if (node->r.subsec) { - raw.right = GetLittleEndian(static_cast(node->r.subsec->index | 0x80000000U)); + raw.right = GetLittleEndian(static_cast(node->r.subsec->index | NF_SUBSECTOR)); } else { @@ -783,7 +777,7 @@ static void PutOneNode_Xgl3(Lump_c *lump, node_t *node, size_t &node_cur_index) } else if (node->l.subsec) { - raw.left = GetLittleEndian(static_cast(node->l.subsec->index | 0x80000000U)); + raw.left = GetLittleEndian(static_cast(node->l.subsec->index | NF_SUBSECTOR)); } else { diff --git a/src/core.hpp b/src/core.hpp index d54e1c0..83c4cbf 100644 --- a/src/core.hpp +++ b/src/core.hpp @@ -714,6 +714,9 @@ using bsp_type_t = enum bsp_type_e : uint8_t BSP_XGLN, BSP_XGL2, BSP_XGL3, + + BSP_MIN = BSP_VANILLA, + BSP_MAX = BSP_XGL3, }; // Obviously, vanilla did not include any magic headers @@ -723,11 +726,14 @@ constexpr auto BSP_MAGIC_XGLN = "XGLN"; constexpr auto BSP_MAGIC_XGL2 = "XGL2"; constexpr auto BSP_MAGIC_XGL3 = "XGL3"; -// Upper-most bit is used for distinguishing tree children as either nodes or sub-sectors +// Upper-most bit is used for distinguishing sub-sectors, i.e tree leaves +constexpr uint32_t NF_SUBSECTOR = UINT32_C(0x80000000); + // All known non-vanilla formats are know to use 32bit indexes -static constexpr size_t LIMIT_VANILLA_NODE = INT16_MAX; -static constexpr size_t LIMIT_VANILLA_SUBSEC = INT16_MAX; -static constexpr size_t LIMIT_VANILLA_SEG = UINT16_MAX; +constexpr uint16_t NF_SUBSECTOR_VANILLA = UINT16_C(0x8000); +constexpr size_t LIMIT_VANILLA_NODE = INT16_MAX; +constexpr size_t LIMIT_VANILLA_SUBSEC = INT16_MAX; +constexpr size_t LIMIT_VANILLA_SEG = UINT16_MAX - 1; // // Vanilla blockmap diff --git a/src/elfbsp.cpp b/src/elfbsp.cpp index 6de85a8..9dedda2 100644 --- a/src/elfbsp.cpp +++ b/src/elfbsp.cpp @@ -449,18 +449,13 @@ void ParseShortArgument(const char *arg) case 'f': config.fast = true; continue; - case 'x': - RaiseValue(config.bsp_type, BSP_XNOD); - continue; - case 's': - RaiseValue(config.bsp_type, BSP_XGL3); - continue; case 'a': config.analysis = true; continue; case 'm': case 'o': + case 't': PrintLine(LOG_ERROR, "cannot use option '-%c' like that", c); return; @@ -601,13 +596,22 @@ int32_t ParseLongArgument(const char *name, const int32_t argc, const char *argv used = 1; } - else if (strcmp(name, "--xnod") == 0) - { - RaiseValue(config.bsp_type, BSP_XNOD); - } - else if (strcmp(name, "--ssect") == 0) + else if (strcmp(name, "--type") == 0) { - RaiseValue(config.bsp_type, BSP_XGL3); + if (argc < 1 || !isdigit(argv[0][0])) + { + PrintLine(LOG_ERROR, "missing value for '--type' option"); + } + + int32_t val = std::stoi(argv[0]); + + if (val < BSP_MIN || val > BSP_MAX) + { + PrintLine(LOG_ERROR, "illegal value for '--type' option"); + } + + RaiseValue(config.bsp_type, static_cast(val)); + used = 1; } else if (strcmp(name, "--cost") == 0) { @@ -701,6 +705,10 @@ void ParseCommandLine(int32_t argc, const char *argv[]) } // handle short args which are isolate and require a value + if (strcmp(arg, "-t") == 0) + { + arg = "--type"; + } if (strcmp(arg, "-c") == 0) { arg = "--cost"; From 529c92f512a5c4ff29b56aeadd8659836c7dbcc7 Mon Sep 17 00:00:00 2001 From: "Guilherme M. Miranda" Date: Sun, 1 Feb 2026 14:44:33 -0300 Subject: [PATCH 08/14] made docs more accurate --- docs/binary_formats.md | 8 ++++---- src/bsp.cpp | 6 +++--- src/core.hpp | 11 ++++------- src/local.hpp | 2 +- 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/docs/binary_formats.md b/docs/binary_formats.md index e7eb577..5b5d7af 100644 --- a/docs/binary_formats.md +++ b/docs/binary_formats.md @@ -129,7 +129,7 @@ Differences from the vanilla BSP data include: * each version is built on top of the previous, replacing the predecessor's given struct type * the removal of angles and offsets from Segments * higher precision data types for fractional coordinates -* "mini-segs", indicated by `NO_INDEX`, to be skipped by the renderer, but needed for traversal +* "minisegs", indicated by `NO_INDEX`, to be skipped by the renderer, but needed for traversal * "linear" storage of Seg indexes on subsectors, see above ### XNOD vertex (`xnod_vertex_t`), 8 bytes @@ -165,7 +165,7 @@ Differences from the vanilla BSP data include: |------------|-------------| | `uint32_t` | Index of starting vertex | | `uint32_t` | Index of ending vertex | -| `uint16_t` | Index of associated linedef, `NO_INDEX` if mini-seg | +| `uint16_t` | Index of associated linedef, `NO_INDEX` if miniseg | | `uint8_t` | False (0) if on the same side as linedef, true (1) if oppposite side | ### XGLN segment (`xgln_seg_t`), 11 bytes @@ -174,7 +174,7 @@ Differences from the vanilla BSP data include: |------------|-------------| | `uint32_t` | Index of starting vertex | | `uint32_t` | Index of ending vertex (Unused, due to the linearity of the subsector structure) | -| `uint16_t` | Index of associated linedef, `NO_INDEX` if mini-seg | +| `uint16_t` | Index of associated linedef, `NO_INDEX` if miniseg | | `uint8_t` | False (0) if on the same side as linedef, true (1) if oppposite side | ### XGL2 segment (`xgl2_seg_t`), 13 bytes @@ -183,7 +183,7 @@ Differences from the vanilla BSP data include: |------------|-------------| | `uint32_t` | Index of starting vertex | | `uint32_t` | Index of ending vertex (Unused, due to the linearity of the subsector structure) | -| `uint32_t` | Index of associated linedef, `NO_INDEX` if mini-seg | +| `uint32_t` | Index of associated linedef, `NO_INDEX` if miniseg | | `uint8_t` | False (0) if on the same side as linedef, true (1) if oppposite side | ### XGL3 node (`xgl3_node_t`), 40 bytes diff --git a/src/bsp.cpp b/src/bsp.cpp index c4b0c0e..973192a 100644 --- a/src/bsp.cpp +++ b/src/bsp.cpp @@ -816,7 +816,7 @@ static void PutNodes_Xgl3(Lump_c *lump, node_t *root) void SaveFormat_Vanilla(node_t *root_node) { - // remove all the mini-segs from subsectors + // remove all the minisegs from subsectors NormaliseBspTree(); // reduce vertex precision for classic DOOM nodes. // some segs can become "degenerate" after this, and these @@ -831,7 +831,7 @@ void SaveFormat_Vanilla(node_t *root_node) void SaveFormat_DeepBSPV4(node_t *root_node) { - // remove all the mini-segs from subsectors + // remove all the minisegs from subsectors NormaliseBspTree(); // reduce vertex precision for classic DOOM nodes. // some segs can become "degenerate" after this, and these @@ -848,7 +848,7 @@ void SaveFormat_Xnod(node_t *root_node) { CreateLevelLump("SEGS")->Finish(); CreateLevelLump("SSECTORS")->Finish(); - // remove all the mini-segs from subsectors + // remove all the minisegs from subsectors NormaliseBspTree(); SortSegs(); diff --git a/src/core.hpp b/src/core.hpp index 83c4cbf..7400dbb 100644 --- a/src/core.hpp +++ b/src/core.hpp @@ -843,21 +843,18 @@ using raw_xnod_seg_t = struct raw_xnod_seg_s uint8_t side; // 0 if on right of linedef, 1 if on left } PACKEDATTR; -// XGLN segs use the same type definition as XNOD segs, with slightly -// different semantics for mini-segs - using raw_xgln_seg_t = struct raw_xgln_seg_s { - uint32_t vertex; // from this vertex... - uint32_t partner; // ... to this vertex + uint32_t vertex; // from this vertex ... to the next + uint32_t partner; // partner seg, unused by most ports outside of U/G/ZDoom uint16_t linedef; // linedef that this seg goes along, or NO_INDEX uint8_t side; // 0 if on right of linedef, 1 if on left } PACKEDATTR; using raw_xgl2_seg_t = struct raw_xgl2_seg_s { - uint32_t vertex; // from this vertex... - uint32_t partner; // ... to this vertex + uint32_t vertex; // from this vertex ... to the next + uint32_t partner; // partner seg, unused by most ports outside of U/G/ZDoom uint32_t linedef; // linedef that this seg goes along, or NO_INDEX uint8_t side; // 0 if on right of linedef, 1 if on left } PACKEDATTR; diff --git a/src/local.hpp b/src/local.hpp index de82b16..62b5422 100644 --- a/src/local.hpp +++ b/src/local.hpp @@ -346,7 +346,7 @@ struct quadtree_c // 256x512). quadtree_c *subs[2]; - // count of real/mini segs contained in this node AND ALL CHILDREN. + // count of real/minisegs contained in this node AND ALL CHILDREN. size_t real_num; size_t mini_num; From 0546aae61a4770164d0960a1253f6526f441e0a2 Mon Sep 17 00:00:00 2001 From: "Guilherme M. Miranda" Date: Mon, 2 Feb 2026 05:34:26 -0300 Subject: [PATCH 09/14] minor tweaks --- src/bsp.cpp | 10 +++++----- src/core.hpp | 2 -- src/local.hpp | 4 ++-- src/node.cpp | 10 +++++----- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/bsp.cpp b/src/bsp.cpp index 973192a..cddd0f1 100644 --- a/src/bsp.cpp +++ b/src/bsp.cpp @@ -743,8 +743,8 @@ static void PutOneNode_Xgl3(Lump_c *lump, node_t *node, size_t &node_cur_index) node->index = node_cur_index++; - raw.x = GetLittleEndian(static_cast(floor(node->x) * 65536.0)); - raw.y = GetLittleEndian(static_cast(floor(node->y) * 65536.0)); + raw.x = GetLittleEndian(static_cast(floor(node->x * 65536.0))); + raw.y = GetLittleEndian(static_cast(floor(node->y * 65536.0))); raw.dx = GetLittleEndian(static_cast(floor(node->dx * 65536.0))); raw.dy = GetLittleEndian(static_cast(floor(node->dy * 65536.0))); @@ -871,7 +871,7 @@ void SaveFormat_Xgln(node_t *root_node) SortSegs(); // WISH : compute a max_size - Lump_c *lump = CreateLevelLump("SSECTORS"); + Lump_c *lump = CreateLevelLump("SSECTORS", CalcXnodNodesSize()); lump->Write(BSP_MAGIC_XGLN, 4); PutVertices_Xnod(lump); PutSubsecs_Xnod(lump); @@ -893,7 +893,7 @@ void SaveFormat_Xgl2(node_t *root_node) SortSegs(); // WISH : compute a max_size - Lump_c *lump = CreateLevelLump("SSECTORS"); + Lump_c *lump = CreateLevelLump("SSECTORS", CalcXnodNodesSize()); lump->Write(BSP_MAGIC_XGL2, 4); PutVertices_Xnod(lump); PutSubsecs_Xnod(lump); @@ -915,7 +915,7 @@ void SaveFormat_Xgl3(node_t *root_node) SortSegs(); // WISH : compute a max_size - Lump_c *lump = CreateLevelLump("SSECTORS"); + Lump_c *lump = CreateLevelLump("SSECTORS", CalcXnodNodesSize()); lump->Write(BSP_MAGIC_XGL3, 4); PutVertices_Xnod(lump); PutSubsecs_Xnod(lump); diff --git a/src/core.hpp b/src/core.hpp index 7400dbb..7608058 100644 --- a/src/core.hpp +++ b/src/core.hpp @@ -30,8 +30,6 @@ constexpr auto PROJECT_COMPANY = "Guilherme Miranda, et al"; constexpr auto PROJECT_COPYRIGHT = "Copyright (C) 1994-2026"; constexpr auto PROJECT_LICENSE = "GNU General Public License, version 2"; -constexpr auto PROJECT_NAME = "ELFBSP"; -constexpr auto PROJECT_VERSION = "v1.1"; constexpr auto PROJECT_STRING = "ELFBSP v1.1"; /* diff --git a/src/local.hpp b/src/local.hpp index 62b5422..96becfc 100644 --- a/src/local.hpp +++ b/src/local.hpp @@ -204,7 +204,7 @@ struct seg_t linedef_t *linedef; // 0 for right, 1 for left - bool side; + bool side = false; // seg on other side, or nullptr if one-sided. This relationship is // always one-to-one -- if one of the segs is split, the partner seg @@ -214,7 +214,7 @@ struct seg_t // seg index. Only valid once the seg has been added to a // subsector. A negative value means it is invalid -- there // shouldn't be any of these once the BSP tree has been built. - size_t index; + size_t index = NO_INDEX; // when true, this seg has become zero length (integer rounding of the // start and end vertices produces the same location). It should be diff --git a/src/node.cpp b/src/node.cpp index 2e23984..0e76fbb 100644 --- a/src/node.cpp +++ b/src/node.cpp @@ -1012,7 +1012,7 @@ void AddMinisegs(intersection_t *cut_list, seg_t *part, seg_t **left_list, seg_t seg->index = buddy->index = NO_INDEX; seg->linedef = buddy->linedef = nullptr; - seg->side = buddy->side = 0; + seg->side = buddy->side = false; seg->source_line = buddy->source_line = part->linedef; @@ -1059,7 +1059,7 @@ void SetPartition(node_t *node, const seg_t *part) { SYS_ASSERT(part->linedef); - if (part->side == 0) + if (part->side == false) { node->x = part->linedef->start->x; node->y = part->linedef->start->y; @@ -1277,7 +1277,7 @@ int OnLineSide(quadtree_c *quadtree, const seg_t *part) return p1; } -seg_t *CreateOneSeg(linedef_t *line, vertex_t *start, vertex_t *end, sidedef_t *side, int what_side) +seg_t *CreateOneSeg(linedef_t *line, vertex_t *start, vertex_t *end, sidedef_t *side, bool what_side) { seg_t *seg = NewSeg(); @@ -1354,7 +1354,7 @@ seg_t *CreateSegs(void) if (line->right != nullptr) { - right = CreateOneSeg(line, line->start, line->end, line->right, 0); + right = CreateOneSeg(line, line->start, line->end, line->right, false); ListAddSeg(&list, right); } else @@ -1365,7 +1365,7 @@ seg_t *CreateSegs(void) if (line->left != nullptr) { - left = CreateOneSeg(line, line->end, line->start, line->left, 1); + left = CreateOneSeg(line, line->end, line->start, line->left, true); ListAddSeg(&list, left); if (right != nullptr) From 9598f7e3ff93e83254b3d2f11db12dcf19382899 Mon Sep 17 00:00:00 2001 From: "Guilherme M. Miranda" Date: Mon, 2 Feb 2026 05:36:37 -0300 Subject: [PATCH 10/14] docs --- docs/binary_formats.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/binary_formats.md b/docs/binary_formats.md index 5b5d7af..7f810b5 100644 --- a/docs/binary_formats.md +++ b/docs/binary_formats.md @@ -165,7 +165,7 @@ Differences from the vanilla BSP data include: |------------|-------------| | `uint32_t` | Index of starting vertex | | `uint32_t` | Index of ending vertex | -| `uint16_t` | Index of associated linedef, `NO_INDEX` if miniseg | +| `uint16_t` | Index of associated linedef | | `uint8_t` | False (0) if on the same side as linedef, true (1) if oppposite side | ### XGLN segment (`xgln_seg_t`), 11 bytes @@ -173,7 +173,7 @@ Differences from the vanilla BSP data include: | Type | Description | |------------|-------------| | `uint32_t` | Index of starting vertex | -| `uint32_t` | Index of ending vertex (Unused, due to the linearity of the subsector structure) | +| `uint32_t` | Index of partner segment (Unused in most ports outside of U/G/ZDoom | | `uint16_t` | Index of associated linedef, `NO_INDEX` if miniseg | | `uint8_t` | False (0) if on the same side as linedef, true (1) if oppposite side | @@ -182,7 +182,7 @@ Differences from the vanilla BSP data include: | Type | Description | |------------|-------------| | `uint32_t` | Index of starting vertex | -| `uint32_t` | Index of ending vertex (Unused, due to the linearity of the subsector structure) | +| `uint32_t` | Index of partner segment (Unused in most ports outside of U/G/ZDoom) | | `uint32_t` | Index of associated linedef, `NO_INDEX` if miniseg | | `uint8_t` | False (0) if on the same side as linedef, true (1) if oppposite side | From a28441a9725566f19687ef5a2ea83ffdd6e08820 Mon Sep 17 00:00:00 2001 From: "Guilherme M. Miranda" Date: Mon, 2 Feb 2026 07:29:57 -0300 Subject: [PATCH 11/14] use proper struct type --- src/bsp.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/bsp.cpp b/src/bsp.cpp index cddd0f1..99404f6 100644 --- a/src/bsp.cpp +++ b/src/bsp.cpp @@ -102,7 +102,7 @@ static void PutVertices_Vanilla(void) raw.x = GetLittleEndian(static_cast(floor(vert->x))); raw.y = GetLittleEndian(static_cast(floor(vert->y))); - lump->Write(&raw, sizeof(raw)); + lump->Write(&raw, sizeof(raw_vertex_t)); count++; } @@ -165,7 +165,7 @@ static void PutSegs_Vanilla(void) raw.flip = GetLittleEndian(seg->side); raw.dist = GetLittleEndian(VanillaSegDist(seg)); - lump->Write(&raw, sizeof(raw)); + lump->Write(&raw, sizeof(raw_seg_vanilla_t)); if (HAS_BIT(config.debug, DEBUG_BSP)) { @@ -199,7 +199,7 @@ static void PutSubsecs_Vanilla(void) raw.first = GetLittleEndian(static_cast(sub->seg_list->index)); raw.num = GetLittleEndian(static_cast(sub->seg_count)); - lump->Write(&raw, sizeof(raw)); + lump->Write(&raw, sizeof(raw_subsec_vanilla_t)); if (HAS_BIT(config.debug, DEBUG_BSP)) { @@ -275,7 +275,7 @@ static void PutOneNode_Vanilla(Lump_c *lump, node_t *node, size_t &node_cur_inde PrintLine(LOG_ERROR, "Bad left child in node %zu", node->index); } - lump->Write(&raw, sizeof(raw)); + lump->Write(&raw, sizeof(raw_node_vanilla_t)); if (HAS_BIT(config.debug, DEBUG_BSP)) { @@ -333,7 +333,7 @@ static void PutSegs_DeepBSPV4(void) raw.flip = GetLittleEndian(seg->side); raw.dist = GetLittleEndian(VanillaSegDist(seg)); - lump->Write(&raw, sizeof(raw)); + lump->Write(&raw, sizeof(raw_seg_deepbspv4_t)); if (HAS_BIT(config.debug, DEBUG_BSP)) { @@ -361,7 +361,7 @@ static void PutSubsecs_DeepBSPV4(void) raw.first = GetLittleEndian(static_cast(sub->seg_list->index)); raw.num = GetLittleEndian(static_cast(sub->seg_count)); - lump->Write(&raw, sizeof(raw)); + lump->Write(&raw, sizeof(raw_subsec_deepbspv4_t)); if (HAS_BIT(config.debug, DEBUG_BSP)) { @@ -431,7 +431,7 @@ static void PutOneNode_DeepBSPV4(Lump_c *lump, node_t *node, size_t &node_cur_in PrintLine(LOG_ERROR, "Bad left child in node %zu", node->index); } - lump->Write(&raw, sizeof(raw)); + lump->Write(&raw, sizeof(raw_node_deepbspv4_t)); if (HAS_BIT(config.debug, DEBUG_BSP)) { @@ -490,7 +490,7 @@ static void PutVertices_Xnod(Lump_c *lump) raw.x = GetLittleEndian(static_cast(floor(vert->x * 65536.0))); raw.y = GetLittleEndian(static_cast(floor(vert->y * 65536.0))); - lump->Write(&raw, sizeof(raw)); + lump->Write(&raw, sizeof(raw_xnod_vertex_t)); count++; } @@ -558,7 +558,7 @@ static void PutSegs_Xnod(Lump_c *lump) raw.end = GetLittleEndian(VertexIndex_XNOD(seg->end)); raw.linedef = GetLittleEndian(static_cast(seg->linedef->index)); raw.side = seg->side; - lump->Write(&raw, sizeof(raw)); + lump->Write(&raw, sizeof(raw_xnod_seg_t)); } } @@ -686,7 +686,7 @@ static void PutSegs_Xgln(Lump_c *lump) raw.linedef = GetLittleEndian(static_cast(seg->linedef ? seg->linedef->index : NO_INDEX)); raw.side = seg->side; - lump->Write(&raw, sizeof(raw)); + lump->Write(&raw, sizeof(raw_xgln_seg_t)); if (HAS_BIT(config.debug, DEBUG_BSP)) { @@ -717,7 +717,7 @@ static void PutSegs_Xgl2(Lump_c *lump) raw.linedef = GetLittleEndian(static_cast(seg->linedef ? seg->linedef->index : NO_INDEX)); raw.side = seg->side; - lump->Write(&raw, sizeof(raw)); + lump->Write(&raw, sizeof(raw_xgl2_seg_t)); if (HAS_BIT(config.debug, DEBUG_BSP)) { From 07583c9c48fef879ff0a20c1a041c4938f53c18a Mon Sep 17 00:00:00 2001 From: "Guilherme M. Miranda" Date: Sun, 8 Feb 2026 00:53:00 -0300 Subject: [PATCH 12/14] okay this better work --- src/bsp.cpp | 22 ++++++---------------- src/core.hpp | 36 ++++++++++++++++++++++++++---------- src/level.cpp | 46 ++++++++++++++++++++++++---------------------- 3 files changed, 56 insertions(+), 48 deletions(-) diff --git a/src/bsp.cpp b/src/bsp.cpp index 99404f6..aa8765b 100644 --- a/src/bsp.cpp +++ b/src/bsp.cpp @@ -28,7 +28,7 @@ void SortSegs(void) } } -static inline uint16_t VanillaSegDist(const seg_t *seg) +static inline int16_t VanillaSegDist(const seg_t *seg) { double lx = seg->side ? seg->linedef->end->x : seg->linedef->start->x; double ly = seg->side ? seg->linedef->end->y : seg->linedef->start->y; @@ -37,7 +37,7 @@ static inline uint16_t VanillaSegDist(const seg_t *seg) double sx = round(seg->start->x); double sy = round(seg->start->y); - return static_cast(floor(hypot(sx - lx, sy - ly) + 0.5)); + return static_cast(floor(hypot(sx - lx, sy - ly) + 0.5)); } static inline short_angle_t VanillaSegAngle(const seg_t *seg) @@ -121,16 +121,6 @@ static void PutVertices_Vanilla(void) } } -static inline uint16_t VertexIndex16Bit(const vertex_t *v) -{ - if (v->is_new) - { - return static_cast(v->index | 0x8000U); - } - - return static_cast(v->index); -} - static inline uint32_t VertexIndex_XNOD(const vertex_t *v) { if (v->is_new) @@ -158,8 +148,8 @@ static void PutSegs_Vanilla(void) const seg_t *seg = lev_segs[i]; - raw.start = GetLittleEndian(VertexIndex16Bit(seg->start)); - raw.end = GetLittleEndian(VertexIndex16Bit(seg->end)); + raw.start = GetLittleEndian(static_cast(seg->start->index)); + raw.end = GetLittleEndian(static_cast(seg->end->index)); raw.angle = GetLittleEndian(VanillaSegAngle(seg)); raw.linedef = GetLittleEndian(static_cast(seg->linedef->index)); raw.flip = GetLittleEndian(seg->side); @@ -177,7 +167,7 @@ static void PutSegs_Vanilla(void) lump->Finish(); - if (lev_segs.size() > LIMIT_VANILLA_SEG) + if (lev_segs.size() > LIMIT_SEG) { PrintLine(LOG_NORMAL, "FAILURE: Number of segs has overflowed."); lev_overflows = true; @@ -208,7 +198,7 @@ static void PutSubsecs_Vanilla(void) } } - if (lev_subsecs.size() > LIMIT_VANILLA_SUBSEC) + if (lev_subsecs.size() > LIMIT_SUBSEC) { PrintLine(LOG_NORMAL, "FAILURE: Number of subsectors has overflowed."); lev_overflows = true; diff --git a/src/core.hpp b/src/core.hpp index 7608058..d8bffba 100644 --- a/src/core.hpp +++ b/src/core.hpp @@ -157,8 +157,7 @@ template constexpr T GetBigEndian(T value) } } -template -constexpr void RaiseValue(T& var, T value) +template constexpr void RaiseValue(T &var, T value) { var = std::max(var, value); } @@ -725,13 +724,30 @@ constexpr auto BSP_MAGIC_XGL2 = "XGL2"; constexpr auto BSP_MAGIC_XGL3 = "XGL3"; // Upper-most bit is used for distinguishing sub-sectors, i.e tree leaves +constexpr uint16_t NF_SUBSECTOR_VANILLA = UINT16_C(0x8000); constexpr uint32_t NF_SUBSECTOR = UINT32_C(0x80000000); -// All known non-vanilla formats are know to use 32bit indexes -constexpr uint16_t NF_SUBSECTOR_VANILLA = UINT16_C(0x8000); -constexpr size_t LIMIT_VANILLA_NODE = INT16_MAX; -constexpr size_t LIMIT_VANILLA_SUBSEC = INT16_MAX; -constexpr size_t LIMIT_VANILLA_SEG = UINT16_MAX - 1; +// +// Binary format upper bounds. +// Some of these, namely the BSP tree indexes, are addressed by using later formats, such as DeepBSPV4, etc +// +// * No known ports seem to reserve 0xFFFF for vertices +// * The linedef index 0xFFFF is reserved for minisegs in XGLN/XGL2/XGL3 nodes +// * The sidedef index 0xFFFF is reserved to mean "no side" in DOOM map format +// * No known ports seem to reserve 0xFFFF for sectors +// +// * Nodes and subsectors share the same index slot, distinguished only by the high bit +// * No known additional limits exist for segments +// + +constexpr size_t LIMIT_VERT = UINT16_MAX; +constexpr size_t LIMIT_LINE = UINT16_MAX - 1; +constexpr size_t LIMIT_SIDE = UINT16_MAX - 1; +constexpr size_t LIMIT_SECTOR = UINT16_MAX; + +constexpr size_t LIMIT_NODE = INT16_MAX; +constexpr size_t LIMIT_SUBSEC = INT16_MAX; +constexpr size_t LIMIT_SEG = UINT16_MAX; // // Vanilla blockmap @@ -774,7 +790,7 @@ using raw_seg_vanilla_t = struct raw_seg_vanilla_s uint16_t angle; // angle (0 = east, 16384 = north, ...) uint16_t linedef; // linedef that this seg goes along uint16_t flip; // true if not the same direction as linedef - uint16_t dist; // distance from starting point + int16_t dist; // distance from starting point } PACKEDATTR; // @@ -802,7 +818,7 @@ using raw_seg_deepbspv4_t = struct raw_seg_deepbspv4_s uint16_t angle; // angle (0 = east, 16384 = north, ...) uint16_t linedef; // linedef that this seg goes along uint16_t flip; // true if not the same direction as linedef - uint16_t dist; // distance from starting point + int16_t dist; // distance from starting point } PACKEDATTR; // @@ -1396,7 +1412,7 @@ struct buildinfo_s // write out CSV for data analysis and visualization bool analysis = false; - bsp_type_t bsp_type = BSP_VANILLA; + bsp_type_t bsp_type = bsp_type_t::BSP_VANILLA; double split_cost = SPLIT_COST_DEFAULT; diff --git a/src/level.cpp b/src/level.cpp index c171634..1fcfa34 100644 --- a/src/level.cpp +++ b/src/level.cpp @@ -1717,44 +1717,45 @@ void ParseUDMF(void) // Check Limits // -static void CheckBinaryFormatLimits(bsp_type_t &bsp_type) +static void CheckBinaryFormatLimits(void) { - // this could potentially be 65536, since there are no reserved values - // for sectors, but there may be source ports or tools treating 0xFFFF - // as a special value, so we are extra cautious here (and in some of - // the other checks below, like the vertex counts). - if (lev_sectors.size() > 65535) + if (lev_sectors.size() > LIMIT_SECTOR) { PrintLine(LOG_NORMAL, "FAILURE: Map has too many sectors."); lev_overflows = true; } - // the sidedef 0xFFFF is reserved to mean "no side" in DOOM map format - if (lev_sidedefs.size() > 65535) + if (lev_sidedefs.size() > LIMIT_SIDE) { PrintLine(LOG_NORMAL, "FAILURE: Map has too many sidedefs."); lev_overflows = true; } - // the linedef 0xFFFF is reserved for minisegs in GL nodes - if (lev_linedefs.size() > 65535) + if (lev_linedefs.size() > LIMIT_LINE) { PrintLine(LOG_NORMAL, "FAILURE: Map has too many linedefs."); lev_overflows = true; } } -void RaiseBSPFormat(bsp_type_t &bsp_type) +bsp_type_t CheckFormatBSP(void) { - if (config.bsp_type < BSP_XNOD) + if (lev_vertices.size() > LIMIT_VERT) { - if (num_old_vert > 32767 || num_new_vert > 32767 || lev_segs.size() > 32767 || lev_nodes.size() > 32767) - { - PrintLine(LOG_NORMAL, "WARNING: Forcing DeepBSPV4 format nodes due to overflows."); - config.total_warnings++; - RaiseValue(config.bsp_type, BSP_DEEPBSPV4); - } + PrintLine(LOG_NORMAL, "WARNING: Vertex overflow. Forcing XNOD node format."); + config.total_warnings++; + return BSP_XNOD; } + + if (lev_vertices.size() <= LIMIT_VERT + && (lev_segs.size() > LIMIT_SEG || lev_subsecs.size() > LIMIT_SUBSEC || lev_nodes.size() > LIMIT_NODE)) + { + PrintLine(LOG_NORMAL, "WARNING: BSP overflow. Forcing DeepBSPV4 node format."); + config.total_warnings++; + return BSP_DEEPBSPV4; + } + + return BSP_VANILLA; } /* ----- whole-level routines --------------------------- */ @@ -1873,11 +1874,12 @@ build_result_e SaveBinaryFormatLevel(node_t *root_node) AddMissingLump("BLOCKMAP", "REJECT"); // check for overflows... - CheckBinaryFormatLimits(config.bsp_type); - RaiseBSPFormat(config.bsp_type); + CheckBinaryFormatLimits(); + + bsp_type_t level_type = CheckFormatBSP(); + level_type = std::max(config.bsp_type, level_type); - /* --- Normal nodes --- */ - switch (config.bsp_type) + switch (level_type) { case BSP_XGL3: { From 6e7e35aa14feed6619309541a286fbae230f3bdb Mon Sep 17 00:00:00 2001 From: "Guilherme M. Miranda" Date: Sun, 8 Feb 2026 12:23:16 -0300 Subject: [PATCH 13/14] updated changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7aa31bd..05c065a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Changes since ELFBSP 1.1 New features: * Added `--analysis` CLI parameter for BSP tree data analysis and visualization * Added `--debug-*` CLI parameters for runtime debugging, previously a compile-time directive, now a runtime option +* Added support for the DeePBSPV4, XGLN and XGL2 node formats, now enforced via the `--type` CLI parameter -BUgfixes: +Bugfixes: * Improved correctness for certain special effects, numerical effects of number 998 & 999 are now read from the line's tag instead of line's special From 0a6b98881b28f3018011e3680735d7f39ecbd1bc Mon Sep 17 00:00:00 2001 From: "Guilherme M. Miranda" Date: Sun, 8 Feb 2026 12:49:28 -0300 Subject: [PATCH 14/14] added copyright header --- src/bsp.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/bsp.cpp b/src/bsp.cpp index aa8765b..eef03d4 100644 --- a/src/bsp.cpp +++ b/src/bsp.cpp @@ -1,3 +1,26 @@ +//------------------------------------------------------------------------------ +// +// ELFBSP +// +//------------------------------------------------------------------------------ +// +// Copyright 2025-2026 Guilherme Miranda +// Copyright 2000-2018 Andrew Apted, et al +// Copyright 1994-1998 Colin Reed +// Copyright 1997-1998 Lee Killough +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +//------------------------------------------------------------------------------ + #include #include "core.hpp"