From c37ec806678b47fb385952d7605b5bdd19f00cf7 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 26 Oct 2021 09:13:48 -0400 Subject: [PATCH] Implement game-agnostic Brush_GetContents --- common/bspfile.cc | 84 +++++++++++++++++++++++++++--- include/common/bspfile.hh | 1 + include/qbsp/map.hh | 2 +- include/qbsp/qbsp.hh | 1 - qbsp/brush.cc | 106 +++++++------------------------------- qbsp/map.cc | 14 ++--- 6 files changed, 105 insertions(+), 103 deletions(-) diff --git a/common/bspfile.cc b/common/bspfile.cc index 1daaa767..fa3bd081 100644 --- a/common/bspfile.cc +++ b/common/bspfile.cc @@ -33,7 +33,7 @@ struct gamedef_generic_t : public gamedef_t bool surf_is_subdivided(const surfflags_t &) const { throw std::bad_cast(); } - bool surfflags_are_valid(const surfflags_t &flags) const { throw std::bad_cast(); } + bool surfflags_are_valid(const surfflags_t &) const { throw std::bad_cast(); } contentflags_t cluster_contents(const contentflags_t &, const contentflags_t &) const { throw std::bad_cast(); } @@ -51,11 +51,11 @@ struct gamedef_generic_t : public gamedef_t contentflags_t create_liquid_contents(const int32_t &, const int32_t &) const { throw std::bad_cast(); } - bool contents_are_empty(const contentflags_t &contents) const { throw std::bad_cast(); } + bool contents_are_empty(const contentflags_t &) const { throw std::bad_cast(); } - bool contents_are_solid(const contentflags_t &contents) const { throw std::bad_cast(); } + bool contents_are_solid(const contentflags_t &) const { throw std::bad_cast(); } - bool contents_are_sky(const contentflags_t &contents) const { throw std::bad_cast(); } + bool contents_are_sky(const contentflags_t &) const { throw std::bad_cast(); } bool contents_are_liquid(const contentflags_t &) const { throw std::bad_cast(); } @@ -63,9 +63,11 @@ struct gamedef_generic_t : public gamedef_t bool portal_can_see_through(const contentflags_t &, const contentflags_t &) const { throw std::bad_cast(); } - std::string get_contents_display(const contentflags_t &contents) const { throw std::bad_cast(); } + std::string get_contents_display(const contentflags_t &) const { throw std::bad_cast(); } const std::initializer_list &get_hull_sizes() const { throw std::bad_cast(); } + + contentflags_t face_get_contents(const std::string &, const surfflags_t &, const contentflags_t &) const { throw std::bad_cast(); }; }; template @@ -245,6 +247,31 @@ struct gamedef_q1_like_t : public gamedef_t return hulls; } + + contentflags_t face_get_contents(const std::string &texname, const surfflags_t &flags, const contentflags_t &) const + { + // check for strong content indicators + if (!Q_strcasecmp(texname.data(), "origin")) { + return create_extended_contents(CFLAGS_ORIGIN); + } else if (!Q_strcasecmp(texname.data(), "hint")) { + return create_extended_contents(CFLAGS_HINT); + } else if (!Q_strcasecmp(texname.data(), "clip")) { + return create_extended_contents(CFLAGS_CLIP); + } else if (texname[0] == '*') { + if (!Q_strncasecmp(texname.data() + 1, "lava", 4)) { + return create_liquid_contents(CONTENTS_LAVA); + } else if (!Q_strncasecmp(texname.data() + 1, "slime", 5)) { + return create_liquid_contents(CONTENTS_SLIME); + } else { + return create_liquid_contents(CONTENTS_WATER); + } + } else if (!Q_strncasecmp(texname.data(), "sky", 3)) { + return create_sky_contents(); + } else { + // and anything else is assumed to be a regular solid. + return create_solid_contents(); + } + } }; struct gamedef_h2_t : public gamedef_q1_like_t @@ -312,7 +339,7 @@ struct gamedef_q2_t : public gamedef_t int32_t get_content_type(const contentflags_t &contents) const { return (contents.native & ((Q2_LAST_VISIBLE_CONTENTS << 1) - 1)) | - (Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP); + (Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP | Q2_CONTENTS_ORIGIN | Q2_CONTENTS_DETAIL | Q2_CONTENTS_TRANSLUCENT | Q2_CONTENTS_AREAPORTAL); } int32_t contents_priority(const contentflags_t &contents) const @@ -457,6 +484,51 @@ struct gamedef_q2_t : public gamedef_t static constexpr std::initializer_list hulls = {}; return hulls; } + + contentflags_t face_get_contents(const std::string &texname, const surfflags_t &flags, const contentflags_t &contents) const + { + contentflags_t surf_contents = contents; + + if (flags.native & (Q2_SURF_TRANS33 | Q2_SURF_TRANS66)) { + surf_contents.native |= Q2_CONTENTS_TRANSLUCENT; + + if (surf_contents.native & Q2_CONTENTS_SOLID) { + surf_contents.native = (surf_contents.native & ~Q2_CONTENTS_SOLID) | Q2_CONTENTS_WINDOW; + } + } + + // add extended flags that we may need + if (surf_contents.native & Q2_CONTENTS_DETAIL) { + surf_contents.extended |= CFLAGS_DETAIL; + } + + if (surf_contents.native & (Q2_CONTENTS_MONSTERCLIP | Q2_CONTENTS_PLAYERCLIP)) { + surf_contents.extended |= CFLAGS_CLIP; + } + + if (surf_contents.native & Q2_CONTENTS_ORIGIN) { + surf_contents.extended |= CFLAGS_ORIGIN; + } + + if (surf_contents.native & Q2_CONTENTS_MIST) { + surf_contents.extended |= CFLAGS_DETAIL_ILLUSIONARY; + } + + if (flags.native & Q2_SURF_HINT) { + surf_contents.extended |= CFLAGS_HINT; + } + + // FIXME: this is a bit of a hack, but this is because clip + // and liquids and stuff are already handled *like* detail by + // the compiler. + if (surf_contents.extended & CFLAGS_DETAIL) { + if (!(surf_contents.native & Q2_CONTENTS_SOLID)) { + surf_contents.extended &= ~CFLAGS_DETAIL; + } + } + + return surf_contents; + } }; // Game definitions, used for the bsp versions below diff --git a/include/common/bspfile.hh b/include/common/bspfile.hh index 22174709..4a54fd7f 100644 --- a/include/common/bspfile.hh +++ b/include/common/bspfile.hh @@ -1762,6 +1762,7 @@ struct gamedef_t virtual bool portal_can_see_through(const contentflags_t &contents0, const contentflags_t &contents1) const = 0; virtual std::string get_contents_display(const contentflags_t &contents) const = 0; virtual const std::initializer_list &get_hull_sizes() const = 0; + virtual contentflags_t face_get_contents(const std::string &texname, const surfflags_t &flags, const contentflags_t &contents) const = 0; }; // BSP version struct & instances diff --git a/include/qbsp/map.hh b/include/qbsp/map.hh index 12986fb3..cf3f3bfc 100644 --- a/include/qbsp/map.hh +++ b/include/qbsp/map.hh @@ -48,7 +48,7 @@ struct mapface_t surfflags_t flags { }; // Q2 stuff - int contents = 0; + contentflags_t contents { }; int value = 0; bool set_planepts(const std::array &pts); diff --git a/include/qbsp/qbsp.hh b/include/qbsp/qbsp.hh index f0058876..0672b2be 100644 --- a/include/qbsp/qbsp.hh +++ b/include/qbsp/qbsp.hh @@ -107,7 +107,6 @@ enum #include #include - struct mtexinfo_t { texvecf vecs; /* [s/t][xyz offset] */ diff --git a/qbsp/brush.cc b/qbsp/brush.cc index 829a17c0..cb307fe1 100644 --- a/qbsp/brush.cc +++ b/qbsp/brush.cc @@ -691,53 +691,30 @@ static void ExpandBrush(hullbrush_t *hullbrush, const aabb3d &hull_size, const f static const int DetailFlag = (1 << 27); +// FIXME: move this to earlier into the map process, like in +// texdef parsing or something static bool Brush_IsDetail(const mapbrush_t *mapbrush) { const mapface_t &mapface = mapbrush->face(0); - if ((mapface.contents & DetailFlag) == DetailFlag) { + if ((mapface.contents.native & DetailFlag) == DetailFlag) { return true; } return false; } -static contentflags_t Brush_GetContents_Q1(const mapbrush_t *mapbrush) +static contentflags_t Brush_GetContents(const mapbrush_t *mapbrush) { - const char *texname; - - // check for strong content indicators - for (int i = 0; i < mapbrush->numfaces; i++) { - const mapface_t &mapface = mapbrush->face(i); + // use the first side as the base contents value + contentflags_t base_contents; + + { + const mapface_t &mapface = mapbrush->face(0); const mtexinfo_t &texinfo = map.mtexinfos.at(mapface.texinfo); - texname = map.miptexTextureName(texinfo.miptex).c_str(); - - if (!Q_strcasecmp(texname, "origin")) - return options.target_game->create_extended_contents(CFLAGS_ORIGIN); - else if (!Q_strcasecmp(texname, "hint")) - return options.target_game->create_extended_contents(CFLAGS_HINT); - else if (!Q_strcasecmp(texname, "clip")) - return options.target_game->create_extended_contents(CFLAGS_CLIP); - else if (texname[0] == '*') { - if (!Q_strncasecmp(texname + 1, "lava", 4)) - return options.target_game->create_liquid_contents(CONTENTS_LAVA); - else if (!Q_strncasecmp(texname + 1, "slime", 5)) - return options.target_game->create_liquid_contents(CONTENTS_SLIME); - else - return options.target_game->create_liquid_contents(CONTENTS_WATER); - } else if (!Q_strncasecmp(texname, "sky", 3)) - return options.target_game->create_sky_contents(); + base_contents = options.target_game->face_get_contents(mapface.texname.data(), texinfo.flags, mapface.contents); } - // and anything else is assumed to be a regular solid. - return options.target_game->create_solid_contents(); -} - -static contentflags_t Brush_GetContents_Q2(const mapbrush_t *mapbrush) -{ - bool is_trans = false; - bool is_hint = false; - contentflags_t contents = {mapbrush->face(0).contents}; - + // validate that all of the sides have valid contents for (int i = 0; i < mapbrush->numfaces; i++) { const mapface_t &mapface = mapbrush->face(i); const mtexinfo_t &texinfo = map.mtexinfos.at(mapface.texinfo); @@ -746,64 +723,20 @@ static contentflags_t Brush_GetContents_Q2(const mapbrush_t *mapbrush) continue; } - if (!is_trans && (texinfo.flags.native & (Q2_SURF_TRANS33 | Q2_SURF_TRANS66))) { - is_trans = true; - } + contentflags_t contents = options.target_game->face_get_contents(mapface.texname.data(), texinfo.flags, mapface.contents); - if (!is_hint && (texinfo.flags.native & Q2_SURF_HINT)) { - is_hint = true; - } - - if (mapface.contents != contents.native) { - LogPrint("mixed face contents ({} != {} at line {})\n", + if (!contents.types_equal(base_contents, options.target_game)) { + LogPrint("mixed face contents ({} != {}) at line {}\n", contentflags_t{mapface.contents}.to_string(options.target_game), contents.to_string(options.target_game), mapface.linenum); break; } } + + // make sure we found a valid type + Q_assert(base_contents.is_valid(options.target_game, false)); - // if any side is translucent, mark the contents - // and change solid to window - if (is_trans) { - contents.native |= Q2_CONTENTS_TRANSLUCENT; - if (contents.native & Q2_CONTENTS_SOLID) { - contents.native = (contents.native & ~Q2_CONTENTS_SOLID) | Q2_CONTENTS_WINDOW; - } - } - - // add extended flags that we may need - if (contents.native & Q2_CONTENTS_DETAIL) { - contents.extended |= CFLAGS_DETAIL; - } - - if (contents.native & (Q2_CONTENTS_MONSTERCLIP | Q2_CONTENTS_PLAYERCLIP)) { - contents.extended |= CFLAGS_CLIP; - } - - if (contents.native & Q2_CONTENTS_ORIGIN) { - contents.extended |= CFLAGS_ORIGIN; - } - - if (contents.native & Q2_CONTENTS_MIST) { - contents.extended |= CFLAGS_DETAIL_ILLUSIONARY; - } - - if (is_hint) { - contents.extended |= CFLAGS_HINT; - } - - // FIXME: this is a bit of a hack, but this is because clip - // and liquids and stuff are already handled *like* detail by - // the compiler. - if (contents.extended & CFLAGS_DETAIL) { - if (!(contents.native & Q2_CONTENTS_SOLID)) { - contents.extended &= ~CFLAGS_DETAIL; - } - } - - Q_assert(contents.is_valid(options.target_game, false)); - - return contents; + return base_contents; } /* @@ -979,9 +912,6 @@ void Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnu /* Origin brush support */ rotation_t rottype = rotation_t::none; - // TODO: move to game - auto Brush_GetContents = (options.target_game->id == GAME_QUAKE_II) ? Brush_GetContents_Q2 : Brush_GetContents_Q1; - for (int i = 0; i < src->nummapbrushes; i++) { const mapbrush_t *mapbrush = &src->mapbrush(i); const contentflags_t contents = Brush_GetContents(mapbrush); diff --git a/qbsp/map.cc b/qbsp/map.cc index 92bb2b45..391d61c9 100644 --- a/qbsp/map.cc +++ b/qbsp/map.cc @@ -1370,7 +1370,7 @@ static void ParseTextureDef(parser_t &parser, mapface_t &mapface, const mapbrush tx->miptex = FindMiptex(mapface.texname.c_str(), extinfo.info); - mapface.contents = extinfo.info->contents; + mapface.contents = { extinfo.info->contents }; tx->flags = mapface.flags = {extinfo.info->flags}; tx->value = mapface.value = extinfo.info->value; @@ -1546,18 +1546,18 @@ mapbrush_t ParseBrush(parser_t &parser, const mapentity_t *entity) if (options.target_game->id == GAME_QUAKE_II) { // translucent objects are automatically classified as detail if ((face->flags.native & (Q2_SURF_TRANS33 | Q2_SURF_TRANS66)) || - (face->contents & (Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP))) { - face->contents |= Q2_CONTENTS_DETAIL; + (face->contents.native & (Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP))) { + face->contents.native |= Q2_CONTENTS_DETAIL; } - if (!(face->contents & + if (!(face->contents.native & (((Q2_LAST_VISIBLE_CONTENTS << 1) - 1) | Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP))) { - face->contents |= Q2_CONTENTS_SOLID; + face->contents.native |= Q2_CONTENTS_SOLID; } // hints and skips are never detail, and have no content if (face->flags.native & (Q2_SURF_HINT | Q2_SURF_SKIP)) { - face->contents = 0; + face->contents.native = 0; } } @@ -1789,7 +1789,7 @@ void ProcessAreaPortal(mapentity_t *entity) FError("func_areaportal can only be a single brush"); map.brushes[entity->firstmapbrush].contents = Q2_CONTENTS_AREAPORTAL; - map.faces[map.brushes[entity->firstmapbrush].firstface].contents = Q2_CONTENTS_AREAPORTAL; + map.faces[map.brushes[entity->firstmapbrush].firstface].contents.native = Q2_CONTENTS_AREAPORTAL; entity->areaportalnum = ++map.numareaportals; // set the portal number as "style" SetKeyValue(entity, "style", std::to_string(map.numareaportals).c_str());