add q3map2-style -blocksize option, using the same algorithm from it. it's optional and disabled by default.
pass through the "use mid split" boolean again remove node_t::side; appeared to be unused in our current code, and needs to be removed anyways to support the other plane splitters re-introduce ChooseMidPlaneFromList, but comment it out as it currently fails on a lot of BSPs.
This commit is contained in:
parent
360daea172
commit
e60babdb9c
|
|
@ -39,7 +39,6 @@ constexpr vec_t EDGE_LENGTH_EPSILON = 0.2;
|
||||||
|
|
||||||
bool WindingIsTiny(const winding_t &w, double size = EDGE_LENGTH_EPSILON);
|
bool WindingIsTiny(const winding_t &w, double size = EDGE_LENGTH_EPSILON);
|
||||||
std::unique_ptr<bspbrush_t> BrushFromBounds(const aabb3d &bounds);
|
std::unique_ptr<bspbrush_t> BrushFromBounds(const aabb3d &bounds);
|
||||||
std::unique_ptr<tree_t> BrushBSP(std::vector<std::unique_ptr<bspbrush_t>> brushlist);
|
|
||||||
|
|
||||||
// compatibility version
|
// compatibility version
|
||||||
std::unique_ptr<tree_t> BrushBSP(mapentity_t *entity);
|
std::unique_ptr<tree_t> BrushBSP(mapentity_t *entity, bool use_mid_split);
|
||||||
|
|
|
||||||
|
|
@ -192,6 +192,58 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// like qvec3f, but integer and allows up to three values (xyz, x y, or x y z)
|
||||||
|
// defaults to 1024 if assigned, otherwise zero.
|
||||||
|
class setting_blocksize : public setting_value<qvec3i>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
inline setting_blocksize(setting_container *dictionary, const nameset &names, qvec3i val,
|
||||||
|
const setting_group *group = nullptr, const char *description = "")
|
||||||
|
: setting_value(dictionary, names, val, group, description)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parse(const std::string &settingName, parser_base_t &parser, source source) override
|
||||||
|
{
|
||||||
|
qvec3d vec = { 1024, 1024, 1024 };
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
if (!parser.parse_token(PARSE_PEEK)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't allow negatives
|
||||||
|
if (parser.token[0] != '-') {
|
||||||
|
try {
|
||||||
|
vec[i] = std::stol(parser.token);
|
||||||
|
parser.parse_token();
|
||||||
|
continue;
|
||||||
|
} catch (std::exception &) {
|
||||||
|
// intentional fall-through
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we didn't parse a valid number, fail
|
||||||
|
if (i == 0) {
|
||||||
|
return false;
|
||||||
|
} else if (i == 1) {
|
||||||
|
// we parsed one valid number; use it all the way through
|
||||||
|
vec[1] = vec[2] = vec[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// for [x, y] z will be left default
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue(vec, source);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string stringValue() const override { return qv::to_string(_value); }
|
||||||
|
|
||||||
|
std::string format() const override { return "[x [y [z]]]"; }
|
||||||
|
};
|
||||||
|
|
||||||
class qbsp_settings : public common_settings
|
class qbsp_settings : public common_settings
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -296,6 +348,10 @@ public:
|
||||||
[](setting_int32 &setting) { return setting.value() == 0 || setting.value() >= 3; }, this, "maxedges", 64,
|
[](setting_int32 &setting) { return setting.value() == 0 || setting.value() >= 3; }, this, "maxedges", 64,
|
||||||
&map_development_group,
|
&map_development_group,
|
||||||
"the max number of edges/vertices on a single face before it is split into another face"};
|
"the max number of edges/vertices on a single face before it is split into another face"};
|
||||||
|
// FIXME: this block size default is from Q3, and is basically derived from having 128x128x128 chunks of the world
|
||||||
|
// since the max world size in Q3 is {-65536, -65536, -65536, 65536, 65536, 65536}. should we dynamically change this?
|
||||||
|
// should we automatically turn this on if the world gets too big but leave it off for smaller worlds?
|
||||||
|
setting_blocksize blocksize{this, "blocksize", { 0, 0, 0 }, &common_format_group, "from q3map2; split the world by x/y/z sized chunks, speeding up split decisions"};
|
||||||
|
|
||||||
void setParameters(int argc, const char **argv) override
|
void setParameters(int argc, const char **argv) override
|
||||||
{
|
{
|
||||||
|
|
@ -568,7 +624,6 @@ struct node_t
|
||||||
twosided<std::unique_ptr<node_t>>
|
twosided<std::unique_ptr<node_t>>
|
||||||
children; // children[0] = front side, children[1] = back side of plane. only valid for decision nodes
|
children; // children[0] = front side, children[1] = back side of plane. only valid for decision nodes
|
||||||
std::list<std::unique_ptr<face_t>> facelist; // decision nodes only, list for both sides
|
std::list<std::unique_ptr<face_t>> facelist; // decision nodes only, list for both sides
|
||||||
side_t *side; // decision node only, the side that created the node
|
|
||||||
|
|
||||||
// information for leafs
|
// information for leafs
|
||||||
contentflags_t contents; // leaf nodes (0 for decision nodes)
|
contentflags_t contents; // leaf nodes (0 for decision nodes)
|
||||||
|
|
|
||||||
397
qbsp/brushbsp.cc
397
qbsp/brushbsp.cc
|
|
@ -54,6 +54,8 @@ struct bspstats_t
|
||||||
std::atomic<int> c_nodes;
|
std::atomic<int> c_nodes;
|
||||||
// number of nodes created by splitting on a side_t which had !visible
|
// number of nodes created by splitting on a side_t which had !visible
|
||||||
std::atomic<int> c_nonvis;
|
std::atomic<int> c_nonvis;
|
||||||
|
// total number of nodes created by block splitting
|
||||||
|
std::atomic<int> c_blocksplit;
|
||||||
// total number of leafs
|
// total number of leafs
|
||||||
std::atomic<int> c_leafs;
|
std::atomic<int> c_leafs;
|
||||||
};
|
};
|
||||||
|
|
@ -270,8 +272,12 @@ TestBrushToPlanenum
|
||||||
static int TestBrushToPlanenum(
|
static int TestBrushToPlanenum(
|
||||||
const bspbrush_t &brush, const qbsp_plane_t &plane, int *numsplits, bool *hintsplit, int *epsilonbrush)
|
const bspbrush_t &brush, const qbsp_plane_t &plane, int *numsplits, bool *hintsplit, int *epsilonbrush)
|
||||||
{
|
{
|
||||||
*numsplits = 0;
|
if (numsplits) {
|
||||||
*hintsplit = false;
|
*numsplits = 0;
|
||||||
|
}
|
||||||
|
if (hintsplit) {
|
||||||
|
*hintsplit = false;
|
||||||
|
}
|
||||||
|
|
||||||
// if the brush actually uses the planenum,
|
// if the brush actually uses the planenum,
|
||||||
// we can tell the side for sure
|
// we can tell the side for sure
|
||||||
|
|
@ -291,44 +297,47 @@ static int TestBrushToPlanenum(
|
||||||
if (s != PSIDE_BOTH)
|
if (s != PSIDE_BOTH)
|
||||||
return s;
|
return s;
|
||||||
|
|
||||||
// if both sides, count the visible faces split
|
if (numsplits && hintsplit && epsilonbrush) {
|
||||||
vec_t d_front = 0;
|
// if both sides, count the visible faces split
|
||||||
vec_t d_back = 0;
|
vec_t d_front = 0;
|
||||||
|
vec_t d_back = 0;
|
||||||
|
|
||||||
for (const side_t &side : brush.sides) {
|
for (const side_t &side : brush.sides) {
|
||||||
if (side.onnode)
|
if (side.onnode)
|
||||||
continue; // on node, don't worry about splits
|
continue; // on node, don't worry about splits
|
||||||
if (!side.visible)
|
if (!side.visible)
|
||||||
continue; // we don't care about non-visible
|
continue; // we don't care about non-visible
|
||||||
auto &w = side.w;
|
auto &w = side.w;
|
||||||
if (!w)
|
if (!w)
|
||||||
continue;
|
continue;
|
||||||
int front = 0;
|
int front = 0;
|
||||||
int back = 0;
|
int back = 0;
|
||||||
for (auto &point : w) {
|
for (auto &point : w) {
|
||||||
const double d = qv::dot(point, plane.get_normal()) - plane.get_dist();
|
const double d = qv::dot(point, plane.get_normal()) - plane.get_dist();
|
||||||
if (d > d_front)
|
if (d > d_front)
|
||||||
d_front = d;
|
d_front = d;
|
||||||
if (d < d_back)
|
if (d < d_back)
|
||||||
d_back = d;
|
d_back = d;
|
||||||
|
|
||||||
if (d > 0.1) // PLANESIDE_EPSILON)
|
if (d > 0.1) // PLANESIDE_EPSILON)
|
||||||
front = 1;
|
front = 1;
|
||||||
if (d < -0.1) // PLANESIDE_EPSILON)
|
if (d < -0.1) // PLANESIDE_EPSILON)
|
||||||
back = 1;
|
back = 1;
|
||||||
}
|
}
|
||||||
if (front && back) {
|
if (front && back) {
|
||||||
if (!(side.get_texinfo().flags.is_hintskip)) {
|
if (!(side.get_texinfo().flags.is_hintskip)) {
|
||||||
(*numsplits)++;
|
(*numsplits)++;
|
||||||
if (side.get_texinfo().flags.is_hint) {
|
if (side.get_texinfo().flags.is_hint) {
|
||||||
*hintsplit = true;
|
*hintsplit = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if ((d_front > 0.0 && d_front < 1.0) || (d_back < 0.0 && d_back > -1.0))
|
if ((d_front > 0.0 && d_front < 1.0) || (d_back < 0.0 && d_back > -1.0)) {
|
||||||
(*epsilonbrush)++;
|
(*epsilonbrush)++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
@ -622,17 +631,270 @@ static bool CheckPlaneAgainstVolume(const qbsp_plane_t &plane, node_t *node)
|
||||||
return good;
|
return good;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate the split plane metric for axial planes
|
||||||
|
*/
|
||||||
|
inline vec_t SplitPlaneMetric_Axial(const qbsp_plane_t &p, const aabb3d &bounds)
|
||||||
|
{
|
||||||
|
vec_t value = 0;
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
if (static_cast<plane_type_t>(i) == p.get_type()) {
|
||||||
|
const vec_t dist = p.get_dist() * p.get_normal()[i];
|
||||||
|
value += (bounds.maxs()[i] - dist) * (bounds.maxs()[i] - dist);
|
||||||
|
value += (dist - bounds.mins()[i]) * (dist - bounds.mins()[i]);
|
||||||
|
} else {
|
||||||
|
value += 2 * (bounds.maxs()[i] - bounds.mins()[i]) * (bounds.maxs()[i] - bounds.mins()[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Split a bounding box by a plane; The front and back bounds returned
|
||||||
|
* are such that they completely contain the portion of the input box
|
||||||
|
* on that side of the plane. Therefore, if the split plane is
|
||||||
|
* non-axial, then the returned bounds will overlap.
|
||||||
|
*/
|
||||||
|
inline void DivideBounds(const aabb3d &in_bounds, const qbsp_plane_t &split, aabb3d &front_bounds, aabb3d &back_bounds)
|
||||||
|
{
|
||||||
|
int a, b, c, i, j;
|
||||||
|
vec_t dist1, dist2, mid, split_mins, split_maxs;
|
||||||
|
qvec3d corner;
|
||||||
|
|
||||||
|
front_bounds = back_bounds = in_bounds;
|
||||||
|
|
||||||
|
if (split.get_type() < plane_type_t::PLANE_ANYX) {
|
||||||
|
front_bounds[0][static_cast<size_t>(split.get_type())] = back_bounds[1][static_cast<size_t>(split.get_type())] = split.get_dist();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make proper sloping cuts... */
|
||||||
|
for (a = 0; a < 3; ++a) {
|
||||||
|
/* Check for parallel case... no intersection */
|
||||||
|
if (fabs(split.get_normal()[a]) < NORMAL_EPSILON)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
b = (a + 1) % 3;
|
||||||
|
c = (a + 2) % 3;
|
||||||
|
|
||||||
|
split_mins = in_bounds.maxs()[a];
|
||||||
|
split_maxs = in_bounds.mins()[a];
|
||||||
|
for (i = 0; i < 2; ++i) {
|
||||||
|
corner[b] = in_bounds[i][b];
|
||||||
|
for (j = 0; j < 2; ++j) {
|
||||||
|
corner[c] = in_bounds[j][c];
|
||||||
|
|
||||||
|
corner[a] = in_bounds[0][a];
|
||||||
|
dist1 = split.distance_to(corner);
|
||||||
|
|
||||||
|
corner[a] = in_bounds[1][a];
|
||||||
|
dist2 = split.distance_to(corner);
|
||||||
|
|
||||||
|
mid = in_bounds[1][a] - in_bounds[0][a];
|
||||||
|
mid *= (dist1 / (dist1 - dist2));
|
||||||
|
mid += in_bounds[0][a];
|
||||||
|
|
||||||
|
split_mins = max(min(mid, split_mins), in_bounds.mins()[a]);
|
||||||
|
split_maxs = min(max(mid, split_maxs), in_bounds.maxs()[a]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (split.get_normal()[a] > 0) {
|
||||||
|
front_bounds[0][a] = split_mins;
|
||||||
|
back_bounds[1][a] = split_maxs;
|
||||||
|
} else {
|
||||||
|
back_bounds[0][a] = split_mins;
|
||||||
|
front_bounds[1][a] = split_maxs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate the split plane metric for non-axial planes
|
||||||
|
*/
|
||||||
|
inline vec_t SplitPlaneMetric_NonAxial(const qbsp_plane_t &p, const aabb3d &bounds)
|
||||||
|
{
|
||||||
|
aabb3d f, b;
|
||||||
|
vec_t value = 0.0;
|
||||||
|
|
||||||
|
DivideBounds(bounds, p, f, b);
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
value += (f.maxs()[i] - f.mins()[i]) * (f.maxs()[i] - f.mins()[i]);
|
||||||
|
value += (b.maxs()[i] - b.mins()[i]) * (b.maxs()[i] - b.mins()[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline vec_t SplitPlaneMetric(const qbsp_plane_t &p, const aabb3d &bounds)
|
||||||
|
{
|
||||||
|
if (p.get_type() < plane_type_t::PLANE_ANYX) {
|
||||||
|
return SplitPlaneMetric_Axial(p, bounds);
|
||||||
|
} else {
|
||||||
|
return SplitPlaneMetric_NonAxial(p, bounds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
==================
|
||||||
|
ChooseMidPlaneFromList
|
||||||
|
|
||||||
|
The clipping hull BSP doesn't worry about avoiding splits
|
||||||
|
==================
|
||||||
|
*/
|
||||||
|
static std::optional<qbsp_plane_t> ChooseMidPlaneFromList(const std::vector<std::unique_ptr<bspbrush_t>> &brushes, const aabb3d &bounds, bool forced)
|
||||||
|
{
|
||||||
|
/* pick the plane that splits the least */
|
||||||
|
vec_t bestaxialmetric = VECT_MAX;
|
||||||
|
std::optional<qbsp_plane_t> bestaxialplane;
|
||||||
|
vec_t bestanymetric = VECT_MAX;
|
||||||
|
std::optional<qbsp_plane_t> bestanyplane;
|
||||||
|
|
||||||
|
for (int pass = 0; pass < 2; pass++) {
|
||||||
|
for (auto &brush : brushes) {
|
||||||
|
if ((pass & 1) && !brush->original->contents.is_any_detail(qbsp_options.target_game)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!(pass & 1) && brush->original->contents.is_any_detail(qbsp_options.target_game)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &side : brush->sides) {
|
||||||
|
if (side.bevel) {
|
||||||
|
continue; // never use a bevel as a spliter
|
||||||
|
}
|
||||||
|
if (!side.w) {
|
||||||
|
continue; // nothing visible, so it can't split
|
||||||
|
}
|
||||||
|
if (side.onnode) {
|
||||||
|
continue; // allready a node splitter
|
||||||
|
}
|
||||||
|
if (side.get_texinfo().flags.is_hintskip) {
|
||||||
|
continue; // skip surfaces are never chosen
|
||||||
|
}
|
||||||
|
|
||||||
|
const qbsp_plane_t &plane = side.plane;
|
||||||
|
/* calculate the split metric, smaller values are better */
|
||||||
|
const vec_t metric = SplitPlaneMetric(plane, bounds);
|
||||||
|
|
||||||
|
if (metric < bestanymetric) {
|
||||||
|
bestanymetric = metric;
|
||||||
|
bestanyplane = plane;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check for axis aligned surfaces */
|
||||||
|
if (plane.get_type() < plane_type_t::PLANE_ANYX) {
|
||||||
|
if (metric < bestaxialmetric) {
|
||||||
|
bestaxialmetric = metric;
|
||||||
|
bestaxialplane = plane;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bestanyplane || bestaxialplane) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prefer the axial split
|
||||||
|
auto bestsurface = !bestaxialplane ? bestanyplane : bestaxialplane;
|
||||||
|
|
||||||
|
if (!bestsurface) {
|
||||||
|
FError("No valid planes in surface list");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ericw -- (!forced) is true on the final SolidBSP phase for the world.
|
||||||
|
// !bestsurface->has_struct means all surfaces in this node are detail, so
|
||||||
|
// mark the surface as a detail separator.
|
||||||
|
// fixme-brushbsp: what to do here?
|
||||||
|
#if 0
|
||||||
|
if (!forced && !bestsurface->has_struct) {
|
||||||
|
bestsurface->detail_separator = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return bestsurface;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
================
|
================
|
||||||
SelectSplitSide
|
SelectSplitPlane
|
||||||
|
|
||||||
Using a hueristic, choses one of the sides out of the brushlist
|
Using heuristics, chooses a plane to partition the brushes with.
|
||||||
to partition the brushes with.
|
Returns nullopt if there are no valid planes to split with.
|
||||||
Returns NULL if there are no valid planes to split with..
|
|
||||||
================
|
================
|
||||||
*/
|
*/
|
||||||
side_t *SelectSplitSide(const std::vector<std::unique_ptr<bspbrush_t>> &brushes, node_t *node)
|
static std::optional<qbsp_plane_t> SelectSplitPlane(const std::vector<std::unique_ptr<bspbrush_t>> &brushes, node_t *node, bool use_mid_split, bspstats_t &stats)
|
||||||
{
|
{
|
||||||
|
// no brushes left to split, so we can't use any plane.
|
||||||
|
if (!brushes.size()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it is crossing a block boundary, force a split;
|
||||||
|
// this is optional q3map2 mode
|
||||||
|
for (size_t i = 0; i < 3; i++) {
|
||||||
|
if (qbsp_options.blocksize.value()[i] <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec_t dist = qbsp_options.blocksize.value()[i] * (floor(node->bounds.mins()[i] / qbsp_options.blocksize.value()[i]) + 1);
|
||||||
|
|
||||||
|
if (node->bounds.maxs()[i] > dist) {
|
||||||
|
qplane3d plane{};
|
||||||
|
plane.normal[i] = 1.0;
|
||||||
|
plane.dist = dist;
|
||||||
|
qbsp_plane_t bsp_plane = plane;
|
||||||
|
stats.c_blocksplit++;
|
||||||
|
|
||||||
|
for (auto &b : brushes) {
|
||||||
|
b->side = TestBrushToPlanenum(*b, bsp_plane, nullptr, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bsp_plane;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fixme-brushbsp: re-introduce
|
||||||
|
#if 0
|
||||||
|
// how much of the map are we partitioning?
|
||||||
|
double fractionOfMap = brushes.size() / (double) map.brushes.size();
|
||||||
|
bool largenode = false;
|
||||||
|
|
||||||
|
if (!use_mid_split) {
|
||||||
|
// decide if we should switch to the midsplit method
|
||||||
|
if (qbsp_options.midsplitsurffraction.value() != 0.0) {
|
||||||
|
// new way (opt-in)
|
||||||
|
largenode = (fractionOfMap > qbsp_options.midsplitsurffraction.value());
|
||||||
|
} else {
|
||||||
|
// old way (ericw-tools 0.15.2+)
|
||||||
|
if (qbsp_options.maxnodesize.value() >= 64) {
|
||||||
|
const vec_t maxnodesize = qbsp_options.maxnodesize.value() - qbsp_options.epsilon.value();
|
||||||
|
|
||||||
|
largenode = (node->bounds.maxs()[0] - node->bounds.mins()[0]) > maxnodesize ||
|
||||||
|
(node->bounds.maxs()[1] - node->bounds.mins()[1]) > maxnodesize ||
|
||||||
|
(node->bounds.maxs()[2] - node->bounds.mins()[2]) > maxnodesize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// do fast way for clipping hull
|
||||||
|
if (use_mid_split || largenode) {
|
||||||
|
if (auto mid_plane = ChooseMidPlaneFromList(brushes, node->bounds, use_mid_split)) {
|
||||||
|
|
||||||
|
for (auto &b : brushes) {
|
||||||
|
b->side = TestBrushToPlanenum(*b, mid_plane.value(), nullptr, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mid_plane;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
side_t *bestside = nullptr;
|
side_t *bestside = nullptr;
|
||||||
int bestvalue = -99999;
|
int bestvalue = -99999;
|
||||||
int bestsplits = 0;
|
int bestsplits = 0;
|
||||||
|
|
@ -749,7 +1011,15 @@ side_t *SelectSplitSide(const std::vector<std::unique_ptr<bspbrush_t>> &brushes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return bestside;
|
if (!bestside) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bestside->visible) {
|
||||||
|
stats.c_nonvis++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestside->plane;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -758,7 +1028,7 @@ SplitBrushList
|
||||||
================
|
================
|
||||||
*/
|
*/
|
||||||
static std::array<std::vector<std::unique_ptr<bspbrush_t>>, 2> SplitBrushList(
|
static std::array<std::vector<std::unique_ptr<bspbrush_t>>, 2> SplitBrushList(
|
||||||
std::vector<std::unique_ptr<bspbrush_t>> brushes, const node_t *node)
|
std::vector<std::unique_ptr<bspbrush_t>> brushes, const qbsp_plane_t &plane)
|
||||||
{
|
{
|
||||||
std::array<std::vector<std::unique_ptr<bspbrush_t>>, 2> result;
|
std::array<std::vector<std::unique_ptr<bspbrush_t>>, 2> result;
|
||||||
|
|
||||||
|
|
@ -767,7 +1037,7 @@ static std::array<std::vector<std::unique_ptr<bspbrush_t>>, 2> SplitBrushList(
|
||||||
|
|
||||||
if (sides == PSIDE_BOTH) {
|
if (sides == PSIDE_BOTH) {
|
||||||
// split into two brushes
|
// split into two brushes
|
||||||
auto [front, back] = SplitBrush(brush->copy_unique(), node->plane);
|
auto [front, back] = SplitBrush(brush->copy_unique(), plane);
|
||||||
|
|
||||||
if (front) {
|
if (front) {
|
||||||
result[0].push_back(std::move(front));
|
result[0].push_back(std::move(front));
|
||||||
|
|
@ -784,7 +1054,7 @@ static std::array<std::vector<std::unique_ptr<bspbrush_t>>, 2> SplitBrushList(
|
||||||
// as a splitter again
|
// as a splitter again
|
||||||
if (sides & PSIDE_FACING) {
|
if (sides & PSIDE_FACING) {
|
||||||
for (auto &side : brush->sides) {
|
for (auto &side : brush->sides) {
|
||||||
if (qv::epsilonEqual(side.plane, node->plane)) {
|
if (qv::epsilonEqual(side.plane, plane)) {
|
||||||
side.onnode = true;
|
side.onnode = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -810,13 +1080,13 @@ BuildTree_r
|
||||||
Called in parallel.
|
Called in parallel.
|
||||||
==================
|
==================
|
||||||
*/
|
*/
|
||||||
static void BuildTree_r(node_t *node, std::vector<std::unique_ptr<bspbrush_t>> brushes, bspstats_t &stats)
|
static void BuildTree_r(node_t *node, std::vector<std::unique_ptr<bspbrush_t>> brushes, bool use_mid_split, bspstats_t &stats)
|
||||||
{
|
{
|
||||||
// find the best plane to use as a splitter
|
// find the best plane to use as a splitter
|
||||||
auto *bestside = const_cast<side_t *>(SelectSplitSide(brushes, node));
|
auto bestplane = SelectSplitPlane(brushes, node, use_mid_split, stats);
|
||||||
if (!bestside) {
|
|
||||||
|
if (!bestplane) {
|
||||||
// this is a leaf node
|
// this is a leaf node
|
||||||
node->side = nullptr;
|
|
||||||
node->is_leaf = true;
|
node->is_leaf = true;
|
||||||
|
|
||||||
stats.c_leafs++;
|
stats.c_leafs++;
|
||||||
|
|
@ -827,29 +1097,34 @@ static void BuildTree_r(node_t *node, std::vector<std::unique_ptr<bspbrush_t>> b
|
||||||
|
|
||||||
// this is a splitplane node
|
// this is a splitplane node
|
||||||
stats.c_nodes++;
|
stats.c_nodes++;
|
||||||
if (!bestside->visible) {
|
|
||||||
stats.c_nonvis++;
|
|
||||||
}
|
|
||||||
|
|
||||||
node->side = bestside;
|
node->plane.set_plane(bestplane.value(), true); // always use front facing
|
||||||
node->plane.set_plane(bestside->plane, true); // always use front facing
|
|
||||||
|
|
||||||
auto children = SplitBrushList(std::move(brushes), node);
|
auto children = SplitBrushList(std::move(brushes), node->plane);
|
||||||
|
|
||||||
// allocate children before recursing
|
// allocate children before recursing
|
||||||
for (int i = 0; i < 2; i++) {
|
for (int i = 0; i < 2; i++) {
|
||||||
auto &newnode = node->children[i] = std::make_unique<node_t>();
|
auto &newnode = node->children[i] = std::make_unique<node_t>();
|
||||||
newnode->parent = node;
|
newnode->parent = node;
|
||||||
|
newnode->bounds = node->bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
if (bestplane->get_normal()[i] == 1.0) {
|
||||||
|
node->children[0]->bounds[0][i] = bestplane->get_dist();
|
||||||
|
node->children[1]->bounds[1][i] = bestplane->get_dist();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto children_volumes = SplitBrush(node->volume->copy_unique(), node->plane);
|
auto children_volumes = SplitBrush(node->volume->copy_unique(), node->plane);
|
||||||
node->children[0]->volume = std::move(children_volumes[0]);
|
node->children[0]->volume = std::move(children_volumes[0]);
|
||||||
node->children[1]->volume = std::move(children_volumes[1]);
|
node->children[1]->volume = std::move(children_volumes[1]);
|
||||||
|
|
||||||
// recursively process children
|
// recursively process children
|
||||||
tbb::task_group g;
|
tbb::task_group g;
|
||||||
g.run([&]() { BuildTree_r(node->children[0].get(), std::move(children[0]), stats); });
|
g.run([&]() { BuildTree_r(node->children[0].get(), std::move(children[0]), use_mid_split, stats); });
|
||||||
g.run([&]() { BuildTree_r(node->children[1].get(), std::move(children[1]), stats); });
|
g.run([&]() { BuildTree_r(node->children[1].get(), std::move(children[1]), use_mid_split, stats); });
|
||||||
g.wait();
|
g.wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -858,7 +1133,7 @@ static void BuildTree_r(node_t *node, std::vector<std::unique_ptr<bspbrush_t>> b
|
||||||
BrushBSP
|
BrushBSP
|
||||||
==================
|
==================
|
||||||
*/
|
*/
|
||||||
static std::unique_ptr<tree_t> BrushBSP(mapentity_t *entity, std::vector<std::unique_ptr<bspbrush_t>> brushlist)
|
static std::unique_ptr<tree_t> BrushBSP(mapentity_t *entity, std::vector<std::unique_ptr<bspbrush_t>> brushlist, bool use_mid_split)
|
||||||
{
|
{
|
||||||
auto tree = std::make_unique<tree_t>();
|
auto tree = std::make_unique<tree_t>();
|
||||||
|
|
||||||
|
|
@ -926,24 +1201,24 @@ static std::unique_ptr<tree_t> BrushBSP(mapentity_t *entity, std::vector<std::un
|
||||||
auto node = std::make_unique<node_t>();
|
auto node = std::make_unique<node_t>();
|
||||||
|
|
||||||
node->volume = BrushFromBounds(tree->bounds.grow(SIDESPACE));
|
node->volume = BrushFromBounds(tree->bounds.grow(SIDESPACE));
|
||||||
|
node->bounds = tree->bounds.grow(SIDESPACE);
|
||||||
|
|
||||||
tree->headnode = std::move(node);
|
tree->headnode = std::move(node);
|
||||||
|
|
||||||
bspstats_t stats{};
|
bspstats_t stats{};
|
||||||
stats.leafstats = qbsp_options.target_game->create_content_stats();
|
stats.leafstats = qbsp_options.target_game->create_content_stats();
|
||||||
BuildTree_r(tree->headnode.get(), std::move(brushlist), stats);
|
BuildTree_r(tree->headnode.get(), std::move(brushlist), use_mid_split, stats);
|
||||||
|
|
||||||
logging::print(logging::flag::STAT, " {:8} visible nodes\n", stats.c_nodes - stats.c_nonvis);
|
logging::print(logging::flag::STAT, " {:8} visible nodes\n", stats.c_nodes - stats.c_nonvis);
|
||||||
logging::print(logging::flag::STAT, " {:8} nonvis nodes\n", stats.c_nonvis);
|
logging::print(logging::flag::STAT, " {:8} nonvis nodes\n", stats.c_nonvis);
|
||||||
|
logging::print(logging::flag::STAT, " {:8} block split nodes\n", stats.c_blocksplit);
|
||||||
logging::print(logging::flag::STAT, " {:8} leafs\n", stats.c_leafs);
|
logging::print(logging::flag::STAT, " {:8} leafs\n", stats.c_leafs);
|
||||||
qbsp_options.target_game->print_content_stats(*stats.leafstats, "leafs");
|
qbsp_options.target_game->print_content_stats(*stats.leafstats, "leafs");
|
||||||
|
|
||||||
return tree;
|
return tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<tree_t> BrushBSP(mapentity_t *entity)
|
std::unique_ptr<tree_t> BrushBSP(mapentity_t *entity, bool use_mid_split)
|
||||||
{
|
{
|
||||||
auto tree = BrushBSP(entity, MakeBspBrushList(entity));
|
return BrushBSP(entity, MakeBspBrushList(entity), use_mid_split);
|
||||||
|
|
||||||
return tree;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
qbsp/qbsp.cc
14
qbsp/qbsp.cc
|
|
@ -589,13 +589,13 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum)
|
||||||
|
|
||||||
std::unique_ptr<tree_t> tree = nullptr;
|
std::unique_ptr<tree_t> tree = nullptr;
|
||||||
if (hullnum > 0) {
|
if (hullnum > 0) {
|
||||||
tree = BrushBSP(entity);
|
tree = BrushBSP(entity, true);
|
||||||
if (entity == map.world_entity() && !qbsp_options.nofill.value()) {
|
if (entity == map.world_entity() && !qbsp_options.nofill.value()) {
|
||||||
// assume non-world bmodels are simple
|
// assume non-world bmodels are simple
|
||||||
MakeTreePortals(tree.get());
|
MakeTreePortals(tree.get());
|
||||||
if (FillOutside(entity, tree.get(), hullnum)) {
|
if (FillOutside(entity, tree.get(), hullnum)) {
|
||||||
// make a really good tree
|
// make a really good tree
|
||||||
tree = BrushBSP(entity);
|
tree = BrushBSP(entity, false);
|
||||||
|
|
||||||
// fill again so PruneNodes works
|
// fill again so PruneNodes works
|
||||||
MakeTreePortals(tree.get());
|
MakeTreePortals(tree.get());
|
||||||
|
|
@ -607,7 +607,11 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum)
|
||||||
|
|
||||||
// fixme-brushbsp: return here?
|
// fixme-brushbsp: return here?
|
||||||
} else {
|
} else {
|
||||||
tree = BrushBSP(entity);
|
if (qbsp_options.forcegoodtree.value()) {
|
||||||
|
tree = BrushBSP(entity, false);
|
||||||
|
} else {
|
||||||
|
tree = BrushBSP(entity, entity == map.world_entity());
|
||||||
|
}
|
||||||
|
|
||||||
// build all the portals in the bsp tree
|
// build all the portals in the bsp tree
|
||||||
// some portals are solid polygons, and some are paths to other leafs
|
// some portals are solid polygons, and some are paths to other leafs
|
||||||
|
|
@ -620,7 +624,7 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum)
|
||||||
// (effectively expanding those brush sides outwards).
|
// (effectively expanding those brush sides outwards).
|
||||||
if (!qbsp_options.nofill.value() && FillOutside(entity, tree.get(), hullnum)) {
|
if (!qbsp_options.nofill.value() && FillOutside(entity, tree.get(), hullnum)) {
|
||||||
// make a really good tree
|
// make a really good tree
|
||||||
tree = BrushBSP(entity);
|
tree = BrushBSP(entity, false);
|
||||||
|
|
||||||
// make the real portals for vis tracing
|
// make the real portals for vis tracing
|
||||||
MakeTreePortals(tree.get());
|
MakeTreePortals(tree.get());
|
||||||
|
|
@ -638,7 +642,7 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum)
|
||||||
FillBrushEntity(entity, tree.get(), hullnum);
|
FillBrushEntity(entity, tree.get(), hullnum);
|
||||||
|
|
||||||
// rebuild BSP now that we've marked invisible brush sides
|
// rebuild BSP now that we've marked invisible brush sides
|
||||||
tree = BrushBSP(entity);
|
tree = BrushBSP(entity, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
MakeTreePortals(tree.get());
|
MakeTreePortals(tree.get());
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue