qbsp: remove old face creation code
This commit is contained in:
parent
57473f7b4a
commit
7e1ede2ac3
393
qbsp/csg4.cc
393
qbsp/csg4.cc
|
|
@ -127,106 +127,6 @@ std::tuple<face_t *, face_t *> SplitFace(face_t *in, const qplane3d &split)
|
|||
return {new_front, new_back};
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
RemoveOutsideFaces
|
||||
|
||||
Quick test before running ClipInside; move any faces that are completely
|
||||
outside the brush to the outside list, without splitting them. This saves us
|
||||
time in mergefaces later on (and sometimes a lot of memory)
|
||||
|
||||
Input is a list of faces in the param `inside`.
|
||||
On return, the ones touching `brush` remain in `inside`, the rest are added to `outside`.
|
||||
=================
|
||||
*/
|
||||
static void RemoveOutsideFaces(const brush_t &brush, std::list<face_t *> *inside, std::list<face_t *> *outside)
|
||||
{
|
||||
std::list<face_t *> oldinside;
|
||||
|
||||
// clear `inside`, transfer it to `oldinside`
|
||||
std::swap(*inside, oldinside);
|
||||
|
||||
for (face_t *face : oldinside) {
|
||||
std::optional<winding_t> w = face->w;
|
||||
for (auto &clipface : brush.faces) {
|
||||
w = w->clip(Face_Plane(&clipface), options.epsilon.value(), false)[SIDE_BACK];
|
||||
if (!w)
|
||||
break;
|
||||
}
|
||||
if (!w) {
|
||||
/* The face is completely outside this brush */
|
||||
outside->push_front(face);
|
||||
} else {
|
||||
inside->push_front(face);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
ClipInside
|
||||
|
||||
Clips all of the faces in the inside list, possibly moving them to the
|
||||
outside list or spliting it into a piece in each list.
|
||||
|
||||
Faces exactly on the plane will stay inside unless overdrawn by later brush
|
||||
=================
|
||||
*/
|
||||
static void ClipInside(
|
||||
const face_t *clipface, bool precedence, std::list<face_t *> *inside, std::list<face_t *> *outside)
|
||||
{
|
||||
std::list<face_t *> oldinside;
|
||||
|
||||
// effectively make a copy of `inside`, and clear it
|
||||
std::swap(*inside, oldinside);
|
||||
|
||||
const qbsp_plane_t &splitplane = map.planes[clipface->planenum];
|
||||
|
||||
for (face_t *face : oldinside) {
|
||||
/* HACK: Check for on-plane but not the same planenum
|
||||
( https://github.com/ericwa/ericw-tools/issues/174 )
|
||||
*/
|
||||
bool spurious_onplane = false;
|
||||
{
|
||||
std::array<size_t, SIDE_TOTAL> counts = face->w.calc_sides(splitplane, nullptr, nullptr, options.epsilon.value());
|
||||
|
||||
if (counts[SIDE_ON] && !counts[SIDE_FRONT] && !counts[SIDE_BACK]) {
|
||||
spurious_onplane = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::array<face_t *, 2> frags;
|
||||
|
||||
/* Handle exactly on-plane faces */
|
||||
if (face->planenum == clipface->planenum || spurious_onplane) {
|
||||
const qplane3d faceplane = Face_Plane(face);
|
||||
const qplane3d clipfaceplane = Face_Plane(clipface);
|
||||
const vec_t dp = qv::dot(faceplane.normal, clipfaceplane.normal);
|
||||
const bool opposite = (dp < 0);
|
||||
|
||||
if (opposite || precedence) {
|
||||
/* always clip off opposite facing */
|
||||
frags[clipface->planeside] = {};
|
||||
frags[!clipface->planeside] = {face};
|
||||
} else {
|
||||
/* leave it on the outside */
|
||||
frags[clipface->planeside] = {face};
|
||||
frags[!clipface->planeside] = {};
|
||||
}
|
||||
} else {
|
||||
/* proper split */
|
||||
std::tie(frags[0], frags[1]) = SplitFace(face, splitplane);
|
||||
}
|
||||
|
||||
if (frags[clipface->planeside]) {
|
||||
outside->push_front(frags[clipface->planeside]);
|
||||
}
|
||||
if (frags[!clipface->planeside]) {
|
||||
inside->push_front(frags[!clipface->planeside]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
face_t *MirrorFace(const face_t *face)
|
||||
{
|
||||
face_t *newface = NewFaceFromFace(face);
|
||||
|
|
@ -237,296 +137,3 @@ face_t *MirrorFace(const face_t *face)
|
|||
|
||||
return newface;
|
||||
}
|
||||
|
||||
static void FreeFaces(std::list<face_t *> &facelist)
|
||||
{
|
||||
for (face_t *face : facelist) {
|
||||
delete face;
|
||||
}
|
||||
facelist.clear();
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
|
||||
static std::vector<std::unique_ptr<brush_t>> SingleBrush(std::unique_ptr<brush_t> a)
|
||||
{
|
||||
std::vector<std::unique_ptr<brush_t>> res;
|
||||
res.push_back(std::move(a));
|
||||
return res;
|
||||
}
|
||||
|
||||
static bool ShouldClipbrushEatBrush(const brush_t &brush, const brush_t &clipbrush)
|
||||
{
|
||||
if (clipbrush.contents.is_any_solid(options.target_game)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (clipbrush.contents.types_equal(brush.contents, options.target_game)) {
|
||||
return clipbrush.contents.will_clip_same_type(options.target_game);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::list<face_t *> CSGFace_ClipAgainstSingleBrush(std::list<face_t *> input, const mapentity_t *srcentity, const brush_t *srcbrush, const brush_t *clipbrush)
|
||||
{
|
||||
if (srcbrush == clipbrush) {
|
||||
//logging::print(" ignoring self-clip\n");
|
||||
return input;
|
||||
}
|
||||
|
||||
const int srcindex = srcbrush->file_order;
|
||||
const int clipindex = clipbrush->file_order;
|
||||
|
||||
if (!ShouldClipbrushEatBrush(*srcbrush, *clipbrush)) {
|
||||
return {input};
|
||||
}
|
||||
|
||||
std::list<face_t *> inside {input};
|
||||
std::list<face_t *> outside;
|
||||
RemoveOutsideFaces(*clipbrush, &inside, &outside);
|
||||
|
||||
// at this point, inside = the faces of `input` which are touching `clipbrush`
|
||||
// outside = the other faces of `input`
|
||||
|
||||
const bool overwrite = (srcindex < clipindex);
|
||||
|
||||
for (auto &clipface : clipbrush->faces)
|
||||
ClipInside(&clipface, overwrite, &inside, &outside);
|
||||
|
||||
// inside = parts of `brush` that are inside `clipbrush`
|
||||
// outside = parts of `brush` that are outside `clipbrush`
|
||||
|
||||
return outside;
|
||||
}
|
||||
|
||||
struct brush_ptr_less
|
||||
{
|
||||
constexpr bool operator()(const brush_t *a, const brush_t *b) const
|
||||
{
|
||||
return a->file_order < b->file_order;
|
||||
}
|
||||
};
|
||||
|
||||
using brush_result_set_t = std::set<const brush_t *, brush_ptr_less>;
|
||||
|
||||
// fixme-brushbsp: add bounds test
|
||||
static void GatherPossibleClippingBrushes_R(const node_t *node, const face_t *srcface, brush_result_set_t &result)
|
||||
{
|
||||
if (node->planenum == PLANENUM_LEAF) {
|
||||
for (auto *brush : node->original_brushes) {
|
||||
result.insert(brush);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
GatherPossibleClippingBrushes_R(node->children[0], srcface, result);
|
||||
GatherPossibleClippingBrushes_R(node->children[1], srcface, result);
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
GatherPossibleClippingBrushes
|
||||
|
||||
Starting a search at `node`, returns brushes that possibly intersect `srcface`.
|
||||
==================
|
||||
*/
|
||||
static brush_result_set_t GatherPossibleClippingBrushes(const mapentity_t* srcentity, const node_t *node, const face_t *srcface)
|
||||
{
|
||||
brush_result_set_t result;
|
||||
|
||||
GatherPossibleClippingBrushes_R(node, srcface, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
CSGFace
|
||||
|
||||
Given `srcface`, which was produced from `srcbrush` and lies on `srcnode`:
|
||||
|
||||
- search srcnode as well as its children for brushes which might clip
|
||||
srcface.
|
||||
|
||||
- clip srcface against all such brushes
|
||||
|
||||
Frees srcface.
|
||||
==================
|
||||
*/
|
||||
std::list<face_t *> CSGFace(face_t *srcface, const mapentity_t *srcentity, const brush_t *srcbrush, const node_t *srcnode)
|
||||
{
|
||||
const auto possible_clipbrushes = GatherPossibleClippingBrushes(srcentity, srcnode, srcface);
|
||||
|
||||
//logging::print("face {} has {} possible clipbrushes\n", (void *)srcface, possible_clipbrushes.size());
|
||||
|
||||
std::list<face_t *> result{srcface};
|
||||
|
||||
for (const brush_t *possible_clipbrush : possible_clipbrushes) {
|
||||
result = CSGFace_ClipAgainstSingleBrush(std::move(result), srcentity, srcbrush, possible_clipbrush);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SubtractBrush
|
||||
|
||||
Returns the fragments from a - b
|
||||
==================
|
||||
*/
|
||||
static std::vector<std::unique_ptr<brush_t>> SubtractBrush(std::unique_ptr<brush_t> a, const brush_t& b)
|
||||
{
|
||||
// first, check if `a` is fully in front of _any_ of b's planes
|
||||
for (const auto &side : b.faces) {
|
||||
// is `a` fully in front of `side`?
|
||||
bool fully_infront = true;
|
||||
|
||||
// fixme-brushbsp: factor this out somewhere
|
||||
for (const auto &a_face : a->faces) {
|
||||
for (const auto &a_point : a_face.w) {
|
||||
if (Face_Plane(&side).distance_to(a_point) < 0) {
|
||||
fully_infront = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!fully_infront) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fully_infront) {
|
||||
// `a` is fully in front of this side of b, so they don't actually intersect
|
||||
return SingleBrush(std::move(a));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<brush_t>> frontlist;
|
||||
std::vector<std::unique_ptr<brush_t>> unclassified = SingleBrush(std::move(a));
|
||||
|
||||
for (const auto &side : b.faces) {
|
||||
std::vector<std::unique_ptr<brush_t>> new_unclassified;
|
||||
|
||||
for (auto &fragment : unclassified) {
|
||||
// destructively processing `unclassified` here
|
||||
auto [front, back] = SplitBrush(std::move(fragment), Face_Plane(&side));
|
||||
if (front) {
|
||||
frontlist.push_back(std::move(front));
|
||||
}
|
||||
if (back) {
|
||||
new_unclassified.push_back(std::move(back));
|
||||
}
|
||||
}
|
||||
|
||||
unclassified = std::move(new_unclassified);
|
||||
}
|
||||
|
||||
return frontlist;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
BrushGE
|
||||
|
||||
Returns a >= b as far as brush clipping
|
||||
==================
|
||||
*/
|
||||
bool BrushGE(const brush_t& a, const brush_t& b)
|
||||
{
|
||||
// same contents clip each other
|
||||
if (a.contents.will_clip_same_type(options.target_game, b.contents)) {
|
||||
// map file order
|
||||
return a.file_order > b.file_order;
|
||||
}
|
||||
|
||||
// only chop if at least one of the two contents is
|
||||
// opaque (solid, sky, or detail)
|
||||
if (!(a.contents.chops(options.target_game) || b.contents.chops(options.target_game))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t a_pri = a.contents.priority(options.target_game);
|
||||
int32_t b_pri = b.contents.priority(options.target_game);
|
||||
|
||||
if (a_pri == b_pri) {
|
||||
// map file order
|
||||
return a.file_order > b.file_order;
|
||||
}
|
||||
|
||||
return a_pri >= b_pri;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
ChopBrushes
|
||||
|
||||
Clips off any overlapping portions of brushes
|
||||
==================
|
||||
*/
|
||||
std::vector<std::unique_ptr<brush_t>> ChopBrushes(const std::vector<std::unique_ptr<brush_t>>& input)
|
||||
{
|
||||
logging::print(logging::flag::PROGRESS, "---- {} ----\n", __func__);
|
||||
|
||||
// each inner vector corresponds to a brush in `input`
|
||||
// (set up this way for thread safety)
|
||||
std::vector<std::vector<std::unique_ptr<brush_t>>> brush_fragments;
|
||||
brush_fragments.resize(input.size());
|
||||
|
||||
/*
|
||||
* For each brush, clip away the parts that are inside other brushes.
|
||||
* Solid brushes override non-solid brushes.
|
||||
* brush => the brush to be clipped
|
||||
* clipbrush => the brush we are clipping against
|
||||
*
|
||||
* The output of this is a face list for each brush called "outside"
|
||||
*/
|
||||
tbb::parallel_for(static_cast<size_t>(0), input.size(), [&](const size_t i) {
|
||||
const auto& brush = input[i];
|
||||
|
||||
// the fragments `brush` is chopped into
|
||||
std::vector<std::unique_ptr<brush_t>> brush_result = SingleBrush(
|
||||
// start with a copy of brush
|
||||
std::make_unique<brush_t>(*brush)
|
||||
);
|
||||
|
||||
for (auto &clipbrush : input) {
|
||||
if (brush == clipbrush) {
|
||||
continue;
|
||||
}
|
||||
if (brush->bounds.disjoint_or_touching(clipbrush->bounds)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (BrushGE(*clipbrush, *brush)) {
|
||||
std::vector<std::unique_ptr<brush_t>> new_result;
|
||||
|
||||
// clipbrush is stronger.
|
||||
// rebuild existing fragments in brush_result, cliping them to clipbrush
|
||||
for (auto ¤t_fragment : brush_result) {
|
||||
for (auto &new_fragment : SubtractBrush(std::move(current_fragment), *clipbrush)) {
|
||||
new_result.push_back(std::move(new_fragment));
|
||||
}
|
||||
}
|
||||
|
||||
brush_result = std::move(new_result);
|
||||
}
|
||||
}
|
||||
|
||||
// save the result
|
||||
brush_fragments[i] = std::move(brush_result);
|
||||
});
|
||||
|
||||
// Non parallel part:
|
||||
std::vector<std::unique_ptr<brush_t>> result;
|
||||
for (auto &fragment_list : brush_fragments) {
|
||||
for (auto &fragment : fragment_list) {
|
||||
result.push_back(std::move(fragment));
|
||||
}
|
||||
}
|
||||
|
||||
logging::print(logging::flag::STAT, " {:8} brushes\n", input.size());
|
||||
logging::print(logging::flag::STAT, " {:8} chopped brushes\n", result.size());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
147
qbsp/surfaces.cc
147
qbsp/surfaces.cc
|
|
@ -579,153 +579,6 @@ void MakeMarkFaces(mapentity_t* entity, node_t* node)
|
|||
MakeMarkFaces(entity, node->children[1]);
|
||||
}
|
||||
|
||||
// the fronts of `faces` are facing `node`, determine what gets clipped away
|
||||
// (return the post-clipping result)
|
||||
static std::list<face_t *> ClipFacesToTree_r(node_t *node, const brush_t *srcbrush, std::list<face_t *> faces)
|
||||
{
|
||||
if (node->planenum == PLANENUM_LEAF) {
|
||||
// fixme-brushbsp: move to contentflags_t?
|
||||
if (node->contents.is_solid(options.target_game)
|
||||
|| node->contents.is_detail_solid(options.target_game)
|
||||
|| node->contents.is_sky(options.target_game)) {
|
||||
// solids eat any faces that reached this point
|
||||
return {};
|
||||
}
|
||||
|
||||
// see what the game thinks about the clip
|
||||
if (srcbrush->contents.will_clip_same_type(options.target_game, node->contents)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// other content types let the faces thorugh
|
||||
return faces;
|
||||
}
|
||||
|
||||
const qbsp_plane_t &splitplane = map.planes.at(node->planenum);
|
||||
|
||||
std::list<face_t *> front, back;
|
||||
|
||||
for (auto *face : faces) {
|
||||
auto [frontFragment, backFragment] = SplitFace(face, splitplane);
|
||||
if (frontFragment) {
|
||||
front.push_back(frontFragment);
|
||||
}
|
||||
if (backFragment) {
|
||||
back.push_back(backFragment);
|
||||
}
|
||||
}
|
||||
|
||||
front = ClipFacesToTree_r(node->children[0], srcbrush, front);
|
||||
back = ClipFacesToTree_r(node->children[1], srcbrush, back);
|
||||
|
||||
// merge lists
|
||||
front.splice(front.end(), back);
|
||||
|
||||
return front;
|
||||
}
|
||||
|
||||
static std::list<face_t *> ClipFacesToTree(node_t *node, const brush_t *srcbrush, std::list<face_t *> faces)
|
||||
{
|
||||
// handles the first level - faces are all supposed to be lying exactly on `node`
|
||||
for (auto *face : faces) {
|
||||
Q_assert(face->planenum == node->planenum);
|
||||
}
|
||||
|
||||
std::list<face_t *> front, back;
|
||||
for (auto *face : faces) {
|
||||
if (face->planeside == 0) {
|
||||
front.push_back(face);
|
||||
} else {
|
||||
back.push_back(face);
|
||||
}
|
||||
}
|
||||
|
||||
front = ClipFacesToTree_r(node->children[0], srcbrush, front);
|
||||
back = ClipFacesToTree_r(node->children[1], srcbrush, back);
|
||||
|
||||
// merge lists
|
||||
front.splice(front.end(), back);
|
||||
|
||||
return front;
|
||||
}
|
||||
|
||||
static void AddFaceToTree_r(mapentity_t* entity, face_t *face, brush_t *srcbrush, node_t* node)
|
||||
{
|
||||
if (node->planenum == PLANENUM_LEAF) {
|
||||
//FError("couldn't find node for face");
|
||||
// after outside filling, this is completely expected
|
||||
return;
|
||||
}
|
||||
|
||||
if (face->planenum == node->planenum) {
|
||||
// found the correct plane - add the face to it.
|
||||
|
||||
++c_nodefaces;
|
||||
|
||||
// csg it
|
||||
std::list<face_t *> faces = CSGFace(face, entity, srcbrush, node);
|
||||
|
||||
// clip them to the descendant parts of the BSP
|
||||
// (otherwise we could have faces floating in the void on this node)
|
||||
faces = ClipFacesToTree(node, srcbrush, faces);
|
||||
|
||||
for (face_t *part : faces) {
|
||||
node->facelist.push_back(part);
|
||||
|
||||
if (srcbrush->contents.is_mirrored(options.target_game)) {
|
||||
node->facelist.push_back(MirrorFace(part));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// fixme-brushbsp: we need to handle the case of the face being near enough that it gets clipped away,
|
||||
// but not face->planenum == node->planenum
|
||||
auto [frontWinding, backWinding] = face->w.clip(map.planes.at(node->planenum));
|
||||
if (frontWinding) {
|
||||
auto *newFace = new face_t{*face};
|
||||
newFace->w = *frontWinding;
|
||||
AddFaceToTree_r(entity, newFace, srcbrush, node->children[0]);
|
||||
}
|
||||
if (backWinding) {
|
||||
auto *newFace = new face_t{*face};
|
||||
newFace->w = *backWinding;
|
||||
AddFaceToTree_r(entity, newFace, srcbrush, node->children[1]);
|
||||
}
|
||||
|
||||
delete face;
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
MakeVisibleFaces
|
||||
|
||||
Given a completed BSP tree and a list of the original brushes (in `entity`),
|
||||
|
||||
- filters the brush faces into the BSP, finding the correct nodes they end up on
|
||||
- clips the faces by other brushes.
|
||||
|
||||
first iteration, we can just do an exhaustive check against all brushes
|
||||
================
|
||||
*/
|
||||
void MakeVisibleFaces(mapentity_t* entity, node_t* headnode)
|
||||
{
|
||||
c_nodefaces = 0;
|
||||
|
||||
for (auto &brush : entity->brushes) {
|
||||
for (auto &face : brush->faces) {
|
||||
if (!face.visible) {
|
||||
continue;
|
||||
}
|
||||
face_t *temp = CopyFace(&face);
|
||||
|
||||
AddFaceToTree_r(entity, temp, brush.get(), headnode);
|
||||
}
|
||||
}
|
||||
|
||||
logging::print(logging::flag::STAT, "{} nodefaces\n", c_nodefaces);
|
||||
}
|
||||
|
||||
struct makefaces_stats_t {
|
||||
int c_nodefaces;
|
||||
int c_merge;
|
||||
|
|
|
|||
Loading…
Reference in New Issue