/* 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 struct lump_t { int32_t fileofs; int32_t filelen; auto stream_data() { return std::tie(fileofs, filelen); } }; // helper functions to quickly numerically cast mins/maxs // and floor/ceil them in the case of float -> integral template inline qvec aabb_mins_cast(const qvec &f, const char *overflow_message = "mins") { if constexpr (std::is_floating_point_v && !std::is_floating_point_v) return {numeric_cast(floor(f[0]), overflow_message), numeric_cast(floor(f[1]), overflow_message), numeric_cast(floor(f[2]), overflow_message)}; else return {numeric_cast(f[0], overflow_message), numeric_cast(f[1], overflow_message), numeric_cast(f[2], overflow_message)}; } template inline qvec aabb_maxs_cast(const qvec &f, const char *overflow_message = "maxs") { if constexpr (std::is_floating_point_v && !std::is_floating_point_v) return {numeric_cast(ceil(f[0]), overflow_message), numeric_cast(ceil(f[1]), overflow_message), numeric_cast(ceil(f[2]), overflow_message)}; else return {numeric_cast(f[0], overflow_message), numeric_cast(f[1], overflow_message), numeric_cast(f[2], overflow_message)}; } // shortcut template to trim (& convert) std::arrays // between two lengths template constexpr ADest array_cast(const ASrc &src, const char *overflow_message = "src") { ADest dest{}; for (size_t i = 0; i < std::min(dest.size(), src.size()); i++) { if constexpr (std::is_arithmetic_v && std::is_arithmetic_v) dest[i] = numeric_cast(src[i], overflow_message); else dest[i] = static_cast(src[i]); } return dest; } struct gamedef_t; struct contentflags_t { // native flags value; what's written to the BSP basically int32_t native = 0; // 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; 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_fence(const gamedef_t *game) const; bool is_detail_illusionary(const gamedef_t *game) const; bool is_mirrored(const gamedef_t *game) const; contentflags_t &set_mirrored(const std::optional &mirror_inside_value) { mirror_inside = mirror_inside_value; return *this; } inline bool will_clip_same_type(const gamedef_t *game) const { return will_clip_same_type(game, *this); } bool will_clip_same_type(const gamedef_t *game, const contentflags_t &other) const; contentflags_t &set_clips_same_type(const std::optional &clips_same_type_value) { clips_same_type = clips_same_type_value; return *this; } bool is_empty(const gamedef_t *game) const; 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); inline bool is_fence(const gamedef_t *game) const { return is_detail_fence(game) || is_detail_illusionary(game); } // 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; }; 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_skip; // completely ignore, allowing non-closed brushes (Q2 SURF_SKIP) bool is_hintskip; // hint surface bool is_hint; // 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 bool no_minlight; // don't expand this face for larger clip hulls bool no_expand; // this face doesn't receive light bool light_ignore; // if non zero, enables phong shading and gives the angle threshold to use vec_t phong_angle; // if non zero, overrides _phong_angle for concave joints vec_t phong_angle_concave; // minlight value for this face vec_t minlight; // red minlight colors for this face qvec3b minlight_color; // custom opacity vec_t light_alpha; constexpr bool needs_write() const { return no_dirt || no_shadow || no_bounce || no_minlight || no_expand || light_ignore || phong_angle || phong_angle_concave || minlight || !qv::emptyExact(minlight_color) || light_alpha; } private: constexpr auto as_tuple() const { return std::tie(native, is_skip, is_hintskip, is_hint, no_dirt, no_shadow, no_bounce, no_minlight, no_expand, light_ignore, phong_angle, phong_angle_concave, minlight, minlight_color, light_alpha); } public: // sort support constexpr bool operator<(const surfflags_t &other) const { return as_tuple() < other.as_tuple(); } constexpr bool operator>(const surfflags_t &other) const { return as_tuple() > other.as_tuple(); } 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 { // 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 *default_base_dir) : default_base_dir(default_base_dir) { } virtual bool surf_is_lightmapped(const surfflags_t &flags) const = 0; virtual bool surf_is_subdivided(const surfflags_t &flags) const = 0; virtual bool surfflags_are_valid(const surfflags_t &flags) 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 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_solid_contents(const contentflags_t &original) const = 0; virtual bool contents_are_type_equal(const contentflags_t &self, const contentflags_t &other) const = 0; virtual bool contents_are_equal(const contentflags_t &self, const contentflags_t &other) const = 0; virtual bool contents_are_any_detail(const contentflags_t &contents) const = 0; virtual bool contents_are_detail_solid(const contentflags_t &contents) const = 0; virtual bool contents_are_detail_fence(const contentflags_t &contents) const = 0; virtual bool contents_are_detail_illusionary(const contentflags_t &contents) const = 0; virtual bool contents_are_mirrored(const contentflags_t &contents) const = 0; virtual bool contents_are_origin(const contentflags_t &contents) const = 0; virtual bool contents_are_clip(const contentflags_t &contents) const = 0; virtual bool contents_are_empty(const contentflags_t &contents) const = 0; virtual bool contents_clip_same_type(const contentflags_t &self, const contentflags_t &other) const = 0; virtual bool contents_are_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, bool transsky) const = 0; virtual bool contents_seals_map(const contentflags_t &contents) const = 0; virtual contentflags_t contents_remap_for_export(const contentflags_t &contents) const = 0; virtual contentflags_t combine_contents(const contentflags_t &a, const contentflags_t &b) const = 0; // 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) { 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 format_to(ctx.out(), "{}:{}", ident, v.version.value()); } // Q1-esque BSPs are printed as, ex, bsp29 return format_to(ctx.out(), "{}", v.short_name); } }; template struct texvec : 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) { for (size_t i = 0; i < 2; i++) for (size_t x = 0; x < 4; x++) { stream >= this->at(i, x); } } void stream_write(std::ostream &stream) const { for (size_t i = 0; i < 2; i++) for (size_t x = 0; x < 4; x++) { stream <= this->at(i, x); } } }; // Fmt support template struct fmt::formatter> : formatter> { }; using texvecf = texvec; #include "bspfile_generic.hh" #include "bspfile_q1.hh" #include "bspfile_q2.hh" #include "bspxfile.hh" using bspxentries_t = std::unordered_map>; struct bspdata_t { const bspversion_t *version, *loadversion; // Stay in monostate until a BSP type is requested. std::variant bsp; // This can be used with any BSP format. struct { bspxentries_t entries; // transfer ownership of the vector into a BSPX lump inline void transfer(const char *xname, std::vector &xdata) { entries.insert_or_assign(xname, std::move(xdata)); } // transfer ownership of the vector into a BSPX lump inline void transfer(const char *xname, std::vector &&xdata) { entries.insert_or_assign(xname, xdata); } } bspx; }; /* table of supported versions */ constexpr const bspversion_t *const bspversions[] = {&bspver_generic, &bspver_q1, &bspver_h2, &bspver_h2bsp2, &bspver_h2bsp2rmq, &bspver_bsp2, &bspver_bsp2rmq, &bspver_hl, &bspver_q2, &bspver_qbism}; void LoadBSPFile(fs::path &filename, bspdata_t *bspdata); // returns the filename as contained inside a bsp void WriteBSPFile(const fs::path &filename, bspdata_t *bspdata); void PrintBSPFileSizes(const bspdata_t *bspdata); /** * Returns false if the conversion failed. */ bool ConvertBSPFormat(bspdata_t *bspdata, const bspversion_t *to_version);