diff --git a/common/bspfile.cc b/common/bspfile.cc index a4c3f746..2791137a 100644 --- a/common/bspfile.cc +++ b/common/bspfile.cc @@ -358,6 +358,28 @@ public: } } + contentflags_t visible_contents(const contentflags_t &a, const contentflags_t &b) const override + { + if (a.equals(this, b)) { + return create_empty_contents(); + } + + int32_t a_pri = contents_priority(a); + int32_t b_pri = contents_priority(b); + + if (a_pri > b_pri) { + return a; + } else { + return b; + } + // fixme-brushbsp: support detail-illusionary intersecting liquids + } + + bool contents_contains(const contentflags_t &a, const contentflags_t &b) const override + { + return a.equals(this, b); + } + std::string get_contents_display(const contentflags_t &contents) const override { std::string base; @@ -850,6 +872,9 @@ struct gamedef_q2_t : public gamedef_t return true; } + /** + * Returns the single content bit of the strongest visible content present + */ constexpr int32_t visible_contents(const int32_t &contents) const { for (int32_t i = 1; i <= Q2_LAST_VISIBLE_CONTENTS; i <<= 1) { @@ -907,6 +932,18 @@ struct gamedef_q2_t : public gamedef_t return {a.native | b.native}; } + contentflags_t visible_contents(const contentflags_t &a, const contentflags_t &b) const override + { + int viscontents = visible_contents(a.native ^ b.native); + + return {viscontents}; + } + + bool contents_contains(const contentflags_t &a, const contentflags_t &b) const override + { + return (a.native & b.native) != 0; + } + std::string get_contents_display(const contentflags_t &contents) const override { if (!contents.native) { diff --git a/include/common/bspfile.hh b/include/common/bspfile.hh index 76b659de..b9b138cb 100644 --- a/include/common/bspfile.hh +++ b/include/common/bspfile.hh @@ -292,6 +292,8 @@ struct gamedef_t virtual bool contents_seals_map(const contentflags_t &contents) const = 0; virtual contentflags_t contents_remap_for_export(const contentflags_t &contents) const = 0; virtual contentflags_t combine_contents(const contentflags_t &a, const contentflags_t &b) const = 0; + virtual contentflags_t visible_contents(const contentflags_t &a, const contentflags_t &b) const = 0; + virtual bool contents_contains(const contentflags_t &a, const contentflags_t &b) const = 0; virtual std::string get_contents_display(const contentflags_t &contents) const = 0; virtual void contents_make_valid(contentflags_t &contents) const = 0; virtual const std::initializer_list &get_hull_sizes() const = 0; diff --git a/include/qbsp/portals.hh b/include/qbsp/portals.hh index 538ec68a..a48c5a1a 100644 --- a/include/qbsp/portals.hh +++ b/include/qbsp/portals.hh @@ -34,6 +34,7 @@ struct portal_t portal_t *next[2]; // [0] = next portal in nodes[0]'s list of portals std::optional winding; + bool sidefound; // false if ->side hasn't been checked face_t *side; // NULL = non-visible // fixme-brushbsp: change to side_t face_t *face[2]; // output face in bsp file }; @@ -56,3 +57,4 @@ void MakeTreePortals(tree_t *tree); void FreeTreePortals_r(node_t *node); void AssertNoPortals(node_t *node); void MakeHeadnodePortals(tree_t *tree); +void MarkVisibleSides(tree_t *tree, mapentity_t* entity); diff --git a/qbsp/portals.cc b/qbsp/portals.cc index 83b0167e..d08c0ff5 100644 --- a/qbsp/portals.cc +++ b/qbsp/portals.cc @@ -426,3 +426,119 @@ void FreeTreePortals_r(node_t *node) } node->portals = nullptr; } + +//============================================================== + +/* +============ +FindPortalSide + +Finds a brush side to use for texturing the given portal +============ +*/ +static void FindPortalSide(portal_t *p) +{ + // decide which content change is strongest + // solid > lava > water, etc + contentflags_t viscontents = options.target_game->visible_contents(p->nodes[0]->contents, p->nodes[1]->contents); + if (viscontents.is_empty(options.target_game)) + return; + + int planenum = p->onnode->planenum; + face_t *bestside = nullptr; + float bestdot = 0; + + for (int j = 0; j < 2; j++) + { + node_t *n = p->nodes[j]; + auto p1 = map.planes.at(p->onnode->planenum); + + for (brush_t *brush : n->original_brushes) + { + if (!options.target_game->contents_contains(brush->contents, viscontents)) + continue; + for (face_t &side : brush->faces) + { + // fixme-brushbsp: port these +// if (side.bevel) +// continue; +// if (side.texinfo == TEXINFO_NODE) +// continue; // non-visible + if (side.planenum == planenum) + { // exact match + bestside = &side; + goto gotit; + } + // see how close the match is + auto p2 = map.planes.at(side.planenum); + float dot = qv::dot(p1.normal, p2.normal); + if (dot > bestdot) + { + bestdot = dot; + bestside = &side; + } + } + } + } + +gotit: + if (!bestside) + logging::print("WARNING: side not found for portal\n"); + + p->sidefound = true; + p->side = bestside; +} + +/* +=============== +MarkVisibleSides_r + +=============== +*/ +static void MarkVisibleSides_r(node_t *node) +{ + if (node->planenum != PLANENUM_LEAF) + { + MarkVisibleSides_r(node->children[0]); + MarkVisibleSides_r(node->children[1]); + return; + } + + // empty leafs are never boundary leafs + if (node->contents.is_empty(options.target_game)) + return; + + // see if there is a visible face + int s; + for (portal_t *p=node->portals ; p ; p = p->next[!s]) + { + s = (p->nodes[0] == node); + if (!p->onnode) + continue; // edge of world + if (!p->sidefound) + FindPortalSide(p); + if (p->side) + p->side->visible = true; + } +} + +/* +============= +MarkVisibleSides + +============= +*/ +void MarkVisibleSides(tree_t *tree, mapentity_t* entity) +{ + logging::print("--- {} ---\n", __func__); + + // clear all the visible flags + for (auto &brush : entity->brushes) { + for (auto &face : brush->faces) { + face.visible = false; + } + } + + // set visible flags on the sides that are used by portals + MarkVisibleSides_r (tree->headnode); +} diff --git a/qbsp/qbsp.cc b/qbsp/qbsp.cc index 41b75d30..fb044970 100644 --- a/qbsp/qbsp.cc +++ b/qbsp/qbsp.cc @@ -902,7 +902,8 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum) MakeTreePortals(tree); - MakeVisibleFaces(entity, tree->headnode); + MarkVisibleSides(tree, entity); + MakeFaces(tree->headnode); if (hullnum <= 0 && entity == map.world_entity() && !map.leakfile) { WritePortalFile(tree); diff --git a/qbsp/surfaces.cc b/qbsp/surfaces.cc index f7515758..fd67e593 100644 --- a/qbsp/surfaces.cc +++ b/qbsp/surfaces.cc @@ -750,10 +750,12 @@ static face_t *FaceFromPortal(portal_t *p, int pside) f->planenum = side->planenum; f->planeside = static_cast(pside); f->portal = p; + f->lmshift = side->lmshift; // don't show insides of windows - if (!side->contents[1].is_mirrored(options.target_game)) - return nullptr; + // fixme-brushbsp: restore this? +// if (!side->contents[1].is_mirrored(options.target_game)) +// return nullptr; if (pside) { @@ -766,6 +768,9 @@ static face_t *FaceFromPortal(portal_t *p, int pside) f->w = *p->winding; f->contents[1] = p->nodes[0]->contents; } + + UpdateFaceSphere(f); + return f; } @@ -812,7 +817,7 @@ static void MakeFaces_r(node_t *node, makefaces_stats_t& stats) face_t *f = FaceFromPortal(p, s); if (f) { - c_nodefaces++; + stats.c_nodefaces++; p->face[s] = f; p->onnode->facelist.push_back(f); }