Skip to content

Commit 39d59a1

Browse files
committed
speed up connected faces selection
from several minutes to 0.5s when accidentally selecting 32k faces at once. Minimum time to select increased a bit since an octree of the entire level is created for each selection.
1 parent 73c65a1 commit 39d59a1

7 files changed

Lines changed: 127 additions & 53 deletions

File tree

src/bsp/Bsp.cpp

Lines changed: 88 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
#include "Fgd.h"
2222
#include "Texture.h"
2323
#include "LeafNavMeshGenerator.h"
24+
#include "NavMeshGenerator.h"
25+
#include "PolyOctree.h"
2426

2527
typedef map< string, vec3 > mapStringToVector;
2628

@@ -4291,53 +4293,102 @@ bool Bsp::rename_texture(const char* oldName, const char* newName) {
42914293
return false;
42924294
}
42934295

4294-
unordered_set<int> Bsp::selectConnected(int modelId, int faceId, unordered_set<int>& ignoreFaces, bool planarTextureOnly) {
4296+
unordered_set<int> Bsp::selectConnected(vector<int>& srcFaces, unordered_set<int>& ignoreFaces, bool planarTextureOnly) {
42954297
unordered_set<int> selected;
4296-
const float epsilon = 1.0f;
4298+
vector<Polygon3D*> selectedFaces;
4299+
queue<Polygon3D*> testPolys;
4300+
unordered_set<int> validMiptex;
4301+
vector<vec3> validNormals;
4302+
vector<Polygon3D*> polys;
4303+
vector<int> polyModels; // maps a polygon index to a model index
4304+
4305+
for (int idx : srcFaces) {
4306+
BSPFACE& face = faces[idx];
4307+
BSPTEXTUREINFO& info = texinfos[face.iTextureInfo];
4308+
BSPPLANE& plane = planes[face.iPlane];
42974309

4298-
BSPMODEL& model = models[modelId];
4310+
vector<vec3> selectedVerts;
4311+
for (int e = 0; e < face.nEdges; e++) {
4312+
int32_t edgeIdx = surfedges[face.iFirstEdge + e];
4313+
BSPEDGE& edge = edges[abs(edgeIdx)];
4314+
int vertIdx = edgeIdx >= 0 ? edge.iVertex[1] : edge.iVertex[0];
4315+
selectedVerts.push_back(verts[vertIdx]);
4316+
}
42994317

4300-
BSPFACE& face = faces[faceId];
4301-
BSPTEXTUREINFO& info = texinfos[face.iTextureInfo];
4302-
BSPPLANE& plane = planes[face.iPlane];
4318+
Polygon3D* poly = new Polygon3D(selectedVerts, idx, true);
4319+
selectedFaces.push_back(poly);
4320+
testPolys.push(poly);
4321+
validMiptex.insert(info.iMiptex);
4322+
push_unique_vec3(validNormals, plane.vNormal, 0.0001f);
4323+
}
43034324

4304-
vector<vec3> selectedVerts;
4305-
for (int e = 0; e < face.nEdges; e++) {
4306-
int32_t edgeIdx = surfedges[face.iFirstEdge + e];
4307-
BSPEDGE& edge = edges[abs(edgeIdx)];
4308-
int vertIdx = edgeIdx >= 0 ? edge.iVertex[1] : edge.iVertex[0];
4309-
selectedVerts.push_back(verts[vertIdx]);
4325+
for (int fa = 0; fa < faceCount; fa++) {
4326+
polyModels.push_back(get_model_from_face(fa));
4327+
4328+
BSPFACE& face = faces[fa];
4329+
4330+
vector<vec3> faceVerts;
4331+
4332+
for (int e = 0; e < face.nEdges; e++) {
4333+
int32_t edgeIdx = surfedges[face.iFirstEdge + e];
4334+
BSPEDGE& edge = edges[abs(edgeIdx)];
4335+
int vertIdx = edgeIdx >= 0 ? edge.iVertex[1] : edge.iVertex[0];
4336+
faceVerts.push_back(verts[vertIdx]);
4337+
}
4338+
4339+
Polygon3D* poly = new Polygon3D(faceVerts, polys.size(), true);
4340+
4341+
polys.push_back(poly);
43104342
}
43114343

4312-
bool anyNewFaces = true;
4313-
while (anyNewFaces) {
4314-
anyNewFaces = false;
4344+
PolygonOctree* octree = NavMeshGenerator::createPolyOctree(this, polys, 6);
43154345

4316-
for (int fa = 0; fa < model.nFaces; fa++) {
4317-
int testFaceIdx = model.iFirstFace + fa;
4318-
BSPFACE& faceA = faces[testFaceIdx];
4319-
BSPTEXTUREINFO& infoA = texinfos[faceA.iTextureInfo];
4320-
BSPPLANE& planeA = planes[faceA.iPlane];
4346+
while (testPolys.size()) {
4347+
Polygon3D* poly = testPolys.front();
4348+
testPolys.pop();
43214349

4322-
if (selected.count(testFaceIdx) || ignoreFaces.count(fa)) {
4350+
int srcModel = polyModels[poly->idx];
4351+
unordered_set<int> regionPolys = octree->getPolysInRegion(poly);
4352+
4353+
for (int ridx : regionPolys) {
4354+
int idx = polys[ridx]->idx;
4355+
BSPFACE& faceA = faces[idx];
4356+
4357+
if (polyModels[idx] != srcModel)
43234358
continue;
4324-
}
43254359

4326-
if (planarTextureOnly && (planeA.vNormal != plane.vNormal || info.iMiptex != infoA.iMiptex)) {
4360+
if (selected.count(idx) || ignoreFaces.count(idx))
43274361
continue;
4362+
4363+
if (planarTextureOnly) {
4364+
BSPTEXTUREINFO& info = texinfos[faceA.iTextureInfo];
4365+
BSPPLANE& plane = planes[faceA.iPlane];
4366+
4367+
if (!validMiptex.count(info.iMiptex))
4368+
continue;
4369+
4370+
bool isPlanar = false;
4371+
for (const vec3& norm : validNormals) {
4372+
if (plane.vNormal == norm) {
4373+
isPlanar = true;
4374+
break;
4375+
}
4376+
}
4377+
4378+
if (!isPlanar)
4379+
continue;
43284380
}
43294381

4330-
vector<vec3> uniqueVerts;
43314382
bool isConnected = false;
43324383

43334384
for (int e = 0; e < faceA.nEdges && !isConnected; e++) {
43344385
int32_t edgeIdx = surfedges[faceA.iFirstEdge + e];
43354386
BSPEDGE& edge = edges[abs(edgeIdx)];
43364387
int vertIdx = edgeIdx >= 0 ? edge.iVertex[1] : edge.iVertex[0];
4388+
const float epsilon = 1.0f;
43374389

4338-
bool isUnique = true;
4339-
vec3 v2 = verts[vertIdx];
4340-
for (vec3 v1 : selectedVerts) {
4390+
vec3& v2 = verts[vertIdx];
4391+
for (const vec3& v1 : poly->verts) {
43414392
if ((v1 - v2).length() < epsilon) {
43424393
isConnected = true;
43434394
break;
@@ -4351,15 +4402,22 @@ unordered_set<int> Bsp::selectConnected(int modelId, int faceId, unordered_set<i
43514402
int32_t edgeIdx = surfedges[faceA.iFirstEdge + e];
43524403
BSPEDGE& edge = edges[abs(edgeIdx)];
43534404
int vertIdx = edgeIdx >= 0 ? edge.iVertex[1] : edge.iVertex[0];
4354-
selectedVerts.push_back(verts[vertIdx]);
43554405
}
43564406

4357-
selected.insert(testFaceIdx);
4358-
anyNewFaces = true;
4407+
selected.insert(idx);
4408+
testPolys.push(polys[ridx]);
43594409
}
43604410
}
43614411
}
43624412

4413+
for (Polygon3D* poly : polys) {
4414+
delete poly;
4415+
}
4416+
for (Polygon3D* poly : selectedFaces) {
4417+
delete poly;
4418+
}
4419+
delete octree;
4420+
43634421
return selected;
43644422
}
43654423

src/bsp/Bsp.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ class Bsp
372372
// select faces connected to the given one
373373
// ignoreFaces will not be connected thru
374374
// planarTextureOnly = only select on the same plane with the same texture
375-
unordered_set<int> selectConnected(int modelId, int faceId, unordered_set<int>& ignoreFaces, bool planarTextureOnly);
375+
unordered_set<int> selectConnected(vector<int>& srcFaces, unordered_set<int>& ignoreFaces, bool planarTextureOnly);
376376

377377
// returns true if the map has eny entities that make use of hull 2
378378
bool has_hull2_ents();

src/editor/Gui.cpp

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -458,13 +458,8 @@ void Gui::draw3dContextMenus() {
458458
if (ImGui::MenuItem("Select connected faces", "", false)) {
459459
Bsp* map = app->pickInfo.getMap();
460460

461-
unordered_set<int> newSelect;
462-
for (int i = 0; i < app->pickInfo.faces.size(); i++) {
463-
int faceIdx = app->pickInfo.faces[i];
464-
int modelIdx = map->get_model_from_face(faceIdx);
465-
unordered_set<int> selectPart = map->selectConnected(modelIdx, faceIdx, app->hiddenFaces, false);
466-
newSelect.insert(selectPart.begin(), selectPart.end());
467-
}
461+
int oldSelectSz = app->pickInfo.faces.size();
462+
unordered_set<int> newSelect = map->selectConnected(app->pickInfo.faces, app->hiddenFaces, false);
468463

469464
g_app->mapRenderer->highlightPickedFaces(false);
470465

@@ -475,21 +470,16 @@ void Gui::draw3dContextMenus() {
475470
g_app->mapRenderer->highlightPickedFaces(true);
476471
g_app->updateTextureAxes();
477472

478-
logf("Selected %d faces\n", app->pickInfo.faces.size());
473+
logf("Selected %d faces\n", app->pickInfo.faces.size() - oldSelectSz);
479474
g_app->pickCount++;
480475
}
481-
tooltip(g, "Recursively select faces connected by edges.");
476+
tooltip(g, "Recursively select faces connected by vertices.");
482477

483478
if (ImGui::MenuItem("Select connected planar faces of this texture", "", false)) {
484479
Bsp* map = app->pickInfo.getMap();
485480

486-
unordered_set<int> newSelect;
487-
for (int i = 0; i < app->pickInfo.faces.size(); i++) {
488-
int faceIdx = app->pickInfo.faces[i];
489-
int modelIdx = map->get_model_from_face(faceIdx);
490-
unordered_set<int> selectPart = map->selectConnected(modelIdx, faceIdx, app->hiddenFaces, true);
491-
newSelect.insert(selectPart.begin(), selectPart.end());
492-
}
481+
int oldSelectSz = app->pickInfo.faces.size();
482+
unordered_set<int> newSelect = map->selectConnected(app->pickInfo.faces, app->hiddenFaces, true);
493483

494484
g_app->mapRenderer->highlightPickedFaces(false);
495485

@@ -500,7 +490,7 @@ void Gui::draw3dContextMenus() {
500490
g_app->mapRenderer->highlightPickedFaces(true);
501491
g_app->updateTextureAxes();
502492

503-
logf("Selected %d faces\n", app->pickInfo.faces.size());
493+
logf("Selected %d faces\n", app->pickInfo.faces.size() - oldSelectSz);
504494
g_app->pickCount++;
505495
}
506496
tooltip(g, "Selects faces connected to this one which lie on the same plane and use the same texture");

src/nav/NavMeshGenerator.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ PolygonOctree* NavMeshGenerator::createPolyOctree(Bsp* map, const vector<Polygon
116116
vec3 treeMin, treeMax;
117117
getOctreeBox(map, treeMin, treeMax);
118118

119-
logf("Create octree depth %d, size %f -> %f\n", treeDepth, treeMax.x, treeMax.x / pow(2, treeDepth));
119+
debugf("Create octree depth %d, size %f -> %f\n", treeDepth, treeMax.x, treeMax.x / pow(2, treeDepth));
120120
PolygonOctree* octree = new PolygonOctree(treeMin, treeMax, treeDepth);
121121

122122
for (int i = 0; i < faces.size(); i++) {

src/nav/NavMeshGenerator.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,17 @@ class NavMeshGenerator {
1414
// returns polygons used to construct the mesh
1515
NavMesh* generate(Bsp* map, int hull);
1616

17+
// group polys that are close together for fewer collision checks later
18+
static PolygonOctree* createPolyOctree(Bsp* map, const vector<Polygon3D*>& faces, int treeDepth);
19+
1720
private:
1821
int octreeDepth = 6;
1922

2023
// get faces of the hull that form the borders of the map
2124
vector<Polygon3D*> getHullFaces(Bsp* map, int hull);
2225

2326
// get smallest octree box that can contain the entire map
24-
void getOctreeBox(Bsp* map, vec3& min, vec3& max);
25-
26-
// group polys that are close together for fewer collision checks later
27-
PolygonOctree* createPolyOctree(Bsp* map, const vector<Polygon3D*>& faces, int treeDepth);
27+
static void getOctreeBox(Bsp* map, vec3& min, vec3& max);
2828

2929
// splits faces along their intersections with each other to clip polys that extend out
3030
// into the void, then tests each poly to see if it faces into the map or into the void.

src/nav/PolyOctree.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@ void PolygonOctree::getPolysInRegion(Polygon3D* poly, vector<bool>& regionPolys)
8585
getPolysInRegion(root, poly, 0, regionPolys);
8686
}
8787

88+
unordered_set<int> PolygonOctree::getPolysInRegion(Polygon3D* poly) {
89+
unordered_set<int> regionPolys;
90+
getPolysInRegion(root, poly, 0, regionPolys);
91+
return regionPolys;
92+
}
93+
8894
void PolygonOctree::getPolysInRegion(PolyOctant* node, Polygon3D* poly, int currentDepth, vector<bool>& regionPolys) {
8995
if (currentDepth >= maxDepth) {
9096
for (auto p : node->polygons) {
@@ -99,3 +105,18 @@ void PolygonOctree::getPolysInRegion(PolyOctant* node, Polygon3D* poly, int curr
99105
}
100106
}
101107
}
108+
109+
void PolygonOctree::getPolysInRegion(PolyOctant* node, Polygon3D* poly, int currentDepth, unordered_set<int>& regionPolys) {
110+
if (currentDepth >= maxDepth) {
111+
for (auto p : node->polygons) {
112+
if (p->idx != -1)
113+
regionPolys.insert(p->idx);
114+
}
115+
return;
116+
}
117+
for (int i = 0; i < 8; ++i) {
118+
if (isPolygonInOctant(poly, node->children[i])) {
119+
getPolysInRegion(node->children[i], poly, currentDepth + 1, regionPolys);
120+
}
121+
}
122+
}

src/nav/PolyOctree.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22
#include "Polygon3D.h"
33
#include <vector>
4+
#include <unordered_set>
45

56
struct PolyOctant {
67
vec3 min;
@@ -32,10 +33,14 @@ class PolygonOctree {
3233

3334
void getPolysInRegion(Polygon3D* poly, vector<bool>& regionPolys);
3435

36+
unordered_set<int> getPolysInRegion(Polygon3D* poly);
37+
3538
private:
3639
void buildOctree(PolyOctant* node, int currentDepth);
3740

3841
void getPolysInRegion(PolyOctant* node, Polygon3D* poly, int currentDepth, vector<bool>& regionPolys);
3942

43+
void getPolysInRegion(PolyOctant* node, Polygon3D* poly, int currentDepth, unordered_set<int>& regionPolys);
44+
4045
void insertPolygon(PolyOctant* node, Polygon3D* polygon, int currentDepth);
4146
};

0 commit comments

Comments
 (0)