From 8b4f9f54805ab7fd9b2caf9bdbfcb92540a68aa5 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sat, 11 Jun 2022 05:37:50 -0400 Subject: [PATCH 1/6] move mirrorinside to gamedef --- common/bspfile.cc | 44 +++++++++++++++++++++++++++++++++++++-- include/common/bspfile.hh | 9 +++++++- qbsp/brush.cc | 14 +++---------- qbsp/surfaces.cc | 11 +--------- 4 files changed, 54 insertions(+), 24 deletions(-) diff --git a/common/bspfile.cc b/common/bspfile.cc index 9c5ee867..881fc385 100644 --- a/common/bspfile.cc +++ b/common/bspfile.cc @@ -84,6 +84,8 @@ struct gamedef_generic_t : public gamedef_t bool contents_are_detail_illusionary(const contentflags_t &contents) const { throw std::bad_cast(); } + bool contents_are_mirror_inside(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 &) const { throw std::bad_cast(); } @@ -255,6 +257,23 @@ struct gamedef_q1_like_t : public gamedef_t return ((contents.extended & CFLAGS_DETAIL_ILLUSIONARY) != 0); } + bool contents_are_mirror_inside(const contentflags_t &contents) const + { + // if we have mirrorinside set, go ahead + if (contents.mirror_inside.has_value()) + return contents.mirror_inside.value(); + + // by default, solid, detail and sky are false, and everything else + // (liquid, etc) is true. + if (!(contents.is_solid(this) || + contents.is_any_detail(this) || + contents.is_sky(this))) { + return true; + } + + return false; + } + bool contents_are_empty(const contentflags_t &contents) const { if (contents.extended & CFLAGS_CONTENTS_MASK) @@ -638,6 +657,22 @@ struct gamedef_q2_t : public gamedef_t || ((contents.native & mist2_type) == mist2_type); } + bool contents_are_mirror_inside(const contentflags_t &contents) const + { + // if we have mirrorinside set, go ahead + if (contents.mirror_inside.has_value()) + return contents.mirror_inside.value(); + + // Q2 is a bit different here. in vanilla tools, + // every content except SOLID is implicitly mirrorinside. + // the only exception is that 4bsp has the unused AUX + // contents to default to not mirroring the insides. + if (contents.native & (Q2_CONTENTS_SOLID | Q2_CONTENTS_AUX)) + return false; + + return true; + } + bool contents_are_empty(const contentflags_t &contents) const { if (contents.extended & CFLAGS_CONTENTS_MASK) @@ -1119,6 +1154,11 @@ bool contentflags_t::is_detail_illusionary(const gamedef_t *game) const return game->contents_are_detail_illusionary(*this); } +bool contentflags_t::is_mirror_inside(const gamedef_t *game) const +{ + return game->contents_are_mirror_inside(*this); +} + bool contentflags_t::is_empty(const gamedef_t *game) const { return game->contents_are_empty(*this); @@ -1148,8 +1188,8 @@ std::string contentflags_t::to_string(const gamedef_t *game) const { std::string s = game->get_contents_display(*this); - if (extended & CFLAGS_BMODEL_MIRROR_INSIDE) { - s += "|BMODEL_MIRROR_INSIDE"; + if (is_mirror_inside(game)) { + s += fmt::format("|BMODEL_MIRROR_INSIDE[{}]", mirror_inside.has_value() ? (mirror_inside.value() ? "true" : "false") : "nullopt"); } if (extended & CFLAGS_NO_CLIPPING_SAME_TYPE) { s += "|NO_CLIPPING_SAME_TYPE"; diff --git a/include/common/bspfile.hh b/include/common/bspfile.hh index 67c303b0..842081f7 100644 --- a/include/common/bspfile.hh +++ b/include/common/bspfile.hh @@ -563,7 +563,6 @@ enum q2_contents_t : int32_t // Special contents flags for the compiler only enum extended_cflags_t : uint16_t { - CFLAGS_BMODEL_MIRROR_INSIDE = nth_bit(3), /* set "_mirrorinside" "1" on a bmodel to mirror faces for when the player is inside. */ CFLAGS_NO_CLIPPING_SAME_TYPE = nth_bit(4), /* Don't clip the same content type. mostly intended for CONTENTS_DETAIL_ILLUSIONARY */ // only one of these flags below should ever be set. CFLAGS_HINT = nth_bit(5), @@ -590,6 +589,10 @@ struct contentflags_t // extra flags, specific to BSP only uint16_t extended; + // the value set directly from `_mirrorinside` on the brush, if available. + // don't use this directly, use `is_mirror_inside` to allow the game to decide. + std::optional mirror_inside = std::nullopt; + constexpr bool operator==(const contentflags_t &other) const { return native == other.native && extended == other.extended; @@ -603,6 +606,9 @@ struct contentflags_t bool is_detail_fence(const gamedef_t *game) const; bool is_detail_illusionary(const gamedef_t *game) const; + bool is_mirror_inside(const gamedef_t *game) const; + contentflags_t &set_mirror_inside(const std::optional &mirror_inside_value) { mirror_inside = mirror_inside_value; return *this; } + bool is_empty(const gamedef_t *game) const; // detail solid or structural solid @@ -1814,6 +1820,7 @@ struct gamedef_t virtual bool contents_are_detail_solid(const contentflags_t &contents) const = 0; virtual bool contents_are_detail_fence(const contentflags_t &contents) const = 0; virtual bool contents_are_detail_illusionary(const contentflags_t &contents) const = 0; + virtual bool contents_are_mirror_inside(const contentflags_t &contents) const = 0; virtual bool contents_are_empty(const contentflags_t &contents) const = 0; virtual bool contents_are_solid(const contentflags_t &contents) const = 0; virtual bool contents_are_sky(const contentflags_t &contents) const = 0; diff --git a/qbsp/brush.cc b/qbsp/brush.cc index feeebab4..a1ef5589 100644 --- a/qbsp/brush.cc +++ b/qbsp/brush.cc @@ -859,7 +859,7 @@ static void Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int /* _mirrorinside key (for func_water etc.) */ const bool mirrorinside_set = *ValueForKey(src, "_mirrorinside"); - const bool all_mirrorinside = !!atoi(ValueForKey(src, "_mirrorinside")); + const std::optional mirrorinside = mirrorinside_set ? decltype(mirrorinside)(atoi(ValueForKey(src, "_mirrorinside")) ? true : false) : std::nullopt; /* _noclipfaces */ const bool noclipfaces = !!atoi(ValueForKey(src, "_noclipfaces")); @@ -880,19 +880,12 @@ static void Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int bool detail = false; bool detail_illusionary = false; bool detail_fence = false; - bool mirrorinside = all_mirrorinside; // inherit the per-entity settings detail |= all_detail; detail_illusionary |= all_detail_illusionary; detail_fence |= all_detail_fence; - if (!mirrorinside_set) { - if (options.target_game->id == GAME_QUAKE_II && (contents.native & (Q2_CONTENTS_AUX | Q2_CONTENTS_MIST))) { - mirrorinside = true; - } - } - /* "origin" brushes always discarded */ if (contents.is_origin()) continue; @@ -975,9 +968,8 @@ static void Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int contents = options.target_game->create_solid_contents(); // apply extended flags - if (mirrorinside) { - contents.extended |= CFLAGS_BMODEL_MIRROR_INSIDE; - } + contents.set_mirror_inside(mirrorinside); + if (noclipfaces) { contents.extended |= CFLAGS_NO_CLIPPING_SAME_TYPE; } diff --git a/qbsp/surfaces.cc b/qbsp/surfaces.cc index 7fe7ddae..969231f6 100644 --- a/qbsp/surfaces.cc +++ b/qbsp/surfaces.cc @@ -654,19 +654,10 @@ static void AddFaceToTree_r(mapentity_t* entity, face_t *face, brush_t *srcbrush for (face_t *part : faces) { node->facelist.push_back(part); - // fixme-brushbsp: move to contentflags_t helper /* * If the brush is non-solid, mirror faces for the inside view */ - bool mirror = (srcbrush->contents.extended & CFLAGS_BMODEL_MIRROR_INSIDE); - - if (!(srcbrush->contents.is_solid(options.target_game) || - srcbrush->contents.is_any_detail(options.target_game) || - srcbrush->contents.is_sky(options.target_game))) { - mirror = true; - } - - if (mirror) { + if (srcbrush->contents.is_mirror_inside(options.target_game)) { node->facelist.push_back(MirrorFace(part)); } } From b33e792458b7459d8df730fbb4b612534bbc4e44 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sat, 11 Jun 2022 06:01:36 -0400 Subject: [PATCH 2/6] move clip-own-typeness over to gamedef & contentflags (fixes Q2 edge case with a solid version of a type bordering a translucent version) --- common/bspfile.cc | 28 ++++++++++++++++++++++------ include/common/bspfile.hh | 16 +++++++++++----- qbsp/brush.cc | 8 +++----- qbsp/csg4.cc | 4 ++-- qbsp/surfaces.cc | 6 +++--- 5 files changed, 41 insertions(+), 21 deletions(-) diff --git a/common/bspfile.cc b/common/bspfile.cc index 0c8e6d90..e337b200 100644 --- a/common/bspfile.cc +++ b/common/bspfile.cc @@ -85,9 +85,11 @@ struct gamedef_generic_t : public gamedef_t bool contents_are_detail_illusionary(const contentflags_t &contents) const { throw std::bad_cast(); } bool contents_are_empty(const contentflags_t &) const { throw std::bad_cast(); } - + bool contents_are_mirrored(const contentflags_t &) const { throw std::bad_cast(); } + bool contents_clip_same_type(const contentflags_t &, const contentflags_t &) const { throw std::bad_cast(); } + bool contents_are_solid(const contentflags_t &) const { throw std::bad_cast(); } bool contents_are_sky(const contentflags_t &) const { throw std::bad_cast(); } @@ -262,7 +264,7 @@ struct gamedef_q1_like_t : public gamedef_t // if we have mirrorinside set, go ahead if (contents.mirror_inside.has_value()) { return contents.mirror_inside.value(); - } + } // If the brush is non-solid, mirror faces for the inside view return (contents.native == CONTENTS_WATER) @@ -270,6 +272,11 @@ struct gamedef_q1_like_t : public gamedef_t || (contents.native == CONTENTS_LAVA); } + bool contents_clip_same_type(const contentflags_t &self, const contentflags_t &other) const + { + return self == other && self.clips_same_type.value_or(true); + } + bool contents_are_empty(const contentflags_t &contents) const { if (contents.extended & CFLAGS_CONTENTS_MASK) @@ -665,6 +672,11 @@ struct gamedef_q2_t : public gamedef_t // contents to default to not mirroring the insides. return !(contents.native & (Q2_CONTENTS_SOLID | Q2_CONTENTS_AUX)); } + + bool contents_clip_same_type(const contentflags_t &self, const contentflags_t &other) const + { + return (self.native & Q2_ALL_VISIBLE_CONTENTS) == (other.native & Q2_ALL_VISIBLE_CONTENTS) && self.clips_same_type.value_or(true); + } bool contents_are_empty(const contentflags_t &contents) const { @@ -1152,6 +1164,11 @@ bool contentflags_t::is_mirrored(const gamedef_t *game) const return game->contents_are_mirrored(*this); } +bool contentflags_t::will_clip_same_type(const gamedef_t *game, const contentflags_t &other) const +{ + return game->contents_clip_same_type(*this, other); +} + bool contentflags_t::is_empty(const gamedef_t *game) const { return game->contents_are_empty(*this); @@ -1182,11 +1199,10 @@ std::string contentflags_t::to_string(const gamedef_t *game) const std::string s = game->get_contents_display(*this); // FIXME: how do we conditionally display this only when it matters (when it's not default basically)? - s += fmt::format("|MIRROR_INSIDE[{}]", mirror_inside.has_value() ? (mirror_inside.value() ? "true" : "false") : "nullopt"); + s += fmt::format("|MIRROR_INSIDE[{}]", mirror_inside.has_value() ? (clips_same_type.value() ? "true" : "false") : "nullopt"); + + s += fmt::format("|CLIPS_SAME_TYPE[{}]", clips_same_type.has_value() ? (mirror_inside.value() ? "true" : "false") : "nullopt"); - if (extended & CFLAGS_NO_CLIPPING_SAME_TYPE) { - s += "|NO_CLIPPING_SAME_TYPE"; - } if (extended & CFLAGS_HINT) { s += "|HINT"; } diff --git a/include/common/bspfile.hh b/include/common/bspfile.hh index 45e1d6a9..e72889b3 100644 --- a/include/common/bspfile.hh +++ b/include/common/bspfile.hh @@ -563,7 +563,6 @@ enum q2_contents_t : int32_t // Special contents flags for the compiler only enum extended_cflags_t : uint16_t { - CFLAGS_NO_CLIPPING_SAME_TYPE = nth_bit(4), /* Don't clip the same content type. mostly intended for CONTENTS_DETAIL_ILLUSIONARY */ // only one of these flags below should ever be set. CFLAGS_HINT = nth_bit(5), CFLAGS_CLIP = nth_bit(6), @@ -590,9 +589,13 @@ struct contentflags_t uint16_t extended; // the value set directly from `_mirrorinside` on the brush, if available. - // don't use this directly, use `is_mirror_inside` to allow the game to decide. + // don't check this directly, use `is_mirror_inside` to allow the game to decide. std::optional mirror_inside = std::nullopt; + // Don't clip the same content type. mostly intended for CONTENTS_DETAIL_ILLUSIONARY. + // don't check this directly, use `will_clip_same_type` to allow the game to decide. + std::optional clips_same_type = std::nullopt; + constexpr bool operator==(const contentflags_t &other) const { return native == other.native && extended == other.extended; @@ -605,9 +608,13 @@ struct contentflags_t bool is_detail_solid(const gamedef_t *game) const; bool is_detail_fence(const gamedef_t *game) const; bool is_detail_illusionary(const gamedef_t *game) const; - + bool is_mirrored(const gamedef_t *game) const; contentflags_t &set_mirrored(const std::optional &mirror_inside_value) { mirror_inside = mirror_inside_value; return *this; } + + inline bool will_clip_same_type(const gamedef_t *game) const { return will_clip_same_type(game, *this); } + bool will_clip_same_type(const gamedef_t *game, const contentflags_t &other) const; + contentflags_t &set_clips_same_type(const std::optional &clips_same_type_value) { clips_same_type = clips_same_type_value; return *this; } bool is_empty(const gamedef_t *game) const; @@ -629,8 +636,6 @@ struct contentflags_t constexpr bool is_origin() const { return extended & CFLAGS_ORIGIN; } - constexpr bool clips_same_type() const { return !(extended & CFLAGS_NO_CLIPPING_SAME_TYPE); } - bool is_fence(const gamedef_t *game) const { return is_detail_fence(game) || is_detail_illusionary(game); } @@ -1822,6 +1827,7 @@ struct gamedef_t virtual bool contents_are_detail_illusionary(const contentflags_t &contents) const = 0; virtual bool contents_are_mirrored(const contentflags_t &contents) const = 0; virtual bool contents_are_empty(const contentflags_t &contents) const = 0; + virtual bool contents_clip_same_type(const contentflags_t &self, const contentflags_t &other) const = 0; virtual bool contents_are_solid(const contentflags_t &contents) const = 0; virtual bool contents_are_sky(const contentflags_t &contents) const = 0; virtual bool contents_are_liquid(const contentflags_t &contents) const = 0; diff --git a/qbsp/brush.cc b/qbsp/brush.cc index 159df599..97d1bb08 100644 --- a/qbsp/brush.cc +++ b/qbsp/brush.cc @@ -862,7 +862,8 @@ static void Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int const std::optional mirrorinside = mirrorinside_set ? decltype(mirrorinside)(atoi(ValueForKey(src, "_mirrorinside")) ? true : false) : std::nullopt; /* _noclipfaces */ - const bool noclipfaces = !!atoi(ValueForKey(src, "_noclipfaces")); + const bool noclipfaces_set = *ValueForKey(src, "_mirrorinside"); + const std::optional noclipfaces = mirrorinside_set ? decltype(noclipfaces)(atoi(ValueForKey(src, "_noclipfaces")) ? false : true) : std::nullopt; const bool func_illusionary_visblocker = (0 == Q_strcasecmp(classname, "func_illusionary_visblocker")); @@ -969,10 +970,7 @@ static void Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int // apply extended flags contents.set_mirrored(mirrorinside); - - if (noclipfaces) { - contents.extended |= CFLAGS_NO_CLIPPING_SAME_TYPE; - } + contents.set_clips_same_type(noclipfaces); if (func_illusionary_visblocker) { contents.extended |= CFLAGS_ILLUSIONARY_VISBLOCKER; } diff --git a/qbsp/csg4.cc b/qbsp/csg4.cc index 994e6baa..30b9d9b4 100644 --- a/qbsp/csg4.cc +++ b/qbsp/csg4.cc @@ -262,7 +262,7 @@ static bool ShouldClipbrushEatBrush(const brush_t &brush, const brush_t &clipbru } if (clipbrush.contents.types_equal(brush.contents, options.target_game)) { - return clipbrush.contents.clips_same_type(); + return clipbrush.contents.will_clip_same_type(options.target_game); } return false; @@ -426,7 +426,7 @@ 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 == b.contents && a.contents.clips_same_type()) { + if (a.contents == b.contents && a.contents.will_clip_same_type(options.target_game)) { // map file order return a.file_order > b.file_order; } diff --git a/qbsp/surfaces.cc b/qbsp/surfaces.cc index 50c26922..40861397 100644 --- a/qbsp/surfaces.cc +++ b/qbsp/surfaces.cc @@ -574,11 +574,11 @@ static std::list ClipFacesToTree_r(node_t *node, const brush_t *srcbru return {}; } - // translucent contents also clip faces - if (node->contents == srcbrush->contents - && srcbrush->contents.clips_same_type()) { + // 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; } From be17bf7c7d9475c7f8f48c8aef23598151d41423 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sat, 11 Jun 2022 07:32:43 -0400 Subject: [PATCH 3/6] fix mistype --- common/bspfile.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/bspfile.cc b/common/bspfile.cc index e337b200..7b60aa48 100644 --- a/common/bspfile.cc +++ b/common/bspfile.cc @@ -1201,7 +1201,7 @@ std::string contentflags_t::to_string(const gamedef_t *game) const // FIXME: how do we conditionally display this only when it matters (when it's not default basically)? s += fmt::format("|MIRROR_INSIDE[{}]", mirror_inside.has_value() ? (clips_same_type.value() ? "true" : "false") : "nullopt"); - s += fmt::format("|CLIPS_SAME_TYPE[{}]", clips_same_type.has_value() ? (mirror_inside.value() ? "true" : "false") : "nullopt"); + s += fmt::format("|CLIPS_SAME_TYPE[{}]", clips_same_type.has_value() ? (clips_same_type.value() ? "true" : "false") : "nullopt"); if (extended & CFLAGS_HINT) { s += "|HINT"; From 10676603e9f9ada1299f7221b972a67cfeb00c2e Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sat, 11 Jun 2022 08:29:55 -0400 Subject: [PATCH 4/6] move clip & origin into game-specific code remove hint from brush; hint is technically a surface flag rather than a content flag, so we can infer it from the sides rather than needing to store it as a unique flag (we could still cache the result somewhere but this makes it consistent between q1 and q2) --- common/bspfile.cc | 133 ++++++++++++++++++++++++++++---------- include/common/bspfile.hh | 27 ++++---- qbsp/brush.cc | 29 +++++++-- qbsp/exportobj.cc | 8 ++- qbsp/qbsp.cc | 4 +- 5 files changed, 143 insertions(+), 58 deletions(-) diff --git a/common/bspfile.cc b/common/bspfile.cc index 7b60aa48..ed1997c0 100644 --- a/common/bspfile.cc +++ b/common/bspfile.cc @@ -54,14 +54,10 @@ struct gamedef_generic_t : public gamedef_t contentflags_t cluster_contents(const contentflags_t &, const contentflags_t &) const { throw std::bad_cast(); } - int32_t get_content_type(const contentflags_t &) const { throw std::bad_cast(); } - int32_t contents_priority(const contentflags_t &) const { throw std::bad_cast(); } bool chops(const contentflags_t &) const { throw std::bad_cast(); } - contentflags_t create_extended_contents(const uint16_t &) const { throw std::bad_cast(); } - contentflags_t create_empty_contents(const uint16_t &) const { throw std::bad_cast(); } contentflags_t create_solid_contents(const uint16_t &) const { throw std::bad_cast(); } @@ -76,6 +72,8 @@ struct gamedef_generic_t : public gamedef_t contentflags_t create_detail_solid_contents(const contentflags_t &original) const { throw std::bad_cast(); } + bool contents_are_equal(const contentflags_t &self, const contentflags_t &other) const { throw std::bad_cast(); } + bool contents_are_any_detail(const contentflags_t &) const { throw std::bad_cast(); } bool contents_are_detail_solid(const contentflags_t &contents) const { throw std::bad_cast(); } @@ -88,6 +86,10 @@ struct gamedef_generic_t : public gamedef_t bool contents_are_mirrored(const contentflags_t &) const { throw std::bad_cast(); } + bool contents_are_origin(const contentflags_t &contents) const { throw std::bad_cast(); } + + bool contents_are_clip(const contentflags_t &contents) const { throw std::bad_cast(); } + bool contents_clip_same_type(const contentflags_t &, const contentflags_t &) const { throw std::bad_cast(); } bool contents_are_solid(const contentflags_t &) const { throw std::bad_cast(); } @@ -118,6 +120,19 @@ struct gamedef_generic_t : public gamedef_t const std::vector &get_default_palette() const { throw std::bad_cast(); }; }; +// extra data for contentflags_t for Quake-like +struct q1_contentflags_data +{ + bool origin = false; // is an origin brush + + bool clip = false; // is a clip brush + + constexpr bool operator==(const q1_contentflags_data &other) const { return origin == other.origin && clip == other.clip; } + constexpr bool operator!=(const q1_contentflags_data &other) const { return !(*this == other); } + + constexpr explicit operator bool() const { return origin && clip; } +}; + template struct gamedef_q1_like_t : public gamedef_t { @@ -161,8 +176,6 @@ struct gamedef_q1_like_t : public gamedef_t return create_solid_contents(); } - int32_t get_content_type(const contentflags_t &contents) const { return contents.native; } - int32_t contents_priority(const contentflags_t &contents) const { if (contents.extended & CFLAGS_DETAIL) { @@ -195,7 +208,7 @@ struct gamedef_q1_like_t : public gamedef_t return contents_are_solid(contents) || contents_are_sky(contents) || (contents.extended & CFLAGS_DETAIL); } - contentflags_t create_extended_contents(const uint16_t &cflags) const { return {0, cflags}; } + inline contentflags_t create_extended_contents(const q1_contentflags_data &data) const { return {0, 0, data}; } contentflags_t create_empty_contents(const uint16_t &cflags = 0) const { @@ -238,6 +251,23 @@ struct gamedef_q1_like_t : public gamedef_t return {0, CFLAGS_DETAIL}; } + bool contents_are_equal(const contentflags_t &self, const contentflags_t &other) const + { + if (self.game_data.has_value() != other.game_data.has_value()) { + return false; + } + + if (self.game_data.has_value()) { + if (std::any_cast(self.game_data) != + std::any_cast(other.game_data)) { + return false; + } + } + + return (self.extended & CFLAGS_CONTENTS_MASK) == (other.extended & CFLAGS_CONTENTS_MASK) && + self.native == other.native; + } + bool contents_are_any_detail(const contentflags_t &contents) const { // in Q1, there are only CFLAGS_DETAIL, CFLAGS_DETAIL_ILLUSIONARY, or CFLAGS_DETAIL_FENCE @@ -272,6 +302,16 @@ struct gamedef_q1_like_t : public gamedef_t || (contents.native == CONTENTS_LAVA); } + bool contents_are_origin(const contentflags_t &contents) const + { + return contents.game_data.has_value() && std::any_cast(contents.game_data).origin; + } + + bool contents_are_clip(const contentflags_t &contents) const + { + return contents.game_data.has_value() && std::any_cast(contents.game_data).clip; + } + bool contents_clip_same_type(const contentflags_t &self, const contentflags_t &other) const { return self == other && self.clips_same_type.value_or(true); @@ -281,6 +321,8 @@ struct gamedef_q1_like_t : public gamedef_t { if (contents.extended & CFLAGS_CONTENTS_MASK) return false; + else if (contents.game_data.has_value() && std::any_cast(contents.game_data)) + return false; return contents.native == CONTENTS_EMPTY; } @@ -289,6 +331,8 @@ struct gamedef_q1_like_t : public gamedef_t { if (contents.extended & CFLAGS_CONTENTS_MASK) return false; + else if (contents.game_data.has_value() && std::any_cast(contents.game_data)) + return false; return contents.native == CONTENTS_SOLID; } @@ -297,6 +341,8 @@ struct gamedef_q1_like_t : public gamedef_t { if (contents.extended & CFLAGS_CONTENTS_MASK) return false; + else if (contents.game_data.has_value() && std::any_cast(contents.game_data)) + return false; return contents.native == CONTENTS_SKY; } @@ -305,6 +351,8 @@ struct gamedef_q1_like_t : public gamedef_t { if (contents.extended & CFLAGS_CONTENTS_MASK) return false; + else if (contents.game_data.has_value() && std::any_cast(contents.game_data)) + return false; return contents.native <= CONTENTS_WATER && contents.native >= CONTENTS_LAVA; } @@ -375,11 +423,11 @@ struct gamedef_q1_like_t : public gamedef_t { // check for strong content indicators if (!Q_strcasecmp(texname.data(), "origin")) { - return create_extended_contents(CFLAGS_ORIGIN); + return create_extended_contents({ true, false }); } else if (!Q_strcasecmp(texname.data(), "hint") || !Q_strcasecmp(texname.data(), "hintskip")) { - return create_extended_contents(CFLAGS_HINT); + return create_empty_contents(); } else if (!Q_strcasecmp(texname.data(), "clip")) { - return create_extended_contents(CFLAGS_CLIP); + return create_extended_contents({ false, true }); } else if (texname[0] == '*') { if (!Q_strncasecmp(texname.data() + 1, "lava", 4)) { return create_liquid_contents(CONTENTS_LAVA); @@ -390,10 +438,10 @@ struct gamedef_q1_like_t : public gamedef_t } } 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(); } + + // and anything else is assumed to be a regular solid. + return create_solid_contents(); } void init_filesystem(const fs::path &, const settings::common_settings &) const @@ -551,8 +599,9 @@ struct gamedef_q2_t : public gamedef_t return c; } - int32_t get_content_type(const contentflags_t &contents) const + inline int32_t get_content_type(const contentflags_t &contents) const { + // fixme-brushbsp: should TRANSLUCENT be here? does it need to be? return contents.native & (Q2_ALL_VISIBLE_CONTENTS | (Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP | Q2_CONTENTS_ORIGIN | Q2_CONTENTS_TRANSLUCENT | Q2_CONTENTS_AREAPORTAL)); @@ -587,8 +636,6 @@ struct gamedef_q2_t : public gamedef_t return !!(contents.native & Q2_CONTENTS_SOLID); } - contentflags_t create_extended_contents(const uint16_t &cflags) const { return {0, cflags}; } - contentflags_t create_empty_contents(const uint16_t &cflags) const { return {0, cflags}; } contentflags_t create_solid_contents(const uint16_t &cflags) const { return {Q2_CONTENTS_SOLID, cflags}; } @@ -625,6 +672,12 @@ struct gamedef_q2_t : public gamedef_t return result; } + bool contents_are_equal(const contentflags_t &self, const contentflags_t &other) const + { + return (self.extended & CFLAGS_CONTENTS_MASK) == (other.extended & CFLAGS_CONTENTS_MASK) && + get_content_type(self) == get_content_type(other); + } + bool contents_are_any_detail(const contentflags_t &contents) const { return ((contents.native & Q2_CONTENTS_DETAIL) != 0); @@ -672,6 +725,16 @@ struct gamedef_q2_t : public gamedef_t // contents to default to not mirroring the insides. return !(contents.native & (Q2_CONTENTS_SOLID | Q2_CONTENTS_AUX)); } + + bool contents_are_origin(const contentflags_t &contents) const + { + return contents.native & Q2_CONTENTS_ORIGIN; + } + + bool contents_are_clip(const contentflags_t &contents) const + { + return contents.native & (Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP); + } bool contents_clip_same_type(const contentflags_t &self, const contentflags_t &other) const { @@ -803,10 +866,8 @@ struct gamedef_q2_t : public gamedef_t const std::string &texname, const surfflags_t &flags, const contentflags_t &contents) const { // hints and skips are never detail, and have no content - if (flags.native & Q2_SURF_HINT) { - return {0, CFLAGS_HINT}; - } else if (flags.native & Q2_SURF_SKIP) { - return {0, 0}; + if (flags.native & (Q2_SURF_HINT | Q2_SURF_SKIP)) { + return {Q2_CONTENTS_EMPTY}; } contentflags_t surf_contents = contents; @@ -819,7 +880,7 @@ struct gamedef_q2_t : public gamedef_t // if we have TRANS33 or TRANS66, we have to be marked as WINDOW, // so unset SOLID, give us WINDOW, and give us TRANSLUCENT if (flags.native & (Q2_SURF_TRANS33 | Q2_SURF_TRANS66)) { - surf_contents.native |= Q2_CONTENTS_TRANSLUCENT | Q2_CONTENTS_DETAIL; + 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; @@ -827,21 +888,16 @@ struct gamedef_q2_t : public gamedef_t } // translucent objects are automatically classified as detail - if (surf_contents.native & Q2_CONTENTS_WINDOW) { + if (surf_contents.native & Q2_CONTENTS_TRANSLUCENT) { surf_contents.native |= Q2_CONTENTS_DETAIL; } else if (surf_contents.native & (Q2_CONTENTS_MIST | Q2_CONTENTS_AUX)) { surf_contents.native |= Q2_CONTENTS_DETAIL; } if (surf_contents.native & (Q2_CONTENTS_MONSTERCLIP | Q2_CONTENTS_PLAYERCLIP)) { - surf_contents.extended |= CFLAGS_CLIP; surf_contents.native |= Q2_CONTENTS_DETAIL; } - if (surf_contents.native & Q2_CONTENTS_ORIGIN) { - surf_contents.extended |= CFLAGS_ORIGIN; - } - return surf_contents; } @@ -1125,8 +1181,7 @@ bool surfflags_t::is_valid(const gamedef_t *game) const bool contentflags_t::types_equal(const contentflags_t &other, const gamedef_t *game) const { - return (extended & CFLAGS_CONTENTS_MASK) == (other.extended & CFLAGS_CONTENTS_MASK) && - game->get_content_type(*this) == game->get_content_type(other); + return game->contents_are_equal(*this, other); } int32_t contentflags_t::priority(const gamedef_t *game) const @@ -1194,6 +1249,16 @@ bool contentflags_t::is_valid(const gamedef_t *game, bool strict) const return game->contents_are_valid(*this, strict); } +bool contentflags_t::is_clip(const gamedef_t *game) const +{ + return game->contents_are_clip(*this); +} + +bool contentflags_t::is_origin(const gamedef_t *game) const +{ + return game->contents_are_origin(*this); +} + std::string contentflags_t::to_string(const gamedef_t *game) const { std::string s = game->get_contents_display(*this); @@ -1203,15 +1268,15 @@ std::string contentflags_t::to_string(const gamedef_t *game) const s += fmt::format("|CLIPS_SAME_TYPE[{}]", clips_same_type.has_value() ? (clips_same_type.value() ? "true" : "false") : "nullopt"); - if (extended & CFLAGS_HINT) { - s += "|HINT"; - } - if (extended & CFLAGS_CLIP) { + // FIXME: duped for Q2, move to Q1? + if (is_clip(game)) { s += "|CLIP"; } - if (extended & CFLAGS_ORIGIN) { + if (is_origin(game)) { s += "|ORIGIN"; } + + // FIXME: duped for Q2, move to Q1? if (extended & CFLAGS_DETAIL) { s += "|DETAIL"; } diff --git a/include/common/bspfile.hh b/include/common/bspfile.hh index e72889b3..f487ae7f 100644 --- a/include/common/bspfile.hh +++ b/include/common/bspfile.hh @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -523,6 +524,7 @@ enum q1_contents_t : int32_t // lower bits are stronger, and will eat weaker brushes completely enum q2_contents_t : int32_t { + Q2_CONTENTS_EMPTY = 0, Q2_CONTENTS_SOLID = nth_bit(0), // an eye is never valid in a solid Q2_CONTENTS_WINDOW = nth_bit(1), // translucent, but not watery Q2_CONTENTS_AUX = nth_bit(2), @@ -564,9 +566,6 @@ enum q2_contents_t : int32_t enum extended_cflags_t : uint16_t { // only one of these flags below should ever be set. - CFLAGS_HINT = nth_bit(5), - CFLAGS_CLIP = nth_bit(6), - CFLAGS_ORIGIN = nth_bit(7), CFLAGS_DETAIL = nth_bit(8), CFLAGS_DETAIL_ILLUSIONARY = nth_bit(9), CFLAGS_DETAIL_FENCE = nth_bit(10), @@ -575,7 +574,7 @@ enum extended_cflags_t : uint16_t CFLAGS_DETAIL_MASK = (CFLAGS_DETAIL | CFLAGS_DETAIL_ILLUSIONARY | CFLAGS_DETAIL_FENCE), // all of the special content types CFLAGS_CONTENTS_MASK = - (CFLAGS_HINT | CFLAGS_CLIP | CFLAGS_ORIGIN | CFLAGS_DETAIL_MASK | CFLAGS_ILLUSIONARY_VISBLOCKER) + (CFLAGS_DETAIL_MASK | CFLAGS_ILLUSIONARY_VISBLOCKER) }; struct gamedef_t; @@ -588,6 +587,9 @@ struct contentflags_t // extra flags, specific to BSP only uint16_t extended; + // extra data supplied by the game + std::any game_data; + // the value set directly from `_mirrorinside` on the brush, if available. // don't check this directly, use `is_mirror_inside` to allow the game to decide. std::optional mirror_inside = std::nullopt; @@ -619,7 +621,7 @@ struct contentflags_t bool is_empty(const gamedef_t *game) const; // detail solid or structural solid - bool is_any_solid(const gamedef_t *game) const { + inline bool is_any_solid(const gamedef_t *game) const { return is_solid(game) || is_detail_solid(game); } @@ -629,14 +631,10 @@ struct contentflags_t bool is_sky(const gamedef_t *game) const; bool is_liquid(const gamedef_t *game) const; bool is_valid(const gamedef_t *game, bool strict = true) const; + bool is_clip(const gamedef_t *game) const; + bool is_origin(const gamedef_t *game) const; - constexpr bool is_hint() const { return extended & CFLAGS_HINT; } - - constexpr bool is_clip() const { return extended & CFLAGS_CLIP; } - - constexpr bool is_origin() const { return extended & CFLAGS_ORIGIN; } - - bool is_fence(const gamedef_t *game) const { + inline bool is_fence(const gamedef_t *game) const { return is_detail_fence(game) || is_detail_illusionary(game); } @@ -1810,10 +1808,8 @@ struct gamedef_t // FIXME: fix so that we don't have to pass a name here virtual bool texinfo_is_hintskip(const surfflags_t &flags, const std::string &name) const = 0; virtual contentflags_t cluster_contents(const contentflags_t &contents0, const contentflags_t &contents1) const = 0; - virtual int32_t get_content_type(const contentflags_t &contents) const = 0; virtual int32_t contents_priority(const contentflags_t &contents) const = 0; virtual bool chops(const contentflags_t &) const = 0; - virtual contentflags_t create_extended_contents(const uint16_t &cflags = 0) const = 0; virtual contentflags_t create_empty_contents(const uint16_t &cflags = 0) const = 0; virtual contentflags_t create_solid_contents(const uint16_t &cflags = 0) const = 0; virtual contentflags_t create_sky_contents(const uint16_t &cflags = 0) const = 0; @@ -1821,11 +1817,14 @@ struct gamedef_t virtual contentflags_t create_detail_illusionary_contents(const contentflags_t &original) const = 0; virtual contentflags_t create_detail_fence_contents(const contentflags_t &original) const = 0; virtual contentflags_t create_detail_solid_contents(const contentflags_t &original) const = 0; + virtual bool contents_are_equal(const contentflags_t &self, const contentflags_t &other) const = 0; virtual bool contents_are_any_detail(const contentflags_t &contents) const = 0; virtual bool contents_are_detail_solid(const contentflags_t &contents) const = 0; virtual bool contents_are_detail_fence(const contentflags_t &contents) const = 0; virtual bool contents_are_detail_illusionary(const contentflags_t &contents) const = 0; virtual bool contents_are_mirrored(const contentflags_t &contents) const = 0; + virtual bool contents_are_origin(const contentflags_t &contents) const = 0; + virtual bool contents_are_clip(const contentflags_t &contents) const = 0; virtual bool contents_are_empty(const contentflags_t &contents) const = 0; virtual bool contents_clip_same_type(const contentflags_t &self, const contentflags_t &other) const = 0; virtual bool contents_are_solid(const contentflags_t &contents) const = 0; diff --git a/qbsp/brush.cc b/qbsp/brush.cc index 97d1bb08..edbc0517 100644 --- a/qbsp/brush.cc +++ b/qbsp/brush.cc @@ -333,6 +333,25 @@ void FixRotateOrigin(mapentity_t *entity) SetKeyValue(entity, "origin", qv::to_string(offset).c_str()); } + +static bool Brush_IsHint(const hullbrush_t &brush) +{ + for (auto &f : brush.faces) + if (f.flags.is_hint) + return true; + + return false; +} + +static bool MapBrush_IsHint(const mapbrush_t &brush) +{ + for (size_t i = 0; i < brush.numfaces; i++) + if (brush.face(i).flags.is_hint) + return true; + + return false; +} + /* ================= CreateBrushFaces @@ -354,7 +373,7 @@ static std::vector CreateBrushFaces(const mapentity_t *src, hullbrush_t hullbrush->bounds = {}; for (auto &mapface : hullbrush->faces) { - if (hullnum <= 0 && hullbrush->contents.is_hint()) { + if (hullnum <= 0 && Brush_IsHint(*hullbrush)) { /* Don't generate hintskip faces */ const mtexinfo_t &texinfo = map.mtexinfos.at(mapface.texinfo); @@ -802,7 +821,7 @@ static void Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int for (int i = 0; i < src->nummapbrushes; i++) { const mapbrush_t *mapbrush = &src->mapbrush(i); const contentflags_t contents = Brush_GetContents(mapbrush); - if (contents.is_origin()) { + if (contents.is_origin(options.target_game)) { if (dst == map.world_entity()) { logging::print("WARNING: Ignoring origin brush in worldspawn\n"); continue; @@ -888,7 +907,7 @@ static void Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int detail_fence |= all_detail_fence; /* "origin" brushes always discarded */ - if (contents.is_origin()) + if (contents.is_origin(options.target_game)) continue; /* -omitdetail option omits all types of detail */ @@ -921,7 +940,7 @@ static void Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int * include them in the model bounds so collision detection works * correctly. */ - if (contents.is_clip() && hullnum != HULL_COLLISION) { + if (contents.is_clip(options.target_game) && hullnum != HULL_COLLISION) { if (hullnum == 0) { std::optional brush = LoadBrush(src, mapbrush, contents, rotate_offset, rottype, hullnum); @@ -937,7 +956,7 @@ static void Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int } /* "hint" brushes don't affect the collision hulls */ - if (contents.is_hint()) { + if (MapBrush_IsHint(*mapbrush)) { if (hullnum > 0) continue; contents = options.target_game->create_empty_contents(); diff --git a/qbsp/exportobj.cc b/qbsp/exportobj.cc index 1c3d594f..54eec22b 100644 --- a/qbsp/exportobj.cc +++ b/qbsp/exportobj.cc @@ -71,7 +71,8 @@ static void ExportObjFace(std::ofstream &f, const face_t *face, int *vertcount) // not sure why -v is needed, .obj uses (0, 0) in the top left apparently? fmt::print(f, "vt {:.9} {:.9}\n", uv[0], -uv[1]); } - + + // fixme-brushbsp fmt::print(f, "usemtl contents{}_{}\n", face->contents[0].native, face->contents[0].extended); f << 'f'; for (int i = 0; i < face->w.size(); i++) { @@ -108,8 +109,9 @@ void ExportObj_Faces(const std::string &filesuffix, const std::vectorcontents.is_clip()) { + if (b->contents.is_clip(options.target_game)) { perbrush.contents = -8; } else { perbrush.contents = b->contents.native; @@ -1008,7 +1008,7 @@ static void BSPX_Brushes_AddModel( // perbrush.contents = -16; // break; default: { - if (b->contents.is_clip()) { + if (b->contents.is_clip(options.target_game)) { perbrush.contents = -8; } else { logging::print("WARNING: Unknown contents: {}. Translating to solid.\n", From 0dfeff934a6e9b0c37e869f2278f141555a4bb2f Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sat, 11 Jun 2022 08:38:30 -0400 Subject: [PATCH 5/6] fix test failures introduced from noclipfaces change --- qbsp/brush.cc | 6 +++--- qbsp/csg4.cc | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qbsp/brush.cc b/qbsp/brush.cc index edbc0517..5b1c6874 100644 --- a/qbsp/brush.cc +++ b/qbsp/brush.cc @@ -881,8 +881,8 @@ static void Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int const std::optional mirrorinside = mirrorinside_set ? decltype(mirrorinside)(atoi(ValueForKey(src, "_mirrorinside")) ? true : false) : std::nullopt; /* _noclipfaces */ - const bool noclipfaces_set = *ValueForKey(src, "_mirrorinside"); - const std::optional noclipfaces = mirrorinside_set ? decltype(noclipfaces)(atoi(ValueForKey(src, "_noclipfaces")) ? false : true) : std::nullopt; + const bool noclipfaces_set = *ValueForKey(src, "_noclipfaces"); + const std::optional clipsametype = noclipfaces_set ? decltype(clipsametype)(atoi(ValueForKey(src, "_noclipfaces")) ? false : true) : std::nullopt; const bool func_illusionary_visblocker = (0 == Q_strcasecmp(classname, "func_illusionary_visblocker")); @@ -989,7 +989,7 @@ static void Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int // apply extended flags contents.set_mirrored(mirrorinside); - contents.set_clips_same_type(noclipfaces); + contents.set_clips_same_type(clipsametype); if (func_illusionary_visblocker) { contents.extended |= CFLAGS_ILLUSIONARY_VISBLOCKER; } diff --git a/qbsp/csg4.cc b/qbsp/csg4.cc index 30b9d9b4..4ebec62a 100644 --- a/qbsp/csg4.cc +++ b/qbsp/csg4.cc @@ -426,7 +426,7 @@ 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 == b.contents && a.contents.will_clip_same_type(options.target_game)) { + if (a.contents.will_clip_same_type(options.target_game, b.contents)) { // map file order return a.file_order > b.file_order; } From 13bf99a7f339b43990fc8473df5d7e64c7cdc90d Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sat, 11 Jun 2022 09:19:51 -0400 Subject: [PATCH 6/6] fix test cases (implement equals(game) properly, and `is_empty` is false depending on get_content_type for Q2 now) remove unnecessary condition from Q2 contents_are_empty since areaportals are already considered a valid non-empty type via get_content_type --- common/bspfile.cc | 40 +++++++++++++++++++++++++++------------ include/common/bspfile.hh | 8 ++------ include/common/qvec.hh | 4 ---- qbsp/brush.cc | 2 +- qbsp/merge.cc | 3 ++- 5 files changed, 33 insertions(+), 24 deletions(-) diff --git a/common/bspfile.cc b/common/bspfile.cc index ed1997c0..2b0f1efb 100644 --- a/common/bspfile.cc +++ b/common/bspfile.cc @@ -72,6 +72,8 @@ struct gamedef_generic_t : public gamedef_t contentflags_t create_detail_solid_contents(const contentflags_t &original) const { throw std::bad_cast(); } + bool contents_are_type_equal(const contentflags_t &self, const contentflags_t &other) const { throw std::bad_cast(); } + bool contents_are_equal(const contentflags_t &self, const contentflags_t &other) const { throw std::bad_cast(); } bool contents_are_any_detail(const contentflags_t &) const { throw std::bad_cast(); } @@ -130,7 +132,7 @@ struct q1_contentflags_data constexpr bool operator==(const q1_contentflags_data &other) const { return origin == other.origin && clip == other.clip; } constexpr bool operator!=(const q1_contentflags_data &other) const { return !(*this == other); } - constexpr explicit operator bool() const { return origin && clip; } + constexpr explicit operator bool() const { return origin || clip; } }; template @@ -156,7 +158,7 @@ struct gamedef_q1_like_t : public gamedef_t contentflags_t cluster_contents(const contentflags_t &contents0, const contentflags_t &contents1) const { - if (contents0 == contents1) + if (contents0.equals(this, contents1)) return contents0; /* @@ -251,7 +253,7 @@ struct gamedef_q1_like_t : public gamedef_t return {0, CFLAGS_DETAIL}; } - bool contents_are_equal(const contentflags_t &self, const contentflags_t &other) const + bool contents_are_type_equal(const contentflags_t &self, const contentflags_t &other) const { if (self.game_data.has_value() != other.game_data.has_value()) { return false; @@ -268,6 +270,11 @@ struct gamedef_q1_like_t : public gamedef_t self.native == other.native; } + bool contents_are_equal(const contentflags_t &self, const contentflags_t &other) const + { + return contents_are_type_equal(self, other); + } + bool contents_are_any_detail(const contentflags_t &contents) const { // in Q1, there are only CFLAGS_DETAIL, CFLAGS_DETAIL_ILLUSIONARY, or CFLAGS_DETAIL_FENCE @@ -314,7 +321,7 @@ struct gamedef_q1_like_t : public gamedef_t bool contents_clip_same_type(const contentflags_t &self, const contentflags_t &other) const { - return self == other && self.clips_same_type.value_or(true); + return self.equals(this, other) && self.clips_same_type.value_or(true); } bool contents_are_empty(const contentflags_t &contents) const @@ -377,7 +384,7 @@ struct gamedef_q1_like_t : public gamedef_t bool portal_can_see_through(const contentflags_t &contents0, const contentflags_t &contents1) const { /* If contents values are the same and not solid, can see through */ - return !(contents0.is_solid(this) || contents1.is_solid(this)) && contents0 == contents1; + return !(contents0.is_solid(this) || contents1.is_solid(this)) && contents0.equals(this, contents1); } bool contents_seals_map(const contentflags_t &contents) const override @@ -672,12 +679,18 @@ struct gamedef_q2_t : public gamedef_t return result; } - bool contents_are_equal(const contentflags_t &self, const contentflags_t &other) const + bool contents_are_type_equal(const contentflags_t &self, const contentflags_t &other) const { return (self.extended & CFLAGS_CONTENTS_MASK) == (other.extended & CFLAGS_CONTENTS_MASK) && get_content_type(self) == get_content_type(other); } + bool contents_are_equal(const contentflags_t &self, const contentflags_t &other) const + { + return (self.extended & CFLAGS_CONTENTS_MASK) == (other.extended & CFLAGS_CONTENTS_MASK) && + self.native == other.native; + } + bool contents_are_any_detail(const contentflags_t &contents) const { return ((contents.native & Q2_CONTENTS_DETAIL) != 0); @@ -746,11 +759,7 @@ struct gamedef_q2_t : public gamedef_t if (contents.extended & CFLAGS_CONTENTS_MASK) return false; - if (contents.native & Q2_CONTENTS_AREAPORTAL) - return false; // HACK: needs to return false in order for LinkConvexFaces to assign Q2_CONTENTS_AREAPORTAL - // to the leaf - - return !(contents.native & Q2_ALL_VISIBLE_CONTENTS); + return !get_content_type(contents); } bool contents_are_solid(const contentflags_t &contents) const @@ -1179,9 +1188,16 @@ bool surfflags_t::is_valid(const gamedef_t *game) const return game->surfflags_are_valid(*this); } +bool contentflags_t::equals(const gamedef_t *game, const contentflags_t &other) const +{ + return game->contents_are_equal(*this, other) && + mirror_inside == other.mirror_inside && + clips_same_type == other.clips_same_type; +} + bool contentflags_t::types_equal(const contentflags_t &other, const gamedef_t *game) const { - return game->contents_are_equal(*this, other); + return game->contents_are_type_equal(*this, other); } int32_t contentflags_t::priority(const gamedef_t *game) const diff --git a/include/common/bspfile.hh b/include/common/bspfile.hh index f487ae7f..d14b6ace 100644 --- a/include/common/bspfile.hh +++ b/include/common/bspfile.hh @@ -598,12 +598,7 @@ struct contentflags_t // don't check this directly, use `will_clip_same_type` to allow the game to decide. std::optional clips_same_type = std::nullopt; - constexpr bool operator==(const contentflags_t &other) const - { - return native == other.native && extended == other.extended; - } - - constexpr bool operator!=(const contentflags_t &other) const { return !(*this == other); } + bool equals(const gamedef_t *game, const contentflags_t &other) const; // is any kind of detail? (solid, liquid, etc.) bool is_any_detail(const gamedef_t *game) const; @@ -1817,6 +1812,7 @@ struct gamedef_t virtual contentflags_t create_detail_illusionary_contents(const contentflags_t &original) const = 0; virtual contentflags_t create_detail_fence_contents(const contentflags_t &original) const = 0; virtual contentflags_t create_detail_solid_contents(const contentflags_t &original) const = 0; + virtual bool contents_are_type_equal(const contentflags_t &self, const contentflags_t &other) const = 0; virtual bool contents_are_equal(const contentflags_t &self, const contentflags_t &other) const = 0; virtual bool contents_are_any_detail(const contentflags_t &contents) const = 0; virtual bool contents_are_detail_solid(const contentflags_t &contents) const = 0; diff --git a/include/common/qvec.hh b/include/common/qvec.hh index c0abd81e..1ed37b6c 100644 --- a/include/common/qvec.hh +++ b/include/common/qvec.hh @@ -1134,10 +1134,6 @@ struct twosided // swap the front and back values constexpr void swap() { std::swap(front, back); } - - // equality checks - constexpr bool operator==(const twosided &other) const { return front == other.front && back == other.back; } - constexpr bool operator!=(const twosided &other) const { return !(*this == other); } }; namespace qv { diff --git a/qbsp/brush.cc b/qbsp/brush.cc index 5b1c6874..aadfe8ca 100644 --- a/qbsp/brush.cc +++ b/qbsp/brush.cc @@ -940,7 +940,7 @@ static void Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int * include them in the model bounds so collision detection works * correctly. */ - if (contents.is_clip(options.target_game) && hullnum != HULL_COLLISION) { + if (hullnum != HULL_COLLISION && contents.is_clip(options.target_game)) { if (hullnum == 0) { std::optional brush = LoadBrush(src, mapbrush, contents, rotate_offset, rottype, hullnum); diff --git a/qbsp/merge.cc b/qbsp/merge.cc index 2a5d4181..c045d37e 100644 --- a/qbsp/merge.cc +++ b/qbsp/merge.cc @@ -69,7 +69,8 @@ static face_t *TryMerge(face_t *f1, face_t *f2) bool keep1, keep2; if (!f1->w.size() || !f2->w.size() || f1->planeside != f2->planeside || f1->texinfo != f2->texinfo || - f1->contents != f2->contents || f1->lmshift != f2->lmshift) + !f1->contents[0].equals(options.target_game, f2->contents[0]) || !f1->contents[1].equals(options.target_game, f2->contents[1]) || + f1->lmshift[0] != f2->lmshift[0] || f1->lmshift[1] != f2->lmshift[1]) return NULL; // find a common edge