/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA See file, 'COPYING', for details. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include namespace settings { class common_settings; } struct lump_t { int32_t fileofs; int32_t filelen; // serialize for streams void stream_write(std::ostream &s) const; 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_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_NO_WATERJUMP = nth_bit(13), // re-release EWT_INVISCONTENTS_PROJECTILECLIP = nth_bit(14), // re-release 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 EWT_INVISCONTENTS_ILLUSIONARY_VISBLOCKER = nth_bit(28), EWT_CFLAG_DETAIL = nth_bit(29), // brushes to be added after vis leafs // unused Q2 contents bits - just present here so we can roundtrip all 32-bit Q2 contents EWT_CFLAG_Q2_UNUSED_7 = nth_bit(30), EWT_CFLAG_Q2_UNUSED_8 = nth_bit(31), EWT_CFLAG_Q2_UNUSED_9 = nth_bit(32), EWT_CFLAG_Q2_UNUSED_10 = nth_bit(33), EWT_CFLAG_Q2_UNUSED_11 = nth_bit(34), EWT_CFLAG_Q2_UNUSED_12 = nth_bit(35), EWT_CFLAG_Q2_UNUSED_30 = nth_bit(36), EWT_CFLAG_Q2_UNUSED_31 = nth_bit(37), // masks 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_ALL_INVISCONTENTS = EWT_INVISCONTENTS_ORIGIN | EWT_INVISCONTENTS_PLAYERCLIP | EWT_INVISCONTENTS_MONSTERCLIP | EWT_INVISCONTENTS_AREAPORTAL | EWT_INVISCONTENTS_ILLUSIONARY_VISBLOCKER | EWT_INVISCONTENTS_PROJECTILECLIP, }; struct gamedef_t; struct contentflags_t { contents_t flags; 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; // is any kind of detail? (solid, liquid, etc.) bool is_any_detail(const gamedef_t *game) const; bool is_detail_solid(const gamedef_t *game) const; bool is_detail_wall(const gamedef_t *game) const; 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; bool is_any_solid(const gamedef_t *game) const; // solid, not detail or any other extended content types bool is_solid(const gamedef_t *game) const; bool is_sky(const gamedef_t *game) const; bool is_liquid(const gamedef_t *game) const; bool is_valid(const gamedef_t *game, bool strict = true) const; bool is_clip(const gamedef_t *game) const; bool is_origin(const gamedef_t *game) const; void make_valid(const gamedef_t *game); bool is_fence(const gamedef_t *game) const; // check if this content's `type` - which is distinct from various // flags that turn things on/off - match. Exactly what the native // "type" is depends on the game, but any of the detail flags must // also match. bool types_equal(const contentflags_t &other, const gamedef_t *game) const; // when multiple brushes contribute to a leaf, the higher priority // one determines the leaf contents int32_t priority(const gamedef_t *game) const; // whether this should chop (if so, only lower priority content brushes get chopped) // should return true only for solid / opaque content types 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; } } if (flags & EWT_INVISCONTENTS_PLAYERCLIP) { return 10; } else if (flags & EWT_INVISCONTENTS_MONSTERCLIP) { return 11; } else if (flags & EWT_INVISCONTENTS_PROJECTILECLIP) { return 14; } 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 { // native flags value; what's written to the BSP basically int32_t native; // an invisible surface (Q1 "skip" texture, Q2 SURF_NODRAW) bool is_nodraw; // hint surface bool is_hint; // is a skip surface from a hint brush bool is_hintskip; // don't receive dirtmapping bool no_dirt; // don't cast a shadow bool no_shadow; // light doesn't bounce off this face bool no_bounce; // opt out of minlight on this face (including opting out of local minlight, so // not the same as just setting minlight to 0). bool no_minlight; // don't expand this face for larger clip hulls bool no_expand; // block any way phong can be enabled bool no_phong; // this face doesn't receive light bool light_ignore; // if true, rescales any surface light emitted by these brushes to emit 50% light at 90 degrees from the surface // normal if false, use a more natural angle falloff of 0% at 90 degrees std::optional surflight_rescale; // override surface lighting style std::optional surflight_style; // override surface lighting targetname std::optional surflight_targetname; // override the textures' surflight color std::optional surflight_color; // surface light rescaling std::optional surflight_minlight_scale; // surface light attenuation std::optional surflight_atten; // if non zero, enables phong shading and gives the angle threshold to use float phong_angle; // if non zero, overrides _phong_angle for concave joints float phong_angle_concave; // _phong_group key, equivalent q2 map format's use of the "value" field int phong_group; // minlight value for this face. empty = inherit from worldspawn. std::optional minlight; // red minlight colors for this face qvec3b minlight_color; // custom opacity std::optional light_alpha; // two-sided lighting std::optional light_twosided; // maxlight value for this face float maxlight; // light color scale float lightcolorscale = 1.0; // surface light group int32_t surflight_group; // custom world_units_per_luxel for this geometry std::optional world_units_per_luxel; std::optional object_channel_mask; bool needs_write() const; public: // sort support bool operator<(const surfflags_t &other) const; bool operator>(const surfflags_t &other) const; bool is_valid(const gamedef_t *game) const; }; // native game target ID enum gameid_t { GAME_UNKNOWN, GAME_QUAKE, GAME_HEXEN_II, GAME_HALF_LIFE, GAME_QUAKE_II, GAME_TOTAL }; struct content_stats_base_t { virtual ~content_stats_base_t() = default; }; // Game definition, which contains data specific to // the game a BSP version is being compiled for. struct gamedef_t { // friendly name, used for commands const char *friendly_name; // ID, used for quick comparisons gameid_t id = GAME_UNKNOWN; // whether the game uses an RGB lightmap or not bool has_rgb_lightmap = false; // whether the game supports content flags on brush models bool allow_contented_bmodels = false; // base dir for searching for paths, in case we are in a mod dir // note: we need this to be able to be overridden via options const std::string default_base_dir = {}; // max values of entity key & value pairs, only used for // printing warnings. size_t max_entity_key = 32; size_t max_entity_value = 128; gamedef_t(const char *friendly_name, const char *default_base_dir); // surface stores lightmap/luxel color data virtual bool surf_is_lightmapped( const surfflags_t &flags, const char *texname, bool light_nodraw, bool lightgrid_enabled) const = 0; // surface can be emissive virtual bool surf_is_emissive(const surfflags_t &flags, const char *texname) const = 0; virtual bool surf_is_subdivided(const surfflags_t &flags) const = 0; virtual bool surfflags_are_valid(const surfflags_t &flags) const = 0; /** * We block certain surface flag combinations from ever smoothing together * e.g. warping and non-warping */ virtual bool surfflags_may_phong(const surfflags_t &a, const surfflags_t &b) const = 0; 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; virtual contentflags_t create_detail_illusionary_contents(const contentflags_t &original) const = 0; virtual contentflags_t create_detail_fence_contents(const contentflags_t &original) const = 0; virtual contentflags_t create_detail_wall_contents(const contentflags_t &original) const = 0; virtual contentflags_t create_detail_solid_contents(const contentflags_t &original) const = 0; virtual contentflags_t clear_detail(const contentflags_t &original) const = 0; virtual contentflags_t set_detail(const contentflags_t &original) const = 0; virtual bool contents_are_type_equal(const contentflags_t &self, const contentflags_t &other) const = 0; virtual bool contents_are_equal(const contentflags_t &self, const contentflags_t &other) const = 0; virtual bool contents_are_any_detail(const contentflags_t &contents) const = 0; virtual bool contents_are_detail_solid(const contentflags_t &contents) const = 0; virtual bool contents_are_detail_wall(const contentflags_t &contents) const = 0; virtual bool contents_are_detail_fence(const contentflags_t &contents) const = 0; virtual bool contents_are_detail_illusionary(const contentflags_t &contents) const = 0; virtual bool contents_are_origin(const contentflags_t &contents) const = 0; virtual bool contents_are_clip(const contentflags_t &contents) const = 0; virtual bool contents_are_empty(const contentflags_t &contents) const = 0; virtual bool contents_clip_same_type(const contentflags_t &self, const contentflags_t &other) const = 0; virtual bool contents_are_any_solid(const contentflags_t &contents) const = 0; virtual bool contents_are_solid(const contentflags_t &contents) const = 0; virtual bool contents_are_sky(const contentflags_t &contents) const = 0; virtual bool contents_are_liquid(const contentflags_t &contents) const = 0; virtual bool contents_are_valid(const contentflags_t &contents, bool strict = true) const = 0; virtual int32_t contents_from_string(const std::string_view &str) const = 0; virtual bool portal_can_see_through( const contentflags_t &contents0, const contentflags_t &contents1, bool transwater) const = 0; virtual bool contents_seals_map(const contentflags_t &contents) const = 0; virtual bool contents_are_opaque(const contentflags_t &contents, bool transwater) const = 0; enum class remap_type_t { brush, leaf }; virtual contentflags_t contents_remap_for_export(const contentflags_t &contents, remap_type_t type) const = 0; virtual contentflags_t combine_contents(const contentflags_t &a, const contentflags_t &b) const = 0; // for a portal with contents from `a` to `b`, returns what type of face should be rendered facing `a` and `b` virtual contentflags_t portal_visible_contents(const contentflags_t &a, const contentflags_t &b) const = 0; // for a brush with the given contents touching a portal with the required `portal_visible_contents`, as determined // by portal_visible_contents, should the `brushside_side` of the brushside generate a face? e.g. liquids generate // front and back sides by default, but for q1 detail_wall/detail_illusionary the back side is opt-in with // _mirrorinside virtual bool portal_generates_face(const contentflags_t &portal_visible_contents, const contentflags_t &brushcontents, planeside_t brushside_side) const = 0; virtual std::string get_contents_display(const contentflags_t &contents) const = 0; virtual void contents_make_valid(contentflags_t &contents) const = 0; virtual const std::initializer_list &get_hull_sizes() const = 0; virtual contentflags_t face_get_contents( const std::string &texname, const surfflags_t &flags, const contentflags_t &contents) const = 0; virtual void init_filesystem(const fs::path &source, const settings::common_settings &settings) const = 0; virtual const std::vector &get_default_palette() const = 0; virtual std::unique_ptr create_content_stats() const = 0; virtual void count_contents_in_stats(const contentflags_t &contents, content_stats_base_t &stats) const = 0; virtual void print_content_stats(const content_stats_base_t &stats, const char *what) const = 0; }; // Lump specification; stores the name and size // of an individual entry in the lump. Count is // calculated as (lump_size / size) struct lumpspec_t { const char *name; size_t size; }; // BSP version struct & instances struct bspversion_t { /* identifier value, the first int32_t in the header */ int32_t ident; /* version value, if supported */ std::optional version; /* short name used for command line args, etc */ const char *short_name; /* full display name for printing */ const char *name; /* lump specification */ const std::initializer_list lumps; /* game ptr */ const gamedef_t *game; /* if we surpass the limits of this format, upgrade to this one */ const bspversion_t *extended_limits; }; // FMT support template<> struct fmt::formatter { constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { return ctx.end(); } template auto format(const bspversion_t &v, FormatContext &ctx) -> decltype(ctx.out()) { if (v.name) { fmt::format_to(ctx.out(), "{} ", v.name); } // Q2-esque BSPs are printed as, ex, IBSP:38 if (v.version.has_value()) { char ident[5] = {(char)(v.ident & 0xFF), (char)((v.ident >> 8) & 0xFF), (char)((v.ident >> 16) & 0xFF), (char)((v.ident >> 24) & 0xFF), '\0'}; return fmt::format_to(ctx.out(), "{}:{}", ident, v.version.value()); } // Q1-esque BSPs are printed as, ex, bsp29 return fmt::format_to(ctx.out(), "{}", v.short_name); } }; struct texvecf : qmat { using qmat::qmat; template constexpr qvec uvs(const qvec &pos) const { return {(pos[0] * this->at(0, 0) + pos[1] * this->at(0, 1) + pos[2] * this->at(0, 2) + this->at(0, 3)), (pos[0] * this->at(1, 0) + pos[1] * this->at(1, 1) + pos[2] * this->at(1, 2) + this->at(1, 3))}; } template constexpr qvec uvs(const qvec &pos, const int32_t &width, const int32_t &height) const { return uvs(pos) / qvec{width, height}; } // Not blit compatible because qmat is column-major but // texvecs are row-major void stream_read(std::istream &stream); void stream_write(std::ostream &stream) const; }; // Fmt support template<> struct fmt::formatter : fmt::formatter> { }; // type to store a hull index; max 256 hulls, zero is valid. using hull_index_t = std::optional;