diff --git a/bsputil/bsputil.cc b/bsputil/bsputil.cc index 5231fc61..973266b6 100644 --- a/bsputil/bsputil.cc +++ b/bsputil/bsputil.cc @@ -495,7 +495,7 @@ static void FindLeaf(const mbsp_t *bsp, const qvec3d &pos) const mleaf_t *leaf = BSP_FindLeafAtPoint(bsp, &bsp->dmodels[0], pos); fmt::print("leaf {}: contents {} ({})\n", (leaf - bsp->dleafs.data()), leaf->contents, - contentflags_t{leaf->contents}.to_string(bsp->loadversion->game)); + bsp->loadversion->game->create_contents_from_native(leaf->contents).to_string(bsp->loadversion->game)); } // map file stuff diff --git a/common/bspfile.cc b/common/bspfile.cc index 6241f9f0..082d62e3 100644 --- a/common/bspfile.cc +++ b/common/bspfile.cc @@ -58,304 +58,97 @@ static std::vector make_palette(std::initializer_list bytes) return result; } +static constexpr const char *bitflag_names[] = { + "SOLID", + "SKY", + "DETAIL_WALL", + "WINDOW", + "AUX", + "LAVA", + "SLIME", + "WATER", + "MIST", + "ORIGIN", + "PLAYERCLIP", + "MONSTERCLIP", + "AREAPORTAL", + "ILLUSIONARY_VISBLOCKER", + "DETAIL", + "MIRROR_INSIDE", + "MIRROR_INSIDE_SET", + "SUPPRESS_CLIPPING_SAME_TYPE", + "CURRENT_0", + "CURRENT_90", + "CURRENT_180", + "CURRENT_270", + "CURRENT_UP", + "CURRENT_DOWN", + "TRANSLUCENT", + "LADDER", + "MONSTER", + "DEADMONSTER", + "Q2_UNUSED_7", + "Q2_UNUSED_8", + "Q2_UNUSED_9", + "Q2_UNUSED_10", + "Q2_UNUSED_11", + "Q2_UNUSED_12", + "Q2_UNUSED_13", + "Q2_UNUSED_14", + "Q2_UNUSED_30", + "Q2_UNUSED_31", + "INVALID_BIT_38", + "INVALID_BIT_39", + "INVALID_BIT_40", + "INVALID_BIT_41", + "INVALID_BIT_42", + "INVALID_BIT_43", + "INVALID_BIT_44", + "INVALID_BIT_45", + "INVALID_BIT_46", + "INVALID_BIT_47", + "INVALID_BIT_48", + "INVALID_BIT_49", + "INVALID_BIT_50", + "INVALID_BIT_51", + "INVALID_BIT_52", + "INVALID_BIT_53", + "INVALID_BIT_54", + "INVALID_BIT_55", + "INVALID_BIT_56", + "INVALID_BIT_57", + "INVALID_BIT_58", + "INVALID_BIT_59", + "INVALID_BIT_60", + "INVALID_BIT_61", + "INVALID_BIT_62", + "INVALID_BIT_63" +}; + +static std::string get_contents_display(contents_t bits) +{ + if (bits == EWT_VISCONTENTS_EMPTY) { + return "EMPTY"; + } + + std::string s; + + for (uint32_t i = 0; i < std::size(bitflag_names); i++) { + if (bits & nth_bit(i)) { + if (!s.empty()) { + s += " | "; + } + + s += bitflag_names[i]; + } + } + + return s; +} + template struct gamedef_q1_like_t : public gamedef_t { -private: - // extra data for contentflags_t for Quake-like - // todo: remove this and contentflags_t::native, and just use q1_contentflags_bits - struct q1_contentflags_data - { - // extended content types. can be combined with native content types - // (e.g. a fence, or mist dipping into water needs to have - // both water and is_fence/is_mist) - bool is_origin = false; - bool is_clip = false; - bool is_wall = false; - bool is_fence = false; - bool is_mist = false; - - // can be combined with any content type including native ones - bool is_detail = false; - - constexpr bool operator==(const q1_contentflags_data &other) const - { - return is_origin == other.is_origin && is_clip == other.is_clip && is_wall == other.is_wall && - is_fence == other.is_fence && is_mist == other.is_mist && is_detail == other.is_detail; - } - - constexpr bool operator!=(const q1_contentflags_data &other) const { return !(*this == other); } - - constexpr explicit operator bool() const - { - return is_origin || is_clip || is_wall || is_fence || is_mist || is_detail; - } - }; - - // returns a blank entry if the given contents don't have - // any game data - inline const q1_contentflags_data &get_data(const contentflags_t &contents) const - { - static constexpr q1_contentflags_data blank_data; - - if (!contents.game_data.has_value()) { - return blank_data; - } - - return std::any_cast(contents.game_data); - } - - // representation of q1 native contents and compiler extended contents, as well as flags, as bit flags - // todo: this should be the only state inside a contentflags_t in q1 mode. - struct q1_contentflags_bits - { - using bitset_t = std::bitset<15>; - - // visible contents - bool solid = false; - bool sky = false; - bool wall = false; // compiler-internal - bool fence = false; // compiler-internal - bool lava = false; - bool slime = false; - bool water = false; - bool mist = false; // compiler-internal - - // non-visible contents - bool origin = false; - bool clip = false; - bool illusionary_visblocker = false; - - // content flags - bool detail = false; - bool mirror_inside = false; - bool mirror_inside_set = false; // whether to use mirror_inside (if false, use content default) - bool suppress_clipping_same_type = false; - - constexpr size_t last_visible_contents() const { return 7; } - constexpr bitset_t bitset() const - { - bitset_t result; - result[0] = solid; - result[1] = sky; - result[2] = wall; - result[3] = fence; - result[4] = lava; - result[5] = slime; - result[6] = water; - result[7] = mist; - result[8] = origin; - result[9] = clip; - result[10] = illusionary_visblocker; - result[11] = detail; - result[12] = mirror_inside; - result[13] = mirror_inside_set; - result[14] = suppress_clipping_same_type; - return result; - } - - q1_contentflags_bits() = default; - explicit q1_contentflags_bits(const bitset_t &bitset) - : solid(bitset[0]), - sky(bitset[1]), - wall(bitset[2]), - fence(bitset[3]), - lava(bitset[4]), - slime(bitset[5]), - water(bitset[6]), - mist(bitset[7]), - origin(bitset[8]), - clip(bitset[9]), - illusionary_visblocker(bitset[10]), - detail(bitset[11]), - mirror_inside(bitset[12]), - mirror_inside_set(bitset[13]), - suppress_clipping_same_type(bitset[14]) - { - } - - static constexpr const char *bitflag_names[] = {"SOLID", "SKY", "WALL", "FENCE", "LAVA", "SLIME", "WATER", - "MIST", "ORIGIN", "CLIP", "ILLUSIONARY_VISBLOCKER", "DETAIL", "MIRROR_INSIDE", "MIRROR_INSIDE_SET", - "SUPPRESS_CLIPPING_SAME_TYPE"}; - - constexpr bool operator[](size_t index) const - { - switch (index) { - case 0: return solid; - case 1: return sky; - case 2: return wall; - case 3: return fence; - case 4: return lava; - case 5: return slime; - case 6: return water; - case 7: return mist; - case 8: return origin; - case 9: return clip; - case 10: return illusionary_visblocker; - case 11: return detail; - case 12: return mirror_inside; - case 13: return mirror_inside_set; - case 14: return suppress_clipping_same_type; - default: throw std::out_of_range("index"); - } - } - - constexpr bool &operator[](size_t index) - { - switch (index) { - case 0: return solid; - case 1: return sky; - case 2: return wall; - case 3: return fence; - case 4: return lava; - case 5: return slime; - case 6: return water; - case 7: return mist; - case 8: return origin; - case 9: return clip; - case 10: return illusionary_visblocker; - case 11: return detail; - case 12: return mirror_inside; - case 13: return mirror_inside_set; - case 14: return suppress_clipping_same_type; - default: throw std::out_of_range("index"); - } - } - - constexpr q1_contentflags_bits operator|(const q1_contentflags_bits &other) const - { - return q1_contentflags_bits(bitset() | other.bitset()); - } - - constexpr q1_contentflags_bits operator^(const q1_contentflags_bits &other) const - { - return q1_contentflags_bits(bitset() ^ other.bitset()); - } - - constexpr bool operator==(const q1_contentflags_bits &other) const { return bitset() == other.bitset(); } - - constexpr bool operator!=(const q1_contentflags_bits &other) const { return !(*this == other); } - - constexpr int32_t visible_contents_index() const - { - for (size_t i = 0; i <= last_visible_contents(); ++i) { - if ((*this)[i]) { - return static_cast(i); - } - } - - return -1; - } - - constexpr q1_contentflags_bits visible_contents() const - { - q1_contentflags_bits result; - - int32_t index = visible_contents_index(); - if (index != -1) { - result[index] = true; - } - - return result; - } - - constexpr bool all_empty() const - { - q1_contentflags_bits empty_test; - return (*this) == empty_test; - } - - /** - * Contents (excluding non-contents flags) are all unset - */ - constexpr bool contents_empty() const - { - q1_contentflags_bits this_test = *this; - - // clear flags - this_test.detail = false; - this_test.mirror_inside = false; - this_test.mirror_inside_set = false; - this_test.suppress_clipping_same_type = false; - - q1_contentflags_bits empty_test; - return this_test == empty_test; - } - }; - - inline q1_contentflags_bits contentflags_to_bits(const contentflags_t &contents) const - { - q1_contentflags_bits result; - - // set bit for native contents - switch (contents.native) { - case CONTENTS_SOLID: result.solid = true; break; - case CONTENTS_WATER: result.water = true; break; - case CONTENTS_SLIME: result.slime = true; break; - case CONTENTS_LAVA: result.lava = true; break; - case CONTENTS_SKY: result.sky = true; break; - } - - // copy in extra flags - auto &data = get_data(contents); - result.origin = data.is_origin; - result.clip = data.is_clip; - result.wall = data.is_wall; - result.fence = data.is_fence; - result.mist = data.is_mist; - result.detail = data.is_detail; - - result.illusionary_visblocker = contents.illusionary_visblocker; - result.mirror_inside_set = contents.mirror_inside.has_value(); - result.mirror_inside = contents.mirror_inside.value_or(false); - result.suppress_clipping_same_type = !contents.clips_same_type.value_or(true); - - return result; - } - - inline contentflags_t contentflags_from_bits(const q1_contentflags_bits &bits) const - { - contentflags_t result; - - // set native contents - if (bits.solid) { - result.native = CONTENTS_SOLID; - } else if (bits.sky) { - result.native = CONTENTS_SKY; - } else if (bits.water) { - result.native = CONTENTS_WATER; - } else if (bits.slime) { - result.native = CONTENTS_SLIME; - } else if (bits.lava) { - result.native = CONTENTS_LAVA; - } else { - result.native = CONTENTS_EMPTY; - } - - // copy in extra flags - q1_contentflags_data data; - data.is_origin = bits.origin; - data.is_clip = bits.clip; - data.is_wall = bits.wall; - data.is_fence = bits.fence; - data.is_mist = bits.mist; - data.is_detail = bits.detail; - - if (data) { - result.game_data = data; - } - - result.illusionary_visblocker = bits.illusionary_visblocker; - if (bits.mirror_inside_set) { - result.mirror_inside = bits.mirror_inside; - } else { - result.mirror_inside = std::nullopt; - } - result.clips_same_type = !bits.suppress_clipping_same_type; - - return result; - } - public: explicit gamedef_q1_like_t(const char *friendly_name = "quake", const char *base_dir = "ID1") : gamedef_t(friendly_name, base_dir) @@ -414,79 +207,99 @@ public: return !string_iequals(name, "hint"); } + contentflags_t create_contents_from_native(int32_t native) const override { + switch (native) { + case CONTENTS_SOLID: + return contentflags_t::make(EWT_VISCONTENTS_SOLID); + case CONTENTS_SKY: + return contentflags_t::make(EWT_VISCONTENTS_SKY); + case CONTENTS_LAVA: + return contentflags_t::make(EWT_VISCONTENTS_LAVA); + case CONTENTS_SLIME: + return contentflags_t::make(EWT_VISCONTENTS_SLIME); + case CONTENTS_WATER: + return contentflags_t::make(EWT_VISCONTENTS_WATER); + case CONTENTS_EMPTY: + return contentflags_t::make(EWT_VISCONTENTS_EMPTY); + } + throw std::invalid_argument( + fmt::format("create_contents_from_native: unknown Q1 contents {}", native)); + } + + int32_t contents_to_native(const contentflags_t &contents) const override { + if (contents.flags & EWT_VISCONTENTS_SOLID) { + return CONTENTS_SOLID; + } else if (contents.flags & EWT_VISCONTENTS_SKY) { + return CONTENTS_SKY; + } else if (contents.flags & EWT_VISCONTENTS_DETAIL_WALL) { + throw std::invalid_argument("EWT_VISCONTENTS_DETAIL_WALL not representable in Q1"); + } else if (contents.flags & EWT_VISCONTENTS_WINDOW) { + throw std::invalid_argument("EWT_VISCONTENTS_WINDOW not representable in Q1"); + } else if (contents.flags & EWT_VISCONTENTS_AUX) { + throw std::invalid_argument("EWT_VISCONTENTS_AUX not representable in Q1"); + } else if (contents.flags & EWT_VISCONTENTS_LAVA) { + return CONTENTS_LAVA; + } else if (contents.flags & EWT_VISCONTENTS_SLIME) { + return CONTENTS_SLIME; + } else if (contents.flags & EWT_VISCONTENTS_WATER) { + return CONTENTS_WATER; + } else if (contents.flags & EWT_VISCONTENTS_MIST) { + throw std::invalid_argument("EWT_VISCONTENTS_MIST not representable in Q1"); + } + return CONTENTS_EMPTY; + } + contentflags_t cluster_contents(const contentflags_t &contents0, const contentflags_t &contents1) const override { - const auto bits0 = contentflags_to_bits(contents0); - const auto bits1 = contentflags_to_bits(contents1); - - auto combined = bits0 | bits1; + contents_int_t combined = contents0.flags | contents1.flags; // a cluster may include some solid detail areas, but // still be seen into - if (!bits0.solid || !bits1.solid) { - combined.solid = false; + if (!(contents0.flags & EWT_VISCONTENTS_SOLID) || !(contents1.flags & EWT_VISCONTENTS_SOLID)) { + combined &= ~EWT_VISCONTENTS_SOLID; } - return contentflags_from_bits(combined); + return contentflags_t::make(combined); } contentflags_t create_empty_contents() const override { - q1_contentflags_bits result; - return contentflags_from_bits(result); + return contentflags_t::make(EWT_VISCONTENTS_EMPTY); } contentflags_t create_solid_contents() const override { - q1_contentflags_bits result; - result.solid = true; - return contentflags_from_bits(result); + return contentflags_t::make(EWT_VISCONTENTS_SOLID); } contentflags_t create_detail_illusionary_contents(const contentflags_t &original) const override { - q1_contentflags_bits result; - result.mist = true; - result.detail = true; - return contentflags_from_bits(result); + return contentflags_t::make(EWT_VISCONTENTS_MIST | EWT_CFLAG_DETAIL); } contentflags_t create_detail_fence_contents(const contentflags_t &original) const override { - q1_contentflags_bits result; - result.fence = true; - result.detail = true; - return contentflags_from_bits(result); + return contentflags_t::make(EWT_VISCONTENTS_WINDOW | EWT_CFLAG_DETAIL); } contentflags_t create_detail_wall_contents(const contentflags_t &original) const override { - q1_contentflags_bits result; - result.wall = true; - result.detail = true; - return contentflags_from_bits(result); + return contentflags_t::make(EWT_VISCONTENTS_DETAIL_WALL | EWT_CFLAG_DETAIL); } contentflags_t create_detail_solid_contents(const contentflags_t &original) const override { - q1_contentflags_bits result; - result.solid = true; - result.detail = true; - return contentflags_from_bits(result); + return contentflags_t::make(EWT_VISCONTENTS_SOLID | EWT_CFLAG_DETAIL); } contentflags_t clear_detail(const contentflags_t &original) const override { - auto bits = contentflags_to_bits(original); - bits.detail = false; - return contentflags_from_bits(bits); + return contentflags_t::make(original.flags & (~EWT_CFLAG_DETAIL)); } contentflags_t set_detail(const contentflags_t &original) const override { - auto bits = contentflags_to_bits(original); - bits.detail = true; - return contentflags_from_bits(bits); + return contentflags_t::make(original.flags | EWT_CFLAG_DETAIL); } bool contents_are_type_equal(const contentflags_t &self, const contentflags_t &other) const override @@ -495,114 +308,108 @@ public: // is it checking for equality of visible content bits (in q2 terminology)? // same highest-priority visible content bit? - return contentflags_to_bits(self) == contentflags_to_bits(other); + return self.flags == other.flags; } bool contents_are_equal(const contentflags_t &self, const contentflags_t &other) const override { - // fixme-brushbsp: document what this is supposed to do, remove if unneeded? - return contents_are_type_equal(self, other); + return self.flags == other.flags; } bool contents_are_any_detail(const contentflags_t &contents) const override { - return contentflags_to_bits(contents).detail; + return (contents.flags & EWT_CFLAG_DETAIL) != 0; } bool contents_are_detail_solid(const contentflags_t &contents) const override { // fixme-brushbsp: document whether this is an exclusive test (i.e. what does it return for water|solid|detail) - const auto bits = contentflags_to_bits(contents); - return bits.detail && bits.solid; + return (contents.flags & EWT_CFLAG_DETAIL) + && (contents.flags & EWT_VISCONTENTS_SOLID); } bool contents_are_detail_wall(const contentflags_t &contents) const override { // fixme-brushbsp: document whether this is an exclusive test (i.e. what does it return for water|fence|detail) - const auto bits = contentflags_to_bits(contents); - return bits.detail && bits.wall; + return (contents.flags & EWT_CFLAG_DETAIL) + && (contents.flags & EWT_VISCONTENTS_DETAIL_WALL); } bool contents_are_detail_fence(const contentflags_t &contents) const override { // fixme-brushbsp: document whether this is an exclusive test (i.e. what does it return for water|fence|detail) - const auto bits = contentflags_to_bits(contents); - return bits.detail && bits.fence; + return (contents.flags & EWT_CFLAG_DETAIL) + && (contents.flags & EWT_VISCONTENTS_WINDOW); + } bool contents_are_detail_illusionary(const contentflags_t &contents) const override { // fixme-brushbsp: document whether this is an exclusive test (i.e. what does it return for water|mist|detail) - const auto bits = contentflags_to_bits(contents); - return bits.detail && bits.mist; + return (contents.flags & EWT_CFLAG_DETAIL) + && (contents.flags & EWT_VISCONTENTS_MIST); } bool contents_are_origin(const contentflags_t &contents) const override { // fixme-brushbsp: document whether this is an exclusive test (i.e. what does it return for water|origin) - const auto bits = contentflags_to_bits(contents); - return bits.origin; + return (contents.flags & EWT_INVISCONTENTS_ORIGIN) != 0; } bool contents_are_clip(const contentflags_t &contents) const override { // fixme-brushbsp: document whether this is an exclusive test (i.e. what does it return for water|clip) - const auto bits = contentflags_to_bits(contents); - return bits.clip; + return (contents.flags & EWT_INVISCONTENTS_PLAYERCLIP) != 0; } bool contents_clip_same_type(const contentflags_t &self, const contentflags_t &other) const override { - return self.equals(this, other) && self.clips_same_type.value_or(true); + if (!self.equals(this, other)) + return false; + + // they're equal types... + + if (self.flags & EWT_CFLAG_SUPPRESS_CLIPPING_SAME_TYPE) + return false; // don't clip + + return true; } bool contents_are_empty(const contentflags_t &contents) const override { - const auto bits = contentflags_to_bits(contents); - return bits.contents_empty(); + if (contents.flags & EWT_INVISCONTENTS_ORIGIN) + return false; + if (contents.flags & EWT_INVISCONTENTS_PLAYERCLIP) + return false; + return (contents.flags & EWT_ALL_VISIBLE_CONTENTS) == 0; } bool contents_are_any_solid(const contentflags_t &contents) const override { - const auto bits = contentflags_to_bits(contents); - return bits.solid; + return (contents.flags & EWT_VISCONTENTS_SOLID) != 0; } // fixme-brushbsp: this is a leftover from q1 tools, and not really used in qbsp3, remove if possible bool contents_are_solid(const contentflags_t &contents) const override { - const auto bits = contentflags_to_bits(contents); - return bits.solid && !bits.detail; + return (contents.flags & EWT_VISCONTENTS_SOLID) + && !(contents.flags & EWT_CFLAG_DETAIL); } bool contents_are_sky(const contentflags_t &contents) const override { - const auto bits = contentflags_to_bits(contents); - return bits.sky; + return (contents.flags & EWT_VISCONTENTS_SKY); } bool contents_are_liquid(const contentflags_t &contents) const override { - const auto bits = contentflags_to_bits(contents); - return bits.water || bits.lava || bits.slime; + return (contents.visible_contents().flags & EWT_ALL_LIQUIDS) != 0; } bool contents_are_valid(const contentflags_t &contents, bool strict) const override { // fixme-brushbsp: document exactly what this is supposed to do - if (!contents.native && !strict) { - return true; - } - - switch (contents.native) { - case CONTENTS_EMPTY: - case CONTENTS_SOLID: - case CONTENTS_WATER: - case CONTENTS_SLIME: - case CONTENTS_LAVA: - case CONTENTS_SKY: return true; - default: return false; - } + return true; } int32_t contents_from_string(const std::string_view &str) const override @@ -614,29 +421,29 @@ public: bool portal_can_see_through( const contentflags_t &contents0, const contentflags_t &contents1, bool transwater, bool transsky) const override { - auto bits_a = contentflags_to_bits(contents0); - auto bits_b = contentflags_to_bits(contents1); + contents_int_t bits_a = contents0.flags; + contents_int_t bits_b = contents1.flags; // can't see through solid - if (bits_a.solid || bits_b.solid) { + if ((bits_a & EWT_VISCONTENTS_SOLID) || (bits_b & EWT_VISCONTENTS_SOLID)) { return false; } - bool a_translucent = transwater ? (bits_a.water || bits_a.slime || bits_a.lava) : false; - bool b_translucent = transwater ? (bits_b.water || bits_b.slime || bits_b.lava) : false; + bool a_translucent = transwater ? ((bits_a & EWT_ALL_LIQUIDS) != 0) : false; + bool b_translucent = transwater ? ((bits_b & EWT_ALL_LIQUIDS) != 0) : false; - if ((bits_a ^ bits_b).visible_contents().all_empty()) + if (((bits_a ^ bits_b) & EWT_ALL_VISIBLE_CONTENTS) == 0) return true; - if (bits_a.detail || a_translucent) - bits_a = q1_contentflags_bits(); - if (bits_b.detail || b_translucent) - bits_b = q1_contentflags_bits(); + if ((bits_a & EWT_CFLAG_DETAIL) || a_translucent) + bits_a = 0; + if ((bits_b & EWT_CFLAG_DETAIL) || b_translucent) + bits_b = 0; - if ((bits_a ^ bits_b).all_empty()) + if ((bits_a ^ bits_b) == 0) return true; // identical on both sides - if ((bits_a ^ bits_b).visible_contents().all_empty()) + if (((bits_a ^ bits_b) & EWT_ALL_VISIBLE_CONTENTS) == 0) return true; return false; } @@ -648,16 +455,16 @@ public: bool contents_are_opaque(const contentflags_t &contents, bool transwater) const override { - auto bits = contentflags_to_bits(contents).visible_contents(); + auto bits = contents.visible_contents().flags; - if (bits.solid) return true; - else if (bits.sky) return true; - else if (bits.wall) return true; - else if (bits.fence) return false; - else if (bits.lava) return !transwater; - else if (bits.slime) return !transwater; - else if (bits.water) return !transwater; - else if (bits.mist) return false; + if (bits == EWT_VISCONTENTS_SOLID) return true; + else if (bits == EWT_VISCONTENTS_SKY) return true; + else if (bits == EWT_VISCONTENTS_DETAIL_WALL) return true; + else if (bits == EWT_VISCONTENTS_WINDOW) return false; + else if (bits == EWT_VISCONTENTS_LAVA) return !transwater; + else if (bits == EWT_VISCONTENTS_SLIME) return !transwater; + else if (bits == EWT_VISCONTENTS_WATER) return !transwater; + else if (bits == EWT_VISCONTENTS_MIST) return false; return false; } @@ -673,100 +480,84 @@ public: if (contents_are_detail_fence(contents) || contents_are_detail_wall(contents)) { return create_solid_contents(); } + if (contents.flags & EWT_VISCONTENTS_MIST) { + // clear mist. detail_illusionary on its own becomes CONTENTS_EMPTY, + // detail_illusionary in water becomes CONTENTS_WATER, etc. + return contentflags_t::make(contents.flags & ~EWT_VISCONTENTS_MIST); + } return contents; } contentflags_t combine_contents(const contentflags_t &a, const contentflags_t &b) const override { - auto bits_a = contentflags_to_bits(a); - auto bits_b = contentflags_to_bits(b); + auto bits_a = a.flags; + auto bits_b = b.flags; if (contents_are_solid(a) || contents_are_solid(b)) { return create_solid_contents(); } if (contents_are_sky(a) || contents_are_sky(b)) { - return contentflags_t{CONTENTS_SKY}; + return contentflags_t::make(EWT_VISCONTENTS_SKY); } - return contentflags_from_bits(bits_a | bits_b); + return contentflags_t::make(bits_a | bits_b); } contentflags_t portal_visible_contents(const contentflags_t &a, const contentflags_t &b) const override { - auto bits_a = contentflags_to_bits(a); - auto bits_b = contentflags_to_bits(b); + auto bits_a = a.flags; + auto bits_b = b.flags; // aviods spamming "sides not found" warning on Q1 maps with sky - if ((bits_a.solid || bits_a.sky) && (bits_b.solid || bits_b.sky)) + if ((bits_a & (EWT_VISCONTENTS_SOLID | EWT_VISCONTENTS_SKY)) + && (bits_b & (EWT_VISCONTENTS_SOLID | EWT_VISCONTENTS_SKY))) return create_empty_contents(); - q1_contentflags_bits result; + contents_int_t result; - if (bits_a.suppress_clipping_same_type || bits_b.suppress_clipping_same_type) { + if ((bits_a & EWT_CFLAG_SUPPRESS_CLIPPING_SAME_TYPE) || (bits_b & EWT_CFLAG_SUPPRESS_CLIPPING_SAME_TYPE)) { result = bits_a | bits_b; } else { result = bits_a ^ bits_b; } - auto strongest_contents_change = result.visible_contents(); + auto strongest_contents_change = contentflags_t::make(result).visible_contents(); - return contentflags_from_bits(strongest_contents_change); + return strongest_contents_change; } bool portal_generates_face(const contentflags_t &portal_visible_contents, const contentflags_t &brushcontents, planeside_t brushside_side) const override { - auto bits_portal = contentflags_to_bits(portal_visible_contents); - auto bits_brush = contentflags_to_bits(brushcontents); + auto bits_portal = portal_visible_contents.flags; + auto bits_brush = brushcontents.flags; // find the highest visible content bit set in portal - int32_t index = bits_portal.visible_contents_index(); + int32_t index = portal_visible_contents.visible_contents_index(); if (index == -1) { return false; } - // check if it's also set in the brush - if (!bits_brush[index]) { + // check if it's not set in the brush + if (!(bits_brush & nth_bit(index))) { return false; } if (brushside_side == SIDE_BACK) { // explicit override? - if (brushcontents.mirror_inside) { - return *brushcontents.mirror_inside; + if (bits_brush & EWT_CFLAG_MIRROR_INSIDE_SET) { + return (bits_brush & EWT_CFLAG_MIRROR_INSIDE) != 0; } - return bits_brush.mirror_inside || bits_brush.water || bits_brush.slime || bits_brush.lava; + return (bits_brush & EWT_ALL_LIQUIDS) != 0; } return true; } - inline std::string get_contents_display(const q1_contentflags_bits &bits) const - { - if (bits.all_empty()) { - return "EMPTY"; - } - - std::string s; - - for (int32_t i = 0; i < std::size(q1_contentflags_bits::bitflag_names); i++) { - if (bits[i]) { - if (!s.empty()) { - s += " | "; - } - - s += q1_contentflags_bits::bitflag_names[i]; - } - } - - return s; - } - std::string get_contents_display(const contentflags_t &contents) const override { - const auto bits = contentflags_to_bits(contents); - return get_contents_display(bits); + return ::get_contents_display(contents.flags); } void contents_make_valid(contentflags_t &contents) const override @@ -775,7 +566,7 @@ public: // todo: anything smarter we can do here? // think this can't even happen in Q1 anyways if (!contents_are_valid(contents, false)) { - contents = {CONTENTS_SOLID}; + contents = contentflags_t::make(EWT_VISCONTENTS_SOLID); } } @@ -792,33 +583,21 @@ public: { // check for strong content indicators if (!Q_strcasecmp(texname.data(), "origin")) { - q1_contentflags_bits result; - result.origin = true; - return contentflags_from_bits(result); + return contentflags_t::make(EWT_INVISCONTENTS_ORIGIN); } else if (!Q_strcasecmp(texname.data(), "hint") || !Q_strcasecmp(texname.data(), "hintskip")) { return create_empty_contents(); } else if (!Q_strcasecmp(texname.data(), "clip")) { - q1_contentflags_bits result; - result.clip = true; - return contentflags_from_bits(result); + return contentflags_t::make(EWT_INVISCONTENTS_PLAYERCLIP); } else if (texname[0] == '*') { if (!Q_strncasecmp(texname.data() + 1, "lava", 4)) { - q1_contentflags_bits result; - result.lava = true; - return contentflags_from_bits(result); + return contentflags_t::make(EWT_VISCONTENTS_LAVA); } else if (!Q_strncasecmp(texname.data() + 1, "slime", 5)) { - q1_contentflags_bits result; - result.slime = true; - return contentflags_from_bits(result); + return contentflags_t::make(EWT_VISCONTENTS_SLIME); } else { - q1_contentflags_bits result; - result.water = true; - return contentflags_from_bits(result); + return contentflags_t::make(EWT_VISCONTENTS_WATER); } } else if (!Q_strncasecmp(texname.data(), "sky", 3)) { - q1_contentflags_bits result; - result.sky = true; - return contentflags_from_bits(result); + return contentflags_t::make(EWT_VISCONTENTS_SKY); } // and anything else is assumed to be a regular solid. @@ -889,8 +668,7 @@ private: struct content_stats_t : public content_stats_base_t { std::mutex stat_mutex; - std::unordered_map native_types; - + std::unordered_map native_types; std::atomic total_brushes; }; @@ -904,12 +682,9 @@ public: { content_stats_t &stats = dynamic_cast(stats_any); - // convert to std::bitset so we can use it as an unordered_map key - const auto bitset = contentflags_to_bits(contents).bitset(); - { std::unique_lock lock(stats.stat_mutex); - stats.native_types[bitset]++; + stats.native_types[contents.flags]++; } stats.total_brushes++; @@ -921,7 +696,8 @@ public: logging::stat_tracker_t stat_print; for (auto [bits, count] : stats.native_types) { - stat_print.register_stat(fmt::format("{} {}", get_contents_display(q1_contentflags_bits(bits)), what)) + auto c = contentflags_t{.flags = bits}; + stat_print.register_stat(fmt::format("{} {}", get_contents_display(c), what)) .count += count; } @@ -1082,152 +858,240 @@ struct gamedef_q2_t : public gamedef_t return !(flags.native & Q2_SURF_HINT); } + contentflags_t create_contents_from_native(int32_t native) const override { + contents_int_t result = 0; + + // visible contents + if (native & Q2_CONTENTS_SOLID) result |= EWT_VISCONTENTS_SOLID; + if (native & Q2_CONTENTS_WINDOW) result |= EWT_VISCONTENTS_WINDOW; + if (native & Q2_CONTENTS_AUX) result |= EWT_VISCONTENTS_AUX; + if (native & Q2_CONTENTS_LAVA) result |= EWT_VISCONTENTS_LAVA; + if (native & Q2_CONTENTS_SLIME) result |= EWT_VISCONTENTS_SLIME; + if (native & Q2_CONTENTS_WATER) result |= EWT_VISCONTENTS_WATER; + if (native & Q2_CONTENTS_MIST) result |= EWT_VISCONTENTS_MIST; + + // invisible contents + if (native & Q2_CONTENTS_AREAPORTAL) result |= EWT_INVISCONTENTS_AREAPORTAL; + if (native & Q2_CONTENTS_PLAYERCLIP) result |= EWT_INVISCONTENTS_PLAYERCLIP; + if (native & Q2_CONTENTS_MONSTERCLIP) result |= EWT_INVISCONTENTS_MONSTERCLIP; + if (native & Q2_CONTENTS_ORIGIN) result |= EWT_INVISCONTENTS_ORIGIN; + + // contents flags + if (native & Q2_CONTENTS_CURRENT_0) result |= EWT_CFLAG_CURRENT_0; + if (native & Q2_CONTENTS_CURRENT_90) result |= EWT_CFLAG_CURRENT_90; + if (native & Q2_CONTENTS_CURRENT_180) result |= EWT_CFLAG_CURRENT_180; + if (native & Q2_CONTENTS_CURRENT_270) result |= EWT_CFLAG_CURRENT_270; + if (native & Q2_CONTENTS_CURRENT_UP) result |= EWT_CFLAG_CURRENT_UP; + if (native & Q2_CONTENTS_CURRENT_DOWN) result |= EWT_CFLAG_CURRENT_DOWN; + if (native & Q2_CONTENTS_DETAIL) result |= EWT_CFLAG_DETAIL; + if (native & Q2_CONTENTS_TRANSLUCENT) result |= EWT_CFLAG_TRANSLUCENT; + if (native & Q2_CONTENTS_LADDER) result |= EWT_CFLAG_LADDER; + + // disallowed flags + if (native & Q2_CONTENTS_MONSTER) result |= EWT_CFLAG_MONSTER; + if (native & Q2_CONTENTS_DEADMONSTER) result |= EWT_CFLAG_DEADMONSTER; + + // other unused flags which are illegal + if (native & Q2_CONTENTS_UNUSED_7) result |= EWT_CFLAG_Q2_UNUSED_7; + if (native & Q2_CONTENTS_UNUSED_8) result |= EWT_CFLAG_Q2_UNUSED_8; + if (native & Q2_CONTENTS_UNUSED_9) result |= EWT_CFLAG_Q2_UNUSED_9; + if (native & Q2_CONTENTS_UNUSED_10) result |= EWT_CFLAG_Q2_UNUSED_10; + if (native & Q2_CONTENTS_UNUSED_11) result |= EWT_CFLAG_Q2_UNUSED_11; + if (native & Q2_CONTENTS_UNUSED_12) result |= EWT_CFLAG_Q2_UNUSED_12; + if (native & Q2_CONTENTS_UNUSED_13) result |= EWT_CFLAG_Q2_UNUSED_13; + if (native & Q2_CONTENTS_UNUSED_14) result |= EWT_CFLAG_Q2_UNUSED_14; + if (native & Q2_CONTENTS_UNUSED_30) result |= EWT_CFLAG_Q2_UNUSED_30; + if (native & Q2_CONTENTS_UNUSED_31) result |= EWT_CFLAG_Q2_UNUSED_31; + + return contentflags_t::make(result); + } + + int32_t contents_to_native(const contentflags_t &contents) const override { + int32_t result = 0; + + if (contents.flags & EWT_VISCONTENTS_SOLID) result |= Q2_CONTENTS_SOLID; + if (contents.flags & EWT_VISCONTENTS_SKY) throw std::invalid_argument("sky not a contents in Q2"); + if (contents.flags & EWT_VISCONTENTS_DETAIL_WALL) throw std::invalid_argument("detail wall not a contents in Q2"); + if (contents.flags & EWT_VISCONTENTS_WINDOW) result |= Q2_CONTENTS_WINDOW; + if (contents.flags & EWT_VISCONTENTS_AUX) result |= Q2_CONTENTS_AUX; + if (contents.flags & EWT_VISCONTENTS_LAVA) result |= Q2_CONTENTS_LAVA; + if (contents.flags & EWT_VISCONTENTS_SLIME) result |= Q2_CONTENTS_SLIME; + if (contents.flags & EWT_VISCONTENTS_WATER) result |= Q2_CONTENTS_WATER; + if (contents.flags & EWT_VISCONTENTS_MIST) result |= Q2_CONTENTS_MIST; + if (contents.flags & EWT_INVISCONTENTS_ORIGIN) result |= Q2_CONTENTS_ORIGIN; + if (contents.flags & EWT_INVISCONTENTS_PLAYERCLIP) result |= Q2_CONTENTS_PLAYERCLIP; + if (contents.flags & EWT_INVISCONTENTS_MONSTERCLIP) result |= Q2_CONTENTS_MONSTERCLIP; + if (contents.flags & EWT_INVISCONTENTS_AREAPORTAL) result |= Q2_CONTENTS_AREAPORTAL; + if (contents.flags & EWT_INVISCONTENTS_ILLUSIONARY_VISBLOCKER) throw std::invalid_argument("illusionary visblocker not a contents in Q2"); + if (contents.flags & EWT_CFLAG_DETAIL) result |= Q2_CONTENTS_DETAIL; + + // cflags + if (contents.flags & EWT_CFLAG_CURRENT_0) result |= Q2_CONTENTS_CURRENT_0; + if (contents.flags & EWT_CFLAG_CURRENT_90) result |= Q2_CONTENTS_CURRENT_90; + if (contents.flags & EWT_CFLAG_CURRENT_180) result |= Q2_CONTENTS_CURRENT_180; + if (contents.flags & EWT_CFLAG_CURRENT_270) result |= Q2_CONTENTS_CURRENT_270; + if (contents.flags & EWT_CFLAG_CURRENT_UP) result |= Q2_CONTENTS_CURRENT_UP; + if (contents.flags & EWT_CFLAG_CURRENT_DOWN) result |= Q2_CONTENTS_CURRENT_DOWN; + if (contents.flags & EWT_CFLAG_TRANSLUCENT) result |= Q2_CONTENTS_TRANSLUCENT; + if (contents.flags & EWT_CFLAG_LADDER) result |= Q2_CONTENTS_LADDER; + if (contents.flags & EWT_CFLAG_MONSTER) result |= Q2_CONTENTS_MONSTER; + if (contents.flags & EWT_CFLAG_DEADMONSTER) result |= Q2_CONTENTS_DEADMONSTER; + if (contents.flags & EWT_CFLAG_Q2_UNUSED_7) result |= Q2_CONTENTS_UNUSED_7; + if (contents.flags & EWT_CFLAG_Q2_UNUSED_8) result |= Q2_CONTENTS_UNUSED_8; + if (contents.flags & EWT_CFLAG_Q2_UNUSED_9) result |= Q2_CONTENTS_UNUSED_9; + if (contents.flags & EWT_CFLAG_Q2_UNUSED_10) result |= Q2_CONTENTS_UNUSED_10; + if (contents.flags & EWT_CFLAG_Q2_UNUSED_11) result |= Q2_CONTENTS_UNUSED_11; + if (contents.flags & EWT_CFLAG_Q2_UNUSED_12) result |= Q2_CONTENTS_UNUSED_12; + if (contents.flags & EWT_CFLAG_Q2_UNUSED_13) result |= Q2_CONTENTS_UNUSED_13; + if (contents.flags & EWT_CFLAG_Q2_UNUSED_14) result |= Q2_CONTENTS_UNUSED_14; + if (contents.flags & EWT_CFLAG_Q2_UNUSED_30) result |= Q2_CONTENTS_UNUSED_30; + if (contents.flags & EWT_CFLAG_Q2_UNUSED_31) result |= Q2_CONTENTS_UNUSED_31; + + return result; + } + contentflags_t cluster_contents(const contentflags_t &contents0, const contentflags_t &contents1) const override { - contentflags_t c = {contents0.native | contents1.native}; - - c.illusionary_visblocker = contents0.illusionary_visblocker || contents1.illusionary_visblocker; + contents_int_t combined = contents0.flags | contents1.flags; // a cluster may include some solid detail areas, but // still be seen into - if (!(contents0.native & Q2_CONTENTS_SOLID) || !(contents1.native & Q2_CONTENTS_SOLID)) { - c.native &= ~Q2_CONTENTS_SOLID; + if (!(contents0.flags & EWT_VISCONTENTS_SOLID) || !(contents1.flags & EWT_VISCONTENTS_SOLID)) { + combined &= ~EWT_VISCONTENTS_SOLID; } - return c; + return contentflags_t::make(combined); } inline int32_t get_content_type(const contentflags_t &contents) const { - // HACK: Q2_CONTENTS_MONSTER is only here for func_detail_wall - return contents.native & (Q2_ALL_VISIBLE_CONTENTS_PLUS_MONSTER | - (Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP | Q2_CONTENTS_ORIGIN | - Q2_CONTENTS_TRANSLUCENT | Q2_CONTENTS_AREAPORTAL)); + return contents.flags & (EWT_ALL_VISIBLE_CONTENTS | EWT_ALL_INVISCONTENTS); } - contentflags_t create_empty_contents() const override { return {Q2_CONTENTS_EMPTY}; } + contentflags_t create_empty_contents() const override { return contentflags_t::make(EWT_VISCONTENTS_EMPTY); } - contentflags_t create_solid_contents() const override { return {Q2_CONTENTS_SOLID}; } + contentflags_t create_solid_contents() const override { return contentflags_t::make(EWT_VISCONTENTS_SOLID); } contentflags_t create_detail_illusionary_contents(const contentflags_t &original) const override { - contentflags_t result = original; - result.native &= ~Q2_CONTENTS_SOLID; - result.native |= Q2_CONTENTS_MIST | Q2_CONTENTS_DETAIL; - return result; + contents_int_t flags = original.flags; + flags &= ~EWT_VISCONTENTS_SOLID; + flags |= EWT_VISCONTENTS_MIST | EWT_CFLAG_DETAIL; + return contentflags_t::make(flags); } contentflags_t create_detail_fence_contents(const contentflags_t &original) const override { - contentflags_t result = original; - result.native &= ~Q2_CONTENTS_SOLID; - result.native |= (Q2_CONTENTS_WINDOW | Q2_CONTENTS_TRANSLUCENT | Q2_CONTENTS_DETAIL); - return result; + contents_int_t flags = original.flags; + flags &= ~EWT_VISCONTENTS_SOLID; + flags |= (EWT_VISCONTENTS_WINDOW | EWT_CFLAG_TRANSLUCENT | EWT_CFLAG_DETAIL); + return contentflags_t::make(flags); } contentflags_t create_detail_wall_contents(const contentflags_t &original) const override { - contentflags_t result = original; - // HACK: borrowing Q2_CONTENTS_MONSTER as a compiler internal flag - result.native &= ~Q2_CONTENTS_SOLID; - result.native |= (Q2_CONTENTS_MONSTER | Q2_CONTENTS_DETAIL); - return result; + contents_int_t flags = original.flags; + flags &= ~EWT_VISCONTENTS_SOLID; + flags |= (EWT_VISCONTENTS_DETAIL_WALL | EWT_CFLAG_DETAIL); + return contentflags_t::make(flags); } contentflags_t create_detail_solid_contents(const contentflags_t &original) const override { - contentflags_t result = original; - result.native |= (Q2_CONTENTS_SOLID | Q2_CONTENTS_DETAIL); - return result; + contents_int_t flags = original.flags; + flags |= (EWT_VISCONTENTS_SOLID | EWT_CFLAG_DETAIL); + return contentflags_t::make(flags); } contentflags_t clear_detail(const contentflags_t &original) const override { - contentflags_t result = original; - result.native &= ~Q2_CONTENTS_DETAIL; - return result; + return contentflags_t::make(original.flags & (~EWT_CFLAG_DETAIL)); } contentflags_t set_detail(const contentflags_t &original) const override { - contentflags_t result = original; - result.native |= Q2_CONTENTS_DETAIL; - return result; + return contentflags_t::make(original.flags | EWT_CFLAG_DETAIL); } bool contents_are_type_equal(const contentflags_t &self, const contentflags_t &other) const override { - return self.illusionary_visblocker == other.illusionary_visblocker && - get_content_type(self) == get_content_type(other); + return get_content_type(self) == get_content_type(other); } bool contents_are_equal(const contentflags_t &self, const contentflags_t &other) const override { - return self.illusionary_visblocker == other.illusionary_visblocker && self.native == other.native; + return self.flags == other.flags; } bool contents_are_any_detail(const contentflags_t &contents) const override { - return ((contents.native & Q2_CONTENTS_DETAIL) != 0); + return (contents.flags & EWT_CFLAG_DETAIL) != 0; } bool contents_are_detail_solid(const contentflags_t &contents) const override { - int32_t test = (Q2_CONTENTS_DETAIL | Q2_CONTENTS_SOLID); - - return ((contents.native & test) == test); + return (contents.flags & EWT_CFLAG_DETAIL) + && (contents.flags & EWT_VISCONTENTS_SOLID); } bool contents_are_detail_wall(const contentflags_t &contents) const override { - if (contents.native & Q2_CONTENTS_SOLID) { + // fixme: Q1 is different + if (contents.flags & EWT_VISCONTENTS_SOLID) { return false; } - int32_t test = (Q2_CONTENTS_DETAIL | Q2_CONTENTS_MONSTER); - return ((contents.native & test) == test); + contents_int_t test = (EWT_CFLAG_DETAIL | EWT_VISCONTENTS_DETAIL_WALL); + return ((contents.flags & test) == test); } bool contents_are_detail_fence(const contentflags_t &contents) const override { - if (contents.native & Q2_CONTENTS_SOLID) { + // fixme: Q1 is different + if (contents.flags & EWT_VISCONTENTS_SOLID) { return false; } - int32_t test = (Q2_CONTENTS_DETAIL | Q2_CONTENTS_WINDOW); - return ((contents.native & test) == test); + contents_int_t test = (EWT_CFLAG_DETAIL | EWT_VISCONTENTS_WINDOW); + return ((contents.flags & test) == test); } bool contents_are_detail_illusionary(const contentflags_t &contents) const override { - if (contents.native & Q2_CONTENTS_SOLID) { + // fixme: Q1 is different + if (contents.flags & EWT_VISCONTENTS_SOLID) { return false; } - int32_t mist1_type = (Q2_CONTENTS_DETAIL | Q2_CONTENTS_MIST); - int32_t mist2_type = (Q2_CONTENTS_DETAIL | Q2_CONTENTS_AUX); + contents_int_t mist1_type = (EWT_CFLAG_DETAIL | EWT_VISCONTENTS_MIST); + contents_int_t mist2_type = (EWT_CFLAG_DETAIL | EWT_VISCONTENTS_AUX); - return ((contents.native & mist1_type) == mist1_type) || ((contents.native & mist2_type) == mist2_type); + return ((contents.flags & mist1_type) == mist1_type) || ((contents.flags & mist2_type) == mist2_type); } bool contents_are_origin(const contentflags_t &contents) const override { - return contents.native & Q2_CONTENTS_ORIGIN; + return contents.flags & EWT_INVISCONTENTS_ORIGIN; } bool contents_are_clip(const contentflags_t &contents) const override { - return contents.native & (Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP); + return (contents.flags & (EWT_INVISCONTENTS_PLAYERCLIP | EWT_INVISCONTENTS_MONSTERCLIP)) != 0; } bool contents_clip_same_type(const contentflags_t &self, const contentflags_t &other) const override { - return (self.native & Q2_ALL_VISIBLE_CONTENTS_PLUS_MONSTER) == - (other.native & Q2_ALL_VISIBLE_CONTENTS_PLUS_MONSTER) && - self.clips_same_type.value_or(true); + if ((self.flags & EWT_ALL_VISIBLE_CONTENTS) != + (other.flags & EWT_ALL_VISIBLE_CONTENTS)) + return false; // not same type + + if (self.flags & EWT_CFLAG_SUPPRESS_CLIPPING_SAME_TYPE) + return false; + + return true; } - inline bool contents_has_extended(const contentflags_t &contents) const { return contents.illusionary_visblocker; } + inline bool contents_has_extended(const contentflags_t &contents) const { return false; } bool contents_are_empty(const contentflags_t &contents) const override { @@ -1236,13 +1100,13 @@ struct gamedef_q2_t : public gamedef_t bool contents_are_any_solid(const contentflags_t &contents) const override { - return (contents.native & Q2_CONTENTS_SOLID) != 0; + return (contents.flags & EWT_VISCONTENTS_SOLID) != 0; } bool contents_are_solid(const contentflags_t &contents) const override { - return !contents_has_extended(contents) && (contents.native & Q2_CONTENTS_SOLID) && - !(contents.native & Q2_CONTENTS_DETAIL); + return !contents_has_extended(contents) && (contents.flags & EWT_VISCONTENTS_SOLID) && + !(contents.flags & EWT_CFLAG_DETAIL); } bool contents_are_sky(const contentflags_t &contents) const override { return false; } @@ -1252,16 +1116,17 @@ struct gamedef_q2_t : public gamedef_t if (contents_has_extended(contents)) return false; - if (contents.native & Q2_CONTENTS_AREAPORTAL) + if (contents.flags & EWT_INVISCONTENTS_AREAPORTAL) return true; // HACK: treat areaportal as a liquid for the purposes of the CSG code - return contents.native & Q2_CONTENTS_LIQUID; + return (contents.flags & EWT_ALL_LIQUIDS) != 0; } bool contents_are_valid(const contentflags_t &contents, bool strict) const override { // check that we don't have more than one visible contents type - const int32_t x = contents.native & Q2_ALL_VISIBLE_CONTENTS_PLUS_MONSTER; + // FIXME: we don't do that + const int32_t x = contents.flags & EWT_ALL_VISIBLE_CONTENTS; // TODO: check other invalid mixes if (!x && strict) { @@ -1287,59 +1152,41 @@ struct gamedef_q2_t : public gamedef_t return 0; } - /** - * Returns the single content bit of the strongest visible content present - */ - constexpr int32_t visible_contents(const int32_t &contents) const - { - // HACK: func_detail_wall (Q2_CONTENTS_MONSTER) fits between Q2_CONTENTS_SOLID and - // Q2_CONTENTS_WINDOW - - if (contents & Q2_CONTENTS_SOLID) - return Q2_CONTENTS_SOLID; - - if (contents & Q2_CONTENTS_MONSTER) - return Q2_CONTENTS_MONSTER; - - for (int32_t i = Q2_CONTENTS_WINDOW; i <= Q2_LAST_VISIBLE_CONTENTS; i <<= 1) { - if (contents & i) { - return i; - } - } - - return 0; - } - contentflags_t portal_visible_contents(const contentflags_t &a, const contentflags_t &b) const override { - contentflags_t result; + auto bits_a = a.flags; + auto bits_b = b.flags; - if (!a.clips_same_type.value_or(true) || !b.clips_same_type.value_or(true)) { - result.native = visible_contents(a.native | b.native); + contents_int_t result; + + if ((bits_a & EWT_CFLAG_SUPPRESS_CLIPPING_SAME_TYPE) || (bits_b & EWT_CFLAG_SUPPRESS_CLIPPING_SAME_TYPE)) { + result = bits_a | bits_b; } else { - result.native = visible_contents(a.native ^ b.native); + result = bits_a ^ bits_b; } - return result; + + auto strongest_contents_change = contentflags_t::make(result).visible_contents(); + + return strongest_contents_change; } bool portal_can_see_through( const contentflags_t &contents0, const contentflags_t &contents1, bool, bool) const override { - int32_t c0 = contents0.native, c1 = contents1.native; + contents_int_t c0 = contents0.flags, c1 = contents1.flags; // can't see through solid - if ((c0 | c1) & Q2_CONTENTS_SOLID) { + if ((c0 & EWT_VISCONTENTS_SOLID) || (c1 & EWT_VISCONTENTS_SOLID)) { return false; } - if (!visible_contents(c0 ^ c1)) { + if (((c0 ^ c1) & EWT_ALL_VISIBLE_CONTENTS) == 0) return true; - } - if ((c0 & Q2_CONTENTS_TRANSLUCENT) || contents0.is_any_detail(this)) { + if ((c0 & EWT_CFLAG_TRANSLUCENT) || (c0 & EWT_CFLAG_DETAIL)) { c0 = 0; } - if ((c1 & Q2_CONTENTS_TRANSLUCENT) || contents1.is_any_detail(this)) { + if ((c1 & EWT_CFLAG_TRANSLUCENT) || (c1 & EWT_CFLAG_DETAIL)) { c1 = 0; } @@ -1347,7 +1194,7 @@ struct gamedef_q2_t : public gamedef_t if (!(c0 ^ c1)) return true; - return !visible_contents(c0 ^ c1); + return (((c0 ^ c1) & EWT_ALL_VISIBLE_CONTENTS) != 0); } bool contents_seals_map(const contentflags_t &contents) const override @@ -1357,14 +1204,14 @@ struct gamedef_q2_t : public gamedef_t bool contents_are_opaque(const contentflags_t &contents, bool transwater) const override { - int32_t c = contents.native; + int32_t c = contents.flags; - if (!this->visible_contents(c)) + if (contents.visible_contents().flags == EWT_VISCONTENTS_EMPTY) return false; // it's visible.. - if (c & Q2_CONTENTS_TRANSLUCENT) { + if (c & EWT_CFLAG_TRANSLUCENT) { return false; } @@ -1373,16 +1220,15 @@ struct gamedef_q2_t : public gamedef_t contentflags_t contents_remap_for_export(const contentflags_t &contents, remap_type_t type) const override { - // HACK: borrowing Q2_CONTENTS_MONSTER for func_detail_wall - if (contents.native & Q2_CONTENTS_MONSTER) { - return {Q2_CONTENTS_SOLID}; + if (contents.flags & EWT_VISCONTENTS_DETAIL_WALL) { + return create_solid_contents(); } // Solid wipes out any other contents // Previously, this was done in LeafNode but we've changed to detail-solid being // non-sealing. if (type == remap_type_t::leaf) { - if (contents.native & Q2_CONTENTS_SOLID) { - return {Q2_CONTENTS_SOLID}; + if (contents.flags & EWT_VISCONTENTS_SOLID) { + return create_solid_contents(); } } @@ -1393,30 +1239,38 @@ struct gamedef_q2_t : public gamedef_t { // structural solid (but not detail solid) eats any other contents if (contents_are_solid(a) || contents_are_solid(b)) { - return {Q2_CONTENTS_SOLID}; + return create_solid_contents(); } - contentflags_t result; - result.native = a.native | b.native; - result.clips_same_type = (a.clips_same_type.value_or(true) && b.clips_same_type.value_or(true)); - result.mirror_inside = (a.mirror_inside.value_or(true) && b.mirror_inside.value_or(true)); - result.illusionary_visblocker = a.illusionary_visblocker || b.illusionary_visblocker; - return result; + auto bits_a = a.flags; + auto bits_b = b.flags; + + return contentflags_t::make(bits_a | bits_b); } bool portal_generates_face(const contentflags_t &portal_visible_contents, const contentflags_t &brushcontents, planeside_t brushside_side) const override { - if ((portal_visible_contents.native & brushcontents.native) == 0) { + auto bits_portal = portal_visible_contents.flags; + auto bits_brush = brushcontents.flags; + + // find the highest visible content bit set in portal + int32_t index = portal_visible_contents.visible_contents_index(); + if (index == -1) { + return false; + } + + // check if it's not set in the brush + if (!(bits_brush & nth_bit(index))) { return false; } if (brushside_side == SIDE_BACK) { // explicit override? - if (brushcontents.mirror_inside) { - return *brushcontents.mirror_inside; + if (bits_brush & EWT_CFLAG_MIRROR_INSIDE_SET) { + return (bits_brush & EWT_CFLAG_MIRROR_INSIDE) != 0; } - if (portal_visible_contents.native & (Q2_CONTENTS_WINDOW | Q2_CONTENTS_AUX)) { + if (portal_visible_contents.flags & (EWT_VISCONTENTS_WINDOW | EWT_VISCONTENTS_AUX)) { // windows or aux don't generate inside faces return false; } @@ -1428,23 +1282,7 @@ struct gamedef_q2_t : public gamedef_t std::string get_contents_display(const contentflags_t &contents) const override { - if (!contents.native) { - return "EMPTY"; - } - - std::string s; - - for (int32_t i = 0; i < std::size(bitflag_names); i++) { - if (contents.native & nth_bit(i)) { - if (s.size()) { - s += " | "; - } - - s += bitflag_names[i]; - } - } - - return s; + return ::get_contents_display(contents.flags); } void contents_make_valid(contentflags_t &contents) const override @@ -1453,18 +1291,22 @@ struct gamedef_q2_t : public gamedef_t return; } + // clear all visible contents bits except the strongest one bool got = false; + contents_int_t flags = contents.flags; - for (int32_t i = 0; i < 8; i++) { + for (int32_t i = 0; i <= EWT_LAST_VISIBLE_CONTENTS_INDEX; i++) { if (!got) { - if (contents.native & nth_bit(i)) { + if (flags & nth_bit(i)) { got = true; continue; } } else { - contents.native &= ~nth_bit(i); + flags &= ~nth_bit(i); } } + + contents = contentflags_t::make(flags); } const std::initializer_list &get_hull_sizes() const override @@ -1478,38 +1320,38 @@ struct gamedef_q2_t : public gamedef_t { // hints and skips are never detail, and have no content if (flags.native & (Q2_SURF_HINT | Q2_SURF_SKIP)) { - return {Q2_CONTENTS_EMPTY}; + return contentflags_t::make(EWT_VISCONTENTS_EMPTY); } - contentflags_t surf_contents = contents; + contents_int_t surf_contents = contents.flags; // if we don't have a declared content type, assume solid. - if (!get_content_type(surf_contents)) { - surf_contents.native |= Q2_CONTENTS_SOLID; + if (!get_content_type(contents)) { + surf_contents |= EWT_VISCONTENTS_SOLID; } // if we have TRANS33 or TRANS66 or ALPHATEST, 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 | Q2_SURF_ALPHATEST)) { - surf_contents.native |= Q2_CONTENTS_TRANSLUCENT; + surf_contents |= EWT_CFLAG_TRANSLUCENT; - if (surf_contents.native & Q2_CONTENTS_SOLID) { - surf_contents.native = (surf_contents.native & ~Q2_CONTENTS_SOLID) | Q2_CONTENTS_WINDOW; + if (surf_contents & EWT_VISCONTENTS_SOLID) { + surf_contents = (surf_contents & ~EWT_VISCONTENTS_SOLID) | EWT_VISCONTENTS_WINDOW; } } // translucent objects are automatically classified as detail - 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 & EWT_CFLAG_TRANSLUCENT) { + surf_contents |= EWT_CFLAG_DETAIL; + } else if (surf_contents & (EWT_VISCONTENTS_MIST | EWT_VISCONTENTS_AUX)) { + surf_contents |= EWT_CFLAG_DETAIL; } - if (surf_contents.native & (Q2_CONTENTS_MONSTERCLIP | Q2_CONTENTS_PLAYERCLIP)) { - surf_contents.native |= Q2_CONTENTS_DETAIL; + if (surf_contents & (EWT_INVISCONTENTS_MONSTERCLIP | EWT_INVISCONTENTS_PLAYERCLIP)) { + surf_contents |= EWT_CFLAG_DETAIL; } - return surf_contents; + return contentflags_t::make(surf_contents); } private: @@ -1685,9 +1527,8 @@ private: struct content_stats_t : public content_stats_base_t { std::mutex stat_mutex; - std::unordered_map native_types; + std::unordered_map native_types; std::atomic total_brushes; - std::atomic visblocker_brushes; }; public: @@ -1702,11 +1543,7 @@ public: { std::unique_lock lock(stats.stat_mutex); - stats.native_types[contents.native]++; - } - - if (contents.illusionary_visblocker) { - stats.visblocker_brushes++; + stats.native_types[contents.flags]++; } stats.total_brushes++; @@ -1717,12 +1554,10 @@ public: const content_stats_t &stats = dynamic_cast(stats_any); logging::stat_tracker_t stat_print; - for (auto &it : stats.native_types) { - stat_print.register_stat(fmt::format("{} {}", get_contents_display({it.first}), what)).count += it.second; - } - - if (stats.visblocker_brushes) { - stat_print.register_stat(fmt::format("VISBLOCKER {}", what)).count += stats.visblocker_brushes; + for (auto [bits, count] : stats.native_types) { + auto c = contentflags_t{.flags = bits}; + stat_print.register_stat(fmt::format("{} {}", get_contents_display(c), what)) + .count += count; } stat_print.register_stat(fmt::format("{} total", what)).count += stats.total_brushes; @@ -1942,8 +1777,7 @@ bool surfflags_t::is_valid(const gamedef_t *game) const 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; + return flags == other.flags; } bool contentflags_t::types_equal(const contentflags_t &other, const gamedef_t *game) const @@ -1978,7 +1812,19 @@ bool contentflags_t::is_detail_illusionary(const gamedef_t *game) const contentflags_t &contentflags_t::set_mirrored(const std::optional &mirror_inside_value) { - mirror_inside = mirror_inside_value; + if (mirror_inside_value.has_value()) { + if (*mirror_inside_value) { + // set to true + flags = static_cast(flags | EWT_CFLAG_MIRROR_INSIDE_SET | EWT_CFLAG_MIRROR_INSIDE); + } else { + // set to false + flags = static_cast(flags | EWT_CFLAG_MIRROR_INSIDE_SET); + flags = static_cast(flags & ~(EWT_CFLAG_MIRROR_INSIDE)); + } + } else { + // unset + flags = static_cast(flags & ~(EWT_CFLAG_MIRROR_INSIDE_SET | EWT_CFLAG_MIRROR_INSIDE)); + } return *this; } @@ -1989,7 +1835,11 @@ bool contentflags_t::will_clip_same_type(const gamedef_t *game, const contentfla contentflags_t &contentflags_t::set_clips_same_type(const std::optional &clips_same_type_value) { - clips_same_type = clips_same_type_value; + if (clips_same_type_value) { + if (!*clips_same_type_value) { + *this = contentflags_t::make(flags | EWT_CFLAG_SUPPRESS_CLIPPING_SAME_TYPE); + } + } return *this; } @@ -2047,19 +1897,6 @@ std::string contentflags_t::to_string(const gamedef_t *game) const { std::string s = game->get_contents_display(*this); - if (mirror_inside != std::nullopt) { - s += fmt::format(" | MIRROR_INSIDE[{}]", mirror_inside.value() ? "true" : "false"); - } - - if (contentflags_t{native}.will_clip_same_type(game) != will_clip_same_type(game)) { - s += fmt::format(" | CLIPS_SAME_TYPE[{}]", - clips_same_type.has_value() ? (clips_same_type.value() ? "true" : "false") : "nullopt"); - } - - if (illusionary_visblocker) { - s += " | ILLUSIONARY_VISBLOCKER"; - } - return s; } diff --git a/common/decompile.cc b/common/decompile.cc index bd5974d1..2d3bdb28 100644 --- a/common/decompile.cc +++ b/common/decompile.cc @@ -186,7 +186,9 @@ struct compiled_brush_t side.valve.shift[0], side.valve.axis.at(1, 0), side.valve.axis.at(1, 1), side.valve.axis.at(1, 2), side.valve.shift[1], 0.0, side.valve.scale[0], side.valve.scale[1]); - if (bsp->loadversion->game->id == GAME_QUAKE_II && (contents.native || side.flags.native || side.value)) { + int native = bsp->loadversion->game->contents_to_native(contents); + + if (bsp->loadversion->game->id == GAME_QUAKE_II && (native || side.flags.native || side.value)) { wal_metadata_t *meta = nullptr; auto it = wals.find(side.texture_name); @@ -207,9 +209,9 @@ struct compiled_brush_t } if (!meta || !((meta->contents & ~(Q2_CONTENTS_SOLID | Q2_CONTENTS_WINDOW)) == - (contents.native & ~(Q2_CONTENTS_SOLID | Q2_CONTENTS_WINDOW)) && + (native & ~(Q2_CONTENTS_SOLID | Q2_CONTENTS_WINDOW)) && meta->flags == side.flags.native && meta->value == side.value)) { - ewt::print(stream, " {} {} {}", contents.native, side.flags.native, side.value); + ewt::print(stream, " {} {} {}", native, side.flags.native, side.value); } } @@ -549,8 +551,10 @@ static const char *DefaultOriginTexture(const mbsp_t *bsp) static const char *DefaultTextureForContents(const mbsp_t *bsp, const contentflags_t &contents) { + int native = bsp->loadversion->game->contents_to_native(contents); + if (bsp->loadversion->game->id == GAME_QUAKE_II) { - int visible = contents.native & Q2_ALL_VISIBLE_CONTENTS; + int visible = native & Q2_ALL_VISIBLE_CONTENTS; if (visible & Q2_CONTENTS_WATER) { return "e1u1/water4"; @@ -558,17 +562,17 @@ static const char *DefaultTextureForContents(const mbsp_t *bsp, const contentfla return "e1u1/sewer1"; } else if (visible & Q2_CONTENTS_LAVA) { return "e1u1/brlava"; - } else if (contents.native & Q2_CONTENTS_PLAYERCLIP) { + } else if (native & Q2_CONTENTS_PLAYERCLIP) { return "e1u1/clip"; - } else if (contents.native & Q2_CONTENTS_MONSTERCLIP) { + } else if (native & Q2_CONTENTS_MONSTERCLIP) { return "e1u1/clip_mon"; - } else if (contents.native & Q2_CONTENTS_AREAPORTAL) { + } else if (native & Q2_CONTENTS_AREAPORTAL) { return "e1u1/trigger"; } return "e1u1/skip"; } else { - switch (contents.native) { + switch (native) { case CONTENTS_WATER: return "*waterskip"; case CONTENTS_SLIME: return "*slimeskip"; case CONTENTS_LAVA: return "*lavaskip"; @@ -585,9 +589,10 @@ static void OverrideTextureForContents( compiled_brush_side_t &side, const mbsp_t *bsp, const char *name, const contentflags_t &contents) { if (bsp->loadversion->game->id == GAME_QUAKE_II) { + int native = bsp->loadversion->game->contents_to_native(contents); - if (contents.native & (Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP)) { - if (!(contents.native & Q2_CONTENTS_PLAYERCLIP)) { + if (native & (Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP)) { + if (!(native & Q2_CONTENTS_PLAYERCLIP)) { side.texture_name = "e1u1/clip_mon"; } else { side.texture_name = "e1u1/clip"; @@ -808,7 +813,9 @@ static std::vector DecompileLeafTaskGeometryOnly( compiled_brush_t brush; brush.source = task.brush; brush.brush_offset = brush_offset; - brush.contents = {task.brush ? task.brush->contents : task.leaf ? task.leaf->contents : task.contents.value()}; + brush.contents = bsp->loadversion->game->create_contents_from_native( + task.brush ? task.brush->contents : task.leaf ? task.leaf->contents : task.contents.value() + ); brush.sides.reserve(task.allPlanes.size()); @@ -868,7 +875,7 @@ static std::vector DecompileLeafTask( compiled_brush_t brush; brush.source = task.brush; brush.brush_offset = brush_offset; - brush.contents = {task.brush ? task.brush->contents : task.leaf ? task.leaf->contents : task.contents.value()}; + brush.contents = bsp->loadversion->game->create_contents_from_native(task.brush ? task.brush->contents : task.leaf ? task.leaf->contents : task.contents.value()); for (auto &finalSide : finalBrush.sides) { compiled_brush_side_t &side = brush.sides.emplace_back(); @@ -876,7 +883,7 @@ static std::vector DecompileLeafTask( side.winding = std::move(finalSide.winding); side.source = finalSide.plane.source; - if (brush.contents.native == 0) { + if (bsp->loadversion->game->contents_to_native(brush.contents) == 0) { // hint brush side.texture_name = "e1u1/hint"; @@ -959,7 +966,9 @@ static std::vector DecompileLeafTaskLeafVisualization( compiled_brush_t brush; brush.source = task.brush; brush.brush_offset = brush_offset; - brush.contents = task.leaf ? contentflags_t{task.leaf->contents} : contentflags_t{task.contents.value()}; + brush.contents = bsp->loadversion->game->create_contents_from_native( + task.leaf ? task.leaf->contents : task.contents.value() + ); for (auto &finalSide : finalBrush.sides) { compiled_brush_side_t &side = brush.sides.emplace_back(); @@ -1324,7 +1333,7 @@ static void DecompileEntity( std::vector &brushlist = compiledBrushes.emplace_back(); compiled_brush_t &brush = brushlist.emplace_back(); brush.brush_offset = brush_offset; - brush.contents = {Q2_CONTENTS_ORIGIN}; + brush.contents = contentflags_t::make(EWT_INVISCONTENTS_ORIGIN); constexpr qplane3d planes[] = { {{-1, 0, 0}, 8}, diff --git a/common/imglib.cc b/common/imglib.cc index 420b007c..4b4079b0 100644 --- a/common/imglib.cc +++ b/common/imglib.cc @@ -145,7 +145,7 @@ std::optional load_wal( tex.meta.name = name; tex.meta.width = tex.width = mt.width; tex.meta.height = tex.height = mt.height; - tex.meta.contents = {mt.contents}; + tex.meta.contents_native = mt.contents; tex.meta.flags = {mt.flags}; tex.meta.value = mt.value; tex.meta.animation = mt.animname.data(); @@ -402,17 +402,20 @@ std::optional load_wal_json_meta( auto &contents = json["contents"]; if (contents.is_number_integer()) { - meta.contents.native = contents.get(); + meta.contents_native = contents.get(); } else if (contents.is_string()) { - meta.contents.native = game->contents_from_string(contents.get()); + meta.contents_native = + game->contents_from_string(contents.get()); } else if (contents.is_array()) { + int native = 0; for (auto &content : contents) { if (content.is_number_integer()) { - meta.contents.native |= content.get(); + native |= content.get(); } else if (content.is_string()) { - meta.contents.native |= game->contents_from_string(content.get()); + native |= game->contents_from_string(content.get()); } } + meta.contents_native = native; } } diff --git a/common/mapfile.cc b/common/mapfile.cc index 26659e90..777c7894 100644 --- a/common/mapfile.cc +++ b/common/mapfile.cc @@ -191,7 +191,7 @@ void brush_side_t::parse_extended_texinfo(parser_t &parser) if (parser.parse_token(PARSE_OPTIONAL)) { texinfo_quake2_t q2_info; - q2_info.contents = {std::stoi(parser.token)}; + q2_info.contents = std::stoi(parser.token); if (parser.parse_token(PARSE_OPTIONAL)) { q2_info.flags.native = std::stoi(parser.token); @@ -482,7 +482,7 @@ parse_error: void brush_side_t::write_extended_info(std::ostream &stream) { if (extended_info) { - ewt::print(stream, " {} {} {}", extended_info->contents.native, extended_info->flags.native, extended_info->value); + ewt::print(stream, " {} {} {}", extended_info->contents, extended_info->flags.native, extended_info->value); } } diff --git a/include/common/bspfile.hh b/include/common/bspfile.hh index d608a5d4..13c45568 100644 --- a/include/common/bspfile.hh +++ b/include/common/bspfile.hh @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -47,26 +48,94 @@ struct lump_t void stream_read(std::istream &s); }; +using contents_int_t = uint64_t; + +/** + * Superset of Q1- and Q2- features, plus EWT extensions. + */ +enum contents_t : contents_int_t { + EWT_VISCONTENTS_EMPTY = 0, + EWT_VISCONTENTS_SOLID = nth_bit(0), // an eye is never valid in a solid + EWT_VISCONTENTS_SKY = nth_bit(1), + EWT_VISCONTENTS_DETAIL_WALL = nth_bit(2), + EWT_VISCONTENTS_WINDOW = nth_bit(3), // translucent, but not watery (detail fence) + EWT_VISCONTENTS_AUX = nth_bit(4), + EWT_VISCONTENTS_LAVA = nth_bit(5), + EWT_VISCONTENTS_SLIME = nth_bit(6), + EWT_VISCONTENTS_WATER = nth_bit(7), + EWT_VISCONTENTS_MIST = nth_bit(8), + + EWT_LAST_VISIBLE_CONTENTS_INDEX = 8, + EWT_LAST_VISIBLE_CONTENTS = EWT_VISCONTENTS_MIST, + + EWT_ALL_LIQUIDS = + EWT_VISCONTENTS_LAVA | + EWT_VISCONTENTS_SLIME | + EWT_VISCONTENTS_WATER, + + EWT_ALL_VISIBLE_CONTENTS = + EWT_VISCONTENTS_SOLID | + EWT_VISCONTENTS_SKY | + EWT_VISCONTENTS_DETAIL_WALL | + EWT_VISCONTENTS_WINDOW | + EWT_VISCONTENTS_AUX | + EWT_VISCONTENTS_LAVA | + EWT_VISCONTENTS_SLIME | + EWT_VISCONTENTS_WATER | + EWT_VISCONTENTS_MIST, + + EWT_INVISCONTENTS_ORIGIN = nth_bit(9), // removed before bsping an entity + // Q1 clip + EWT_INVISCONTENTS_PLAYERCLIP = nth_bit(10), + EWT_INVISCONTENTS_MONSTERCLIP = nth_bit(11), + EWT_INVISCONTENTS_AREAPORTAL = nth_bit(12), + EWT_INVISCONTENTS_ILLUSIONARY_VISBLOCKER = nth_bit(13), + + EWT_ALL_INVISCONTENTS = + EWT_INVISCONTENTS_ORIGIN | + EWT_INVISCONTENTS_PLAYERCLIP | + EWT_INVISCONTENTS_MONSTERCLIP | + EWT_INVISCONTENTS_AREAPORTAL | + EWT_INVISCONTENTS_ILLUSIONARY_VISBLOCKER, + + EWT_CFLAG_DETAIL = nth_bit(14), // brushes to be added after vis leafs + EWT_CFLAG_MIRROR_INSIDE = nth_bit(15), + EWT_CFLAG_MIRROR_INSIDE_SET = nth_bit(16), + EWT_CFLAG_SUPPRESS_CLIPPING_SAME_TYPE = nth_bit(17), + + EWT_CFLAG_CURRENT_0 = nth_bit(18), + EWT_CFLAG_CURRENT_90 = nth_bit(19), + EWT_CFLAG_CURRENT_180 = nth_bit(20), + EWT_CFLAG_CURRENT_270 = nth_bit(21), + EWT_CFLAG_CURRENT_UP = nth_bit(22), + EWT_CFLAG_CURRENT_DOWN = nth_bit(23), + EWT_CFLAG_TRANSLUCENT = nth_bit(24), // auto set if any surface has trans, + EWT_CFLAG_LADDER = nth_bit(25), + EWT_CFLAG_MONSTER = nth_bit(26), // disallowed in maps, only for gamecode use + EWT_CFLAG_DEADMONSTER = nth_bit(27), // disallowed in maps, only for gamecode use + +// unused Q2 contents bits - just present here so we can roundtrip all 32-bit Q2 contents + EWT_CFLAG_Q2_UNUSED_7 = nth_bit(28), + EWT_CFLAG_Q2_UNUSED_8 = nth_bit(29), + EWT_CFLAG_Q2_UNUSED_9 = nth_bit(30), + EWT_CFLAG_Q2_UNUSED_10 = nth_bit(31), + EWT_CFLAG_Q2_UNUSED_11 = nth_bit(32), + EWT_CFLAG_Q2_UNUSED_12 = nth_bit(33), + EWT_CFLAG_Q2_UNUSED_13 = nth_bit(34), + EWT_CFLAG_Q2_UNUSED_14 = nth_bit(35), + EWT_CFLAG_Q2_UNUSED_30 = nth_bit(36), + EWT_CFLAG_Q2_UNUSED_31 = nth_bit(37) +}; + struct gamedef_t; struct contentflags_t { - // native flags value; what's written to the BSP basically - int32_t native = 0; + contents_t flags; - // 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; - - // 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; - - // always blocks vis, even if it normally wouldn't - bool illusionary_visblocker = false; + static contentflags_t make(contents_int_t f) { + return contentflags_t{.flags = static_cast(f)}; + } bool equals(const gamedef_t *game, const contentflags_t &other) const; @@ -77,10 +146,22 @@ struct contentflags_t bool is_detail_fence(const gamedef_t *game) const; bool is_detail_illusionary(const gamedef_t *game) const; + std::optional mirror_inside() const { + if (flags & EWT_CFLAG_MIRROR_INSIDE_SET) { + return {(flags & EWT_CFLAG_MIRROR_INSIDE) != 0}; + } + return std::nullopt; + } contentflags_t &set_mirrored(const std::optional &mirror_inside_value); 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; + std::optional clips_same_type() const { + if (flags & EWT_CFLAG_SUPPRESS_CLIPPING_SAME_TYPE) { + return {false}; + } + return std::nullopt; + } contentflags_t &set_clips_same_type(const std::optional &clips_same_type_value); bool is_empty(const gamedef_t *game) const; @@ -112,6 +193,26 @@ struct contentflags_t bool chops(const gamedef_t *game) const; std::string to_string(const gamedef_t *game) const; + + // returns the bit index (starting from 0) of the strongest visible content type + // set, or -1 if no visible content bits are set (i.e. EWT_VISCONTENTS_EMPTY) + int visible_contents_index() const { + for (uint32_t index = 0; nth_bit(index) <= EWT_LAST_VISIBLE_CONTENTS; ++index) { + if (flags & nth_bit(index)) { + return index; + } + } + return -1; + } + + // returns the strongest EWT_VISCONTENTS_ bit, discarding all other flags + contentflags_t visible_contents() const { + int index = visible_contents_index(); + if (index >= 0) { + return contentflags_t::make(static_cast(nth_bit(index))); + } + return contentflags_t::make(EWT_VISCONTENTS_EMPTY); + } }; struct surfflags_t @@ -270,6 +371,8 @@ struct gamedef_t virtual int32_t surfflags_from_string(const std::string_view &str) const = 0; // 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 create_contents_from_native(int32_t native) const = 0; + virtual int32_t contents_to_native(const contentflags_t &contents) const = 0; virtual contentflags_t cluster_contents(const contentflags_t &contents0, const contentflags_t &contents1) const = 0; virtual contentflags_t create_empty_contents() const = 0; virtual contentflags_t create_solid_contents() const = 0; diff --git a/include/common/bspfile_q2.hh b/include/common/bspfile_q2.hh index 125b5701..5636cc02 100644 --- a/include/common/bspfile_q2.hh +++ b/include/common/bspfile_q2.hh @@ -108,6 +108,16 @@ enum q2_contents_t : int32_t Q2_CONTENTS_SLIME = nth_bit(4), Q2_CONTENTS_WATER = nth_bit(5), Q2_CONTENTS_MIST = nth_bit(6), + + Q2_CONTENTS_UNUSED_7 = nth_bit(7), + Q2_CONTENTS_UNUSED_8 = nth_bit(8), + Q2_CONTENTS_UNUSED_9 = nth_bit(9), + Q2_CONTENTS_UNUSED_10 = nth_bit(10), + Q2_CONTENTS_UNUSED_11 = nth_bit(11), + Q2_CONTENTS_UNUSED_12 = nth_bit(12), + Q2_CONTENTS_UNUSED_13 = nth_bit(13), + Q2_CONTENTS_UNUSED_14 = nth_bit(14), + Q2_LAST_VISIBLE_CONTENTS = Q2_CONTENTS_MIST, Q2_ALL_VISIBLE_CONTENTS = Q2_CONTENTS_SOLID | Q2_CONTENTS_WINDOW | Q2_CONTENTS_AUX | Q2_CONTENTS_LAVA | Q2_CONTENTS_SLIME | Q2_CONTENTS_WATER | Q2_CONTENTS_MIST, @@ -137,8 +147,8 @@ enum q2_contents_t : int32_t Q2_CONTENTS_TRANSLUCENT = nth_bit(28), // auto set if any surface has trans Q2_CONTENTS_LADDER = nth_bit(29), - // HACK: using Q2_CONTENTS_MONSTER for func_detail_wall - Q2_ALL_VISIBLE_CONTENTS_PLUS_MONSTER = Q2_ALL_VISIBLE_CONTENTS | Q2_CONTENTS_MONSTER, + Q2_CONTENTS_UNUSED_30 = nth_bit(30), + Q2_CONTENTS_UNUSED_31 = nth_bit(31) }; struct q2_dnode_t diff --git a/include/common/imglib.hh b/include/common/imglib.hh index 53ef1ec5..42e1dd19 100644 --- a/include/common/imglib.hh +++ b/include/common/imglib.hh @@ -57,7 +57,7 @@ struct texture_meta // Q2/WAL only surfflags_t flags{}; - contentflags_t contents{}; + uint32_t contents_native = 0; int32_t value = 0; std::string animation; }; diff --git a/include/common/mapfile.hh b/include/common/mapfile.hh index 122ac45f..e0be658d 100644 --- a/include/common/mapfile.hh +++ b/include/common/mapfile.hh @@ -64,7 +64,7 @@ struct texdef_etp_t : texdef_quake_ed_t // extra Q2 info struct texinfo_quake2_t { - contentflags_t contents; + int contents; surfflags_t flags; int value; }; diff --git a/include/qbsp/qbsp.hh b/include/qbsp/qbsp.hh index f6e6d47a..5bf4c09d 100644 --- a/include/qbsp/qbsp.hh +++ b/include/qbsp/qbsp.hh @@ -72,7 +72,7 @@ enum class conversion_t // used by Q2 format; used by various systems. struct extended_texinfo_t { - contentflags_t contents = {0}; + uint32_t contents_native = 0; surfflags_t flags = {0}; int value = 0; std::string animation; @@ -410,7 +410,6 @@ struct nodedata_t { struct leafdata_t { // information for leafs - contentflags_t contents; // leaf nodes (0 for decision nodes) std::vector markfaces; // leaf nodes only, point to node faces int outside_distance; // -1 = can't reach outside, 0 = first void node, >0 = distance from void, in number of // portals used to write leak lines that take the shortest path to the void @@ -419,6 +418,7 @@ struct leafdata_t { uint32_t firstleafbrush; // Q2 uint32_t numleafbrushes; int32_t area; + contentflags_t contents; // leaf nodes (0 for decision nodes) std::vector original_brushes; bspbrush_t::container bsp_brushes; }; diff --git a/light/ltface.cc b/light/ltface.cc index b9393351..03b81a5c 100644 --- a/light/ltface.cc +++ b/light/ltface.cc @@ -490,7 +490,8 @@ static bool Mod_LeafPvs(const mbsp_t *bsp, const mleaf_t *leaf, uint8_t *out) static const std::vector *Mod_LeafPvs(const mbsp_t *bsp, const mleaf_t *leaf) { - if (bsp->loadversion->game->contents_are_liquid({leaf->contents})) { + if (bsp->loadversion->game->contents_are_liquid( + bsp->loadversion->game->create_contents_from_native(leaf->contents))) { // the liquid case is because leaf->contents might be in an opaque liquid, // which we typically want light to pass through, but visdata would report that // there's no visibility across the opaque liquid. so, skip culling and do the raytracing. @@ -540,7 +541,8 @@ static void CalcPvs(const mbsp_t *bsp, lightsurf_t *lightsurf) /* copy the pvs for this leaf into pointpvs */ Mod_LeafPvs(bsp, leaf, pointpvs); - if (bsp->loadversion->game->contents_are_liquid({leaf->contents})) { + if (bsp->loadversion->game->contents_are_liquid( + bsp->loadversion->game->create_contents_from_native(leaf->contents))) { // hack for when the sample point might be in an opaque liquid, blocking vis, // but we typically want light to pass through these. // see also VisCullEntity() which handles the case when the light emitter is in liquid. @@ -1154,9 +1156,11 @@ static bool VisCullEntity(const mbsp_t *bsp, const std::vector &pvs, co return false; } - if (bsp->loadversion->game->contents_are_solid({entleaf->contents}) || - bsp->loadversion->game->contents_are_sky({entleaf->contents}) || - bsp->loadversion->game->contents_are_liquid({entleaf->contents})) { + auto contents = bsp->loadversion->game->create_contents_from_native(entleaf->contents); + + if (bsp->loadversion->game->contents_are_solid(contents) || + bsp->loadversion->game->contents_are_sky(contents) || + bsp->loadversion->game->contents_are_liquid(contents)) { // the liquid case is because entleaf->contents might be in an opaque liquid, // which we typically want light to pass through, but visdata would report that // there's no visibility across the opaque liquid. so, skip culling and do the raytracing. diff --git a/maputil/maputil.cc b/maputil/maputil.cc index e258ad9d..fb5ed6cb 100644 --- a/maputil/maputil.cc +++ b/maputil/maputil.cc @@ -358,7 +358,7 @@ static void maputil_make_brush_side(lua_State *state, const brush_side_t &side) if (side.extended_info) { // set info lua_createtable(state, 0, 3); - lua_pushnumber(state, side.extended_info->contents.native); + lua_pushnumber(state, side.extended_info->contents); lua_setfield(state, -2, "contents"); lua_pushnumber(state, side.extended_info->value); lua_setfield(state, -2, "value"); @@ -538,7 +538,7 @@ static void maputil_copy_side(lua_State *state, brush_side_t &side) texinfo_quake2_t q2; lua_getfield(state, -1, "contents"); - q2.contents.native = lua_tonumber(state, -1); + q2.contents = lua_tonumber(state, -1); lua_pop(state, 1); lua_getfield(state, -1, "value"); @@ -716,7 +716,7 @@ static int l_load_texture_meta(lua_State *state) lua_createtable(state, 0, 5); - lua_pushnumber(state, result.contents.native); + lua_pushnumber(state, result.contents_native); lua_setfield(state, -2, "contents"); lua_pushnumber(state, result.flags.native); lua_setfield(state, -2, "flags"); diff --git a/qbsp/brush.cc b/qbsp/brush.cc index 60d1fb48..c65375b8 100644 --- a/qbsp/brush.cc +++ b/qbsp/brush.cc @@ -840,8 +840,8 @@ static void Brush_LoadEntity(mapentity_t &dst, mapentity_t &src, hull_index_t hu // fixme-brushbsp: function calls above can override the values below // so we have to re-set them to be sure they stay what the mapper intended.. - contents.set_mirrored(mapbrush.contents.mirror_inside); - contents.set_clips_same_type(mapbrush.contents.clips_same_type); + contents.set_mirrored(mapbrush.contents.mirror_inside()); + contents.set_clips_same_type(mapbrush.contents.clips_same_type()); auto brush = LoadBrush(src, mapbrush, contents, hullnum, num_clipped); diff --git a/qbsp/exportobj.cc b/qbsp/exportobj.cc index d9526a6e..8daeffe1 100644 --- a/qbsp/exportobj.cc +++ b/qbsp/exportobj.cc @@ -92,7 +92,7 @@ static void ExportObjFace( static void WriteContentsMaterial(std::ofstream &mtlf, contentflags_t contents, float r, float g, float b) { // fixme-brushbsp - ewt::print(mtlf, "newmtl contents{}\n", contents.native); + ewt::print(mtlf, "newmtl contents{}\n", qbsp_options.target_game->contents_to_native(contents)); mtlf << "Ka 0 0 0\n"; ewt::print(mtlf, "Kd {} {} {}\n", r, g, b); mtlf << "Ks 0 0 0\n"; @@ -105,14 +105,15 @@ void ExportObj_Faces(const std::string &filesuffix, const std::vectorcontents.back.native); + std::string mtlname = fmt::format("contents{}\n", qbsp_options.target_game->contents_to_native(face->contents.back)); ExportObjFace(objfile, mtlname, face->w, face->get_texinfo(), &vertcount); } diff --git a/qbsp/map.cc b/qbsp/map.cc index 45140691..44e5e118 100644 --- a/qbsp/map.cc +++ b/qbsp/map.cc @@ -462,7 +462,7 @@ int FindMiptex(const char *name, std::optional &extended_inf auto wal = map.load_image_meta(name); if (wal && !internal && !extended_info.has_value()) { - extended_info = extended_texinfo_t{wal->contents, wal->flags, wal->value, wal->animation}; + extended_info = extended_texinfo_t{wal->contents_native, wal->flags, wal->value, wal->animation}; } if (!extended_info.has_value()) { @@ -906,7 +906,8 @@ static quark_tx_info_t ParseExtendedTX(parser_t &parser) } else { // Parse extra Quake 2 surface info if (parser.parse_token(PARSE_OPTIONAL)) { - result.info = extended_texinfo_t{{std::stoi(parser.token)}}; + uint32_t native = std::stoul(parser.token); + result.info = extended_texinfo_t{.contents_native = native}; if (parser.parse_token(PARSE_OPTIONAL)) { result.info->flags.native = std::stoi(parser.token); @@ -1781,20 +1782,20 @@ static void ParseTextureDef(const mapentity_t &entity, parser_t &parser, mapface // first one first if (auto &wal = map.load_image_meta(mapface.texname.c_str())) { if (!extinfo.info) { - extinfo.info = extended_texinfo_t{wal->contents, wal->flags, wal->value}; + extinfo.info = extended_texinfo_t{wal->contents_native, wal->flags, wal->value}; } extinfo.info->animation = wal->animation; } else if (!extinfo.info) { extinfo.info = extended_texinfo_t{}; } - if (extinfo.info->contents.native & Q2_CONTENTS_TRANSLUCENT) { + if (extinfo.info->contents_native & Q2_CONTENTS_TRANSLUCENT) { // remove TRANSLUCENT; it's only meant to be set by the compiler - extinfo.info->contents.native &= ~Q2_CONTENTS_TRANSLUCENT; + extinfo.info->contents_native &= ~Q2_CONTENTS_TRANSLUCENT; // but give us detail if we lack trans. this is likely what they intended if (!(extinfo.info->flags.native & (Q2_SURF_TRANS33 | Q2_SURF_TRANS66))) { - extinfo.info->contents.native |= Q2_CONTENTS_DETAIL; + extinfo.info->contents_native |= Q2_CONTENTS_DETAIL; if (qbsp_options.verbose.value()) { logging::print("WARNING: {}: swapped TRANSLUCENT for DETAIL\n", mapface.line); @@ -1817,23 +1818,25 @@ static void ParseTextureDef(const mapentity_t &entity, parser_t &parser, mapface // Mixing visible contents on the input brush is illegal { - const int32_t visible_contents = extinfo.info->contents.native & Q2_ALL_VISIBLE_CONTENTS; + const int32_t visible_contents = extinfo.info->contents_native & Q2_ALL_VISIBLE_CONTENTS; // TODO: Move to bspfile.hh API for (int32_t i = Q2_CONTENTS_SOLID; i <= Q2_LAST_VISIBLE_CONTENTS; i <<= 1) { if (visible_contents & i) { if (visible_contents != i) { FError("{}: Mixed visible contents: {}", mapface.line, - extinfo.info->contents.to_string(qbsp_options.target_game)); + qbsp_options.target_game->create_contents_from_native(extinfo.info->contents_native) + .to_string(qbsp_options.target_game)); } } } } // Other Q2 hard errors - if (extinfo.info->contents.native & (Q2_CONTENTS_MONSTER | Q2_CONTENTS_DEADMONSTER)) { + if (extinfo.info->contents_native & (Q2_CONTENTS_MONSTER | Q2_CONTENTS_DEADMONSTER)) { FError( - "{}: Illegal contents: {}", mapface.line, extinfo.info->contents.to_string(qbsp_options.target_game)); + "{}: Illegal contents: {}", mapface.line, qbsp_options.target_game->create_contents_from_native( + extinfo.info->contents_native).to_string(qbsp_options.target_game)); } // If Q2 style phong is enabled on a mirrored face, `light` will erroneously try to blend normals between @@ -1841,8 +1844,8 @@ static void ParseTextureDef(const mapentity_t &entity, parser_t &parser, mapface const bool wants_phong = !(extinfo.info->flags.native & Q2_SURF_LIGHT) && (extinfo.info->value != 0); // Technically this is not the 100% correct check for mirrored, but we don't have the full brush // contents set up at this point. Correct would be to call `portal_generates_face()`. - bool mirrored = (extinfo.info->contents.native != 0) && - !(extinfo.info->contents.native & + bool mirrored = (extinfo.info->contents_native != 0) && + !(extinfo.info->contents_native & (Q2_CONTENTS_DETAIL | Q2_CONTENTS_SOLID | Q2_CONTENTS_WINDOW | Q2_CONTENTS_AUX)); if (entity.epairs.has("_mirrorinside") && !entity.epairs.get_int("_mirrorinside")) { @@ -1855,7 +1858,10 @@ static void ParseTextureDef(const mapentity_t &entity, parser_t &parser, mapface } tx->miptex = FindMiptex(mapface.texname.c_str(), extinfo.info); - mapface.contents = {extinfo.info->contents}; + if (extinfo.info->contents_native != 0) + mapface.contents = qbsp_options.target_game->create_contents_from_native(extinfo.info->contents_native); + else + mapface.contents = contentflags_t::make(EWT_VISCONTENTS_EMPTY); tx->flags = {extinfo.info->flags}; tx->value = extinfo.info->value; @@ -2485,8 +2491,9 @@ static contentflags_t Brush_GetContents(const mapentity_t &entity, const mapbrus base_contents.set_clips_same_type(std::nullopt); } - base_contents.illusionary_visblocker = - string_iequals(entity.epairs.get("classname"), "func_illusionary_visblocker"); + if (string_iequals(entity.epairs.get("classname"), "func_illusionary_visblocker")) { + base_contents = contentflags_t::make(base_contents.flags | EWT_INVISCONTENTS_ILLUSIONARY_VISBLOCKER); + } // non-Q2: -transwater implies liquids are detail if (qbsp_options.target_game->id != GAME_QUAKE_II && qbsp_options.transwater.value()) { @@ -2996,10 +3003,10 @@ void ProcessAreaPortal(mapentity_t &entity) } for (auto &brush : entity.mapbrushes) { - brush.contents.native = Q2_CONTENTS_AREAPORTAL; + brush.contents = contentflags_t::make(EWT_INVISCONTENTS_AREAPORTAL); for (auto &face : brush.faces) { - face.contents.native = brush.contents.native; + face.contents = brush.contents; face.texinfo = map.skip_texinfo; } } @@ -3530,7 +3537,7 @@ static void ConvertMapFace(std::ofstream &f, const mapface_t &mapface, const con fprintDoubleAndSpc(f, quakeed.scale[1]); if (mapface.raw_info.has_value()) { - f << mapface.raw_info->contents.native << " " << mapface.raw_info->flags.native << " " + f << mapface.raw_info->contents_native << " " << mapface.raw_info->flags.native << " " << mapface.raw_info->value; } @@ -3554,7 +3561,7 @@ static void ConvertMapFace(std::ofstream &f, const mapface_t &mapface, const con fprintDoubleAndSpc(f, valve.scale[1]); if (mapface.raw_info.has_value()) { - f << mapface.raw_info->contents.native << " " << mapface.raw_info->flags.native << " " + f << mapface.raw_info->contents_native << " " << mapface.raw_info->flags.native << " " << mapface.raw_info->value; } @@ -3580,7 +3587,7 @@ static void ConvertMapFace(std::ofstream &f, const mapface_t &mapface, const con ewt::print(f, ") ) {} ", mapface.texname); if (mapface.raw_info.has_value()) { - f << mapface.raw_info->contents.native << " " << mapface.raw_info->flags.native << " " + f << mapface.raw_info->contents_native << " " << mapface.raw_info->flags.native << " " << mapface.raw_info->value; } else { f << "0 0 0"; diff --git a/qbsp/portals.cc b/qbsp/portals.cc index 832d4883..64e8f962 100644 --- a/qbsp/portals.cc +++ b/qbsp/portals.cc @@ -66,7 +66,8 @@ bool Portal_VisFlood(const portal_t *p) contentflags_t contents1 = ClusterContents(p->nodes[1]); /* Can't see through func_illusionary_visblocker */ - if (contents0.illusionary_visblocker || contents1.illusionary_visblocker) + if ((contents0.flags & EWT_INVISCONTENTS_ILLUSIONARY_VISBLOCKER) + || (contents1.flags & EWT_INVISCONTENTS_ILLUSIONARY_VISBLOCKER)) return false; // Check per-game visibility @@ -613,7 +614,7 @@ static void FloodAreas_r(node_t *node) auto *leafdata = node->get_leafdata(); Q_assert(leafdata); - if (leafdata->contents.native & Q2_CONTENTS_AREAPORTAL) { + if (leafdata->contents.flags & EWT_INVISCONTENTS_AREAPORTAL) { // grab the func_areanode entity mapentity_t *entity = AreanodeEntityForLeaf(node); @@ -701,7 +702,7 @@ static void FindAreas_r(node_t *node) // area portals are always only flooded into, never // out of - if (leafdata->contents.native & Q2_CONTENTS_AREAPORTAL) + if (leafdata->contents.flags & EWT_INVISCONTENTS_AREAPORTAL) return; map.c_areas++; @@ -781,7 +782,7 @@ static void FindAreaPortalExits_R(node_t *n, std::unordered_set &visit continue; // is this an exit? - if (!(neighbour->get_leafdata()->contents.native & Q2_CONTENTS_AREAPORTAL) && + if (!(neighbour->get_leafdata()->contents.flags & EWT_INVISCONTENTS_AREAPORTAL) && !neighbour->get_leafdata()->contents.is_solid(qbsp_options.target_game)) { exits.emplace_back(p, neighbour); continue; @@ -789,7 +790,7 @@ static void FindAreaPortalExits_R(node_t *n, std::unordered_set &visit // valid edge to explore? // if this isn't an exit, don't leave AREAPORTAL - if (!(neighbour->get_leafdata()->contents.native & Q2_CONTENTS_AREAPORTAL)) + if (!(neighbour->get_leafdata()->contents.flags & EWT_INVISCONTENTS_AREAPORTAL)) continue; // continue exploding @@ -839,8 +840,8 @@ static void DebugAreaPortalBothSidesLeak(node_t *node) return false; // don't go back into an areaportal - if ((p->nodes[0]->get_leafdata()->contents.native & Q2_CONTENTS_AREAPORTAL) || - (p->nodes[1]->get_leafdata()->contents.native & Q2_CONTENTS_AREAPORTAL)) + if ((p->nodes[0]->get_leafdata()->contents.flags & EWT_INVISCONTENTS_AREAPORTAL) || + (p->nodes[1]->get_leafdata()->contents.flags & EWT_INVISCONTENTS_AREAPORTAL)) return false; return true; @@ -898,7 +899,7 @@ static void SetAreaPortalAreas_r(node_t *node) auto *leafdata = node->get_leafdata(); - if (leafdata->contents.native != Q2_CONTENTS_AREAPORTAL) + if (leafdata->contents.flags != EWT_INVISCONTENTS_AREAPORTAL) return; if (leafdata->area) diff --git a/qbsp/qbsp.cc b/qbsp/qbsp.cc index f332d879..7ecb9224 100644 --- a/qbsp/qbsp.cc +++ b/qbsp/qbsp.cc @@ -632,7 +632,9 @@ void qbsp_settings::load_texture_def(const std::string &pathname) } if (parser.parse_token(PARSE_SAMELINE | PARSE_OPTIONAL)) { - texinfo = extended_texinfo_t{{std::stoi(parser.token)}}; + uint32_t native = std::stoul(parser.token); + + texinfo = extended_texinfo_t{.contents_native = native}; if (parser.parse_token(PARSE_SAMELINE | PARSE_OPTIONAL)) { texinfo->flags.native = std::stoi(parser.token); @@ -803,7 +805,9 @@ static void ExportBrushList_r(const mapentity_t &entity, node_t *node, brush_lis { if (node->is_leaf()) { auto *leafdata = node->get_leafdata(); - if (leafdata->contents.native) { + int native = qbsp_options.target_game->contents_to_native( + qbsp_options.target_game->contents_remap_for_export(leafdata->contents, gamedef_t::remap_type_t::leaf)); + if (native) { if (leafdata->original_brushes.size()) { leafdata->numleafbrushes = leafdata->original_brushes.size(); stats.total_leaf_brushes += leafdata->numleafbrushes; @@ -813,12 +817,13 @@ static void ExportBrushList_r(const mapentity_t &entity, node_t *node, brush_lis if (!b->mapbrush->outputnumber.has_value()) { b->mapbrush->outputnumber = {static_cast(map.bsp.dbrushes.size())}; + int brushcontents = qbsp_options.target_game->contents_to_native(qbsp_options.target_game + ->contents_remap_for_export(b->contents, gamedef_t::remap_type_t::brush)); + dbrush_t &brush = map.bsp.dbrushes.emplace_back( dbrush_t{.firstside = static_cast(map.bsp.dbrushsides.size()), .numsides = 0, - .contents = qbsp_options.target_game - ->contents_remap_for_export(b->contents, gamedef_t::remap_type_t::brush) - .native}); + .contents = brushcontents}); for (auto &side : b->mapbrush->faces) { @@ -1348,8 +1353,9 @@ static bspxbrushes_permodel BSPX_Brushes_AddModel(int modelnum, const std::vecto perbrush.bounds = b->bounds; const auto &contents = b->contents; + const int native = qbsp_options.target_game->contents_to_native(contents); - switch (contents.native) { + switch (native) { // contents should match the engine. case CONTENTS_EMPTY: // really an error, but whatever case CONTENTS_SOLID: // these are okay @@ -1360,7 +1366,7 @@ static bspxbrushes_permodel BSPX_Brushes_AddModel(int modelnum, const std::vecto if (contents.is_clip(qbsp_options.target_game)) { perbrush.contents = BSPXBRUSHES_CONTENTS_CLIP; } else { - perbrush.contents = contents.native; + perbrush.contents = native; } break; // case CONTENTS_LADDER: diff --git a/qbsp/writebsp.cc b/qbsp/writebsp.cc index b9fd9e61..845be046 100644 --- a/qbsp/writebsp.cc +++ b/qbsp/writebsp.cc @@ -114,7 +114,7 @@ ExportClipNodes static size_t ExportClipNodes(node_t *node) { if (auto *leafdata = node->get_leafdata()) { - return leafdata->contents.native; + return qbsp_options.target_game->contents_to_native(leafdata->contents); } auto *nodedata = node->get_nodedata(); @@ -173,7 +173,7 @@ static void ExportLeaf(node_t *node) remapped.to_string(qbsp_options.target_game)); } - dleaf.contents = remapped.native; + dleaf.contents = qbsp_options.target_game->contents_to_native(remapped); if (node->bounds.maxs()[0] < node->bounds.mins()[0]) { throw std::runtime_error("leaf bounds was unassigned"); @@ -330,7 +330,7 @@ void BeginBSPFile() // Leave room for leaf 0 (must be solid) auto &solid_leaf = map.bsp.dleafs.emplace_back(); - solid_leaf.contents = qbsp_options.target_game->create_solid_contents().native; + solid_leaf.contents = qbsp_options.target_game->contents_to_native(qbsp_options.target_game->create_solid_contents()); solid_leaf.cluster = CLUSTER_INVALID; Q_assert(map.bsp.dleafs.size() == 1); } diff --git a/tests/test_common.cc b/tests/test_common.cc index bfefe840..6aa71bb4 100644 --- a/tests/test_common.cc +++ b/tests/test_common.cc @@ -27,21 +27,22 @@ TEST_SUITE("common") const auto detail_fence = game_q1->create_detail_fence_contents(solid); const auto detail_illusionary = game_q1->create_detail_illusionary_contents(solid); - const std::array test_contents{contentflags_t{CONTENTS_EMPTY}, contentflags_t{CONTENTS_SOLID}, - contentflags_t{CONTENTS_WATER}, contentflags_t{CONTENTS_SLIME}, contentflags_t{CONTENTS_LAVA}, - contentflags_t{CONTENTS_SKY}, - - detail_solid, detail_wall, detail_fence, detail_illusionary}; + const std::array test_contents{game_q1->create_contents_from_native(CONTENTS_EMPTY), + game_q1->create_contents_from_native(CONTENTS_SOLID), + game_q1->create_contents_from_native(CONTENTS_WATER), + game_q1->create_contents_from_native(CONTENTS_SLIME), + game_q1->create_contents_from_native(CONTENTS_LAVA), + game_q1->create_contents_from_native(CONTENTS_SKY), + detail_solid, detail_wall, detail_fence, detail_illusionary}; SUBCASE("solid combined with others") { - CHECK(solid.native == CONTENTS_SOLID); - CHECK(!solid.game_data.has_value()); + CHECK(game_q1->contents_to_native(solid) == CONTENTS_SOLID); for (const auto &c : test_contents) { auto combined = game_q1->combine_contents(solid, c); - CHECK(combined.native == CONTENTS_SOLID); + CHECK(game_q1->contents_to_native(combined) == CONTENTS_SOLID); CHECK(combined.is_solid(game_q1)); CHECK(!combined.is_any_detail(game_q1)); @@ -50,15 +51,15 @@ TEST_SUITE("common") SUBCASE("detail_illusionary plus water") { - auto combined = game_q1->combine_contents(detail_illusionary, contentflags_t{CONTENTS_WATER}); + auto combined = game_q1->combine_contents(detail_illusionary, game_q1->create_contents_from_native(CONTENTS_WATER)); - CHECK(combined.native == CONTENTS_WATER); + CHECK(game_q1->contents_to_native(combined) == CONTENTS_WATER); CHECK(combined.is_detail_illusionary(game_q1)); } SUBCASE("detail_solid plus water") { - auto combined = game_q1->combine_contents(detail_solid, contentflags_t{CONTENTS_WATER}); + auto combined = game_q1->combine_contents(detail_solid, game_q1->create_contents_from_native(CONTENTS_WATER)); CHECK(combined.is_any_solid(game_q1)); CHECK(combined.is_detail_solid(game_q1)); @@ -68,7 +69,7 @@ TEST_SUITE("common") SUBCASE("detail_solid plus sky") { - auto combined = game_q1->combine_contents(detail_solid, contentflags_t{CONTENTS_SKY}); + auto combined = game_q1->combine_contents(detail_solid, game_q1->create_contents_from_native(CONTENTS_SKY)); CHECK(!combined.is_detail_solid(game_q1)); CHECK(combined.is_sky(game_q1)); @@ -123,7 +124,7 @@ TEST_SUITE("common") { auto *game = bspver_q2.game; - auto origin = game->face_get_contents("", {}, {Q2_CONTENTS_ORIGIN}); + auto origin = game->face_get_contents("", {}, game->create_contents_from_native(Q2_CONTENTS_ORIGIN)); CHECK(origin.is_origin(game)); CHECK(!origin.is_empty(game)); @@ -219,24 +220,30 @@ TEST_SUITE("common") TEST_CASE("q2 contents") { - const std::array test_contents{contentflags_t{Q2_CONTENTS_EMPTY}, contentflags_t{Q2_CONTENTS_SOLID}, - contentflags_t{Q2_CONTENTS_WINDOW}, contentflags_t{Q2_CONTENTS_AUX}, contentflags_t{Q2_CONTENTS_LAVA}, - contentflags_t{Q2_CONTENTS_SLIME}, contentflags_t{Q2_CONTENTS_WATER}, contentflags_t{Q2_CONTENTS_MIST}, - - contentflags_t{Q2_CONTENTS_DETAIL | Q2_CONTENTS_SOLID}, - contentflags_t{Q2_CONTENTS_DETAIL | Q2_CONTENTS_WINDOW}, - contentflags_t{Q2_CONTENTS_DETAIL | Q2_CONTENTS_AUX}, contentflags_t{Q2_CONTENTS_DETAIL | Q2_CONTENTS_LAVA}, - contentflags_t{Q2_CONTENTS_DETAIL | Q2_CONTENTS_SLIME}, - contentflags_t{Q2_CONTENTS_DETAIL | Q2_CONTENTS_WATER}, - contentflags_t{Q2_CONTENTS_DETAIL | Q2_CONTENTS_MIST}}; - auto *game_q2 = bspver_q2.game; + const std::array test_contents{ + game_q2->create_contents_from_native(Q2_CONTENTS_EMPTY), + game_q2->create_contents_from_native(Q2_CONTENTS_SOLID), + game_q2->create_contents_from_native(Q2_CONTENTS_WINDOW), + game_q2->create_contents_from_native(Q2_CONTENTS_AUX), + game_q2->create_contents_from_native(Q2_CONTENTS_LAVA), + game_q2->create_contents_from_native(Q2_CONTENTS_SLIME), + game_q2->create_contents_from_native(Q2_CONTENTS_WATER), + game_q2->create_contents_from_native(Q2_CONTENTS_MIST), + game_q2->create_contents_from_native(Q2_CONTENTS_DETAIL | Q2_CONTENTS_SOLID), + game_q2->create_contents_from_native(Q2_CONTENTS_DETAIL | Q2_CONTENTS_WINDOW), + game_q2->create_contents_from_native(Q2_CONTENTS_DETAIL | Q2_CONTENTS_AUX), + game_q2->create_contents_from_native(Q2_CONTENTS_DETAIL | Q2_CONTENTS_LAVA), + game_q2->create_contents_from_native(Q2_CONTENTS_DETAIL | Q2_CONTENTS_SLIME), + game_q2->create_contents_from_native(Q2_CONTENTS_DETAIL | Q2_CONTENTS_WATER), + game_q2->create_contents_from_native(Q2_CONTENTS_DETAIL | Q2_CONTENTS_MIST) + }; + SUBCASE("solid combined with others") { auto solid = game_q2->create_solid_contents(); - CHECK(solid.native == Q2_CONTENTS_SOLID); - CHECK(!solid.game_data.has_value()); + CHECK(game_q2->contents_to_native(solid) == Q2_CONTENTS_SOLID); for (const auto &c : test_contents) { // solid is treated specially in Q2 and wipes out any other content @@ -244,30 +251,60 @@ TEST_SUITE("common") auto combined = game_q2->contents_remap_for_export( game_q2->combine_contents(solid, c), gamedef_t::remap_type_t::leaf); - CHECK(combined.native == Q2_CONTENTS_SOLID); - CHECK(!combined.game_data.has_value()); + CHECK(game_q2->contents_to_native(combined) == Q2_CONTENTS_SOLID); CHECK(combined.is_solid(game_q2)); } } SUBCASE("water combined with others") { - contentflags_t water{Q2_CONTENTS_WATER}; + contentflags_t water = game_q2->create_contents_from_native(Q2_CONTENTS_WATER); for (const auto &c : test_contents) { auto combined = game_q2->combine_contents(water, c); - CHECK(!combined.game_data.has_value()); SUBCASE(fmt::format("water combined with {}", c.to_string(game_q2)).c_str()) { - if (!(c.native & Q2_CONTENTS_SOLID)) { - CHECK(combined.native == (Q2_CONTENTS_WATER | c.native)); + if (!(game_q2->contents_to_native(c) & Q2_CONTENTS_SOLID)) { + CHECK(game_q2->contents_to_native(combined) == (Q2_CONTENTS_WATER | game_q2->contents_to_native(c))); } } } } } + TEST_CASE("q1 contents roundtrip") + { + auto *game_q1 = bspver_q1.game; + + for (int i = CONTENTS_EMPTY; i >= CONTENTS_MIN; --i) { + contentflags_t test_internal = game_q1->create_contents_from_native(i); + + uint32_t test_out = game_q1->contents_to_native(test_internal); + + INFO("contents " << i); + CHECK(test_out == i); + } + } + + TEST_CASE("q2 contents roundtrip") + { + auto *game_q2 = bspver_q2.game; + + CHECK(game_q2->contents_to_native(game_q2->create_contents_from_native(0)) == 0); + + for (int i = 0; i <= 31; ++i) { + uint32_t test_in = nth_bit(i); + + contentflags_t test_internal = game_q2->create_contents_from_native(test_in); + + uint32_t test_out = game_q2->contents_to_native(test_internal); + + INFO("contents bit " << i); + CHECK(test_out == test_in); + } + } + TEST_CASE("imglib png loader") { auto *game = bspver_q2.game; diff --git a/tests/test_ltface.cc b/tests/test_ltface.cc index 9d2c2ccc..15d632c7 100644 --- a/tests/test_ltface.cc +++ b/tests/test_ltface.cc @@ -47,6 +47,7 @@ static testresults_t QbspVisLight_Common(const std::filesystem::path &name, std: args.push_back("-noverbose"); } else { args.push_back("-nopercent"); + args.push_back("-loghulls"); } for (auto &extra : extra_qbsp_args) { args.push_back(extra); diff --git a/tests/test_main.cc b/tests/test_main.cc index 598cd617..72b31c46 100644 --- a/tests/test_main.cc +++ b/tests/test_main.cc @@ -17,12 +17,16 @@ int main(int argc, char **argv) for (int i = 1; i < argc; ++i) { // parse "-threads 1" - if (!strcmp("-threads", argv[i]) && (i + 1) < argc) { + if (!strcmp("-threads", argv[i]) || !strcmp("--threads", argv[i])) { + if (!(i + 1 < argc)) { + logging::print("--threads requires an argument\n"); + exit(1); + } configureTBB(atoi(argv[i + 1]), false); continue; } // parse "-verbose" - if (!strcmp("-verbose", argv[i])) { + if (!strcmp("-verbose", argv[i]) || !strcmp("--verbose", argv[i])) { tests_verbose = true; continue; } diff --git a/tests/test_qbsp.cc b/tests/test_qbsp.cc index d46c00ff..224be03c 100644 --- a/tests/test_qbsp.cc +++ b/tests/test_qbsp.cc @@ -106,6 +106,7 @@ std::tuple> LoadTestmap( args.push_back("-noverbose"); } else { args.push_back("-nopercent"); + args.push_back("-loghulls"); } for (auto &arg : extra_args) { @@ -351,7 +352,9 @@ TEST_CASE("duplicatePlanes" * doctest::test_suite("qbsp")) REQUIRE(1 == worldspawn.mapbrushes.size()); CHECK(6 == worldspawn.mapbrushes.front().faces.size()); - auto brush = LoadBrush(worldspawn, worldspawn.mapbrushes.front(), {CONTENTS_SOLID}, 0, std::nullopt); + auto *game = bspver_q1.game; + + auto brush = LoadBrush(worldspawn, worldspawn.mapbrushes.front(), game->create_contents_from_native(CONTENTS_SOLID), 0, std::nullopt); CHECK(6 == brush->sides.size()); } @@ -1413,13 +1416,15 @@ TEST_CASE("q1_sealing" * doctest::test_suite("testmaps_q1")) TEST_CASE("q1_csg" * doctest::test_suite("testmaps_q1")) { + auto *game = bspver_q1.game; + auto &entity = LoadMapPath("q1_csg.map"); REQUIRE(entity.mapbrushes.size() == 2); bspbrush_t::container bspbrushes; for (int i = 0; i < 2; ++i) { - auto b = LoadBrush(entity, entity.mapbrushes[i], {CONTENTS_SOLID}, 0, std::nullopt); + auto b = LoadBrush(entity, entity.mapbrushes[i], game->create_contents_from_native(CONTENTS_SOLID), 0, std::nullopt); CHECK(6 == b->sides.size()); diff --git a/tests/test_qbsp_q2.cc b/tests/test_qbsp_q2.cc index e8e2bba8..569ab9bb 100644 --- a/tests/test_qbsp_q2.cc +++ b/tests/test_qbsp_q2.cc @@ -773,13 +773,13 @@ TEST_CASE("q2_detail_wall" * doctest::test_suite("testmaps_q2")) { INFO("check leaf / brush contents"); - CAPTURE(contentflags_t{detail_wall_leaf->contents}.to_string(game)); + CAPTURE(game->create_contents_from_native(detail_wall_leaf->contents).to_string(game)); CHECK(Q2_CONTENTS_SOLID == detail_wall_leaf->contents); REQUIRE(1 == Leaf_Brushes(&bsp, detail_wall_leaf).size()); auto *brush = Leaf_Brushes(&bsp, detail_wall_leaf).at(0); - CAPTURE(contentflags_t{brush->contents}.to_string(game)); + CAPTURE(game->create_contents_from_native(brush->contents).to_string(game)); CHECK(Q2_CONTENTS_SOLID == brush->contents); } @@ -817,7 +817,7 @@ TEST_CASE("q2_detail_fence" * doctest::test_suite("testmaps_q2")) { INFO("check leaf / brush contents"); - CAPTURE(contentflags_t{detail_wall_leaf->contents}.to_string(game)); + CAPTURE(game->create_contents_from_native(detail_wall_leaf->contents).to_string(game)); CHECK( (Q2_CONTENTS_WINDOW | Q2_CONTENTS_DETAIL | Q2_CONTENTS_TRANSLUCENT) == detail_wall_leaf->contents);