From fe0d84f54c41a5483213f40f51a4b9aaddce1db7 Mon Sep 17 00:00:00 2001 From: wootguy Date: Fri, 26 Apr 2024 07:27:47 -0700 Subject: [PATCH] add merge option to map viewer does a simple -noripent -noscript merge and gives alignment options if the maps overlap. --- src/bsp/Bsp.cpp | 38 +++++- src/bsp/Bsp.h | 1 + src/bsp/BspMerger.cpp | 47 +++++-- src/bsp/BspMerger.h | 32 ++++- src/editor/Gui.cpp | 288 ++++++++++++++++++++++++++++++---------- src/editor/Gui.h | 1 + src/editor/Renderer.cpp | 47 +++++++ src/editor/Renderer.h | 3 + src/main.cpp | 5 +- 9 files changed, 376 insertions(+), 86 deletions(-) diff --git a/src/bsp/Bsp.cpp b/src/bsp/Bsp.cpp index e41f52fb..fbb76c2f 100644 --- a/src/bsp/Bsp.cpp +++ b/src/bsp/Bsp.cpp @@ -56,8 +56,27 @@ Bsp::Bsp() { valid = true; } +Bsp::Bsp(const Bsp& other) { + header = other.header; + lumps = new byte*[HEADER_LUMPS]; + path = other.path; + name = other.name; + + for (int i = 0; i < HEADER_LUMPS; i++) { + lumps[i] = new byte[header.lump[i].nLength]; + memcpy(lumps[i], other.lumps[i], header.lump[i].nLength); + } + + load_ents(); + update_lump_pointers(); + + valid = true; +} + Bsp::Bsp(std::string fpath) { + lumps = NULL; + if (fpath.size() < 4 || toLowerCase(fpath).rfind(".bsp") != fpath.size() - 4) { fpath = fpath + ".bsp"; } @@ -84,10 +103,12 @@ Bsp::Bsp(std::string fpath) Bsp::~Bsp() { - for (int i = 0; i < HEADER_LUMPS; i++) - if (lumps[i]) - delete [] lumps[i]; - delete [] lumps; + if (lumps) { + for (int i = 0; i < HEADER_LUMPS; i++) + if (lumps[i]) + delete[] lumps[i]; + delete[] lumps; + } for (int i = 0; i < ents.size(); i++) delete ents[i]; @@ -105,6 +126,12 @@ void Bsp::get_bounding_box(vec3& mins, vec3& maxs) { mins = thisWorld.nMins; maxs = thisWorld.nMaxs; + + if (ents.size() && ents[0]->hasKey("origin")) { + vec3 origin = ents[0]->getOrigin(); + mins += origin; + maxs += origin; + } } void Bsp::get_model_vertex_bounds(int modelIdx, vec3& mins, vec3& maxs) { @@ -3441,7 +3468,8 @@ bool Bsp::isValid() { && textureCount < g_limits.max_textures && lightDataLength < g_limits.max_lightdata && visDataLength < g_limits.max_visdata - && ents.size() < g_limits.max_entities; + && ents.size() < g_limits.max_entities + && ceilf(calc_allocblock_usage()) < g_limits.max_allocblocks; } bool Bsp::validate_vis_data() { diff --git a/src/bsp/Bsp.h b/src/bsp/Bsp.h index 23412aab..f1c67ef8 100644 --- a/src/bsp/Bsp.h +++ b/src/bsp/Bsp.h @@ -75,6 +75,7 @@ class Bsp vector ents; Bsp(); + Bsp(const Bsp& other); Bsp(std::string fname); ~Bsp(); diff --git a/src/bsp/BspMerger.cpp b/src/bsp/BspMerger.cpp index 793f18d4..4be96e8d 100644 --- a/src/bsp/BspMerger.cpp +++ b/src/bsp/BspMerger.cpp @@ -11,15 +11,28 @@ BspMerger::BspMerger() { } -Bsp* BspMerger::merge(vector maps, vec3 gap, string output_name, bool noripent, bool noscript) { - if (maps.size() < 1) { +MergeResult BspMerger::merge(vector maps, vec3 gap, string output_name, bool noripent, bool noscript, bool nomove) { + MergeResult result; + result.fpath = ""; + result.map = NULL; + result.moveFixes = vec3(); + result.overflow = false; + + if (maps.size() <= 1) { logf("\nMore than 1 map is required for merging. Aborting merge.\n"); - return NULL; + return result; } - vector>> blocks = separate(maps, gap); + result.fpath = maps[1]->path; + + vector>> blocks = separate(maps, gap, nomove, result); + + if (blocks.empty()) { + return result; + } - logf("\nArranging maps so that they don't overlap:\n"); + if (!nomove) + logf("\nArranging maps so that they don't overlap:\n"); for (int z = 0; z < blocks.size(); z++) { for (int y = 0; y < blocks[z].size(); y++) { @@ -108,7 +121,9 @@ Bsp* BspMerger::merge(vector maps, vec3 gap, string output_name, bool nori update_map_series_entity_logic(output, flattenedBlocks, maps, output_name, maps[0]->name, noscript); } - return output; + result.map = output; + result.overflow = !output->isValid(); + return result; } void BspMerger::merge(MAPBLOCK& dst, MAPBLOCK& src, string resultType) { @@ -120,7 +135,7 @@ void BspMerger::merge(MAPBLOCK& dst, MAPBLOCK& src, string resultType) { merge(*dst.map, *src.map); } -vector>> BspMerger::separate(vector& maps, vec3 gap) { +vector>> BspMerger::separate(vector& maps, vec3 gap, bool nomove, MergeResult& result) { vector blocks; vector>> orderedBlocks; @@ -153,18 +168,30 @@ vector>> BspMerger::separate(vector& maps, vec3 ga for (int k = 0; k < blocks.size(); k++) { if (i != k && blocks[i].intersects(blocks[k])) { noOverlap = false; + + if (nomove) { + logf("Merge aborted because the maps overlap.\n"); + blocks[i].suggest_intersection_fix(blocks[k], result); + } + break; } } } if (noOverlap) { - logf("Maps do not overlap. They will be merged without moving.\n"); + if (!nomove) + logf("Maps do not overlap. They will be merged without moving.\n"); vector> col; vector row; for (const MAPBLOCK& block : blocks) { row.push_back(block); + if (block.map->ents[0]->hasKey("origin")) { + // apply the transform move in the GUI + block.map->move(block.map->ents[0]->getOrigin()); + block.map->ents[0]->removeKeyvalue("origin"); + } } col.push_back(row); orderedBlocks.push_back(col); @@ -172,6 +199,10 @@ vector>> BspMerger::separate(vector& maps, vec3 ga return orderedBlocks; } + if (nomove) { + return orderedBlocks; + } + maxDims += gap; int maxMapsPerRow = (MAX_MAP_COORD * 2.0f) / maxDims.x; diff --git a/src/bsp/BspMerger.h b/src/bsp/BspMerger.h index 8a22f4ac..23834e82 100644 --- a/src/bsp/BspMerger.h +++ b/src/bsp/BspMerger.h @@ -1,6 +1,17 @@ +#pragma once #include "util.h" #include "Bsp.h" +struct MergeResult { + Bsp* map; + + // merge failed if map is null, and below are suggested fixes + string fpath; + vec3 moveFixes; + vec3 moveFixes2; + bool overflow; +}; + // bounding box for a map, used for arranging maps for merging struct MAPBLOCK { @@ -13,6 +24,22 @@ struct MAPBLOCK (mins.y <= other.maxs.y && maxs.y >= other.mins.y) && (mins.z <= other.maxs.z && maxs.z >= other.mins.z); } + + void suggest_intersection_fix(MAPBLOCK& other, MergeResult& result) { + float xdelta_neg = other.maxs.x - mins.x; + float xdelta_pos = maxs.x - other.mins.x; + float ydelta_neg = other.maxs.y - mins.y; + float ydelta_pos = maxs.y - other.mins.y; + float zdelta_neg = other.maxs.z - mins.z; + float zdelta_pos = maxs.z - other.mins.z; + + int xdelta = xdelta_neg < xdelta_pos ? ceilf(xdelta_neg + 1.5f) : -ceilf(xdelta_pos + 1.5f); + int ydelta = ydelta_neg < ydelta_pos ? ceilf(ydelta_neg + 1.5f) : -ceilf(ydelta_pos + 1.5f); + int zdelta = zdelta_neg < zdelta_pos ? ceilf(zdelta_neg + 1.5f) : -ceilf(zdelta_pos + 1.5f); + + result.moveFixes = vec3(ceilf(xdelta_neg + 1.5f), ceilf(ydelta_neg + 1.5f), ceilf(zdelta_neg + 1.5f)); + result.moveFixes2 = vec3(-ceilf(xdelta_pos + 1.5f), -ceilf(ydelta_pos + 1.5f), -ceilf(zdelta_pos + 1.5f)); + } }; class BspMerger { @@ -22,7 +49,8 @@ class BspMerger { // merges all maps into one // noripent - don't change any entity logic // noscript - don't add support for the bspguy map script (worse performance + buggy, but simpler) - Bsp* merge(vector maps, vec3 gap, string output_name, bool noripent, bool noscript); + // nomove - abort the merge if the maps overlap + MergeResult merge(vector maps, vec3 gap, string output_name, bool noripent, bool noscript, bool nomove); private: int merge_ops = 0; @@ -33,7 +61,7 @@ class BspMerger { // merge BSP data bool merge(Bsp& mapA, Bsp& mapB); - vector>> separate(vector& maps, vec3 gap); + vector>> separate(vector& maps, vec3 gap, bool nomove, MergeResult& result); // for maps in a series: // - changelevels should be replaced with teleports or respawn triggers diff --git a/src/editor/Gui.cpp b/src/editor/Gui.cpp index a27ba0f0..76e5930b 100644 --- a/src/editor/Gui.cpp +++ b/src/editor/Gui.cpp @@ -14,6 +14,7 @@ #include #include "tinyfiledialogs.h" #include +#include "BspMerger.h" // embedded binary data #include "fonts/robotomono.h" @@ -675,17 +676,13 @@ void Gui::drawMenuBar() { { static char const* bspFilterPatterns[1] = { "*.bsp" }; - if (ImGui::MenuItem("Reload", 0, false, !app->isLoading)) { - app->reloadMaps(); - refresh(); - } - - if (ImGui::MenuItem("Open", NULL)) { + if (ImGui::MenuItem("Open", NULL, false, !app->isLoading)) { char* fname = tinyfd_openFileDialog("Open Map", "", 1, bspFilterPatterns, "GoldSrc Map Files (*.bsp)", 1); g_app->openMap(fname); } + if (ImGui::MenuItem("Save", NULL)) { Bsp* map = app->getMapContainingCamera()->map; map->update_ent_lump(); @@ -830,7 +827,28 @@ void Gui::drawMenuBar() { } */ + ImGui::Separator(); + + if (ImGui::MenuItem("Merge", NULL, false, !app->isLoading)) { + char* fname = tinyfd_openFileDialog("Merge Map", "", + 1, bspFilterPatterns, "GoldSrc Map Files (*.bsp)", 1); + + if (fname) + g_app->merge(fname); + } + Bsp* map = g_app->mapRenderers[0]->map; + tooltip(g, ("Merge one other BSP into the current file.\n\n" + "Equivalent CLI command:\nbspguy merge " + map->name + " -noscript -noripent -maps \"" + + map->name + ",other_map\"\n\nUse the CLI for automatic arrangement and optimization of " + "many maps. The CLI also offers ripent fixes and script setup which can " + "generate a playable map without you having to make any manual edits (Sven Co-op only).").c_str()); + if (ImGui::MenuItem("Reload", 0, false, !app->isLoading)) { + app->reloadMaps(); + refresh(); + } + tooltip(g, "Discard all changes and reload the map.\n"); + if (ImGui::MenuItem("Validate")) { for (int i = 0; i < app->mapRenderers.size(); i++) { Bsp* map = app->mapRenderers[i]->map; @@ -1454,6 +1472,131 @@ void Gui::drawMenuBar() { } ImGui::EndMainMenuBar(); + + if (!g_app->mergeResult.map && !g_app->mergeResult.overflow && g_app->mergeResult.fpath.size()) + ImGui::OpenPopup("Merge Overlap"); + + if (ImGui::BeginPopupModal("Merge Overlap", NULL, ImGuiWindowFlags_AlwaysAutoResize)) + { + Bsp* thismap = g_app->mapRenderers[0]->map; + string name = stripExt(basename(g_app->mergeResult.fpath)); + vec3 mergeMove = g_app->mergeResult.moveFixes; + vec3 mergeMove2 = g_app->mergeResult.moveFixes2; + + ImGui::Text((thismap->name + " overlaps " + name + " and must be moved before merging.").c_str()); + ImGui::Dummy(ImVec2(0.0f, 20.0f)); + ImGui::Text(("How do you want to move " + thismap->name + "?").c_str()); + + ImGui::Separator(); + ImGui::Dummy(ImVec2(0.0f, 20.0f)); + + ImGuiStyle& style = ImGui::GetStyle(); + float padding = style.WindowPadding.x * 2 + style.FramePadding.x * 2; + float inputWidth = (ImGui::GetWindowWidth() - padding) * 0.33f; + + ImGui::Columns(3, 0, false); + + string xmove = "Move X +" + to_string((int)mergeMove.x); + string ymove = "Move Y +" + to_string((int)mergeMove.y); + string zmove = "Move Z +" + to_string((int)mergeMove.z); + + string xmove2 = "Move X " + to_string((int)mergeMove2.x); + string ymove2 = "Move Y " + to_string((int)mergeMove2.y); + string zmove2 = "Move Z " + to_string((int)mergeMove2.z); + + vec3 adjustment; + if (ImGui::Button(xmove.c_str(), ImVec2(inputWidth, 0))) { + ImGui::CloseCurrentPopup(); + adjustment = vec3(mergeMove.x, 0, 0); + } + + ImGui::NextColumn(); + if (ImGui::Button(ymove.c_str(), ImVec2(inputWidth, 0))) { + ImGui::CloseCurrentPopup(); + adjustment = vec3(0, mergeMove.y, 0); + } + + ImGui::NextColumn(); + if (ImGui::Button(zmove.c_str(), ImVec2(inputWidth, 0))) { + ImGui::CloseCurrentPopup(); + adjustment = vec3(0, 0, mergeMove.z); + } + + ImGui::NextColumn(); + if (ImGui::Button(xmove2.c_str(), ImVec2(inputWidth, 0))) { + ImGui::CloseCurrentPopup(); + adjustment = vec3(mergeMove2.x, 0, 0); + } + + ImGui::NextColumn(); + if (ImGui::Button(ymove2.c_str(), ImVec2(inputWidth, 0))) { + ImGui::CloseCurrentPopup(); + adjustment = vec3(0, mergeMove2.y, 0); + } + + ImGui::NextColumn(); + if (ImGui::Button(zmove2.c_str(), ImVec2(inputWidth, 0))) { + ImGui::CloseCurrentPopup(); + adjustment = vec3(0, 0, mergeMove2.z); + } + + if (adjustment != vec3()) { + vec3 newOri = thismap->ents[0]->getOrigin() + adjustment; + thismap->ents[0]->setOrAddKeyvalue("origin", newOri.toKeyvalueString()); + g_app->merge(g_app->mergeResult.fpath); + } + + ImGui::Dummy(ImVec2(0.0f, 20.0f)); + + ImGui::NextColumn(); + ImGui::NextColumn(); + if (ImGui::Button("Cancel", ImVec2(inputWidth, 0))) { + ImGui::CloseCurrentPopup(); + g_app->mergeResult.fpath = ""; + } + ImGui::Dummy(ImVec2(0.0f, 20.0f)); + + ImGui::SetItemDefaultFocus(); + ImGui::EndPopup(); + } + + if (g_app->mergeResult.overflow && g_app->mergeResult.fpath.size()) { + ImGui::OpenPopup("Merge Failed"); + loadedStats = false; + } + + if (ImGui::BeginPopupModal("Merge Failed", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings)) + { + string engineName = g_settings.engine == ENGINE_HALF_LIFE ? "Half-Life" : "Sven Co-op"; + ImGui::Text(("Merging the selected maps would overflow \"" + engineName + "\" engine limits.\n" + "Optimize the maps or manually remove unused structures before trying again.").c_str()); + + ImGui::Dummy(ImVec2(0.0f, 20.0f)); + + ImGui::Separator(); + + drawLimitsSummary(g_app->mergeResult.map, true); + + ImGuiStyle& style = ImGui::GetStyle(); + float padding = style.WindowPadding.x * 2 + style.FramePadding.x * 2; + float inputWidth = (ImGui::GetWindowWidth() - padding) * 0.33f; + + ImGui::Dummy(ImVec2(0.0f, 20.0f)); + ImGui::Columns(3, 0, false); + + ImGui::NextColumn(); + if (ImGui::Button("OK", ImVec2(inputWidth, 0))) { + ImGui::CloseCurrentPopup(); + g_app->mergeResult.fpath = ""; + delete g_app->mergeResult.map; + g_app->mergeResult.map = NULL; + loadedStats = false; + } + ImGui::Dummy(ImVec2(0.0f, 20.0f)); + + ImGui::SetItemDefaultFocus(); + ImGui::EndPopup(); + } } void Gui::drawToolbar() { @@ -1767,7 +1910,7 @@ void Gui::drawDebugWidget() { } if (ImGui::CollapsingHeader((bspTreeTitle + "##bsptree").c_str(), ImGuiTreeNodeFlags_DefaultOpen)) { - if (app->pickInfo.modelIdx >= 0) { + if (app->pickInfo.map && app->pickInfo.modelIdx >= 0) { Bsp* map = app->pickInfo.map; vec3 localCamera = app->cameraOrigin - app->mapRenderers[app->pickInfo.mapIdx]->mapOffset; @@ -3110,6 +3253,74 @@ void Gui::drawAbout() { ImGui::End(); } +void Gui::drawLimitsSummary(Bsp* map, bool modalMode) { + if (!loadedStats) { + stats.clear(); + stats.push_back(calcStat("AllocBlock", map->calc_allocblock_usage(), g_limits.max_allocblocks, false)); + stats.push_back(calcStat("clipnodes", map->clipnodeCount, g_limits.max_clipnodes, false)); + stats.push_back(calcStat("nodes", map->nodeCount, g_limits.max_nodes, false)); + stats.push_back(calcStat("leaves", map->leafCount, g_limits.max_leaves, false)); + stats.push_back(calcStat("models", map->modelCount, g_limits.max_models, false)); + stats.push_back(calcStat("faces", map->faceCount, g_limits.max_faces, false)); + stats.push_back(calcStat("texinfos", map->texinfoCount, g_limits.max_texinfos, false)); + stats.push_back(calcStat("textures", map->textureCount, g_limits.max_textures, false)); + stats.push_back(calcStat("planes", map->planeCount, g_limits.max_planes, false)); + stats.push_back(calcStat("vertexes", map->vertCount, g_limits.max_vertexes, false)); + stats.push_back(calcStat("edges", map->edgeCount, g_limits.max_edges, false)); + stats.push_back(calcStat("surfedges", map->surfedgeCount, g_limits.max_surfedges, false)); + stats.push_back(calcStat("marksurfaces", map->marksurfCount, g_limits.max_marksurfaces, false)); + stats.push_back(calcStat("entdata", map->header.lump[LUMP_ENTITIES].nLength, g_limits.max_entdata, true)); + stats.push_back(calcStat("visdata", map->visDataLength, g_limits.max_visdata, true)); + stats.push_back(calcStat("lightdata", map->lightDataLength, g_limits.max_lightdata, true)); + loadedStats = true; + } + + if (!modalMode) + ImGui::BeginChild("##content"); + ImGui::Dummy(ImVec2(0, 10)); + ImGui::PushFont(consoleFontLarge); + + int midWidth = consoleFontLarge->CalcTextSizeA(fontSize * 1.1f, FLT_MAX, FLT_MAX, " Current / Max ").x; + int otherWidth = (ImGui::GetWindowWidth() - midWidth) / 2; + ImGui::Columns(3); + ImGui::SetColumnWidth(0, otherWidth); + ImGui::SetColumnWidth(1, midWidth); + ImGui::SetColumnWidth(2, otherWidth); + + ImGui::Text("Data Type"); ImGui::NextColumn(); + ImGui::Text(" Current / Max"); ImGui::NextColumn(); + ImGui::Text("Fullness"); ImGui::NextColumn(); + + ImGui::Columns(1); + ImGui::Separator(); + if (!modalMode) + ImGui::BeginChild("##chart"); + ImGui::Columns(3); + ImGui::SetColumnWidth(0, otherWidth); + ImGui::SetColumnWidth(1, midWidth); + ImGui::SetColumnWidth(2, otherWidth); + + for (int i = 0; i < stats.size(); i++) { + ImGui::TextColored(stats[i].color, stats[i].name.c_str()); ImGui::NextColumn(); + + string val = stats[i].val + " / " + stats[i].max; + ImGui::TextColored(stats[i].color, val.c_str()); + ImGui::NextColumn(); + + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.5f, 0.4f, 0, 1)); + ImGui::ProgressBar(stats[i].progress, ImVec2(-1, 0), stats[i].fullness.c_str()); + ImGui::PopStyleColor(1); + ImGui::NextColumn(); + } + + ImGui::Columns(1); + if (!modalMode) + ImGui::EndChild(); + ImGui::PopFont(); + if (!modalMode) + ImGui::EndChild(); +} + void Gui::drawLimits() { ImGui::SetNextWindowSize(ImVec2(550, 630), ImGuiCond_FirstUseEver); @@ -3125,68 +3336,7 @@ void Gui::drawLimits() { if (ImGui::BeginTabBar("##tabs")) { if (ImGui::BeginTabItem("Summary")) { - - if (!loadedStats) { - stats.clear(); - stats.push_back(calcStat("AllocBlock", map->calc_allocblock_usage(), g_limits.max_allocblocks, false)); - stats.push_back(calcStat("clipnodes", map->clipnodeCount, g_limits.max_clipnodes, false)); - stats.push_back(calcStat("nodes", map->nodeCount, g_limits.max_nodes, false)); - stats.push_back(calcStat("leaves", map->leafCount, g_limits.max_leaves, false)); - stats.push_back(calcStat("models", map->modelCount, g_limits.max_models, false)); - stats.push_back(calcStat("faces", map->faceCount, g_limits.max_faces, false)); - stats.push_back(calcStat("texinfos", map->texinfoCount, g_limits.max_texinfos, false)); - stats.push_back(calcStat("textures", map->textureCount, g_limits.max_textures, false)); - stats.push_back(calcStat("planes", map->planeCount, g_limits.max_planes, false)); - stats.push_back(calcStat("vertexes", map->vertCount, g_limits.max_vertexes, false)); - stats.push_back(calcStat("edges", map->edgeCount, g_limits.max_edges, false)); - stats.push_back(calcStat("surfedges", map->surfedgeCount, g_limits.max_surfedges, false)); - stats.push_back(calcStat("marksurfaces", map->marksurfCount, g_limits.max_marksurfaces, false)); - stats.push_back(calcStat("entdata", map->header.lump[LUMP_ENTITIES].nLength, g_limits.max_entdata, true)); - stats.push_back(calcStat("visdata", map->visDataLength, g_limits.max_visdata, true)); - stats.push_back(calcStat("lightdata", map->lightDataLength, g_limits.max_lightdata, true)); - loadedStats = true; - } - - ImGui::BeginChild("content"); - ImGui::Dummy(ImVec2(0, 10)); - ImGui::PushFont(consoleFontLarge); - - int midWidth = consoleFontLarge->CalcTextSizeA(fontSize * 1.1f, FLT_MAX, FLT_MAX, " Current / Max ").x; - int otherWidth = (ImGui::GetWindowWidth() - midWidth) / 2; - ImGui::Columns(3); - ImGui::SetColumnWidth(0, otherWidth); - ImGui::SetColumnWidth(1, midWidth); - ImGui::SetColumnWidth(2, otherWidth); - - ImGui::Text("Data Type"); ImGui::NextColumn(); - ImGui::Text(" Current / Max"); ImGui::NextColumn(); - ImGui::Text("Fullness"); ImGui::NextColumn(); - - ImGui::Columns(1); - ImGui::Separator(); - ImGui::BeginChild("chart"); - ImGui::Columns(3); - ImGui::SetColumnWidth(0, otherWidth); - ImGui::SetColumnWidth(1, midWidth); - ImGui::SetColumnWidth(2, otherWidth); - - for (int i = 0; i < stats.size(); i++) { - ImGui::TextColored(stats[i].color, stats[i].name.c_str()); ImGui::NextColumn(); - - string val = stats[i].val + " / " + stats[i].max; - ImGui::TextColored(stats[i].color, val.c_str()); - ImGui::NextColumn(); - - ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.5f, 0.4f, 0, 1)); - ImGui::ProgressBar(stats[i].progress, ImVec2(-1, 0), stats[i].fullness.c_str()); - ImGui::PopStyleColor(1); - ImGui::NextColumn(); - } - - ImGui::Columns(1); - ImGui::EndChild(); - ImGui::PopFont(); - ImGui::EndChild(); + drawLimitsSummary(map, false); ImGui::EndTabItem(); } diff --git a/src/editor/Gui.h b/src/editor/Gui.h index 1ce3c978..30030397 100644 --- a/src/editor/Gui.h +++ b/src/editor/Gui.h @@ -125,6 +125,7 @@ class Gui { void drawHelp(); void drawAbout(); void drawLimits(); + void drawLimitsSummary(Bsp* map, bool modalMode); void drawLightMapTool(); void drawTextureTool(); void drawLimitTab(Bsp* map, int sortMode); diff --git a/src/editor/Renderer.cpp b/src/editor/Renderer.cpp index 2cc13ecf..85a977e1 100644 --- a/src/editor/Renderer.cpp +++ b/src/editor/Renderer.cpp @@ -15,6 +15,7 @@ #include "globals.h" #include "NavMesh.h" #include +#include "BspMerger.h" // everything except VIS, ENTITIES, MARKSURFS #define EDIT_MODEL_LUMPS (PLANES | TEXTURES | VERTICES | NODES | TEXINFO | FACES | LIGHTING | CLIPNODES | LEAVES | EDGES | SURFEDGES | MODELS) @@ -553,6 +554,7 @@ void Renderer::openMap(const char* fpath) { clearUndoCommands(); clearRedoCommands(); + gui->refresh(); logf("Loaded map: %s\n", fpath); } @@ -3041,3 +3043,48 @@ void Renderer::calcUndoMemoryUsage() { undoMemoryUsage += redoHistory[i]->memoryUsage(); } } + +void Renderer::merge(string fpath) { + Bsp* thismap = g_app->mapRenderers[0]->map; + thismap->update_ent_lump(); + + Bsp* map2 = new Bsp(fpath); + Bsp* thisCopy = new Bsp(*thismap); + + if (!map2->valid) { + delete map2; + logf("Merge aborted because the BSP load failed.\n"); + return; + } + + vector maps; + + maps.push_back(thisCopy); + maps.push_back(map2); + + BspMerger merger; + mergeResult = merger.merge(maps, vec3(), thismap->name, true, true, true); + + if (!mergeResult.map || !mergeResult.map->valid) { + delete map2; + if (mergeResult.map) + delete mergeResult.map; + + mergeResult.map = NULL; + return; + } + + if (mergeResult.overflow) { + return; // map deleted later in gui modal, after displaying limit overflows + } + + mapRenderers.clear(); + pickInfo.valid = false; + addMap(mergeResult.map); + + clearUndoCommands(); + clearRedoCommands(); + gui->refresh(); + + logf("Merged maps!\n"); +} \ No newline at end of file diff --git a/src/editor/Renderer.h b/src/editor/Renderer.h index b6f25b4a..9c674882 100644 --- a/src/editor/Renderer.h +++ b/src/editor/Renderer.h @@ -3,6 +3,7 @@ #include "primitives.h" #include "BspRenderer.h" #include "bsptypes.h" +#include "BspMerger.h" class Gui; class Fgd; @@ -74,6 +75,7 @@ class Renderer { int debugNavPoly = -1; vec3 debugTraceStart; TraceResult debugTrace; + MergeResult mergeResult; bool hideGui = false; @@ -89,6 +91,7 @@ class Renderer { void openMap(const char* path); void saveSettings(); void loadSettings(); + void merge(string fpath); private: GLFWwindow* window; diff --git a/src/main.cpp b/src/main.cpp index 5ef4ecc4..ebfc19fc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -170,7 +170,7 @@ int test() { removed.print_delete_stats(1); BspMerger merger; - Bsp* result = merger.merge(maps, vec3(1, 1, 1), "yabma_move", false, false); + Bsp* result = merger.merge(maps, vec3(1, 1, 1), "yabma_move", false, false, false).map; logf("\n"); if (result != NULL) { result->write("yabma_move.bsp"); @@ -227,7 +227,8 @@ int merge_maps(CommandLine& cli) { string output_name = cli.hasOption("-o") ? cli.getOption("-o") : cli.bspfile; BspMerger merger; - Bsp* result = merger.merge(maps, gap, output_name, cli.hasOption("-noripent"), cli.hasOption("-noscript")); + Bsp* result = merger.merge(maps, gap, output_name, + cli.hasOption("-noripent"), cli.hasOption("-noscript"), false).map; logf("\n"); if (result->isValid()) result->write(output_name);